#
# This file is part of StorageDisplay
#
# This software is copyright (c) 2014-2023 by Vincent Danjean.
#
# This is free software; you can redistribute it and/or modify it under
# the same terms as the Perl 5 programming language system itself.
#
use strict;
use warnings;

package StorageDisplay::Data::Partition;
# ABSTRACT: Handle partition tables data for StorageDisplay

our $VERSION = '2.01'; # VERSION

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Elem';

with (
    'StorageDisplay::Role::HasBlock',
    'StorageDisplay::Role::Style::Label::HTML::Table',
    'StorageDisplay::Role::Style::WithSize',
    );

sub disk {
    my $self = shift;
    return $self->block(@_);
}

has 'kind' => (
    is    => 'ro',
    isa   => 'Str',
    required => 1,
    );

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $block = shift;
    my $st = shift;

    $st->log({level=>1}, 'Partition table on '.$block->dname);

    return $class->$orig(
        'name' => $block->name,
        'part_infos' => $st->get_info('partitions', $block->name),
        'block' => $block,
        'consume' => [$block],
        @_
        );
};

has 'table' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::Partition::Table',
    required => 1,
    default  => sub {
        my $self = shift;
        return $self->newChild(
	    'Partition::Table',
	    'ignore_name' => 1,
	    'partition' => $self,
            );
    },
    lazy => 1,
    );

sub dotStyleNode {
    my $self=shift;
    my $t=shift;

    my $fc='';
    my $it = $self->table->iterator(
        recurse => 1,
        filter => sub {
            my $part = shift;
            return ! $part->isa('StorageDisplay::Data::Partition::Table::Part::SubTable');
        },
        );
    while (defined(my $part = $it->next)) {
        my $state="free";
        if (! $part->isa("StorageDisplay::Data::Partition::Table::Part::Free")) {
            $state = "busy";#$part->block->state;
        }
        my $color=$self->statecolor($state);
        $fc .= ':' if $fc ne '';
        $fc .= "$color;".sprintf("%.6f", $part->size/$self->size);
    }
    return (
        $self->_dotDefaultStyleNode($t, @_),
        "// Style node",
        "color=white;",
        "fillcolor=lightgrey;",
        'shape="rectangle";',
        #'gradientangle="270";',
        'style=striped;',
        'fillcolor="'.$fc.'";',
        );
}

sub dotStyleTable {
    my $self=shift;

    return "BORDER=\"0\" CELLPADDING=\"0\" CELLSPACING=\"0\"";
}

sub dotLabel {
    my $self = shift;
    my @label = ($self->disk->dname);
    if (defined($self->disk->blk_info('MODEL'))) {
        push @label, 'Model: '.$self->disk->blk_info('MODEL');
    }
    if (defined($self->disk->blk_info('SERIAL'))) {
        push @label, 'Serial: '.$self->disk->blk_info('SERIAL');
    }
    push @label, 'Label: '.$self->kind;
    return @label;
}

sub dotTable {
    my $self = shift;
    my $t = shift // "\t";
    my $it = shift;

    my @tablecontents = (
        "<TR> <TD COLSPAN=\"2\">".$self->label."</TD> </TR>".
        "<TR><TD >".$self->dotFormatedFullLabel($t, @_)."</TD>".
        "    <TD BGCOLOR=\"lightgrey\">",
        $self->table->dotNode($t, @_),
        "</TD> </TR>".
        "<TR> <TD COLSPAN=\"2\"> </TD> </TR>");

    return @tablecontents;
}

1;

##################
package StorageDisplay::Data::Partition::Table;

use Moose;
use namespace::sweep;

use Carp;

extends 'StorageDisplay::Data::Elem';

with (
    'StorageDisplay::Role::Style::IsLabel',
    'StorageDisplay::Role::Style::Label::HTML::Table',
    );

has 'disk' => (
    is       => 'ro',
    isa      => 'StorageDisplay::Block',
    default  => sub {
        my $self = shift;
        return $self->elem->disk;
    },
    lazy     => 1,
    required => 1,
    );

has 'partition' => (
    is       => 'ro',
    isa      => 'StorageDisplay::Data::Partition',
    required => 1,
    );

sub elem {
    my $self = shift;
    return $self->partition(@_);
}

sub addPart {
    my $self = shift;
    my $part = shift;

    if ($part->isa('StorageDisplay::Data::Partition::Table::Part::SubTable')) {
        $part->block->state("special");
    } elsif ($part->isa('StorageDisplay::Data::Partition::Table::Part::Data')) {
        if ($part->label =~ /efi|grub/i || $part->flags =~ /boot/i) {
            $part->block->state("special");
        }
    } elsif ($part->isa('StorageDisplay::Data::Partition::Table::Part::Free')) {

    } else {
        carp "W: unsupported part ".$part->name." (".$part.")\n";
    }
    return $self->addChild($part);
}

sub dotTable {
    my $self = shift;
    return $self->partDotTable(@_);
}

