#
# 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::LVM;
# ABSTRACT: Handle LVM data for StorageDisplay

our $VERSION = '2.01'; # VERSION

1;

##################################################################
package StorageDisplay::Data::LVM::Group;

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

with ('StorageDisplay::Role::Style::IsSubGraph');

has 'vg' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Block',
    required => 1,
    );

has 'vgname' => (
    is    => 'ro',
    isa   => 'Str',
    required => 1,
    init_arg => undef,
    lazy     => 1,
    default  => sub {
	my $self = shift;
	return $self->vg->name;
    },
    );

has 'pvs' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::LVM::PVs::Base',
    writer => '_pvs',
    required => 0,
    );

sub dname {
    my $self=shift;
    return 'LVM VG: '.$self->name;
}

sub dotLabel {
    my $self = shift;
    return 'LVM: '.$self->vgname;
}

sub _xv {
    my $self = shift;
    my $kind = shift;
    my $name = shift;

    my $it = $self->$kind->iterator(recurse => 0);
    while (defined(my $e=$it->next)) {
        return $e if $e->lvmname eq $name;
    }
    #print STDERR "E: no $kind with name $name\n";
    return;
}

use StorageDisplay::Moose::Cached;

has 'pv' => (
    cached_hash => "StorageDisplay::Data::LVM::VG::PVs::PV",
    compute => sub {
        my $self = shift;
        my $name = shift;
        return $self->_xv("pvs", $name);
    },
    );

##################################################################
package StorageDisplay::Data::LVM::UnassignedPVs;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::Group';

with (
    'StorageDisplay::Role::Style::Grey',
    'StorageDisplay::Role::Elem::Kind'
    => { kind => "Unassigned PVs" }
    );

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

    $st->log({level=>1}, 'Unassigned PVs');

    my $vgblock = StorageDisplay::Block::NoSystem->new(
        'name' => $class,
        );

    my $info = $st->get_info('lvm', $vgname);

    return $class->$orig(
        'ignore_name' => 1,
        'vg' => $vgblock,
        'consume' => [],
        'lvm-info' => $info,
        'st' => $st,
        @_
        );
};

sub BUILD {
    my $self=shift;
    my $args=shift;
    my $st = $args->{st};

    $self->_pvs($self->newChild('LVM::UnassignedPVs::PVs',
				$self, $st, $args->{'lvm-info'}));
    return $self;
};

1;

##################################################################
package StorageDisplay::Data::LVM::VG;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::Group';

with (
    'StorageDisplay::Role::Style::WithFree',
    );

has 'lvs' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::LVM::VG::LVs',
    writer => '_lvs',
    required => 0,
    );

