# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html

package xCAT::PPCscan;
use strict;
use Getopt::Long;
use Socket;
use XML::Simple;
$XML::Simple::PREFERRED_PARSER = 'XML::Parser';
use xCAT::PPCcli qw(SUCCESS EXPECT_ERROR RC_ERROR NR_ERROR);
use xCAT::PPCdb;
use xCAT::GlobalDef;
use xCAT::Usage;
use xCAT::NetworkUtils;
require xCAT::data::ibmhwtypes;

##############################################
# Globals
##############################################
my @header = (
    [ "type",          "%-8s" ],
    [ "name",          "placeholder" ],
    [ "id",            "%-8s" ],
    [ "type-model",    "%-12s" ],
    [ "serial-number", "%-15s" ],
    [ "side",          "%-6s\n" ]);

my @attribs = qw(nodetype node id mtm serial side hcp pprofile parent groups mgt cons hwtype);
my %globalnodetype = (
    fsp   => $::NODETYPE_PPC,
    bpa   => $::NODETYPE_PPC,
    cec   => $::NODETYPE_PPC,
    frame => $::NODETYPE_PPC,
    lpar  => "$::NODETYPE_PPC,$::NODETYPE_OSI"
);
my %globalhwtype = (
    fsp   => $::NODETYPE_FSP,
    bpa   => $::NODETYPE_BPA,
    lpar  => $::NODETYPE_LPAR,
    cec   => $::NODETYPE_CEC,
    frame => $::NODETYPE_FRAME,
);


##########################################################################
# Parse the command line for options and operands
##########################################################################
sub parse_args {

    my $request = shift;
    my %opt     = ();
    my $cmd     = $request->{command};
    my $args    = $request->{arg};

    #############################################
    # Responds with usage statement
    #############################################
    local *usage = sub {
        my $usage_string = xCAT::Usage->getUsage($cmd);
        return ([ $_[0], $usage_string ]);
    };
    #############################################
    # Process command-line arguments
    #############################################
    if (!defined($args)) {
        $request->{method} = $cmd;
        return (\%opt);
    }
    #############################################
    # Checks case in GetOptions, allows opts
    # to be grouped (e.g. -vx), and terminates
    # at the first unrecognized option.
    #############################################
    @ARGV                     = @$args;
    $Getopt::Long::ignorecase = 0;
    Getopt::Long::Configure("bundling");

    if (!GetOptions(\%opt, qw(V|verbose u w x z))) {
        return (usage());
    }
    ####################################
    # Check for "-" with no option
    ####################################
    if (grep(/^-$/, @ARGV)) {
        return (usage("Missing option: -"));
    }
    ####################################
    # Check for an argument
    ####################################
    if (defined($ARGV[0])) {
        return (usage("Invalid Argument: $ARGV[0]"));
    }
    #############################################
    # Check for mutually-exclusive formatting
    #############################################
    if ((exists($opt{x}) + exists($opt{z})) > 1) {
        return (usage());
    }
    #############################################
    # Check for mutually-exclusive flags
    #############################################
    if ((exists($opt{u}) + exists($opt{w})) > 1) {
        return (usage("Flag -u cannot be used with flag -w"));
    }
    ####################################
    # No operands - add command name
    ####################################
    $request->{method} = $cmd;
    return (\%opt);
}



##########################################################################
# Returns short-hostname given an IP
##########################################################################
sub getshorthost {

    my $ip = shift;

    my $host = xCAT::NetworkUtils->gethostname($ip);
    if ($host and !$!) {
        ##############################
        # Get short-hostname
        ##############################
        if ($host =~ /([^\.]+)\./) {
            return ($1);
        }
    }
    ##################################
    # Failed
    ##################################
    return undef;
}


