package PS::MOPS::Test::Utility;

## @class PS::MOPS::Test::Utility.pm
# Library routines to support testing units in PS1-MOPS.
# @par DESCRIPTION
# This module includes functions that support unit testing.

use strict;
use warnings;
use Error;

require Exporter;

our @ISA=qw(Exporter);
our @EXPORT=qw(getFormattedTime
               isArray
               readCSVFile
               writeCSVFile
               checkFileExists
               checkDirectoryExists
               makeDirectory
               runSystemCommand
               validate
               getRootPath
               getLastDirectory
               removeDirectory
               getVCSVersion
               getVCSTime
               convertToUnixTime
               getHMSString
             );
our $g_PATH_SEP_CHAR='/';

# Convert epoch time to localtime.
# Return as a formatted string in the format
#
#     yyyymmdd_hhmmss
#
# for use in a MySQL database name.
#
# @return Formatted string of the current time
sub getFormattedTime
{
    use Time::Local;
    my @local_time=localtime(time);
    my $year=$local_time[5]+1900;
    my $day=$local_time[3];
    my $month=$local_time[4]+1;
    my $hour=$local_time[2];
    my $minute=$local_time[1];
    my $second=$local_time[0];
    my $datetime=sprintf("%4d%02d%02d_%02d%02d%02d",$year,$month,$day,$hour,$minute,$second);
    return $datetime;
}

# TODO:
# Use a real test for array.
#
# ref=reference
# @param ref Scalar data structure reference.
# @return 1 if ref is an array, 0 if not.
sub isArray
{
    my(%arg)=@_;
    if($arg{ref}=~/ARRAY/){
        return 1;
    }
    else {
        return 0;
    }
}

# Return 1 if file exists, 0 otherwise.
#
# filename
# @param filename
# @return 1 if file exists, 0 if not.
sub checkFileExists
{
    my(%arg)=@_;
    if(-e $arg{filename}){return 1;}
    else{return 0;}
}

# Read a CSV formatted file and place its content into an
# array of arrays.
# @note If a blank line is encountered, reading will end.
# @todo Wrap this in an exception handler.
# @todo Change this to support reading the header line.
# @todo Add support for comments in CSV files.
# @todo Consider adding label to header line.
# @param filename
# @return Array reference.
sub readCSVFile
{
    my(%arg)=@_;
    if(! (checkFileExists(filename=>$arg{filename})) ){
        die "ERROR: File $arg{filename} does not exist!";
    }
    my @data;
    open(FILE,"<$arg{filename}");
    while(<FILE>){
        s/#.*//; # Eliminate comments.
        next if /^(\s)*$/; # Ignore blank lines.
        push(@data,$_);
    }
    close(FILE);
    my @content=();
    my $i=0; # Counter.
    foreach my $line (@data){
        if($i!=0){ # Ignore header line.
            chomp($line);
            my @tmp=split(/,/, $line);

            # @todo Remove handling of blank lines here.
            if (!defined($tmp[0])) { # Blank line.
                next; # Don't store a blank line.
            }

            $content[$i-1]=\@tmp;
        }
        $i++;
    }
    return \@content;
}

# Given a reference to an array of arrays, write all the data to a CSV file.
#
# @todo Wrap this in an exception handler.
#
# @param filename Name of file to be written.
# @param header Array of header values.
# @param data
sub writeCSVFile
{
    my(%arg)=@_;
    validate(\%arg,data=>1,header=>1);
    my $ref=$arg{data};
    my $head=$arg{header};

    open(FILE,">$arg{filename}");
    my $num_data_columns=0;

    # Write header line.
    my $num_head_columns=scalar(@{$head});
    for (my $j=0;$j<$num_head_columns;$j++) { # Iterate over header values.
        if (defined(@{$head}[$j])){
            print FILE @{$head}[$j];
            print FILE "," unless ($j>=$num_head_columns-1);
        }
    }
    print FILE "\n";

    # Write data.
    for (my $i=0;$i<scalar(@{$ref});$i++) { # Iterate over data lines.
        $num_data_columns=scalar(@{@{$ref}[$i]});
        die "ERROR: Number of header columns don't match data columns." if ($num_data_columns != $num_head_columns);
        for (my $j=0;$j<$num_data_columns;$j++) { # Iterate over data columns.
            if (defined(@{$ref}[$i]->[$j])) {
                print FILE @{$ref}[$i]->[$j];
            }
            print FILE "," unless $j>=$num_data_columns-1;
        }
        print FILE "\n";
    }
    close(FILE);
}

# Check existence of a directory.
#
# @param fullpath Full path of directory to check.
# @return 1 if directory exists, 0 if not.
sub checkDirectoryExists
{
    my(%arg)=@_;
    validate(\%arg,fullpath=>1);
    if(-e $arg{fullpath} && -d $arg{fullpath}){return 1;}
    else{return 0;}
}

# If directory at full path does not exist, then create it.
# Return 1 if successful, 0 otherwise.
#
# fullpath = Full path of directory to be created.
sub makeDirectory
{
    my(%arg)=@_;
    validate(\%arg,fullpath=>1);
    if(checkDirectoryExists(fullpath=>$arg{fullpath})==0){
        if(mkdir($arg{fullpath})){
            return 1;
        }
    }
    return 0; # Failed to make directory.
}