sub partDotTable {
    my $self = shift;
    my $t = shift;
    my $it = shift;

    my @text;
    #print STDERR "dotTable in ".$self->name." (".$self.")\n";
    while (defined(my $e = $it->next)) {
        push @text, '<TR>',
            $self->dotIndent($t, $e->dotNode($t, @_)),
            '</TR>';
    }
    #use Data::Dumper;
    #print STDERR "RES: ", Dumper(\@text);
    return @text;
}

1;

##################
package StorageDisplay::Data::Partition::Table::Part;

use Moose;
use namespace::sweep;

extends 'StorageDisplay::Data::Elem';

with (
    'StorageDisplay::Role::Style::Label::HTML',
    'StorageDisplay::Role::Style::IsLabel',
    'StorageDisplay::Role::Style::WithSize',
    );

has 'table' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::Partition::Table',
    required => 1,
    );

has 'start' => (
    is    => 'ro',
    isa   => 'Int',
    required => 1,
    );

has 'label' => (
    is    => 'ro',
    isa   => 'Str',
    required => 0,
    );

sub BUILD {
    my $self = shift;

    #print STDERR "BUILD in ".__PACKAGE__."\n";
    $self->table->addPart($self);
}

sub partStyle {
    my $self = shift;

    return '';
}

sub dotNode {
    my $self = shift;
    return  (
        "<TD ".$self->partStyle(@_).">",
        $self->_dotDefaultNode(@_),
        "</TD>",
        );
}

1;

##################
package StorageDisplay::Data::Partition::Table::Part::Data;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Partition::Table::Part';

has 'id' => (
    is    => 'ro',
    isa   => 'Int',
    required => 1,
    );

has 'flags' => (
    is    => 'ro',
    isa   => 'Str',
    required => 0,
    );


use Carp;
around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $args = { @_ };

    my $block;

    #print STDERR "BUILDARGS in ".__PACKAGE__."\n";
    my $part_id = $args->{id};
    my $table = $args->{table};
    my $it = $table->disk->iterator(
        'recurse' => 0,
        'uniq' => 1,
        );
    while(defined(my $b=$it->next)) {
        # PARTN does not exists for kpartx mapped partitions
        my $num = $b->udev_info("ID_PART_ENTRY_NUMBER") // -1;
        next if $num != $part_id;
        $block = $b;
        last;
    }
    if (! defined($block)) {
        my $b = StorageDisplay::Block::NoSystem->new(
            'id' => $part_id,
            );
        $block=$b;
    }

    return $class->$orig(
        'name' => $block->name,
        'block' => $block,
        @_
        );
};

sub BUILD {
    my $self = shift;

    #print STDERR "BUILD in ".__PACKAGE__."\n";
    #print STDERR "Looking for ", $self->id, " into ", $self->table->disk->name, "\n";
    $self->provideBlock($self->block);
}

sub rawlinkname {
    my $self = shift;

    confess "No rawlinkname for ".$self->fullname;
}

sub linkname {
    my $self = shift;

    return $self->table->elem->linkname.':"'.$self->id.'"';
}

sub partStyle {
    my $self = shift;

    my $state = "unknown";
    if (defined($self->block)) {
        $state = $self->block->state;
    }

    return 'PORT="'.$self->id.'"'.
        ' BGCOLOR="'.$self->statecolor($state).'"';
}

sub dotLabel {
    my $self = shift;
    my $dev;
    if (defined($self->block)) {
        $dev = $self->block->dname;
    } else {
        $dev = $self->name;
    }
    if ($self->label) {
        return ($dev, $self->label);
    } else {
        return $dev;
    }
}

with (
    'StorageDisplay::Role::HasBlock',
    );

1;

##################
package StorageDisplay::Data::Partition::Table::Part::SubTable;

use Moose;
use namespace::sweep;

# keep Table::Part::Data first to pick its dotNode redefinition
extends
    'StorageDisplay::Data::Partition::Table::Part::Data',
    'StorageDisplay::Data::Partition::Table';

sub dotNode {
    my $self = shift;
    my $t = shift;
    #print STDERR "BUILD in ".__PACKAGE__."\n";
    return (
        '<TD>',
        $self->dotIndent(
            $t,
            '<TABLE BORDER="0" CELLPADDING="0" CELLSPACING="0"><TR>',
            '<TD '.$self->partStyle($t, @_).'>',
            #$self->dotLabel($t, @_),
            $self->dotFormatedFullLabel($t, @_),
            '</TD></TR><TR><TD>',
            $self->_dotDefaultNode(@_),
            '</TD></TR></TABLE>',
        ),
        '</TD>',
        );
}

sub dotTable {
    my $self = shift;
    return $self->partDotTable(@_);
}

with (
    'StorageDisplay::Role::Style::IsLabel',
    'StorageDisplay::Role::Style::Label::HTML::Table',
    );

1;

##################
package StorageDisplay::Data::Partition::Table::Part::Free;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Partition::Table::Part';

sub block {
    my $self = shift;
    return
}

sub dotLabel {
    my $self = shift;
    return "Free";
}

