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

#This contains functions common to most plugins

package xCAT::Common;

use File::stat;
use File::Copy;
use xCAT::Usage;
use Thread qw/yield/;
use Storable qw/store_fd fd_retrieve/;

BEGIN
{
    $::XCATROOT = $ENV{'XCATROOT'} ? $ENV{'XCATROOT'} : '/opt/xcat';
}


# forward_data is a function used to aggregate output passed up from a set of
# children.  This is commonly used due to make absolutely certain multiple
# writers trying to use a common file descriptor wouldn't corrupt each other.
# So instead, each child is given a dedicated filehandle and the parent
# uses this function to organize child data and send it up.
# locking might be a more straightforward approach, but locking experiments
# weren't as successful.
sub forward_data {
    my $callback  = shift;
    my $fds       = shift;
    my @ready_fds = $fds->can_read(1);
    my $rfh;
    my $rc = @ready_fds;
    foreach $rfh (@ready_fds) {
        my $responses;
        eval {
            $responses = fd_retrieve($rfh);
        };
        if ($@ and $@ =~ /^Magic number checking on storable file/) { #this most likely means we ran over the end of available input
            $fds->remove($rfh);
            close($rfh);
        } else {
            eval { print $rfh "ACK\n"; }; #Ignore ack loss due to child giving up and exiting, we don't actually explicitly care about the acks
            foreach (@$responses) {
                $callback->($_);
            }
        }
    }
    yield();    #Try to avoid useless iterations as much as possible
    return $rc;
}

#This function pairs with the above.
#It either accepts pre-structured responses or the common ':' delimeted description/value pairs
#for example:
#  send_data('n1',"Temperature: cold","Voltage: normal"); #report normal text, two pieces of data
#  send_data('n1',[1,"Timeout communicationg with foobar]); #Report an error with a code
#  send_data({<custom response packet>},{<other custome response>});
sub send_data {
    my $node;
    if (not ref $_[0]) {
        $node = shift;
    }
    foreach (@_) {
        my %output;
        if (ref($_) eq HASH) {
            store_fd([$_], $out);
            yield();
            waitforack($out);
            next;
        }
        my $line;
        my $rc;
        if (ref($_) eq ARRAY) {
            $rc   = $_->[0];
            $line = $_->[1];
        } else {
            $line = $_;
        }


        (my $desc, my $text) = split(/:/, $line, 2);
        unless ($text) {
            $text = $desc;
        } else {
            $desc =~ s/^\s+//;
            $desc =~ s/\s+$//;
            if ($desc) {
                $output{node}->[0]->{data}->[0]->{desc}->[0] = $desc;
            }
        }
        $text =~ s/^\s+//;
        $text =~ s/\s+$//;
        $output{node}->[0]->{name}->[0] = $node;
        if ($rc) {
            $output{node}->[0]->{errorcode} = $rc;
            $output{node}->[0]->{error}->[0]->{contents}->[0] = $text;
        } else {
            $output{node}->[0]->{data}->[0]->{contents}->[0] = $text;
        }
        store_fd([ \%output ], $out);
        yield();
        waitforack($out);
    }
}

#This function is intended to be used to process a request through the usage
#module.
sub usage_noderange {
    my $request  = shift;
    my $callback = shift;

    #display usage statement if -h is present or no noderage is specified
    my $noderange = $request->{node};           #Should be arrayref
    my $command   = $request->{command}->[0];
    my $extrargs  = $request->{arg};
    my @exargs    = ($request->{arg});
    if (ref($extrargs)) {
        @exargs = @$extrargs;
    }

    my $usage_string = xCAT::Usage->parseCommand($command, @exargs);
    if ($usage_string) {
        $callback->({ data => $usage_string });
        $request = {};
        return;
    }

    if (!$noderange) {
        $usage_string = "Missing Noderange\n";
        $usage_string .= xCAT::Usage->getUsage($command);
        $callback->({ error => [$usage_string], errorcode => [1] });
        $request = {};
        return;
    }
}

# copy, overwriting only if the source file is newer
sub copy_if_newer {
    my ($source, $dest) = @_;

    die "ERROR: source file doesn't exist\n" unless (-e $source);

    # resolve destination path
    if ($dest =~ m/\/$/ || -d $dest) {
        $dest .= '/' if ($dest !~ m/\/$/);
        $dest .= $1  if $source =~ m/([^\/]+)$/;
    }

    if (-e $dest) {
        my $smtime = stat($source)->mtime;
        my $dmtime = stat($dest)->mtime;

        return if ($smtime < $dmtime);
    }

    copy($source, $dest);
}

1;
