## Modified on November 2007 by Joseph.Emeras@imag.fr
## added: OAR2 compatibility
##        added methods used for managing OAR2 nodes and display

## This package handles OAR stuff...
## It uses OARNode.pm and OARJob.pm to store nodes and Jobs descriptions

package OAR::Monika::OAR;

use strict;
use warnings;
use Data::Dumper;
use OAR::Monika::Conf;
use OAR::Monika::db_io;
use OAR::Monika::OARNode;
use OAR::Monika::OARJob;
use Tie::IxHash;

## Class constructor
sub new {
    my $proto = shift;
    my $class = ref($proto) || $proto;
    my $self  = {};
    $self->{ALLNODES}   = {};
    $self->{PROPERTIES} = {};
    $self->{ALLJOBS}    = {};
    bless($self, $class);
    return $self;
}

## return all nodes
sub allnodes {
    my $self = shift;
    return $self->{ALLNODES};
}

sub properties {
    my $self = shift;
    return $self->{PROPERTIES};
}

## acces DataBase and get information about nodes
sub oarnodes {

    my $self     = shift;
    my $hostname = OAR::Monika::Conf::myself->hostname;
    my $port     = OAR::Monika::Conf::myself->dbport;
    my $dbtype   = OAR::Monika::Conf::myself->dbtype;
    my $dbname   = OAR::Monika::Conf::myself->dbname;
    my $username = OAR::Monika::Conf::myself->username;
    my $pwd      = OAR::Monika::Conf::myself->password;

    my $dbh = OAR::Monika::db_io::dbConnection($hostname, $port, $dbtype, $dbname, $username, $pwd);
    my @nodeNames = OAR::Monika::db_io::list_nodes($dbh);
    foreach my $currentNode (@nodeNames) {
        next unless $currentNode;
        my @currentNodeRessources =
          OAR::Monika::db_io::get_all_resources_on_node($dbh, $currentNode);
        my %hashInfoCurrentNodeRessources;
        foreach my $currentRessource (@currentNodeRessources) {
            my %hashInfosJobs;
            $hashInfosJobs{infos} = OAR::Monika::db_io::get_resource_info($dbh, $currentRessource)
              ;    ## get_resource_info returns a hash ref
            my @jobs = OAR::Monika::db_io::get_resource_job($dbh, $currentRessource)
              ;    ## get_resource_job returns an array
            $hashInfosJobs{jobs} = \@jobs;
            $hashInfoCurrentNodeRessources{$currentRessource} = \%hashInfosJobs;
        }
        if ($currentNode =~ OAR::Monika::Conf::myself()->nodenameRegex()) {
            my $node = new OAR::Monika::OARNode($currentNode, \%hashInfoCurrentNodeRessources);
            $self->allnodes()->{ $node->displayname } = $node;
        }
    }
    OAR::Monika::db_io::dbDisconnect($dbh);
}

