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

our $VERSION = '2.01'; # VERSION

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

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

has '_devices' => (
    traits   => [ 'Array' ],
    is    => 'ro',
    isa   => 'ArrayRef[StorageDisplay::Data::RAID::Device]',
    required => 1,
    default  => sub { return []; },
    handles  => {
        '_add_device' => 'push',
            'devices' => 'elements',
    }
    );

has 'raid-devices' => (
    traits   => [ 'Array' ],
    is    => 'ro',
    isa   => 'ArrayRef[StorageDisplay::Data::RAID::RaidDevice]',
    required => 1,
    default  => sub { return []; },
    handles  => {
        '_add_raid_device' => 'push',
            'raid_devices' => 'elements',
    }
    );

around '_add_raid_device' => sub {
    my $orig = shift;
    my $self = shift;
    my $raid_device = shift;
    my $state = shift;
    die "Invalid state" if $state->raid_device != $raid_device;
    $raid_device->_state($state);
    $self->addChild($state);
    return $self->$orig($raid_device);
};

1;

##################################################################
package StorageDisplay::Data::RAID::Container;
use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID';

has 'container-devices' => (
    traits   => [ 'Array' ],
    is    => 'ro',
    isa   => 'ArrayRef[StorageDisplay::Data::RAID::ContainerDevice]',
    required => 1,
    default  => sub { return []; },
    handles  => {
        '_add_container_device' => 'push',
            'container_devices' => 'elements',
    }
    );

sub _add_raid_device {
    die "Internal error";
}

around '_add_container_device' => sub {
    my $orig = shift;
    my $self = shift;
    my $container_device = shift;
    $self->addChild($container_device);
    return $self->$orig($container_device);
};

1;

###########################################################################
package StorageDisplay::Data::RAID::Elem;

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

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

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

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

1;

###########################################################################
package StorageDisplay::Data::RAID::State;

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

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

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

has 'extra-info' => (
    is    => 'ro',
    isa   => 'Str',
    required => 0,
    predicate => 'has_extra_info',
    reader => 'extra_info',
    );

has 'raid_device' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::RAID::RaidDevice',
    required => 1,
    );

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

    return $class->$orig(
        $raid_device->raid, $st,
        'consume' => [],
        'raid_device' => $raid_device,
        @_
        );
};

sub dotStyleNode {
    my $self = shift;
    my $color = 'special';
    my $state = $self->state;

    if ($state =~ /degraded|DGD/i) {
        $color = 'warning';
    } elsif ($state =~ /failed|offline/i) {
        $color = 'error';
    } elsif ($state =~ /clean|active|active-idle|optimal|OKY/i) {
        $color = 'ok';
    }

    return (
        "shape=oval",
        "fillcolor=".$self->statecolor($color),
        );
}

sub dotLabel {
    my $self = shift;
    my @label=('state: '.$self->state);
    if ($self->has_extra_info) {
        push @label, $self->extra_info;
    }

    return @label;
}

1;

###########################################################################
package StorageDisplay::Data::RAID::ContainerDevice;

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

with (
    'StorageDisplay::Role::HasBlock',
    #'StorageDisplay::Role::Style::Plain',
    );

has 'container-type' => (
    is    => 'ro',
    isa   => 'Str',
    reader => 'container_type',
    required => 1,
    );

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

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

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

    #print STDERR "container device ", $self->name, " with providing block ", $self->block->dname,"\n";
    $self->block->providedBy($self);
}

sub dotLabel {
    my $self = shift;

    return (
        $self->block->dname,
        #$self->container_type,
        );
}

sub dotStyleNode {
    my $self = shift;
    return (
        "shape=oval;",
        "fillcolor=".$self->statecolor('special').";",
        );
};

1;

###########################################################################
package StorageDisplay::Data::RAID::RaidDevice;

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

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

has 'state' => (
    is    => 'ro',
    isa   => 'StorageDisplay::Data::RAID::State',
    writer => '_state',
    required => 0,
    );

has 'raid-level' => (
    is    => 'ro',
    isa   => 'Str',
    reader => 'raid_level',
    required => 1,
    );

