#!/usr/bin/env perl
# IBM(c) 2007 EPL license http://www.eclipse.org/legal/epl-v10.html
#####################################################################
BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : -d '/opt/xcat' ? '/opt/xcat' : '/usr';
}
use lib "$::XCATROOT/lib/perl";
use xCAT::MsgUtils;
use xCAT::DSHCLI;
use locale;
use Getopt::Std;

eval "use Sort::Versions qw/versioncmp/; 1;" or  *versioncmp = sub ($$) { ($a,$b)= @_ ; return $a cmp $b };

#####################################################################
#                                                                   #
# Module: xdshbak                                                   #
#                                                                   #
#-------------------------------------------------------------------#
#                                                                   #
# Description: Filters output from multiple nodes by listing        #
#              each distinct set of lines only once, or formats     #
#              output from multiple nodes preceded by the hostname  #
#                                                                   #
# Inputs:                                                           #
#  -b : bare output, don't prepend hostname per line. only with -x  #
#  -c : list distinct output only once                              #
#  -h : usage                                                       #
#  -x : omit extra header output for each node.                     #
#            Can not be used with -c.                               #
#  -q : quiet mode                                                  #
#                                                                   #
# Ouputs:                                                           #
#       Filtered output                                             #
#                                                                   #
# Syntax (example):                                                 #
#       xdsh host1,host2 -vi ls | xdshbak                           #
#                                                                   #
# External Ref: None                                                #
#                                                                   #
# Internal Ref: None                                                #
#                                                                   #
#####################################################################

#-----------------------------------------------------------------------------
#
# Main line code. First, error checking.
#
#-----------------------------------------------------------------------------

$::dsh_command = 'xdshbak';

#
# Process the command line...
#
if (!getopts('bcxhq'))
{    # Gather options; if errors
    &d_syntax;
    exit(-1);
}

if ($::opt_h)
{
    &d_syntax;
    exit(0);
}

if ($::opt_c && $::opt_x)
{
    &d_syntax;
    exit(-1);
}    # these 2 options are mutually exclusive

if ($::opt_b && !($::opt_x)) {
    &d_syntax;
    exit(-1);
}    # -b only makes sense with -x

if ($::opt_c)
{
    $compress++;
}

if ($::opt_q)
{
    $quiet++;
}

#
# Read stdin until eof. If compaction is not specified, create an array
# of the following data structures, one element per host:
#
# hostname: line1.line2...linen
#
# newlines are left in.
#
# If compaction is specified, create a binary tree with one element per
# distinct hostname output. Associated with each tree element is an
# string of hostnames. Each of these hostnames had output corresponding
# to the line1.sepchar.line2.sepchar.....linen element in the tree.
#
#
# Input is hostname: line
#
# Assumption is made that all lines have this format - if not, they are
# ignored, or the hostname may be invalid
#

select(STDOUT);
$| = 1;

LINE:
while (<STDIN>)
{

    #
    # feedback on lines processed
    #
    $num_lines++;
    if (!($quiet)) {
        $num_lines % 1000 == 0 && print STDOUT ".";
    }
    if (/: /)
    {
        @fields = split(': ', $_);
        $hn     = shift(@fields);
        $ln     = join(': ', @fields);
        if (!defined($cur_hn))
        {
            $cur_hn = $hn;
            push(@hs, $hn);
            $long_ln = $ln;
            next LINE;
        }
        if ($hn eq $cur_hn)
        {
            $long_ln = $long_ln . $ln;
            next LINE;
        }
        else
        {
            if ($compress)
            {
                if ($long_ln eq $prev_ln)
                {
                    $hdr{$prev_index} = $hdr{$prev_index} . ":" . $cur_hn;
                }
                else
                {
                    $prev_index = &insert_tree($cur_hn, $long_ln);
                    $prev_ln = $long_ln;
                }
                $long_ln = $ln;
                $cur_hn  = $hn;
                push(@hs, $hn);
            }
            else
            {
                $ls{$cur_hn} = $long_ln;
                $long_ln     = $ln;
                $cur_hn      = $hn;
                push(@hs, $hn);
            }
        }
    }
}

#
# If compression specified:
# Print the lines for each set of hosts with identical output preceded
# by the hostnames in [ hostname - hostname ] format.
# The hostnames are not sorted.
#
#
# If compression not specified:
# Print the lines for each host preceded by an underlined host name
# The hostnames are sorted alphabetically
#

!($quiet) && $num_lines > 999 && print STDOUT "\n";
if ($compress)
{
    if ($long_ln eq $prev_ln)
    {
        $hdr{$prev_index} = $hdr{$prev_index} . ":" . $cur_hn;
    }
    else
    {
        &insert_tree($cur_hn, $long_ln);
    }
    &print_tree;
}
else
{
    $ls{$cur_hn} = $long_ln;
    &print_list;
}

#-----------------------------------------------------------------------------
#
# d_syntax
#
# Display help info
#
#-----------------------------------------------------------------------------