## retrieve a OAR job description.
sub getJobProperties {
    my $myself       = shift;
    my $currentJobId = shift;
    my $cgi          = shift;
    my $hostname     = OAR::Monika::Conf::myself->hostname;
    my $port         = OAR::Monika::Conf::myself->dbport;
    my $dbtype       = OAR::Monika::Conf::myself->dbtype;
    my $dbname       = OAR::Monika::Conf::myself->dbname;
    my $username     = OAR::Monika::Conf::myself->username;
    my $pwd          = OAR::Monika::Conf::myself->password;

    my $dbh = OAR::Monika::db_io::dbConnection($hostname, $port, $dbtype, $dbname, $username, $pwd);

    my $jobInfos  = OAR::Monika::db_io::get_job_stat_infos($dbh, $currentJobId);
    my $jobEvents = OAR::Monika::db_io::get_job_events($dbh, $currentJobId);
    my $job       = OAR::Monika::OARJob->new($currentJobId);
    foreach my $key (keys %{$jobInfos}) {
        my $value = $jobInfos->{$key};
        $job->set($key, $value, $cgi);
    }
    $job->set("events", $jobEvents, $cgi);
    ## let's count the nodes and cpu used.

    my $structure =
      OAR::Monika::db_io::get_resources_data_structure_current_job($dbh, $currentJobId);
    my $parrayRessources = $structure->[0]->[0]->[0]->{'resources'};
    my $property         = $structure->[0]->[0]->[0]->{'property'};
    my $walltime         = $structure->[0]->[1];
    my $string           = "-l \"{$property}";
    foreach my $ressourceGroup (@$parrayRessources) {
        $string .= "/" . $ressourceGroup->{resource} . "=" . $ressourceGroup->{value};
    }

    my $sec = $walltime % 60;
    $walltime /= 60;
    my $min = $walltime % 60;
    $walltime = int($walltime / 60);
    my $hour      = $walltime;
    my $hWallTime = "$hour:$min:$sec";
    $job->set("walltime", $hWallTime, $cgi);
    $string .= ",walltime=$hWallTime\"";
    $job->set("wanted_resources", $string, $cgi);

    ## dates formatting
    my $submission_time = $job->get("submission_time");

    #my ($year,$mon,$mday,$hour,$min,$sec)= OAR::Monika::db_io::local_to_ymdhms($submission_time);
    #$submission_time= "$year-$mon-$mday $hour:$min:$sec";
    $job->set("submission_time", OAR::Monika::db_io::local_to_sql($submission_time), $cgi);

    my $start_time = $job->get("start_time");
    $job->set("start_time", OAR::Monika::db_io::local_to_sql($start_time), $cgi);

    my $stop_time = $job->get("stop_time");
    $job->set("stop_time", OAR::Monika::db_io::local_to_sql($stop_time), $cgi);
    OAR::Monika::db_io::dbDisconnect($dbh);
    return $job;
}

## retrieve OAR jobs description and store them in the ALLJOBS hash.
sub qstat {
    my $self     = shift;
    my $cgi      = shift;
    my $hostname = OAR::Monika::Conf::myself->hostname;
    my $port     = OAR::Monika::Conf::myself->dbport;
    my $dbtype   = OAR::Monika::Conf::myself->dbtype;
    my $dbname   = OAR::Monika::Conf::myself->dbname;
    my $username = OAR::Monika::Conf::myself->username;
    my $pwd      = OAR::Monika::Conf::myself->password;

    my $dbh = OAR::Monika::db_io::dbConnection($hostname, $port, $dbtype, $dbname, $username, $pwd);
    my @jobIds = OAR::Monika::db_io::get_queued_jobs($dbh);
    foreach my $currentJobId (@jobIds) {
        my $jobInfos  = OAR::Monika::db_io::get_job_stat_infos($dbh, $currentJobId);
        my $jobEvents = OAR::Monika::db_io::get_job_events($dbh, $currentJobId);
        my $job       = OAR::Monika::OARJob->new($currentJobId);
        foreach my $key (keys %{$jobInfos}) {
            my $value = $jobInfos->{$key};
            $job->set($key, $value, $cgi);
        }
        $job->set("events", $jobEvents, $cgi);

        ## let's count the nodes and cpu used.

        my $structure =
          OAR::Monika::db_io::get_resources_data_structure_current_job($dbh, $currentJobId);
        my @wanted_resources;
        foreach my $rp (@{ $structure->[0]->[0] }) {
            my $property = $rp->{'property'};
            my @hierarchy;
            foreach my $r (@{ $rp->{'resources'} }) {
                push(@hierarchy, "$r->{resource}=$r->{value}");
            }
            push(@wanted_resources, "{$property}/" . join("/", @hierarchy));
        }
        my $string   = "-l \"" . join("+", @wanted_resources);
        my $walltime = $structure->[0]->[1];
        my $sec      = $walltime % 60;
        $walltime /= 60;
        my $min = $walltime % 60;
        $walltime = int($walltime / 60);
        my $hour      = $walltime;
        my $hWallTime = "$hour:$min:$sec";
        $job->set("walltime", $hWallTime, $cgi);
        $string .= ",walltime=$hWallTime\"";
        $job->set("wanted_resources", $string, $cgi);

        ## dates formatting
        my $submission_time = $job->get("submission_time");

      #my ($year,$mon,$mday,$hour,$min,$sec)= OAR::Monika::db_io::local_to_ymdhms($submission_time);
      #$submission_time= "$year-$mon-$mday $hour:$min:$sec";
        $job->set("submission_time", OAR::Monika::db_io::local_to_sql($submission_time), $cgi);

        my $start_time = $job->get("start_time");
        if ($start_time ne '0') {
            $job->set("start_time", OAR::Monika::db_io::local_to_sql($start_time), $cgi);
        } else {
            $job->set("start_time", "n/a", $cgi);
        }
        my @scheduled_start_array =
          OAR::Monika::db_io::get_gantt_job_start_time($dbh, $currentJobId);
        my $scheduled_start = $scheduled_start_array[0];
        if (defined $scheduled_start && $scheduled_start ne '0') {
            $job->set("scheduled_start", OAR::Monika::db_io::local_to_sql($scheduled_start), $cgi);
        } else {
            $job->set("scheduled_start", "no prediction", $cgi);
        }

        $self->alljobs()->{$currentJobId} = $job;
    }
    OAR::Monika::db_io::dbDisconnect($dbh);
}