around '_state' => sub {
    my $orig  = shift;
    my $self = shift;
    my $ret = $self->$orig(@_);
    $self->state->addChild($self);
    return $ret;
};

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

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

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

    $self->block->providedBy($self);
}

sub dotLabel {
    my $self = shift;

    return (
        $self->block->dname,
        $self->raid_level,
        );
}

1;

###########################################################################
package StorageDisplay::Data::RAID::Device;

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

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

has 'state' => (
    is    => 'ro',
    isa   => 'Maybe[Str]',
    required => 0,
    );

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

around BUILDARGS => sub {
    my $orig  = shift;
    my $class = shift;
    my $raid = shift;
    my $st = shift;
    my $devname = shift;
    my $info = shift;
    my $args = { @_ };
    my $container_block = $args->{'container-block'};

    my $block = $st->block($devname);

    #print STDERR "RAID::Device ", $block->dname, " container ", ($container_block // $block)->dname, "\n";

    return $class->$orig(
        $raid, $st,
        'block' => $block,
        'name' => $block->name,
        'consume' => [$container_block // $block],
        'state' => $info->{state},
        'raiddevice' => $info->{raiddevice},
        @_
        );
};

around 'dotStyleNode' => sub {
    my $orig = shift;
    my $self = shift;
    my @text = $self->$orig(@_);

    my $state = $self->state;
    my $s;

    if (not defined($state)) {
        # devices in RAID containers
        $s = 'used';
    } elsif ($state =~ /active|Online, Spun Up|OPT|RDY/i) {
        $s = 'used';
    } elsif ($state =~ /rebuild|RBLD/i) {
        $s = 'warning';
    } elsif ($state =~ /spare|HSP/i) {
        $s = 'free';
    } elsif ($state =~ /faulty|error|FLD|MIS|bad|offline/i) {
        $s = 'error';
    } elsif ($state =~ /Unconfigured.*good|AVL/i) {
        $s = 'unused';
    } elsif ($state =~ /JBOD/i) {
        $s = $self->block->state;
    } else {
        $s = 'warning';
    }
    my $color = $self->statecolor($s);

    push @text, "fillcolor=$color";
    return @text;
};

sub dotLabel {
    my $self = shift;
    my @label = ($self->raiddevice.': '.$self->block->dname);
    if (defined($self->state)) {
        push @label, $self->state;
    }
    return @label;
}

1;

###########################################################################
package StorageDisplay::Data::RAID::RawDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::Device';
with(
    'StorageDisplay::Role::Style::WithSize',
    );

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

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

sub dotLabel {
    my $self = shift;

    return (
        $self->model,
        $self->raiddevice.': slot '.$self->slot,
        $self->state,
        );
}

1;

##################################################################
package StorageDisplay::Data::RAID::MD::Container;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::Container';

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

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

    #$st->get_infos
    $st->log({level=>1}, 'MD Container managed by '.$devname);

    my $info = $st->get_info('md', $devname);
    my $block = $st->block($devname);
    #print STDERR "MD::Container $devname -> ", $block->dname, "\n";

    return $class->$orig(
        'name' => $block->name,
        'block' => $block,
        'consume' => [],
        'st' => $st,
        'raid-name' => '', # in case $info has no name
        %{$info},
        @_
        );
};

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

    my $container_device = StorageDisplay::Data::RAID::ContainerDevice->new(
        $self, $st,
        $self->block,
        'container-type' => $args->{'raid-version'},
        );
    $self->_add_container_device($container_device);

    foreach my $dev (sort keys %{$args->{'devices'}}) {
        my $d = StorageDisplay::Data::RAID::Device->new($self, $st, $dev, $args->{'devices'}->{$dev});
        $self->_add_device($d);
        $self->addChild($d);
    }

    return $self;
};

has 'raid-version' => (
    is    => 'ro',
    isa   => 'Str',
    required => 1,
    reader => 'container_type',
    );

sub dname {
    my $self=shift;
    return 'MD: '.$self->block->dname;
}

sub dotLabel {
    my $self = shift;
    return (
        'Software RAID container',
        'Type: '.$self->container_type,
        #$self->disp_size($self->used_dev_size).' used per device',
        );
}

sub dotLinks {
    my $self = shift;
    # Always one container device for MD RAID
    my $raidlinkname = ($self->container_devices)[0]->linkname;
    return (
        map {
            $_->linkname.' -> '.$raidlinkname
        } $self->devices
    );
}

