package xCAT_plugin::tree;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}
use lib "$::XCATROOT/lib/perl";

use xCAT::NodeRange;
use Data::Dumper;
use xCAT::Utils;
use Sys::Syslog;
use xCAT::GlobalDef;
use xCAT::Table;
use Getopt::Long;
use xCAT::SvrUtils;

#use strict;

sub handled_commands
{
    # currently, only for lstree command.
    return {
        lstree => "tree",
    };
}

sub usage
{
    my $command  = shift;
    my $callback = shift;

    if ($command eq "lstree")
    {
        &usage_lstree($callback);
        return 0;
    }
    else
    {
        return 1;
    }
}

#----------------------------------------------------------------------------

=head3  usage_lstree

=cut

#-----------------------------------------------------------------------------
sub usage_lstree
{
    my $callback = shift;

    my $rsp;
    push @{ $rsp->{data} },
"\n  lstree - Display the tree of service node hierarchy, hardware hierarchy, or VM hierarchy.";
    push @{ $rsp->{data} }, "  Usage: ";
    push @{ $rsp->{data} }, "\tlstree [-h | --help]";
    push @{ $rsp->{data} }, "or";
    push @{ $rsp->{data} }, "\tlstree [-s | --servicenode] [-H | --hardwaremgmt] [-v | --virtualmachine] [noderange]";

    xCAT::MsgUtils->message("I", $rsp, $callback);
    return 0;
}

#----------------------------------------------------------------------------

=head3  process_request

=cut

#-----------------------------------------------------------------------------
sub process_request
{
    my $request  = shift;
    my $callback = shift;
    my @nodes    = ();

    if ($request->{node})
    {
        @nodes = @{ $request->{node} };
    }
    else
    {
        # handle all nodes by default.
        my $nodelist = xCAT::Table->new('nodelist');
        unless ($nodelist)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open nodelist table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        my @entries = ($nodelist->getAllNodeAttribs([qw(node)]));
        foreach (@entries)
        {
            push @nodes, $_->{node};
        }
    }

    my $command = $request->{command}->[0];
    if ($command eq "lstree")
    {
        &lstree($request, $callback, \@nodes);
        return 0;
    }
    else
    {
        $callback->({ error => ["error in code..."], errorcode => [127] });
        $request = {};
        return 0;
    }
}

#----------------------------------------------------------------------------

=head3  lstree

=cut