has '_pv_lv_links' => (
    traits   => [ 'Array' ],
    is       => 'ro',
    isa      => "ArrayRef",
    required => 1,
    default  => sub { return []; },
    handles  => {
        '_add_link' => 'push',
            'internal_links' => 'elements',
    }
    );

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

    $st->log({level=>1}, 'VG '.$vgname);

    my $vgblock = StorageDisplay::Block::NoSystem->new(
        'name' => $vgname,
        );

    my $info = $st->get_info('lvm', $vgname);

    return $class->$orig(
        'name' => $vgname,
        'vg' => $vgblock,
        'consume' => [],
        'lvm-info' => $info,
        'st' => $st,
        'size' => ($info->{'vgs-vg'}->{'vg_size'} =~ s/B$//r),
        'free' => ($info->{'vgs-vg'}->{'vg_free'} =~ s/B$//r),
        @_
        );
};

sub BUILD {
    my $self=shift;
    my $args=shift;
    my $st = $args->{st};

    $self->_pvs($self->newChild('LVM::VG::PVs', $self, $st,
				$args->{'lvm-info'}));
    if ($args->{'name'} ne '') {
        #print STDERR "name: ", $args->{'name'}, "\n";
        $self->_lvs($self->newChild('LVM::VG::LVs', $self, $st,
				    $args->{'lvm-info'}));
        #my $links = $args->{'lvm-info'}->{'pvs'};
        #foreach my $l (@{$links}) {
        #    if ($l->{segtype} ne "free"
        #        && $l->{lv_role} ne "private,pool,spare"
        #        && $l->{lv_role} ne "private,thin,pool,metadata"
        #        && $l->{lv_role} ne "private,thin,pool,data") {
        #        $self->_add_link({pv => $l->{pv_name},
        #                          lv => $l->{lv_name}});
        #    }
        #}
        my $links = $args->{'lvm-info'}->{'lvs'};
        foreach my $l (@{$links}) {
            if ($l->{'pool_lv'} ne '') {
                $self->_add_link({source_name => $l->{'pool_lv'},
                                  source_type => 'lv',
                                  lv => $l->{lv_name}});
            }
            if ($l->{'lv_parent'} ne '') {
                $self->_add_link({source_name => $l->{lv_name},
                                  source_type => 'lv',
                                  lv => $l->{lv_parent}});
            }
            foreach my $devpos (split(/,/, $l->{'seg_le_ranges'})) {
                if ($devpos !~ m/^(.*):[0-9]+-[0-9]+$/) {
                    $st->warn("Cannot parse seg_le_ranges $devpos for ".$l->{lv_name});
                } else {
                    my $source_name = $1;
                    #$st->warn("Parsing seg_pe_ranges $devpos for ".$l->{lv_name});
                    my $type = 'pv';
                    if (index($l->{'seg_pe_ranges'},$devpos) == -1) {
                        $type = 'lv';
                        #$st->warn("switching to LV for $devpos for ".$l->{lv_name});
                    #} else {
                    #    $st->warn("ok : $devpos in ".$l->{'seg_pe_ranges'});
                    }
                    $self->_add_link({source_name => $source_name,
                                      source_type => $type,
                                      lv => $l->{lv_name}});
                }
            }
        }
    }
    return $self;
};

use StorageDisplay::Moose::Cached;

has 'lv' => (
    cached_hash => "StorageDisplay::Data::LVM::VG::LVs::LV",
    compute => sub {
        my $self = shift;
        my $name = shift;
        return $self->_xv("lvs", $name);
    },
    );

sub dotLinks {
    my $self = shift;
    my $source = sub {
        my $name = shift;
        my $type = shift;
        my $source =  $self->$type($name);
        if (defined($source)) {
            return $source->linkname;
        }
        print STDERR "E: FIXME: No source for $name ($type)\n";
        return "''";
    };
    return map {
        $source->($_->{source_name}, $_->{source_type}).' -> '.$self->lv($_->{lv})->linkname
    } $self->internal_links;
}

1;

##################################################################
package StorageDisplay::Data::LVM::Elem;

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

has 'vg' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::LVM::Group',
    required => 1,
    );

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

    return $class->$orig(
        'vg' => $vg,
        'st' => $st,
        'lvm-info' => $info,
        @_
        );
};

1;

##################################################################
package StorageDisplay::Data::LVM::PVs::Base;

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

with (
    'StorageDisplay::Role::Style::IsSubGraph',
    'StorageDisplay::Role::Style::SubInternal',
    );

1;

##################################################################
package StorageDisplay::Data::LVM::VG::PVs;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::PVs::Base';

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

    return $class->$orig(
        $vg,
        @_,
        'ignore_name' => 1,
        'consume' => [],
        );
};

sub BUILD {
    my $self=shift;
    my $args=shift;
    my @pvnames = sort keys %{$args->{'lvm-info'}->{'vgs-pv'}};
    if (scalar(@pvnames) == 0) {
        # PV without a VG
        @pvnames = map { $_->{'pv_name'} } @{$args->{'lvm-info'}->{'pvs'}};
    }
    foreach my $pv_name (sort keys %{$args->{'lvm-info'}->{'vgs-pv'}}) {
        $self->newChild('LVM::VG::PVs::PV', $pv_name, $self->vg, $args->{st},
			$args->{'lvm-info'});
    }
    return $self;
};