sub partStyle {
    my $self = shift;
    return 'bgcolor="green"';
}

1;

##################################################################
package StorageDisplay::Data::Partition::None;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Elem';

with (
    'StorageDisplay::Role::HasBlock',
    'StorageDisplay::Role::Style::WithSize',
    'StorageDisplay::Role::Style::FromBlockState',
    );

sub disk {
    my $self = shift;
    return $self->block(@_);
}

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $block = shift;
    my $st = shift;

    $st->log({level=>1}, 'Disk with no partition tables on '.$block->dname);

    return $class->$orig(
        'name' => $block->name,
        'block' => $block,
        'provide' => [$block],
        'size' => $st->get_info('lsblk', $block->name, 'size'),
        @_
        );
};

sub BUILD {
    my $self = shift;

    #print STDERR "BUILD in ".__PACKAGE__."\n";
    #print STDERR "Looking for ", $self->id, " into ", $self->table->disk->name, "\n";
    $self->provideBlock($self->block);
}

sub dotLabel {
    my $self = shift;
    my @label = ($self->disk->dname);
    if (defined($self->disk->blk_info('MODEL'))) {
        push @label, 'Model: '.$self->disk->blk_info('MODEL');
    }
    if (defined($self->disk->blk_info('SERIAL'))) {
        push @label, 'Serial: '.$self->disk->blk_info('SERIAL');
    }
    return @label;
}

around 'dotStyleNode' => sub {
    my $orig = shift;
    my $self = shift;
    return (
        $self->$orig(@_),
        'style=filled',
        'shape=rectangle',
        );
};

1;

##################################################################
package StorageDisplay::Data::Partition::GPT;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Partition';

use Carp;

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $block = shift;
    my $st = shift;

    return $class->$orig(
        $block,
        $st,
        'kind' => 'gpt',
        %{$st->get_info('partitions', $block->name) // {} }, # size, label, parts
        @_
        );
};

sub BUILD {
    my $self = shift;
    my $args = shift;

    #print STDERR "Managing ".$self->disk->dname." (".($self->disk).")\n";

    my $id_free = 1;

    foreach my $part (@{$args->{'parts'}}) {
        #print STDERR "*******************\n";

        if ($part->{kind} eq 'free') {
            delete($part->{kind});
            StorageDisplay::Data::Partition::Table::Part::Free->new(
                'name' => '_'.$id_free,
                'table' => $self->table,
                %{$part},
                );
            $id_free ++;
        } elsif ($part->{kind} eq 'part') {
            delete($part->{kind});
            StorageDisplay::Data::Partition::Table::Part::Data->new(
                'table' => $self->table,
                %{$part},
                );
        } else {
            use Data::Dumper;
            print STDERR Dumper($part);
            croak "ARghh for ".$self->disk->dname;
        }
    }
}

1;

##################################################################
package StorageDisplay::Data::Partition::MSDOS;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::Partition';

use Carp;

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $block = shift;
    my $st = shift;

    my $info = $st->get_info('partitions', $block->name) // {};

    return $class->$orig(
        $block,
        $st,
        'kind' => 'msdos',
        (map { $_ => $info->{$_} } ("size", "label", "parts")),
        'extended_num' => $info->{'extended'},
        @_
        );
};

has 'extended' => (
    is    => 'rw',
    isa   => 'StorageDisplay::Data::Partition::Table',
    required => 0,
    );

sub BUILD {
    my $self = shift;
    my $args = shift;

    my $extended = $args->{'extended_num'} // '';
    my $id_free = 1;

    foreach my $part (@{$args->{'parts'}}) {
        if ($part->{kind} eq 'free') {
            delete($part->{kind});
            StorageDisplay::Data::Partition::Table::Part::Free->new(
                'name' => '_'.$id_free,
                'table' => $self->table,
                %{$part},
                );
            $id_free ++;
        } elsif ($part->{kind} eq 'part') {
            delete($part->{kind});
            if ($part->{id} eq $extended) {
                $self->extended(
                    StorageDisplay::Data::Partition::Table::Part::SubTable->new(
                        'table' => $self->table,
                        'partition' => $self,
                        %{$part},
                    ));
            } elsif ($part->{id} <= 4) {
                StorageDisplay::Data::Partition::Table::Part::Data->new(
                    'table' => $self->table,
                    %{$part},
                    );
            } else {
                confess if not defined($self->extended);
                StorageDisplay::Data::Partition::Table::Part::Data->new(
                    'table' => $self->extended,
                    %{$part},
                    );
            }
        } else {
            croak "ARghh";
        }
    }
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

StorageDisplay::Data::Partition - Handle partition tables data for StorageDisplay

=head1 VERSION

version 2.01

=head1 AUTHOR

Vincent Danjean <Vincent.Danjean@ens-lyon.org>

=head1 COPYRIGHT AND LICENSE

This software is copyright (c) 2014-2023 by Vincent Danjean.

This is free software; you can redistribute it and/or modify it under
the same terms as the Perl 5 programming language system itself.

=cut