1;

##################################################################
package StorageDisplay::Data::RAID::MD;

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

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

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

    #$st->get_infos
    $st->log({level=>1}, 'MD Raid for device '.$devname);

    my $info = $st->get_info('md', $devname);
    my $block = $st->block($devname);

    return $class->$orig(
        'name' => $block->name,
        'block' => $block,
        'consume' => [],
        'st' => $st,
        'raid-name' => '', # in case $info has no name
        %{$info},
        @_
        );
};

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

    my $raid_device = $self->newElem(
	'RAID::MD::State::RaidDevice', $self, $st, $self->block,
	'raid-level' => $args->{'raid-level'},
	'size' => $args->{'array-size'});
    my $state = $self->newElem(
	'RAID::MD::State', $raid_device, $st,
	'state' => $args->{'raid-state'},
	'ignore_name' => 1,
	);
    $self->_add_raid_device($raid_device, $state);

    my $container_device_block = undef;
    if (exists($args->{'raid-container-device'})) {
        $container_device_block = $st->block($args->{'raid-container-device'});
        #print STDERR "RAID::MD Container ", $args->{'raid-container-device'}, " -> ",
        #    $container_device_block->dname, "\n";
    }

    foreach my $dev (sort keys %{$args->{'devices'}}) {
        my $d = $self->newChild(
	    'RAID::MD::Device',
	    $self, $st, $dev, $args->{'devices'}->{$dev},
            'container-block' => $container_device_block);
        $self->_add_device($d);
    }

    return $self;
};

has 'used-dev-size' => (
    is    => 'ro',
    isa   => 'Int',
    required => 0, # not present with RAID0
    predicate => 'has_used_dev_size',
    reader => 'used_dev_size',
    );

has 'raid-name' => (
    is    => 'ro',
    isa   => 'Str',
    required => 1,
    reader => 'raid_name',
    );

sub dname {
    my $self=shift;
    return 'MD: '.$self->block->dname;
}

sub dotLabel {
    my $self = shift;
    my @label = ($self->raid_name);
    if ($self->has_used_dev_size) {
	push @label, $self->disp_size($self->used_dev_size).' used per device';
    }
    return @label;
}

sub dotLinks {
    my $self = shift;
    # Always one raid device for MD RAID
    my $raidlinkname = ($self->raid_devices)[0]->linkname;
    return (
        map {
            $_->linkname.' -> '.$raidlinkname
        } $self->devices
    );
}

1;

##################################################################
package StorageDisplay::Data::RAID::MD::State;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::State';

1;

##################################################################
package StorageDisplay::Data::RAID::MD::State::RaidDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RaidDevice';

1;

##################################################################
package StorageDisplay::Data::RAID::MD::Device;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::Device';

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::Megacli;

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

has 'controller' => (
    is    => 'ro',
    isa   => 'Num',
    required => 1,
    );

has 'hw_model' => (
    is    => 'ro',
    isa   => 'Str',
    init_arg => 'H/W Model',
    required => 1,
    );

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

has 'named-raid-devices' => (
    traits   => [ 'Hash' ],
    is    => 'ro',
    isa   => 'HashRef[StorageDisplay::Data::RAID::RaidDevice]',
    required => 1,
    default  => sub { return {}; },
    handles  => {
        '_add_named_raid_device' => 'set',
            'raid_device' => 'get',
    }
    );

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

    #$st->get_infos
    $st->log({level=>1}, 'Megacli Raid controller '.$controller);

    my $info = $st->get_info('lsi-megacli', $controller);

    return $class->$orig(
        'name' => $controller,
        'controller' => $controller,
        'consume' => [],
        'st' => $st,
        %{$info->{'Controller'}->{'c'.$controller}},
        %{$info},
        @_
        );
};