##########################################################################
# Returns I/O bus information
##########################################################################
sub enumerate {

    my $exp    = shift;
    my $hwtype = @$exp[2];
    my $server = @$exp[3];
    my @values = ();
    my %cage   = ();
    my %hwconn = ();
    my $Rc;
    my $filter;

    #########################################
    # Get hardware control point info
    #########################################
    {
        my $hcp = xCAT::PPCcli::lshmc($exp);
        $Rc = shift(@$hcp);

        #########################################
        # Return error
        #########################################
        if ($Rc != SUCCESS) {
            return (@$hcp[0]);
        }
        #########################################
        # Success
        #########################################
        my ($model, $serial) = split /,/, @$hcp[0];
        my $id   = "";
        my $prof = "";
        my $ips  = "";
        my $bpa  = "";
        my $side = "";

        push @values, join(",",
            $hwtype, $server, $id, $model, $serial, $side, $server, $prof, $bpa, $ips);
    }

    #########################################
    # Save hardware connections
    #########################################
    if ($hwtype ne "ivm") {    #Not applicable for IVM
        $filter = "type_model_serial_num,ipaddr,sp,side";
        my $conns = xCAT::PPCcli::lssysconn($exp, "alls", $filter);
        $Rc = shift(@$conns);

        #########################################
        # Return error
        #########################################
        if ($Rc != SUCCESS) {
            return (@$conns[0]);
        }

        foreach my $con (@$conns) {
            my ($mtms, $ipaddr, $sp, $side) = split /,/, $con;
            my $value = undef;

            if ($sp =~ /^primary$/ or $side =~ /^a$/) {
                $value = "A";
            } elsif ($sp =~ /^secondary$/ or $side =~ /^b$/) {
                $value = "B";
            }

            $hwconn{$ipaddr} = "$mtms,$value";
        }
    }

    #########################################
    # Enumerate frames (IVM has no frame)
    #########################################
    if ($hwtype ne "ivm") {
        $filter = "type_model,serial_num,name,frame_num,ipaddr_a,ipaddr_b";
        my $frames = xCAT::PPCcli::lssyscfg($exp, "bpas", $filter);
        $Rc = shift(@$frames);

        #####################################
        # Expect error
        #####################################
        if ($Rc == EXPECT_ERROR) {
            return (@$frames[0]);
        }
        #####################################
        # CLI error
        #####################################
        if ($Rc == RC_ERROR) {
            return (@$frames[0]);
        }
        #####################################
        # If frames found, enumerate cages
        #####################################
        if ($Rc != NR_ERROR) {
            $filter = "cage_num,type_model_serial_num";

            foreach my $val (@$frames) {
                my ($model, $serial) = split /,/, $val;
                my $mtms = "$model*$serial";

                my $cages = xCAT::PPCcli::lssyscfg($exp, "cage", $mtms, $filter);
                $Rc = shift(@$cages);

                #############################
                # Skip...
                # Frame in bad state
                #############################
                if ($Rc != SUCCESS) {
                    push @values, "# $mtms: ERROR @$cages[0]";
                    next;
                }
                #############################
                # Success
                #############################
                foreach (@$cages) {
                    my ($cageid, $mtms) = split /,/;
                    $cage{$mtms} = "$cageid,$val";
                }
            }
        }
    }
    #########################################
    # Enumerate CECs
    #########################################
    $filter = "name,type_model,serial_num,ipaddr";
    my $cecs = xCAT::PPCcli::lssyscfg($exp, "fsps", $filter);
    $Rc = shift(@$cecs);

    #########################################
    # Return error
    #########################################
    if ($Rc != SUCCESS) {
        return (@$cecs[0]);
    }
    foreach (@$cecs) {
        #####################################
        # Get CEC information
        #####################################
        my ($fsp, $model, $serial, $ips) = split /,/;
        my $mtms   = "$model*$serial";
        my $cageid = "";
        my $fname  = "";

        #####################################
        # Get cage CEC is in
        #####################################
        my $frame = $cage{$mtms};

        #####################################
        # Save frame information
        #####################################
        if (defined($frame)) {
            my ($cage, $model, $serial, $name, $id, $ipa, $ipb) = split /,/, $frame;
            my $prof = "";
            my $bpa  = "";
            $cageid = $cage;
            $fname  = $name;

            #######################################
            # Convert IP-A to short-hostname.
            # If fails, use user-defined FSP name
            #######################################
            my $host = getshorthost($ipa);
            if (defined($host)) {
                $fname = $host;
            }

            #######################################
            # Save two sides of BPA seperately
            #######################################
            #my $bpastr = join( ",","bpa",$fname,$id,$model,$serial,"A",$server,$prof,$bpa,$ipa);
            #if ( !grep /^\Q$bpastr\E$/, @values)
            #{
            #    push @values, join( ",",
            #        "bpa",$fname,$id,$model,$serial,"A",$server,$prof,$bpa,$ipa);
            #}
            #$bpastr = join( ",","bpa",$fname,$id,$model,$serial,"B",$server,$prof,$bpa,$ipb);
            #if ( !grep /^\Q$bpastr\E$/, @values)
            #{
            #    push @values, join( ",",
            #        "bpa",$fname,$id,$model,$serial,"B",$server,$prof,$bpa,$ipb);
            #}
            push @values, join(",",
"frame", $fname, $id, $model, $serial, "", $server, $prof, $bpa, "");

        }
        #####################################
        # Save CEC information
        #####################################
        my $prof = "";

        #######################################
        # Convert IP to short-hostname.
        # If fails, use user-defined FSP name
        #######################################
        my $host = getshorthost($ips);
        if (defined($host)) {
            $fsp = $host;
        }

        my $mtmss = $hwconn{$ips};
        if ($hwtype ne "ivm") {    #Not applicable for IVM
            my ($mtms, $side) = split /,/, $mtmss;
        }
        push @values, join(",",
"cec", $fsp, $cageid, $model, $serial, "", $server, $prof, $fname, "");

        #####################################
        # Enumerate LPARs
        #####################################
        $filter = "name,lpar_id,default_profile,curr_profile";
        my $lpars = xCAT::PPCcli::lssyscfg($exp, "lpar", $mtms, $filter);
        $Rc = shift(@$lpars);

        ####################################
        # Expect error
        ####################################
        if ($Rc == EXPECT_ERROR) {
            return (@$lpars[0]);
        }
        ####################################
        # Skip...
        # CEC could be "Incomplete" state
        ####################################
        if ($Rc == RC_ERROR) {
            push @values, "# $mtms: ERROR @$lpars[0]";
            next;
        }
        ####################################
        # No results found
        ####################################
        if ($Rc == NR_ERROR) {
            next;
        }
        foreach (@$lpars) {
            my ($name, $lparid, $dprof, $curprof) = split /,/;
            my $prof = (length($curprof) && ($curprof !~ /^none$/)) ? $curprof : $dprof;
            my $ips  = "";
            my $port = "";

            #####################################
            # Save LPAR information
            #####################################
            push @values, join(",",
"lpar", $name, $lparid, $model, $serial, $port, $server, $prof, $fsp, $ips);
        }
    }
    return (\@values);
}



