#------------------------------------------------------------
# Copyright (C) 2010 RoyaltyShare, Inc.   All Rights Reserved
#------------------------------------------------------------
package Job::RunStatus;

use strict;
use warnings;
use Data::Dumper;
use Data::Compare;

use lib '/app/tools/common/lib';
use Common::Log;
use Common::Client;
use Common::Assert;

use lib '/app/tools/job/lib/';
use Job::DB::Item::Job;
use Job::DB::Item::JobHostClass;

sub new {
    my $class = shift;
    my $self = bless {}, $class;

    return $self->_init(@_);
}

sub _init {
    my $self = shift;

    $self->refresh();

    return $self;
}

sub forceRefresh {
    my $self = shift;

    $self->{_jobCount} = $self->_getRunCount();

    # !!! I think this is where we'll also want to determine the number of _active_
    # !!! hosts that belong to each job_host_class.
    #
    $self->{_jobHostClassCount} = $self->_getJobHostClassCount();
}

sub refresh {
    my $self = shift;

    my $cacheDate = $self->isStale();

    # This means there has been an update since we last checked the cache
    if ($cacheDate) {

        $self->forceRefresh();

        $self->{_lastUpdate} = $cacheDate;
    }
}

sub isStale {
    my $self = shift;

    return Job::DB::Item::Job->getCacheDate( $self->{_lastUpdate} );
}

sub hasRunStateChanged {
    my $self  = shift;
    my $count = $self->_getRunCount();

    return Compare( $count, $self->{jobCount} );
}

# !!! Want to replace this iterative code with a single query.
#
sub _getRunCount {
    my $self  = shift;
    my $count = {};

    # !!! To allow the load balanced rules to work, I also need to examine
    # !!! all the queued jobs - I need to know how many clients have jobs in either state.
    #
    #    my $collection = Job::DB::Item::Job->GetRunningJobs();
    #    my $collection = Job::DB::Item::Job->GetRunningOrQueuedJobs();
    my $runStateArray = Job::DB::Item::Job->GetRunningOrQueuedJobsArray();

    foreach my $hr (@$runStateArray) {
        $count->{'CLIENT_QUEUED_OR_RUNNING'}{ $hr->{client_id} }{ $hr->{class} }{ $hr->{subclass} } += $hr->{count};
        $count->{'CLIENT_QUEUED_OR_RUNNING'}{ $hr->{client_id} }{ $hr->{class} }{'GLOBAL'} += $hr->{count};
        if ( $hr->{state} eq 'r' ) {
            $count->{'CLIENT'}{ $hr->{client_id} }{ $hr->{class} }{ $hr->{subclass} } += $hr->{count};
            $count->{'CLIENT'}{ $hr->{client_id} }{ $hr->{class} }{'GLOBAL'} += $hr->{count};

            # Update per host total
            $count->{ $hr->{run_hostname} }{ $hr->{class} }{ $hr->{subclass} } += $hr->{count};
            $count->{ $hr->{run_hostname} }{ $hr->{class} }{'GLOBAL'} += $hr->{count};

            # Update global total
            $count->{'GLOBAL'}{ $hr->{class} }{ $hr->{subclass} } += $hr->{count};
            $count->{'GLOBAL'}{ $hr->{class} }{'GLOBAL'} += $hr->{count};
        }
    }

    return $count;
}

sub _getJobHostClassCount {
    my $self  = shift;
    my $count = {};

    # Let's just have the DB::Item class return a hash.
    # { 'CLASS' => $count, ... }
    #
    return Job::DB::Item::JobHostClass->GetLiveHostClassCounts();
}