## return nodes verifying a property
sub nodelistByProperty {
    my $self     = shift;
    my $property = shift;
    my @nodes    = values %{ $self->allnodes };
    my %alreadyCounted;
    my @nodesSelected;
    foreach my $node (@nodes) {
        my %hashRessProp = $node->properties();
        foreach my $ress (keys %hashRessProp) {
            my %hashProp = %{ $hashRessProp{$ress} };
            foreach my $p (keys %hashProp) {
                my $hidden = undef;
                my $prop   = $p . "=" . (defined($hashProp{$p}) ? $hashProp{$p} : "?");
                if ($prop eq $property) {
                    unless (defined($alreadyCounted{$node})) {
                        $alreadyCounted{$node} = 1;
                        push @nodesSelected, $node->displayname;
                    }
                }
            }
        }
    }

    #print STDOUT Dumper(@nodesSelected);
    return \@nodesSelected;
}

## return all jobs
sub alljobs {
    my $self = shift;
    return $self->{ALLJOBS};
}

## print a HTML summary table of the current usage of the nodes.
sub htmlSummaryTable {
    my $self            = shift;
    my $cgi             = shift;
    my $output          = "";
    my $summary_display = OAR::Monika::Conf::myself->summary_display;
    my $nodes_synonym   = OAR::Monika::Conf::myself->nodes_synonym;
    $summary_display = $summary_display . ";";    ## add a ., to the end for parsing the string
    my %hash_display;
    tie %hash_display, "Tie::IxHash";             ## for hash insertion order

    while ($summary_display ne "") {
        $summary_display =~ s/(.*?);//;
        my $tmp = $1;
        my $key;
        if ($tmp =~ m/(.*?):/) {
            $tmp = $tmp . ",";
            $tmp =~ s/(.*?)://;
            $key = ($1);
            my @array_values;
            while ($tmp ne "") {
                $tmp =~ s/(.*?),//;
                my $value = $1;
                if ($value eq 'nodes_synonym') {
                    $value = $nodes_synonym;
                }
                push @array_values, $value;
            }
            $hash_display{$key} = \@array_values;
        } else {
            $key = $tmp;
            my @array_values;
            push @array_values, "resource_id";
            $hash_display{"$key"} = \@array_values;
        }
    }
    $output .= $cgi->start_table(
        {   -border => "1",
            -align  => "center" });
    $output .= $cgi->start_Tr({ -align => "center" });
    my $pt_resources = $self->getResources();
    foreach my $type_res (keys %hash_display) {
        $output .= $cgi->td();
        $output .= $cgi->start_table({ -border => "1", -align => "center" });
        $output .= $cgi->start_Tr({ -align => "center" });
        $output .= $cgi->td($cgi->i($cgi->u($type_res . " summary")));
        $output .= $cgi->end_Tr();
        $output .= $cgi->start_Tr({ -align => "center" });
        $output .= $cgi->td($cgi->i(""));
        $output .= $cgi->td($cgi->b("Free"));
        $output .= $cgi->td($cgi->b("Busy"));
        $output .= $cgi->td($cgi->b("Total"));
        $output .= $cgi->end_Tr();

        if ($type_res eq 'default') {
            foreach my $val (@{ $hash_display{$type_res} }) {
                $output .= $cgi->td($cgi->b($val));
                my ($free, $busy, $total) = $self->resourceCount($type_res, $val, $pt_resources);
                $output .= $cgi->td([ $free, $busy, $total ]);
                $output .= $cgi->end_Tr();
            }
        } else {
            foreach my $val (@{ $hash_display{$type_res} }) {
                my ($free, $busy, $total) = $self->resourceCount($type_res, $val, $pt_resources);
                $output .= $cgi->td($cgi->b($val));
                $output .= $cgi->td([ $free, $busy, $total ]);
                $output .= $cgi->end_Tr();
            }
        }
        $output .= $cgi->end_table();
    }
    $output .= $cgi->end_Tr();
    $output .= $cgi->end_table();
    return $output;
}