##########################################################################
# Format responses
##########################################################################
sub format_output {

    my $request    = shift;
    my $exp        = shift;
    my $values     = shift;
    my $opt        = $request->{opt};
    my %output     = ();
    my $hwtype     = @$exp[2];
    my $max_length = 0;
    my $result;

    ###########################################
    # -w flag for write to xCat database
    ###########################################
    if (exists($opt->{w})) {
        my $server = @$exp[3];
        my $uid    = @$exp[4];
        my $pw     = @$exp[5];

        #######################################
        # Strip errors for results
        #######################################
        my @val = grep(!/^#.*: ERROR /, @$values);
        xCAT::PPCdb::add_ppc($hwtype, \@val, '', '', "PPC");
    }

    ###########################################
    # -u flag for write to xCat database
    ###########################################
    if (exists($opt->{u})) {
        #######################################
        # Strip errors for results
        #######################################
        my @val = grep(!/^#.*: ERROR /, @$values);
        $values = xCAT::PPCdb::update_ppc($hwtype, \@val);
        if (exists($opt->{x}) or exists($opt->{z}))
        {
            unshift @$values, "hmc";
        }
    }

    ###########################################
    # -x flag for xml format
    ###########################################
    if (exists($opt->{x})) {
        $result .= format_xml($hwtype, $values);
    }
    ###########################################
    # -z flag for stanza format
    ###########################################
    elsif (exists($opt->{z})) {
        $result .= format_stanza($hwtype, $values);
    }
    else {
        $result = sprintf("#Updated following nodes:\n") if (exists($opt->{u}));
        #######################################
        # Get longest name for formatting
        #######################################
        my $nodehash;
        my @errmsg;
        foreach (@$values) {
            ##############################################
            # Skip error message after saving it for last
            ##############################################
            if (/^#.*: ERROR /) {
                push @errmsg, $_;
                next;
            }
            /([^\,]+),([^\,]+),/;
            $nodehash->{ $1 . $2 } = $_;
            my $length = length($2);
            $max_length = ($length > $max_length) ? $length : $max_length;
        }
        my $format = sprintf("%%-%ds", ($max_length + 2));
        $header[1][1] = $format;

        #######################################
        # Add header
        #######################################
        foreach (@header) {
            $result .= sprintf(@$_[1], @$_[0]);
        }
        #######################################
        # Add node information
        #######################################
        foreach (sort keys %$nodehash) {
            my @data = split /,/, $nodehash->{$_};
            my $i = 0;

            foreach (@header) {
                my $d = $data[ $i++ ];

                ###############################
                # Use IPs instead of
                # hardware control address
                ###############################
                if (@$_[0] eq "address") {
                    if ($data[0] !~ /^(hmc|ivm)$/) {
                        $d = $data[8];
                    }
                }
                $result .= sprintf(@$_[1], $d);
            }
        }
        #######################################
        # Add any error messages
        #######################################
        foreach (@errmsg) {
            $result .= "\n$_";
        }
    }
    $output{data} = [$result];
    return ([ \%output ]);
}



##########################################################################
# Stanza formatting
##########################################################################
sub format_stanza {

    my $hwtype = shift;
    my $values = shift;

    my $result;
    my $nodehash;

    #####################################
    # Skip hardware control point
    #####################################
    shift(@$values);

    foreach (@$values) {
        ###################################
        # Skip error message
        ###################################
        if (/^#.*: ERROR /) {
            next;
        }
        /[^\,]+,([^\,]+),/;
        $nodehash->{$1} = $_;
    }

    foreach (sort keys %$nodehash) {
        my @data = split /,/, $nodehash->{$_};
        my $type = $data[0];
        my $i    = 0;

        #################################
        # Node attributes
        #################################
        $result .= "$data[1]:\n\tobjtype=node\n";

        #################################
        # Add each attribute
        #################################
        my $mtm = undef;
        foreach (@attribs) {
            my $d = $data[ $i++ ];

            if (/^node$/) {
                next;
            } elsif (/^nodetype$/) {
                $d = $globalnodetype{$type};
            } elsif (/^hwtype$/) {
                $d = $globalhwtype{$type};
            } elsif (/^groups$/) {
                next;

                #$d = "$type,all";
            } elsif (/^mgt$/) {
                $d = $hwtype;
            } elsif (/^cons$/) {
                if ($type eq "lpar") {
                    $d = $hwtype;
                } else {
                    $d = undef;
                }

            } elsif (/^(mtm|serial)$/) {
                if ($type eq "lpar") {
                    $d = undef;
                } elsif (/^mtm$/) {
                    $mtm = $d;
                }
            } elsif (/^side$/) {
                unless ($type =~ /^fsp|bpa$/) {
                    next;
                }
            }
            $result .= "\t$_=$d\n";
        }
        my $tmp_groups = "$type,all";
        if (defined($mtm)) {
            my $tmp_pre = xCAT::data::ibmhwtypes::parse_group($mtm);
            if (defined($tmp_pre)) {
                $tmp_groups .= ",$tmp_pre";
            }
        }
        $result .= "\tgroups=$tmp_groups\n";
    }
    return ($result);
}


##########################################################################
# XML formatting
##########################################################################
sub format_xml {

    my $hwtype = shift;
    my $values = shift;
    my $xml;
    my $nodehash;

    #####################################
    # Skip hardware control point
    #####################################
    shift(@$values);

    foreach (@$values) {
        ###################################
        # Skip error message
        ###################################
        if (/^#.*: ERROR /) {
            next;
        }
        /[^\,]+,([^\,]+),/;
        $nodehash->{$1} = $_;
    }
    #####################################
    # Create XML formatted attributes
    #####################################
    foreach (sort keys %$nodehash) {
        my @data = split /,/, $nodehash->{$_};
        my $type = $data[0];
        my $i    = 0;

        #################################
        # Initialize hash reference
        #################################
        my $href = {
            Node => {}
        };
        #################################
        # Add each attribute
        #################################
        my $mtm = undef;
        foreach (@attribs) {
            my $d = $data[ $i++ ];

            if (/^nodetype$/) {
                $d = $globalnodetype{$type};
            } elsif (/^hwtype$/) {
                $d = $globalhwtype{$type};
            } elsif (/^groups$/) {
                next;

                #$d = "$type,all";
            } elsif (/^mgt$/) {
                $d = $hwtype;
            } elsif (/^cons$/) {
                if ($type eq "lpar") {
                    $d = $hwtype;
                } else {
                    $d = undef;
                }
            } elsif (/^(mtm|serial)$/) {
                if ($type eq "lpar") {
                    $d = undef;
                } elsif (/^mtm$/) {
                    $mtm = $d;
                }
            } elsif (/^side$/) {
                unless ($type =~ /^fsp|bpa$/) {
                    next;
                }
            }
            $href->{Node}->{$_} = $d;
        }
        my $tmp_groups = "$type,all";
        if (defined($mtm)) {
            my $tmp_pre = xCAT::data::ibmhwtypes::parse_group($mtm);
            if (defined($tmp_pre)) {
                $tmp_groups .= ",$tmp_pre";
            }
        }
        $href->{Node}->{groups} = $tmp_groups;
        #################################
        # XML encoding
        #################################
        $xml .= XMLout($href,
            NoAttr   => 1,
            KeyAttr  => [],
            RootName => undef);
    }
    return ($xml);
}



##########################################################################
# Returns I/O bus information
##########################################################################
sub rscan {

    my $request = shift;
    my $dummy   = shift;
    my $exp     = shift;
    my $args    = $request->{arg};
    my $server  = @$exp[3];

    ###################################
    # Enumerate all the hardware
    ###################################
    my $values = enumerate($exp);
    if (ref($values) ne 'ARRAY') {
        return ([ [ $server, $values, 1 ] ]);
    }
    ###################################
    # Success
    ###################################
    my $result = format_output($request, $exp, $values);
    unshift @$result, "FORMATDATA6sK4ci";
    return ($result);

}



1;