sub _incrementJobCount {
    my $self    = shift;
    my $job     = shift;
    my $storage = shift || die;

    assert($job);

    no warnings;

    # Update per client total
    # JPK - Adding an additional layer here.
    # The client limits are used in a slightly different fashion, and I want to
    # simplify iterating over them.
    #
    if ( 0 == $job->start_date ) {
        $storage->{'CLIENT_QUEUED_OR_RUNNING'}{ $job->client_id }->{ $job->class }->{ $job->subclass }++;
        $storage->{'CLIENT_QUEUED_OR_RUNNING'}{ $job->client_id }->{ $job->class }->{'GLOBAL'}++;
    } else {
        $storage->{'CLIENT_QUEUED_OR_RUNNING'}{ $job->client_id }->{ $job->class }->{ $job->subclass }++;
        $storage->{'CLIENT_QUEUED_OR_RUNNING'}{ $job->client_id }->{ $job->class }->{'GLOBAL'}++;

        $storage->{'CLIENT'}{ $job->client_id }->{ $job->class }->{ $job->subclass }++;
        $storage->{'CLIENT'}{ $job->client_id }->{ $job->class }->{'GLOBAL'}++;

        # Update per host total
        $storage->{ $job->run_hostname }->{ $job->class }->{ $job->subclass }++;
        $storage->{ $job->run_hostname }->{ $job->class }->{'GLOBAL'}++;

        # Update global total
        $storage->{'GLOBAL'}->{ $job->class }->{ $job->subclass }++;
        $storage->{'GLOBAL'}->{ $job->class }->{'GLOBAL'}++;
    }
}

# Check to see if we can run a rule based on counts (local and global)
sub canRun {
    my $self     = shift;
    my $rule     = shift;
    my $hostname = shift;

    assert( $rule,     "rule required" );
    assert( $hostname, "hostname required" );

    my $subclass = $rule->Subclass() || 'GLOBAL';

    my $globalLimit = $rule->GlobalMax() || 0;
    my $localLimit  = $rule->LocalMax()  || 0;

    # For load-balanced rules, ClientMax is determined based
    # on the number of hosts that might run the rule, and
    # the number of clients with that class of job queued.
    #
    my $clientLimit = $rule->ClientMax() || 0;
    if ( $rule->LoadBalanced() && $localLimit ) {
        my $clientCount;
        my $clientHash = $self->{_jobCount}{'CLIENT_QUEUED_OR_RUNNING'};
        foreach my $clientID ( keys %$clientHash ) {
            if ( $clientHash->{$clientID}{ $rule->Class() }{$subclass} ) {
                $clientCount++;
            }
        }

        # Get the number of hosts that match this rule's 'hostname' (which should be a host class name)
        #
        my $hostCount = $self->{_jobHostClassCount}{ $rule->Hostname };
        if ( $hostCount && $clientCount ) {

            # Round up when determining the client limit, in order to ensure we
            # utilize all available capacity.
            #
            my $maxJobs = $localLimit * $hostCount;
            $clientLimit = int( $maxJobs / $clientCount );
            $clientLimit++ if ( $maxJobs % $clientCount );
            $clientLimit = 1 if $clientLimit < 1;
        }

        # !!! So, if there aren't any jobs in this class currently running, I think it's ok
        # to leave clientLimit set to 0 (which means 'no limit')
        #
    }

    my $currentGlobal = $self->{_jobCount}->{'GLOBAL'}->{ $rule->Class }->{$subclass};
    my $currentLocal  = $self->{_jobCount}->{$hostname}->{ $rule->Class }->{$subclass}
      if ( $self->{_jobCount}->{$hostname} );

    # Determine which client ids have reached (or exceeded!) the client max limit.
    # We'll let the rule know which client ids are maxed out, so that later
    # when constructing the SQL we can use that info to add in a 'AND client_id NOT IN (...)' clause.
    #
    # Oh, and we need to clear out the maxedClientID list, since these rules persist.
    #
    $rule->clearMaxedClientIDs();
    if ($clientLimit) {
        foreach my $clientID ( keys %{ $self->{_jobCount}->{'CLIENT'} } ) {
            my $currentClient = $self->{_jobCount}->{'CLIENT'}->{$clientID}->{ $rule->Class }->{$subclass};

            if ( defined $currentClient && $currentClient >= $clientLimit ) {
                $rule->addMaxedClientID($clientID);
            }
        }
    }

    # check global totals
    return if ( $globalLimit && $currentGlobal && $currentGlobal >= $globalLimit );

    # check per host totals
    return if ( $localLimit && $currentLocal && $currentLocal >= $localLimit );

    # Looks like we didn't violate any max runs, so we can run this rule
    return 1;
}

###
1;    #
###