sub getResources {
    my $self = shift;
    my %resources;
    foreach my $resource_name (keys %{ $self->{'ALLNODES'} }) {
        foreach my $resource_id (keys %{ $self->{'ALLNODES'}->{$resource_name}->{'Ressources'} }) {
            $resources{$resource_id} =
              $self->{'ALLNODES'}->{$resource_name}->{'Ressources'}->{$resource_id};
        }
    }
    return \%resources;
}

## compute a summary of the usage of OAR nodes
sub resourceCount($$$$) {
    my $self          = shift;
    my $type_resource = shift;
    my $att_name      = shift;
    my $all_resources = shift;
    my ($free, $busy, $total) = (0, 0, 0);
    my %hashtotal;
    my %hashfree;
    my %alreadyCounted;
    my @associated_resources;

    foreach my $resource_id (keys %{$all_resources}) {
        foreach my $att (keys %{ $all_resources->{$resource_id}->{'infos'} }) {
            my $value = $all_resources->{$resource_id}->{'infos'}->{$att};
            if ($att eq $att_name &&
                $type_resource eq $all_resources->{$resource_id}->{'infos'}->{'type'}) {
                push @associated_resources, $resource_id;
                unless (exists $alreadyCounted{$value}) {
                    $total++;
                    if (!(@{ $all_resources->{$resource_id}->{'jobs'} } > 0)) {
                        if ($all_resources->{$resource_id}->{'infos'}->{'state'} eq 'Alive') {
                            $hashfree{"$value:$att:$type_resource"}++;
                        }
                    }
                    $alreadyCounted{$value} = "$att:$type_resource";
                    $hashtotal{"$value:$att:$type_resource"} = 1;
                } else {
                    $hashtotal{"$value:$att:$type_resource"}++;
                    unless (@{ $all_resources->{$resource_id}->{'jobs'} } > 0) {
                        $hashfree{"$value:$att:$type_resource"}++;
                    }
                }
            }
        }
    }
    my %cpt_att;
    foreach (@associated_resources) {
        if (@{ $all_resources->{$_}->{'jobs'} } > 0) {
            my $value = $all_resources->{$_}->{'infos'}->{$att_name};
            $cpt_att{$value} = '';
        }
    }
    foreach (keys %cpt_att) {
        $busy++;
    }
    foreach (keys %hashfree) {
        if ($hashfree{$_} eq $hashtotal{$_}) {
            $free++;
        }
    }
    return ($free, $busy, $total);
}

## print a HTML tables describing current OAR jobs.
sub htmlJobTable {
    my $self   = shift;
    my $cgi    = shift;
    my $output = "";

    #  $output .= $cgi->start_form();
    $output .= $cgi->start_table({ -border => "1", -align => "center" });
    my $alljobs = $self->alljobs();
    my @keys    = keys %$alljobs;
    if ($#keys < 0) {
        $output .= $cgi->Tr($cgi->td("No job currently in queues"));
    } else {

        $output .= $cgi->start_Tr();
        $output .= $cgi->td({ -align => "center" }, "Id");
        $output .= $cgi->td({ -align => "center" }, "User");
        $output .= $cgi->td({ -align => "center" }, "State");
        $output .= $cgi->td({ -align => "center" }, "Queue");
        $output .= $cgi->td({ -align => "center" }, "Name");

        #$output .= $cgi->td({-align => "center"},"NbNodes");
        #$output .= $cgi->td({-align => "center"},"NbCores");
        $output .= $cgi->td({ -align => "center" }, "wanted_resources");
        $output .= $cgi->td({ -align => "center" }, "Type");
        $output .= $cgi->td({ -align => "center" }, "Properties");
        $output .= $cgi->td({ -align => "center" }, "Reservation");
        $output .= $cgi->td({ -align => "center" }, "Walltime");
        $output .= $cgi->td({ -align => "center" }, "Submission Time");
        $output .= $cgi->td({ -align => "center" }, "Start Time");

        #        $output .= $cgi->td({-align => "center"},"Comment");
        $output .= $cgi->td({ -align => "center" }, "Scheduled Start");
        $output .= $cgi->end_Tr();

        @keys = sort { $a <=> $b } @keys;
        foreach my $key (@keys) {
            $output .= $alljobs->{$key}->htmlTableRow($cgi);
        }
    }
    $output .= $cgi->end_table();
    return $output;
}