sub d_syntax
{
    # Duplicates POD - pod2usage ?
    my @usage;
    push @usage, "Usage: xdshbak   [-c | -x [-b] | -h | -q]";
    push @usage, "    -b : bare output, don't prepend hostname per line. only with -x";
    push @usage, "    -c : compresses the output by listing unique output only once.";
    push @usage, "    -h : help";
    push @usage, "    -x : omit extra header output for each node.  Can not be used with -c.";
    push @usage, "    -q : quiet mode.";
    my $usage = join "\n", @usage;
    xCAT::MsgUtils->message("I", $usage);

}

#-----------------------------------------------------------------------------
#
# print_list
#
# Print the host output, by sorted host, in the following format:
#
# HOST: hostname
# --------------
# line
# line
#
# Two global data structures are used. @hs is an array of all the
# hostnames found. %ls{} is an associative array of (concatenated)
# lines, indexed on hostnames.
#
#-----------------------------------------------------------------------------
sub print_list
{

    local (@lines, $numhosts, $hn_string, $l_string);

    foreach $hostname (sort { versioncmp($a, $b) } @hs)
    {
        if (!$::opt_x) { ($num_hosts >= 1) && print "\n"; }
        $num_hosts++;

        if ($::opt_x) {
            if ($::opt_b) {
                # Bare output
                print $ls{$hostname};
            } else {
                # No header. hostname prepended on every line
                map { print "$hostname: $_\n" } split(/\n/, $ls{$hostname});
            }
        }
        else
        {

            #$hn_string = `$SPMSG DSH $MSGCAT  INFO510 'HOST: %1\$s\n' $hostname`;
            xCAT::MsgUtils->message("I", "HOST:$hostname");

            printf '%.' . (6 + length($hostname)) . "s\n",
              '---------------------------------------------------------------';
            print "$ls{$hostname}";
        }
    }
}

#-----------------------------------------------------------------------------
#
# display_wc
#
# Display the hostnames returning output.
#
#-----------------------------------------------------------------------------

sub display_wc
{

    local ($i);
    $i = 0;
    while ($i <= $#wc - 1)
    {
        print "$wc[$i], ";
        $i++;
    }
    print "$wc[$#wc]\n";
}

#-----------------------------------------------------------------------------
#
# print_tree
#
# Print the host output, in the following format:
#
# HOSTS --------------------------------------
# hostname  hostname hostname
# --------------------------------------------
# line
# line
#

#-----------------------------------------------------------------------------
sub print_tree
{

    local ($num_hosts, $hn_string, $pager);

    foreach my $index (@indices)
    {
        ($num_hosts >= 1) && print "\n";
        $num_hosts++;
        @wc = split(/:/, $hdr{$index});
        @wc = sort { versioncmp($a, $b) } @wc;

        #system "$SPMSG DSH $MSGCAT  INFO511 'HOSTS '";
        xCAT::MsgUtils->message("I",
          "HOSTS -------------------------------------------------------------------------"
        );
        &display_wc;
        print
          "-------------------------------------------------------------------------------\n";
        print $str{$index};
    }
}

#-----------------------------------------------------------------------------
#
# insert_tree
#
# This routine implements a binary search tree for keeping previously
# found strings, to keep the number of string comparisons from
# growing exponentially by host.
#
# This routine creates the following data structures:
#
# 1. An array of characters strings , @indices[0..n], that contains the indices
#    into the binary search tree array for the strings. These array is
#    in the order of the strings coming in.
#    The indices are used as the key to the associative arrays, %str and %hdr.
#
#    The character strings for indices are of the form "p" to designate
#    the parent  and "l" for left children  and "r" for the  right children.
#    The indices are concatenated together to form a character string.
#    Therefore the binary tree node "pl" designates the  left subtree
#    of the parent.The character string "plr" denotes the right subtree
#    node of the  left subtree of the  parent. A node that is at a depth
#    of 10  would be represented by a unique 10 character string.
#
# 2. An associative array of strings, %strs{characters},
#    consisting of all the output to be displayed. Each element is
#    all the distinct output from a host or set of hosts with lines
#    concatenated together and separated with $sepchar.
# 3. An associative array of strings, %hdrs{characters}, consisting of the
#    header strings to be output prior to each set of distinct output
#    lines. These strings are of the form: hostname:hostname
#
#
#-----------------------------------------------------------------------------
sub insert_tree
{

    local ($h, $l, $i) = @_;
    local ($no_match);

    $i = "p";    # start binary search at parent which is the root of the tree

    while (1)
    {
        if (!defined($str{$i}))
        {        # found no match, insert new
            $str{$i} = $l;
            $hdr{$i} = $h;
            push(@indices, $i);
            return $i;
        }
        $no_match = ($l cmp $str{$i});
        if (!$no_match)
        {        # found match, update host hdr
            $hdr{$i} = $hdr{$i} . ":" . $h;
            return $i;
        }
        elsif ($no_match == -1)
        {        # keep looking
            $i = $i . "l";    # concatenate "l" for the left subtree
        }
        else
        {
            $i = $i . "r";    # concatenate "r" for the right subtree
        }
    }
}