sub hwsize {
    my $self = shift;
    my $hwsize = shift;

    if ($hwsize =~ /^[0-9]+$/) {
        return $hwsize;
    } elsif ($hwsize =~ /^([0-9]+)([BKMGTP])$/) {
        my %pow = (
            'B' => 0,
            'K' => 1,
            'M' => 2,
            'G' => 3,
            'T' => 4,
            'P' => 5,
            );
        return $1 * (1024 ** $pow{$2});
    } else {
        print STDERR "Warning: cannot interpret size $hwsize, using -1\n";
        return -1;
    }
}

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

    #my $state = StorageDisplay::Data::RAID::State->new($self, $st,
    #                                             'state' => $args->{'raid-state'});
    #$self->addChild($state);

    #$self->_add_raid_device(StorageDisplay::Data::RAID::RaidDevice->new($self, $st,
    #                                                              $self->block,
    #                                                              $state,
    #                                                              'size' => $args->{'array-size'}));
    my $cid = $self->controller;

    #use Data::Dumper;
    #print STDERR Dumper($st);
    $self->newChild('RAID::LSI::Megacli::BBU::Status',
		    $self, $st, 'status' => $args->{'BBU'});

    use bignum qw/hex/;
    foreach my $dev (sort { $a->{'LSI ID'} <=> $b->{'LSI ID'} }
                     (values %{$args->{'Disk'}})) {
	my $devname = $dev->{'Path'} // '';
        my $devpath = 'LSI@'.$dev->{'Slot ID'};
        if ($dev->{'ID'} !~ /^c[0-9]+uXpY$/) {
            $devpath = 'LSI@'.$dev->{'ID'};
        }
	my $block;
	my @block;
	if ($devname ne '' && $devname ne 'N/A') {
		$block = $st->block($devname);
		@block = ('block' => $block);
	}
        my $d = $self->newChild(
	    'RAID::LSI::Megacli::RawDevice',
            $self, $st, $devpath, $dev,
            'raiddevice' => $dev->{'ID'},
            'state' => $dev->{'Status'},
            'model' => $dev->{'Drive Model'},
            'slot' => $dev->{'Slot ID'},
            'size' => (hex($dev->{'# sectors'}) * ($dev->{'sector size'} // 512))->numify(),
	    @block,
            );
        $self->_add_device($d);
	if ($block) {
		$d->provideBlock($block);
	}
    }
    foreach my $dev (sort { $a->{'ID'} cmp $b->{'ID'} }
                     (values %{$args->{'Array'}})) {
        #print STDERR Dumper($dev);
        my $devname = $dev->{'OS Path'};
        my $block = $st->block($devname);
        #print STDERR Dumper($block->blk_info("SIZE")//$self->hwsize($dev->{'Size'}));
        my $raid_device = $self->newElem(
	    'RAID::LSI::Megacli::State::RaidDevice',
            $self, $st, $block,
            # If the disk is not attached to linux (or have been 'deleted')
            # the SIZE would be unknown from blk
            'size' => $block->blk_info("SIZE")//$self->hwsize($dev->{'Size'}),
            'raid-level' => $dev->{'Type'},
            %{$dev},
            );
        my %inprogress=();
        if ($dev->{'InProgress'} ne 'None') {
            %inprogress=('extra-info' => $dev->{'InProgress'});
        }
        my $state = $self->newElem(
	    'RAID::LSI::Megacli::State', $raid_device, $st,
	    'state' => $dev->{'Status'},
	    'name' => $raid_device->block->name,
	    %inprogress);

        $self->_add_raid_device($raid_device, $state);
        $self->_add_named_raid_device($dev->{'ID'}, $raid_device);
    }

    return $self;
};

sub dname {
    my $self=shift;
    return 'MegaCli: Controller '.$self->ID;
}

sub dotLabel {
    my $self = shift;
    return (
        $self->hw_model,
        "Controller: ".$self->ID,
        #$self->raid_level.': '.$self->raid_name,
        #$self->disp_size($self->used_dev_size).' used per device',
        );
}

sub dotLinks {
    my $self = shift;
    return (
        map {
            if ($_->raiddevice =~ /^(c[0-9]+u[0-9]+)p([0-9]+|Y)$/) {
                my $raid_device = $self->raid_device($1);
                $_->linkname.' -> '.$raid_device->linkname;
            }
        } $self->devices
    );
}

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::Megacli::BBU::Status;

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

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

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

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

sub dotStyleNode {
    my $self = shift;
    my $color = 'special';
    my $status = $self->status;

    if ($status =~ /REPL|error/i) {
        $color = 'red';
    } elsif ($status =~ /missing/i || $status eq '') {
        $color = 'warning';
    } elsif ($status =~ /absent/i) {
        $color = 'unused';
    } elsif ($status =~ /good/i) {
        $color = 'ok';
    }

    return (
        "shape=oval",
        "fillcolor=".$self->statecolor($color),
        );
}

sub dotLabel {
    my $self = shift;

    return (
        'BBU Status: '.$self->status,
        );
}

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::Megacli::State;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::State';

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::Megacli::RawDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RawDevice';

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::Megacli::State::RaidDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RaidDevice';

has 'lsi-id' => (
    is    => 'ro',
    isa   => 'Str',
    init_arg => 'ID',
    reader=> 'lsi_id',
    required => 1,
    );

around 'dotLabel' => sub {
    my $orig  = shift;
    my $self = shift;
    my @ret = $self->$orig(@_);
    $ret[0] .= " (".$self->lsi_id.")";
    return @ret;
};

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::SASIrcu;

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

my $missing_count=0;

has 'controller' => (
    is    => 'ro',
    isa   => 'Num',
    init_arg => 'controllerID',
    required => 1,
    );

has 'hw_model' => (
    is    => 'ro',
    isa   => 'Str',
    init_arg => 'controller-type',
    required => 1,
    );

has 'named-raw-devices' => (
    traits   => [ 'Hash' ],
    is    => 'ro',
    isa   => 'HashRef[StorageDisplay::Data::RAID::RawDevice]',
    required => 1,
    default  => sub { return {}; },
    handles  => {
        '_add_named_raw_device' => 'set',
            'raw_device' => 'get',
    }
    );

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

    #$st->get_infos
    $st->log({level=>1}, 'LSI Raid controller (SAS2Ircu) '.$controller);

    my $info = $st->get_info('lsi-sas-ircu', $controller);

    return $class->$orig(
        'name' => $controller,
        'controllerID' => $controller,
        'consume' => [],
        'st' => $st,
        %{$info->{'controller'}},
        %{$info},
        @_
        );
};

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

    my $cid = $self->controller;
    my $cur_missing_count = $missing_count;
    foreach my $dev (sort { $a->{'enclosure'} <=> $b->{'enclosure'}
                            or $a->{'slot'} <=> $b->{'slot'}
                     }
                     @{$args->{'devices'}}) {
        my ($id,$d);
        if ($dev->{'state'} =~ /MIS/) {
            # disk missing
            $id = $dev->{'enclosure'}.":".$dev->{'slot'}." (".($missing_count++).")";
            my $devpath = 'LSISASIrcu@'.$id;
            $d = $self->newChild(
		'RAID::LSI::SASIrcu::MissingRawDevice',
                $self, $st, $devpath, $dev,
                'raiddevice' => $id,
                'state' => $dev->{'state'},
                'model' => 'Disk missing',
                'size' => 0,
                'slot' => 'none',
                );
        } else {
            $id=$dev->{'enclosure'}.":".$dev->{'slot'};
            if ($id eq '0:0') { # can be the case of FLD drives
                $id .= ' ('.($cur_missing_count++).')';
            }
            #print STDERR "Adding $id\n";
            my $devpath = 'LSISASIrcu@'.$id;
            my $block;
            {
                my $serial = $dev->{'serial-no'}//'';
                if ($serial ne '') {
                    #print STDERR "Serial for $id is $serial\n";
                    $block = $st->blockBySerial($serial);
                }# else { # GUID/WWN is not always unique
                #    $serial = $dev->{'guid'}//'';
                #    if ($serial ne '') {
                #        print STDERR "Guid for $id is $serial\n";
                #        $block = $st->blockBySerial($serial);
                #    }
                #}
            }
            $d = $self->newChild(
		'RAID::LSI::SASIrcu::RawDevice',
                $self, $st, $devpath, $dev,
                'raiddevice' => $id,
                'state' => $dev->{'state'},
                'model' => join(' ', $dev->{'manufacturer'}, $dev->{'model-number'}, $dev->{'serial-no'}),
                'size' => $dev->{'size'}//'0', # No size on some FLD devices
                'slot' => $id,
                );
            if (defined($block)) {
                #print STDERR "$id provide ", $block->name,"\n";
                $d->provideBlock($block);
            }
        }
        $self->_add_device($d);
        $self->_add_named_raw_device($id, $d);
    }
    my $defined_missing_count = $cur_missing_count;
    $cur_missing_count = $missing_count;
    foreach my $dev (sort { $a->{'id'} cmp $b->{'id'} }
                     @{$args->{'volumes'}}) {
        my $devname = $args->{'wwid'}->{$dev->{'wwid'}};
        my $block = $st->block($devname);
        my $raid_device = $self->newElem(
	    'RAID::LSI::SASIrcu::State::RaidDevice',
            $self, $st, $block,
            'size' => $block->blk_info("SIZE"),
            'raid-level' => $dev->{'Type'},
            %{$dev},
            );
        my $state = $self->newElem(
	    'RAID::LSI::SASIrcu::State', $raid_device, $st,
	    'state' => $dev->{'status'},
	    'name' => $raid_device->block->name
	    );

        $self->_add_raid_device($raid_device, $state);
        foreach my $phyid (keys %{$dev->{'PHY'} // {}}) {
            my $phy = $dev->{'PHY'}->{$phyid};
            my $id = $phy->{'enclosure'}.":".$phy->{'slot'};
            if ($id eq '0:0') {
                $id .= ' ('.($cur_missing_count++).')';
            }
            #print STDERR "Getting $id\n";
            my $rdsk = $self->raw_device($id);
            $rdsk->volume($raid_device);
            $rdsk->phyid($phyid);
        }
    }
    if ($cur_missing_count != $defined_missing_count) {
        print STDERR "Internal warning: wrong total of missing drives: $cur_missing_count != $defined_missing_count\n";
    }
    $missing_count = ($cur_missing_count > $defined_missing_count)?$cur_missing_count:$defined_missing_count;
    return $self;
};