#-----------------------------------------------------------------------------
sub lstree
{
    my $request  = shift;
    my $callback = shift;
    my $nodelist = shift;

    unless ($request->{arg} || defined $nodelist)
    {
        &usage_lstree($callback);
        return 1;
    }

    # parse the options
    Getopt::Long::Configure("no_pass_through");
    Getopt::Long::Configure("bundling");

    if ($request->{arg})
    {
        @ARGV = @{ $request->{arg} };

        if (
            !GetOptions(
                'h|help'           => \$::HELP,
                's|servicenode'    => \$::SVCNODE,
                'H|hardwaremgmt'   => \$::HDWR,
                'v|virtualmachine' => \$::VMACHINE,
            )
          )
        {
            &usage_lstree($callback);
            return 1;
        }
    }

    if ($::HELP)
    {
        &usage_lstree($callback);
        return 0;
    }

    if (!$::SVCNODE && !$::HDWR && !$::VMACHINE)
    {
        # show both vmtree and hwtree by default.
        $::SHOWALL = 1;
    }

    my %hwtrees;
    my %vmtrees;
    my %sntrees;

    # servicenode hierarchy
    if ($::SVCNODE)
    {
        # build up sn tree hash.
        my $snhash;
        my @entries;
        my $restab = xCAT::Table->new('noderes');
        if ($restab)
        {
            $snhash = $restab->getNodesAttribs($nodelist, ['servicenode']);
            @entries = $restab->getAllNodeAttribs([ 'node', 'servicenode' ]);
        }
        else
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open noderes table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        foreach my $node (@$nodelist)
        {
            # servicenode defined for this node
            if ($snhash->{$node}->[0]->{'servicenode'})
            {
                unless (grep(/$node/, @{ $sntrees{ $snhash->{$node}->[0]->{'servicenode'} } }))
                {
                    push @{ $sntrees{ $snhash->{$node}->[0]->{'servicenode'} } }, $node;
                }
            }
            else    # need to know if itself is service node
            {
                foreach my $ent (@entries)
                {
                    if ($ent->{servicenode} eq $node)
                    {
                        unless (grep(/$ent->{node}/, @{ $sntrees{$node} }))
                        {
                            push @{ $sntrees{$node} }, $ent->{node};
                        }
                    }
                }
            }

        }

        #print Dumper(\%sntreehash);

        # show service node tree
        &showtree(\%sntrees, 0, 0, $callback);
    }

    # get hwtree hash from each plugin
    if ($::HDWR || $::SHOWALL)
    {
        # classify the nodes
        # read nodehm.mgt
        my $hmhash;
        my $hmtab = xCAT::Table->new('nodehm');
        if ($hmtab)
        {
            $hmhash = $hmtab->getNodesAttribs($nodelist, ['mgt']);
        }
        else
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open nodehm table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        # may add new support later.
        my @supportedhw = ("hmc", "fsp", "blade", "ipmi");
        my %hwnodes;

        foreach my $node (@$nodelist)
        {
            # mgt defined for this node
            unless ($hmhash->{$node}->[0]->{'mgt'})
            {
                next;
            }

            if (grep(/$hmhash->{$node}->[0]->{'mgt'}/, @supportedhw))
            {
                push @{ $hwnodes{ $hmhash->{$node}->[0]->{'mgt'} } }, $node;
            }
        }

        if (%hwnodes)
        {
            foreach my $type (keys %hwnodes)
            {
                eval "require xCAT_plugin::$type";
                $hwtrees{$type} = ${ "xCAT_plugin::" . $type . "::" }{genhwtree}->(\@{ $hwnodes{$type} }, $callback);
            }
        }

        #print Dumper(\%hwtrees);
    }

    if ($::HDWR && !$::VMACHINE)
    {
        &showtree(0, \%hwtrees, 0, $callback);
    }

    # generate vmtree from xcat vm table
    if ($::VMACHINE || $::SHOWALL)
    {

        # here is a special case for zvm.
        ########### start ################
        # read nodehm.mgt
        my $hmhash;
        my $hmtab = xCAT::Table->new('nodehm');
        if ($hmtab)
        {
            $hmhash = $hmtab->getNodesAttribs($nodelist, ['mgt']);
        }
        else
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open nodehm table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        my @znodes;

        foreach my $node (@$nodelist)
        {
            # mgt defined for this node
            unless ($hmhash->{$node}->[0]->{'mgt'})
            {
                next;
            }

            if ($hmhash->{$node}->[0]->{'mgt'} =~ /zvm/)
            {
                push @znodes, $node;
            }
        }

        if (scalar @znodes)
        {
            eval "require xCAT_plugin::zvm";
            my $ret = xCAT_plugin::zvm::listTree($callback, \@znodes);
        }

        ########### end ################

        # read vm.host
        my $vmhash;

        my $vmtab = xCAT::Table->new('vm');
        if ($vmtab)
        {
            $vmhash = $vmtab->getNodesAttribs($nodelist, ['host']);
        }
        else
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open vm table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        my @entries = $vmtab->getAllNodeAttribs([ 'node', 'host' ]);

        foreach my $node (@$nodelist)
        {
            foreach my $ent (@entries)
            {
                if ($ent->{host} =~ /$node/)    # host
                {
                    push @{ $vmtrees{$node} }, $ent->{node};
                }
            }

            if ($vmhash && $vmhash->{$node}->[0]->{'host'})    # vm
            {
                unless (grep(/$node/, @{ $vmtrees{ $vmhash->{$node}->[0]->{'host'} } }))
                {
                    push @{ $vmtrees{ $vmhash->{$node}->[0]->{'host'} } }, $node;
                }
            }
        }

        #print Dumper(\%vmtrees);
    }

    # vm tree output
    if (!$::HDWR && $::VMACHINE)
    {
        &showtree(0, 0, \%vmtrees, $callback);
    }

    if ($::SHOWALL || ($::HDWR && $::VMACHINE))
    {
        &showtree(0, \%hwtrees, \%vmtrees, $callback);
    }
    return;

}    # lstree end