# Run a system command.
# Return the result value of the command.
#
# cmd = Command to be run.
sub runSystemCommand
{
    my(%arg)=@_;
    my $error=system($arg{cmd});
    if ($error) {
        print "ERROR: Command '$arg{cmd}' failed.\n";
        exit;
    }
    return 1;
}

# Given a fullpath of a subdirectory, return the full path to the root of the
# subdirectory.
#
# TODO: Make this capable of handling more general cases.
#
# fullpath
sub getRootPath
{
    my(%arg)=@_;
    validate(\%arg,fullpath=>1);
    if(checkDirectoryExists(fullpath=>$arg{fullpath})==1){
        my @dirs=split(/$g_PATH_SEP_CHAR/,$arg{fullpath});
        my $root='';
        my $num_dirs=scalar(@dirs);
        if($num_dirs>0){
            # Rebuild path.
            for(my $i=0;$i<$num_dirs-1;$i++){
                $root.=$dirs[$i];
                $root.=$g_PATH_SEP_CHAR unless ($i>=$num_dirs-2);
            }
            return $root;
        }
    }
    return 0;
}

# Given a fullpath return the last directory in the path.
#
# fullpath
sub getLastDirectory
{
    my(%arg)=@_;
    validate(\%arg,fullpath=>1);
    my @dirs=split(/$g_PATH_SEP_CHAR/,$arg{fullpath});
    if(checkDirectoryExists(fullpath=>$arg{fullpath})==1){
        if(scalar(@dirs)>0){
            my $last=$dirs[scalar(@dirs)-1];
            return $last;
        }
    }
    return 0;
}

# fullpath
sub removeDirectory
{
    my(%arg)=@_;
    validate(\%arg,fullpath=>1);
    if(checkDirectoryExists(fullpath=>$arg{fullpath})==1){
        my $subdir=getLastDirectory(fullpath=>$arg{fullpath});
        my $root=getRootPath(fullpath=>$arg{fullpath});
        if($subdir && $root){
            if(chdir($root)){
                my $cmd_remove="rm -rf $subdir";
                print STDERR "Removing directory $arg{fullpath}...\n";
                if(runSystemCommand(cmd=>$cmd_remove)){
                    return 1;
                }
            }
        }
    }
    return 0;
}

# Validate argument hash.
# For each key in the hash, validate the argument value corresponding to that key.
#
# Functionality:
#
# Members of the argument hash of the to-be-validated function can be verified
# as defined by passing arguments of the form
#
#     {argument key}=>1
#
# into validate().
#
# ref=Hash reference to argument hash.
sub validate
{
    my($ref)=shift;
    my(%arg)=@_;
    my $success=1;
    my $k1; # Key in the hash referenced by $ref.
    my $v1; # Value in the hash referenced by $ref.
    my $k2; # Key in the hash %arg.
    my $v2; # Value in the hash %arg.

    while (($k2,$v2) = each %arg) { # Iterate over all key/value pairs in hash %arg.
        if($v2==1){
            if(!(defined($$ref{$k2}))){ # Check defined state of the value in the referenced
                                        # hash with the key passed in through the subroutine arguments.
                $success=0;
                print "Validate ERROR: $k2 is not defined.\n";
            }
        }
    }

    if(!$success){
        exit;
    }
}

# @param module
# @param vcs_versions
# @return VCS version for a module.
sub getVCSVersion
{
    my(%arg)=@_;
    # Iterate over modules in vcs_versions array.
    foreach my $row (@{$arg{vcs_versions}}) {
        if($row->{name} eq $arg{module}){
            return $row->{version};
        }
    }
    return "NA";
}

# @param vcs_versions VCS versions data structure.
# @param module Module name.
# @return VCS time for a module.
sub getVCSTime
{
    my(%arg)=@_;
    # Iterate over modules in vcs_versions array.
    foreach my $row (@{$arg{vcs_versions}}) {
        if($row->{name} eq $arg{module}){
            return $row->{time};
        }
    }
    return "NA";
}

sub verifyTestingState
{
    # TBW.
}

# Convert a VCS datetime to a Unix time value.
# @arg time Local time value.
sub convertToUnixTime
{
    my(%arg)=@_;
    use Time::Local;

    my $gmtime=0;

    # Parse VCS time string.
    if($arg{time}=~/(\d+)\-(\d+)\-(\d+)\s+(\d+)\:(\d+)\:(\d+)\s+(\W?\w+)\s/){
        if($7 eq "-1000") { # Only process HST.
            $gmtime=timegm($6,$5,$4,$3,$2-1,$1);
        }
    }
    return $gmtime if $gmtime;
    return 0;
}

# Return a formatted string given a quantity of seconds.
# @param s Number of seconds.
# @return string
sub getHMSString
{
    my ($s)=@_;
    if($s<60){
        return sprintf("%.2f s", $s);
    }
    if($s<3600){
        my $min=int($s/60);
        my $sec=$s-($min*60);
        return sprintf("%d m, %.2f s", $min, $sec);
    }
    my $hour=int($s/3600);
    my $min=int($s/60)-($hour*60);
    my $sec=$s-($min*60)-($hour*3600);
    return sprintf("%.2f h, %d m, $.2f s", $hour, $min, $sec);
}

1;