sub dotLabel {
    my $self = shift;
    return ($self->vg->vgname.'\'s PVs');
}

1;

##################################################################
package StorageDisplay::Data::LVM::UnassignedPVs::PVs;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::PVs::Base';

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

    return $class->$orig(
        $vg,
        @_,
	'ignore_name' => 1,
        'consume' => [],
        );
};

sub BUILD {
    my $self=shift;
    my $args=shift;
    foreach my $pv_name (sort map { $_->{'pv_name'} } @{$args->{'lvm-info'}->{'pvs'}}) {
        $self->newChild('LVM::UnassignedPVs::PV', $pv_name, $self->vg,
			$args->{st}, $args->{'lvm-info'});
    }
    return $self;
};

sub dotLabel {
    my $self = shift;
    return ();
}

1;

##################################################################
package StorageDisplay::Data::LVM::VG::LVs;

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

with (
    'StorageDisplay::Role::Style::IsSubGraph',
    'StorageDisplay::Role::Style::SubInternal',
    );

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

    return $class->$orig(
        $vg,
        @_,
        'ignore_name' => 1,
        'consume' => [],
        );
};

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

    foreach my $lv_name (sort keys %{$args->{'lvm-info'}->{'vgs-lv'}}) {
        $self->newChild('LVM::VG::LVs::LV', $lv_name, $self->vg,
			$args->{st}, $args->{'lvm-info'});
    }
    return $self;
};

sub dotLabel {
    my $self = shift;
    return ($self->vg->vgname.'\'s LVs');
}
1;

##################################################################
package StorageDisplay::Data::LVM::XV;

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

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

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

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

    return $class->$orig(
        $vg, $st, $info,
        'lvmname' => $lvmname,
        'block' => $block,
        @_
        );
};

1;

##################################################################
package StorageDisplay::Data::LVM::VG::PVs::PV;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::XV';

with (
    'StorageDisplay::Role::Style::WithUsed',
    );

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $pvblockname = shift;
    my $vg = shift;
    my $st = shift;
    my $info = shift;

    my $pvblock = $st->block($pvblockname);

    my $pvinfo = $info->{'vgs-pv'}->{$pvblockname};

    if (not defined($pvinfo)) {
        # only PV, no assigned VG
        my @pv = grep { $_->{'pv_name'} eq $pvblockname } @{$info->{'pvs'}};
        $pvinfo = $pv[0];
    }

    return $class->$orig(
        $vg, $st, $info, $pvblockname, $pvblock,
        'name' => $pvblock->name,
        'consume' => [$pvblock],
        'size' => ($pvinfo->{'pv_size'} =~ s/B$//r),
        'free' => ($pvinfo->{'pv_free'} =~ s/B$//r),
        'used' => ($pvinfo->{'pv_used'} =~ s/B$//r),
        @_
        );
};

sub dotLabel {
    my $self = shift;
    return ('PV: '.$self->block->dname);
}

1;

##################################################################
package StorageDisplay::Data::LVM::VG::LVs::LV;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::LVM::XV';

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

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $lvname = shift;
    my $vg = shift;
    my $st = shift;
    my $info = shift;

    my $lvblock = $st->block($vg->vgname.'/'.$lvname);

    my $lvinfo = $info->{'vgs-lv'}->{$lvname};

    return $class->$orig(
        $vg, $st, $info, $lvname, $lvblock,
        'name' => $lvname,
        'consume' => [],
         'size' => ($lvinfo->{'lv_size'} =~ s/B$//r),
        @_
        );
};

sub BUILD {
    my $self = shift;
    $self->provideBlock($self->block);
}

sub dotLabel {
    my $self = shift;
    return ('LV: '.$self->lvmname);
}

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

StorageDisplay::Data::LVM - Handle LVM 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