sub htmlPropertyChooser {
    my $self   = shift;
    my $cgi    = shift;
    my $output = "";

    # do not show hidden properties...
    my @checkboxes       = ();
    my @hiddenProperties = OAR::Monika::Conf::myself()->hiddenProperties();
    my @nodes            = values %{ $self->allnodes };

    #my %alreadyCounted;
    my %hiddenHash;
    foreach (@hiddenProperties) {
        $hiddenHash{$_} = '';
    }
    my $hostname = OAR::Monika::Conf::myself->hostname;
    my $port     = OAR::Monika::Conf::myself->dbport;
    my $dbtype   = OAR::Monika::Conf::myself->dbtype;
    my $dbname   = OAR::Monika::Conf::myself->dbname;
    my $username = OAR::Monika::Conf::myself->username;
    my $pwd      = OAR::Monika::Conf::myself->password;
    my $dbh = OAR::Monika::db_io::dbConnection($hostname, $port, $dbtype, $dbname, $username, $pwd);
    my $result = OAR::Monika::db_io::get_properties_values($dbh, \%hiddenHash);
    OAR::Monika::db_io::dbDisconnect($dbh);
    my %hashcheckboxes;

    foreach (keys %{$result}) {
        foreach my $prop (keys %{ $result->{$_} }) {
            if (defined($result->{$_}->{$prop})) {
                my $str = $prop . "=" . $result->{$_}->{$prop};
                $hashcheckboxes{$str} = '';
            }
        }
    }
    foreach (keys %hashcheckboxes) {
        push @checkboxes, $_;
    }

    # old one, very slow...
    #foreach my $node(@nodes){
    #  my %hashRessProp= $node->properties();
    #  foreach my $ress (keys %hashRessProp){
    #        my %hashProp= %{$hashRessProp{$ress}};
    #        foreach my $p (keys %hashProp) {
    #                my $hidden = undef;
    #                my $prop= $p."=".$hashProp{$p};
    #                foreach my $h (@hiddenProperties) {
    #                        $prop =~ /^$h=/ and $hidden = 1;
    #                }
    #                unless (defined($alreadyCounted{$prop})){
    #                  defined $hidden or push @checkboxes, $prop;
    #                  $alreadyCounted{$prop}= 1;
    #                }
    #        }
    #  }
    #}
    if ($#checkboxes >= 0) {
        my @sortedCheckboxes = sort { $a cmp $b } @checkboxes;
        $output .= $cgi->start_div({ -align => "center" });
        $output .= $cgi->start_form({ -method => "get" });
        $output .= $cgi->b("OAR properties:");
        $output .= $cgi->checkbox_group(
            {   -name    => 'props',
                -values  => \@sortedCheckboxes,
                -columns => 5,
                -title   => "click to select property" });
        $output .= $cgi->submit("Action", "Display nodes for these properties");
        $output .= $cgi->end_div();
        $output .= $cgi->end_form();
    }
    return $output;
}

sub htmlNodeByProperty {
    my $self     = shift;
    my $cgi      = shift;
    my $output   = "";
    my @selected = sort $cgi->param('props');

    #my @nodesList;
    foreach my $prop (@selected) {
        if (defined($self->nodelistByProperty($prop))) {
            $output .= $cgi->h3({ -align => "center" }, "Reservations for property $prop:");

            #push @nodesList, @{$self->nodelistByProperty($prop)};
            $output .=
              $cgi->nodeReservationTable($self->allnodes(), $self->nodelistByProperty($prop));
        }
    }

    #$output .= $cgi->nodeReservationTable($self->allnodes(),\@nodesList);
    return $output;
}

## that's all.
return 1;