sub dname {
    my $self=shift;
    return 'MegaCli: Controller '.$self->controller;
}

sub dotLabel {
    my $self = shift;
    return (
        $self->hw_model,
        #$self->ID,
        #$self->raid_level.': '.$self->raid_name,
        #$self->disp_size($self->used_dev_size).' used per device',
        );
}

sub dotLinks {
    my $self = shift;
    return (
        map {
            if ($_->has_volume) {
                $_->linkname.' -> '.$_->volume->linkname;
            }
        } $self->devices
    );
}

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::SASIrcu::MissingRawDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RawDevice';

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

has 'volume' => (
    is       => 'rw',
    isa      => 'StorageDisplay::Data::RAID::RaidDevice',
    required => 0,
    predicate => 'has_volume',
    );

has 'phyid' => (
    is       => 'rw',
    isa      => 'Num',
    required => 0,
    predicate => 'has_phyid',
    );

around 'dotLabel' => sub {
    my $orig  = shift;
    my $self = shift;
    my @ret = $self->$orig(@_);
    if ($self->has_phyid) {
        $ret[1] = $self->phyid.": enc/slot: ".$self->slot;
    } else {
        $ret[1] = "enc/slot: ".$self->slot;
    }
    return @ret;
};

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::SASIrcu::RawDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RawDevice';

has 'volume' => (
    is       => 'rw',
    isa      => 'StorageDisplay::Data::RAID::RaidDevice',
    required => 0,
    predicate => 'has_volume',
    );

has 'phyid' => (
    is       => 'rw',
    isa      => 'Num',
    required => 0,
    predicate => 'has_phyid',
    );

around 'dotLabel' => sub {
    my $orig  = shift;
    my $self = shift;
    my @ret = $self->$orig(@_);
    if ($self->has_phyid) {
        $ret[1] = $self->phyid.": enc/slot: ".$self->slot;
    } else {
        $ret[1] = "enc/slot: ".$self->slot;
    }
    return @ret;
};

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::SASIrcu::State;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::State';

1;

##################################################################
package StorageDisplay::Data::RAID::LSI::SASIrcu::State::RaidDevice;

use Moose;
use namespace::sweep;
extends 'StorageDisplay::Data::RAID::RaidDevice';

1;

__END__

=pod

=encoding UTF-8

=head1 NAME

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