#----------------------------------------------------------------------------

=head3  showtree

=cut

#-----------------------------------------------------------------------------
sub showtree
{
    my $snthash  = shift;
    my $hwthash  = shift;
    my $vmthash  = shift;
    my $callback = shift;

    my %sntrees = %$snthash;
    my %hwtrees = %$hwthash;
    my %vmtrees = %$vmthash;

    # temp workaround before we integrate LPARs into vm table
    my $lparinvm = 0;
    my @entries;

    if (!$lparinvm)
    {
        # read ppc table
        my $ppctab = xCAT::Table->new('ppc');
        unless ($ppctab)
        {
            my $rsp = {};
            $rsp->{data}->[0] = "Can not open ppc table.\n";
            xCAT::MsgUtils->message("E", $rsp, $callback, 1);
        }

        @entries = $ppctab->getAllNodeAttribs([ 'node', 'id', 'parent' ]);
    }

    if (%sntrees)
    {
        my $rsp;

        foreach my $sn (sort(keys %sntrees))
        {
            push @{ $rsp->{data} }, "Service Node: $sn";
            foreach my $cn (sort(@{ $sntrees{$sn} }))
            {
                push @{ $rsp->{data} }, "|__$cn";
            }
            push @{ $rsp->{data} }, "\n";
        }
        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    # show hardware hierarchy
    if (%hwtrees)
    {
        my $rsp;

        # hmc tree output
        foreach my $hmc (sort(keys %{ $hwtrees{hmc} }))
        {
            push @{ $rsp->{data} }, "HMC: $hmc";
            foreach my $frame (sort(keys %{ $hwtrees{hmc}{$hmc} }))
            {
                if ($frame eq '0')
                {
                    # no frame
                    push @{ $rsp->{data} }, "|__Frame: n/a";
                }
                else
                {
                    # get bpas for frame.
                    my $bpas = xCAT::DBobjUtils->getchildren($frame);
                    my $bpalist = join ',', @$bpas;
                    push @{ $rsp->{data} }, "|__Frame: $frame $bpalist";
                }

                foreach my $cec (sort @{ $hwtrees{hmc}{$hmc}{$frame} })
                {

                    # get fsps for cec.
                    my $fsps = xCAT::DBobjUtils->getchildren($cec);
                    my $fsplist = join ',', @$fsps;
                    push @{ $rsp->{data} }, "   |__CEC: $cec $fsplist";

                    if ($lparinvm)
                    {
                        # if show both, tailing with vmtree.
                        if (%vmtrees)
                        {
                            foreach my $host (sort(keys %vmtrees))
                            {
                                if ($host =~ /$cec/)
                                {
                                    foreach my $vm (sort(@{ $vmtrees{$host} }))
                                    {
                                        push @{ $rsp->{data} }, "      |__ $vm";
                                    }

                                    # remove it as shown
                                    undef $vmtrees{$host};
                                }
                            }
                        }
                    }
                    elsif ($::VMACHINE || $::SHOWALL) # temp workaround before we integrate LPARs into vm table
                    {
                        my %vm;

                        # get all lpars in this cec.
                        foreach my $ent (@entries)
                        {
                            if ($ent->{parent} =~ /$cec/)
                            {
                                $vm{ $ent->{id} } = $ent->{node};
                            }
                        }

                        foreach my $id (sort(keys %vm))
                        {
                            push @{ $rsp->{data} }, "      |__LPAR $id: $vm{$id}";
                        }
                    }
                }
            }
            push @{ $rsp->{data} }, "\n";
        }

        # DFM tree output
        foreach my $sfp (sort(keys %{ $hwtrees{fsp} }))
        {
            if ($sfp eq '0')
            {
                # no frame
                push @{ $rsp->{data} }, "Service Focal Point: n/a";
            }
            else
            {
                push @{ $rsp->{data} }, "Service Focal Point: $sfp";
            }

            foreach my $frame (sort(keys %{ $hwtrees{fsp}{$sfp} }))
            {
                if ($frame eq '0')
                {
                    # no frame
                    push @{ $rsp->{data} }, "|__Frame: n/a";
                }
                else
                {
                    # get bpas for frame.
                    my $bpas = xCAT::DBobjUtils->getchildren($frame);
                    my $bpalist = join ',', @$bpas;
                    push @{ $rsp->{data} }, "|__Frame: $frame $bpalist";
                }

                foreach my $cec (sort @{ $hwtrees{fsp}{$sfp}{$frame} })
                {

                    # get fsps for cec.
                    my $fsps = xCAT::DBobjUtils->getchildren($cec);
                    my $fsplist = join ',', @$fsps;
                    push @{ $rsp->{data} }, "   |__CEC: $cec $fsplist";

                    if ($lparinvm)
                    {
                        # if show both, tailing with vmtree.
                        if (%vmtrees)
                        {
                            foreach my $host (sort(keys %vmtrees))
                            {
                                if ($host =~ /$cec/)
                                {
                                    foreach my $vm (sort(@{ $vmtrees{$host} }))
                                    {
                                        push @{ $rsp->{data} }, "      |__ $vm";
                                    }

                                    # remove it as shown
                                    undef $vmtrees{$host};
                                }
                            }
                        }
                    }
                    elsif ($::VMACHINE || $::SHOWALL) # temp workaround before we integrate LPARs into vm table
                    {
                        my %vm;

                        # get all lpars in this cec.
                        foreach my $ent (@entries)
                        {
                            if ($ent->{parent} =~ /$cec/)
                            {
                                $vm{ $ent->{id} } = $ent->{node};
                            }
                        }

                        foreach my $id (sort(keys %vm))
                        {
                            push @{ $rsp->{data} }, "      |__LPAR $id: $vm{$id}";
                        }
                    }
                }
            }
            push @{ $rsp->{data} }, "\n";
        }

        # blade tree output
        foreach my $mm (sort(keys %{ $hwtrees{blade} }))
        {
            push @{ $rsp->{data} }, "Management Module: $mm";

            foreach my $slot (sort(keys %{ $hwtrees{blade}{$mm} }))
            {
                my $blade = $hwtrees{blade}{$mm}{$slot};
                push @{ $rsp->{data} }, "|__Blade $slot: $blade";

                # if show both, tailing with vmtree.
                if (%vmtrees)
                {
                    foreach my $host (sort(keys %vmtrees))
                    {
                        if ($host =~ /$blade/)
                        {
                            foreach my $vm (sort(@{ $vmtrees{$host} }))
                            {
                                push @{ $rsp->{data} }, "   |__ $vm";
                            }

                            # remove it as shown
                            undef $vmtrees{$host};
                        }
                    }
                }
            }
            push @{ $rsp->{data} }, "\n";
        }

        # ipmi tree output
        foreach my $bmc (sort(keys %{ $hwtrees{ipmi} }))
        {
            push @{ $rsp->{data} }, "BMC: $bmc";
            foreach my $svr (sort(@{ $hwtrees{ipmi}{$bmc} }))
            {
                push @{ $rsp->{data} }, "|__Server: $svr";

                # if show both, tailing with vmtree.
                if (%vmtrees)
                {
                    foreach my $host (sort(keys %vmtrees))
                    {
                        if ($host =~ /$svr/)
                        {
                            foreach my $vm (sort(@{ $vmtrees{$host} }))
                            {
                                push @{ $rsp->{data} }, "   |__ $vm";
                            }

                            # remove it as shown
                            undef $vmtrees{$host};
                        }
                    }
                }
            }
            push @{ $rsp->{data} }, "\n";
        }

        xCAT::MsgUtils->message("I", $rsp, $callback);
    }

    # show VM hierarchy
    if (%vmtrees)
    {
        my $rsp;
        foreach my $host (sort(keys %vmtrees))
        {
            if (defined $vmtrees{$host})
            {
                push @{ $rsp->{data} }, "Server: $host";
                foreach my $vm (sort(@{ $vmtrees{$host} }))
                {
                    push @{ $rsp->{data} }, "|__ $vm";
                }
            }
            push @{ $rsp->{data} }, "\n";
        }
        xCAT::MsgUtils->message("I", $rsp, $callback);

    }

    return;
}



1;