# $Id$
package OAR::Schedulers::GanttHoleStorage_with_quotas;
require Exporter;
use OAR::Schedulers::ResourceTree;
use OAR::Schedulers::QuotaStorage;
use OAR::Modules::Judas qw(oar_debug oar_warn oar_error set_current_log_category);
use Data::Dumper;
use POSIX ":sys_wait_h";
use POSIX qw(strftime);
use Storable qw(store_fd fd_retrieve dclone);
use warnings;
use strict;

# Note : All dates are in seconds
# Resources are integer so we store them in bit vectors
# Warning : this gantt cannot manage overlaping time slots

# 2^32 is infinity in 32 bits stored time
my $Infinity = 4294967296;

# Prototypes
# gantt chart management
sub new($$);
sub new_with_1_hole($$$$$$);
sub add_new_resources($$);
sub set_occupation($$$$);
sub get_free_resources($$$);
sub find_first_hole($$$$$$$$$$$$);
sub pretty_print($);
sub get_infinity_value();

###############################################################################

sub get_infinity_value(){
    return($Infinity);
}

sub pretty_print($){
    my $gantt = shift;
   
    print("--------------------------------------------------------------------\n");
    my @bits = split(//, unpack("b*", $gantt->[0]->[2]));
    print("@bits\n");
    foreach my $g (@{$gantt}){
        print("BEGIN : $g->[0](".strftime("%F %T",localtime($g->[0])).")\n");
        foreach my $h (@{$g->[1]}){
            @bits = split(//, unpack("b*", $h->[1]));
            print("    $h->[0](".strftime("%F %T",localtime($h->[0])).") : @bits\n");
        }
        print("\n");
    }
    print("--------------------------------------------------------------------\n");
}

# Creates an empty Gantt
# arg : number of the max resource id
sub new($$){
    my $max_resource_number = shift;
    my $minimum_hole_duration = shift;

    $minimum_hole_duration = 0 if (!defined($minimum_hole_duration));

    my $empty_vec = '';
    vec($empty_vec, $max_resource_number, 1) = 0;
    
    my $result =[                               # Gantt structure: a Gantt is defined as the list of the biggest holes
                    [                           # (rectange shapes) where a job could be placed (holes obviously can overlap)
                        0,                      # Each item of this subarray is a set of holes beginning a same time: t_start
                        [                       # The set is stored as a sub-subarray of holes sorted by end time
                            [                   # Holes are stored as arrays of 2 elements, with:
                                $Infinity,      # - t_end: end time for the hole
                                $empty_vec      # - vec: resource vector for the hole
                            ]
                        ],                      # The next 4 fields are only set in the first hole set (apply to the gantt):
                        $empty_vec,             # - base resources vector for the gantt
                        $empty_vec,             # - reference empty vec (filled with 0)
                        $minimum_hole_duration, # - minimum duration time for a hole (see oar.conf)
                        [$Infinity,$Infinity]   # - [t_start,t_end] of the last hole inpected in the previous find_first_hole
                    ]                           #   calls, if a timeout was triggered.
                ];
    
    return($result);
}


# Creates a Gantt with 1 hole
# arg : number of the max resource id
sub new_with_1_hole($$$$$$){
    my $max_resource_number = shift;
    my $minimum_hole_duration = shift;
    my $date = shift;
    my $duration = shift;
    my $resources_vec = shift;
    my $all_resources_vec = shift;

    my $gantt = new($max_resource_number, $minimum_hole_duration);

    # initiate the first hole with a fake date (ensure to keep it intact with all the configuration)
    $gantt->[0]->[1]->[0]->[0] = 86400;
   
    # Init the whole resource list directly
    $gantt->[0]->[2] = $all_resources_vec;
    # Feed vector with enough 0
    $resources_vec |= $gantt->[0]->[3];

    # Create the only hole
    $gantt->[1]->[0] = $date;
    $gantt->[1]->[1] = [[($date + $duration), $resources_vec]];

    return($gantt);
}

# Build a new gantt from the merger of two existing gantts
# Algo: 
# (1) First convert the gantts to stripes defined by the start and end times of all jobs
#     The 2 striped gantts are then easy merged: for every jobs, gantt_stripe(t) |= stripe(job, t)
# (2) Then convert the striped gantt back to the original gantt structure
# arg : gantt ref1, gantt ref2
sub merge_clone($$) {
    my ($gantt1,$gantt2) = @_;
    #pretty_print($gantt1);
    #pretty_print($gantt2);

    # Sanity check: are those 2 gantts compatible ?
    if ($gantt1->[0]->[2] ne $gantt2->[0]->[2]) {
    }

    # Initialize the stipes by retrieving all jobs start and end times.
    my $empty_vec = $gantt1->[0]->[3];
    my $stripes = {};
    foreach my $h (@$gantt1,@$gantt2) {
        $stripes->{$h->[0]} =  $empty_vec;
        foreach my $hh (@{$h->[1]}) {
            $stripes->{$hh->[0]} = $empty_vec;
        }
    }
    #foreach my $l (sort keys(%$stripes)) {
    #   print(strftime("%F_%T",localtime($l)).": ".unpack("b*", $stripes->{$l})."\n");
    #}

    # Fill the strips with all jobs
    foreach my $h (@$gantt1,@$gantt2) {
        my $t0 = $h->[0];
        foreach my $hh (@{$h->[1]}) {
            my $t1 = $hh->[0];
            foreach my $l (sort keys(%$stripes)) {
                if ($t0 < $l and $l <= $t1) {
                    #print("-> ".strftime("%F_%T",localtime($l))." in ]".strftime("%F_%T",localtime($t0)).", ".strftime("%F_%T",localtime($t1))."] = ".unpack("b*", $stripes->{$l})." | ".unpack("b*", $hh->[1])."\n");
                    $stripes->{$l} |= ($hh->[1]);
                    #foreach my $l (sort keys(%$stripes)) {
                    #    print(strftime("%F_%T",localtime($l)).": ".unpack("b*", $stripes->{$l})."\n");
                    #}
                }
            }
        }
    }
    #print("emtpy_vec= ".unpack("b*", $empty_vec)."\n");
    #my $t=0;
    #foreach my $l (sort keys(%$stripes)) {
    #    print("t=".$t++." l=".strftime("%F_%T",localtime($l)).": ".unpack("b*", $stripes->{$l})."\n");
    #}
    
    # Convert the striped gantt back to the original structure
    my $merged_gantt = [];
    my @times=sort keys(%$stripes);
    # Try and find new hole start time for every stripe
    for(my $t=0;$t<$#times;$t++) {
        my $holes = [];
        #print("test possible hole starting at $t (".strftime("%F_%T",localtime($times[$t])).") ? : ".unpack("b*", (~ $stripes->{$times[$t]} & $stripes->{$times[$t+1]}))."\n");
        # We have a new hole if for any resource r, vec(r,t-1)=1 and vec(r,t)=0
        if ((~ $stripes->{$times[$t]} & $stripes->{$times[$t+1]}) ne $empty_vec) {
            #print "YES: hole at starttime: t=$t l=".$times[$t]." (".strftime("%F_%T",localtime($times[$t])).")\n";
            my $vec = $stripes->{$times[$t+1]};
            # Compute all holes with this start time, as long as the hole vector is not empty
            while ($vec ne $empty_vec) {
                #print("vec= ".unpack("b*", $vec)." \@t=".($t+1)." l=".$times[$t+1]." (".strftime("%F_%T",localtime($times[$t+1])).")\n");
                my $tt = $t+1;
                # Try and extend the hole to the next stripes: ok if for any r, vec(r,current_hole) => vec(r,tt+1)
                while (($tt < $#times) and ((~ $vec | $stripes->{$times[$tt+1]}) eq ~ $empty_vec)) {
                    #print("ok-> ".unpack("b*", $stripes->{$times[$tt]})." tt=".($tt+1)." l=".$times[$tt+1]." (".strftime("%F_%T",localtime($times[$tt+1])).")\n");
                    $tt++;
                }
                # We now reached the end time for this hole
                #print "endtime: $tt l=".$times[$tt]." (".strftime("%F_%T",localtime($times[$tt])).")\n";
                push @$holes, [$times[$tt], $vec];
                # Test if we did not reach the end of the stripes
                if ($tt < $#times) {
                    $vec &= $stripes->{$times[$tt+1]};
                } else {
                    $vec = $empty_vec;
                }
            }
            # Store the new start time with its holes
            push @$merged_gantt, [$times[$t], $holes, undef, undef, undef, undef ];
        #} else {
        #    print "NOP: no hole at starttime: t=$t l=".$times[$t]." (".strftime("%F_%T",localtime($times[$t])).")\n";
        }
    }
    # Well done, now fill the global values of the gantt and return
    $merged_gantt->[0]->[2] = $gantt1->[0]->[2];
    $merged_gantt->[0]->[3] = $gantt1->[0]->[3];
    $merged_gantt->[0]->[4] = $gantt1->[0]->[4];
    $merged_gantt->[0]->[5] = $gantt1->[0]->[5];
    #pretty_print($merged_gantt);
    return $merged_gantt;
}

# Helper function to test if a Gantt is defined, without side effect
sub defined_gantt($$$$$) {
    my $gantt = shift;
    my $container = shift;
    my $placeholder = shift;
    my $user = shift;
    my $name = shift;
    return (exists($gantt->{$container}) and defined($gantt->{$container})
        and exists($gantt->{$container}->{$placeholder}) and defined($gantt->{$container}->{$placeholder})
        and exists($gantt->{$container}->{$placeholder}->{$user}) and defined($gantt->{$container}->{$placeholder}->{$user})
        and exists($gantt->{$container}->{$placeholder}->{$user}->{$name}) and defined($gantt->{$container}->{$placeholder}->{$user}->{$name}));
}

# Manage the different gantts used in the schedulers handling container, timesharing and placeholder
# See oar_sched_gantt_with_timesharing_and_placeholder
# This allows to factorize code since this function is called in the 2 phases (running jobs, and to schedule jobs)
sub manage_gantt_for_timesharing_and_placeholder($$$$$$) {
    my $Gantt = shift;
    my $job_user = shift;
    my $job_name = shift;
    my $types = shift;
    my $inner_id = shift;
    my $log_prefix = shift;
    my $placeholder_name = "";
    my $allowed_name = "";
    my $timesharing_user = "";
    my $timesharing_name = "";
    if (defined($types->{placeholder})){ # A placeholder job cannot be allowed or timesharing. 
        $placeholder_name = $types->{placeholder};
        oar_debug("$log_prefix job is ($inner_id,$placeholder_name,,)\n");
        if (not defined_gantt($Gantt,$inner_id,$placeholder_name,"","")){
            $Gantt->{$inner_id}->{$placeholder_name}->{""}->{""} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
            oar_debug("$log_prefix placeholder job: cloned gantt ($inner_id,$placeholder_name,,) from ($inner_id,,,)\n");
        }
    } else {
        if (defined($types->{allowed})){
            $allowed_name = $types->{allowed};
        }
        if (defined($types->{timesharing})){
            $timesharing_user = "*";
            $timesharing_name = "*";
            foreach my $s (split(',', $types->{timesharing})){
                if ($s =~ m/^\s*([\w\*]+)\s*$/m){
                    if ($1 eq "user"){
                        $timesharing_user = $job_user;
                    }elsif ($1 eq "name") {
                        if (defined($job_name) and $job_name ne "") {
                            $timesharing_name = $job_name;
                        } else {
                            oar_debug("$log_prefix timesharing on name but no job name defined, using *\n");
                        }
                    }
                }
            }
        }
        oar_debug("$log_prefix job is ($inner_id,$allowed_name,$timesharing_user,$timesharing_name)\n");
        if (not defined_gantt($Gantt,$inner_id,$allowed_name,$timesharing_user,$timesharing_name)) {
            if (not defined_gantt($Gantt,$inner_id,$allowed_name,"","") and not defined_gantt($Gantt,$inner_id,"",$timesharing_user,$timesharing_name)) {
                $Gantt->{$inner_id}->{$allowed_name}->{$timesharing_user}->{$timesharing_name} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
                oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,$allowed_name,$timesharing_user,$timesharing_name) from ($inner_id,,,)\n");
                if ($allowed_name ne "") {
                    $Gantt->{$inner_id}->{$allowed_name}->{""}->{""} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
                    oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,$allowed_name,,) from ($inner_id,,,)\n");
                }
                if ($timesharing_user ne "" or $timesharing_name ne "") {
                    $Gantt->{$inner_id}->{""}->{$timesharing_user}->{$timesharing_name} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
                    oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,,$timesharing_user,$timesharing_name) from ($inner_id,,,)\n");
                }
            } elsif (not defined_gantt($Gantt,$inner_id,$allowed_name,"","")) { #G($i,,$u,$n) is defined
                $Gantt->{$inner_id}->{$allowed_name}->{""}->{""} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
                oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,$allowed_name,,) from ($inner_id,,,)\n");
                if ($timesharing_user ne "" and $timesharing_name ne "") {
                    $Gantt->{$inner_id}->{$allowed_name}->{$timesharing_user}->{$timesharing_name} = dclone($Gantt->{$inner_id}->{""}->{$timesharing_user}->{$timesharing_name});
                    oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,$allowed_name,$timesharing_user,$timesharing_name) from ($inner_id,,$timesharing_user,$timesharing_name)\n");
                }
            } elsif (not defined_gantt($Gantt,$inner_id,"",$timesharing_user,$timesharing_name)) { # G($i,$p,,) is defined
                $Gantt->{$inner_id}->{""}->{$timesharing_user}->{$timesharing_name} = dclone($Gantt->{$inner_id}->{""}->{""}->{""});
                oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,,$timesharing_user,$timesharing_name) from ($inner_id,,,)\n");
                if ($allowed_name ne "") {
                    $Gantt->{$inner_id}->{$allowed_name}->{$timesharing_user}->{$timesharing_name} = dclone($Gantt->{$inner_id}->{$allowed_name}->{""}->{""});
                    oar_debug("$log_prefix allowed/timesharing job: cloned gantt ($inner_id,$allowed_name,$timesharing_user,$timesharing_name) from ($inner_id,$allowed_name,,)\n");
                }
            } else { # Both G($i,$p,,) and G($i,,$u,$n) are defined. We need to merge them to create G($i,$p,$u,$n) 
                $Gantt->{$inner_id}->{$allowed_name}->{$timesharing_user}->{$timesharing_name} = merge_clone($Gantt->{$inner_id}->{$allowed_name}->{""}->{""},$Gantt->{$inner_id}->{""}->{$timesharing_user}->{$timesharing_name});
                oar_debug("$log_prefix allowed/timesharing job: merged gantt ($inner_id,$allowed_name,$timesharing_user,$timesharing_name) from ($inner_id,$allowed_name,,) and ($inner_id,,$timesharing_user,$timesharing_name)\n");
            }
        }
    }
    return ($placeholder_name, $allowed_name, $timesharing_user, $timesharing_name);
}

# Fill the gantts different gantts used in the schedulers handling container, timesharing and placeholder
# See oar_sched_gantt_with_timesharing_and_placeholder
# This allows to factorize code since this function is called in the 2 phases (running jobs, and to schedule jobs)
sub fill_gantts($$$$$$$$$$) {
    my $Gantt = shift;
    my $date = shift;
    my $duration = shift;
    my $resources_vec = shift;
    my $inner_id = shift;
    my $placeholder_name = shift;
    my $allowed_name = shift;
    my $timesharing_user = shift;
    my $timesharing_name = shift;
    my $log_prefix = shift;
    my $queue = shift;
    my $project = shift;
    my $types_arrayref = shift;
    my $user = shift;

    
    foreach my $p (keys(%{$Gantt->{$inner_id}})){
        foreach my $u (keys(%{$Gantt->{$inner_id}->{$p}})){
            foreach my $n (keys(%{$Gantt->{$inner_id}->{$p}->{$u}})){
                if (not (($p ne "" and $p eq $placeholder_name) or ($u ne "" and $u eq $timesharing_user and $n ne "" and $n eq $timesharing_name))){
                    oar_debug("$log_prefix add job occupation in gantt ($inner_id,$p,$u,$n)\n");
                    OAR::Schedulers::GanttHoleStorage_with_quotas::set_occupation( $Gantt->{$inner_id}->{$p}->{$u}->{$n},
                                                        $date,
                                                        $duration,
                                                        $resources_vec
                                                      );
                } else {
                    if ($placeholder_name ne "") {
                        oar_debug("$log_prefix skip job occupation in gantt ($inner_id,$p,$u,$n) because job is ($inner_id,s:$placeholder_name,,)\n");
                    } else {
                        oar_debug("$log_prefix skip job occupation in gantt ($inner_id,$p,$u,$n) because job is ($inner_id,u:$allowed_name,$timesharing_user,$timesharing_name)\n");
                    }
                }
            }
        }
    }
}

# Adds and initializes new resources in the gantt
# args : gantt ref, bit vector of resources
sub add_new_resources($$) {
    my ($gantt, $resources_vec) = @_;

    # Feed vector with enough 0
    $resources_vec |= $gantt->[0]->[3]; 
    
    # Verify which resources are not already inserted
    my $resources_to_add_vec = $resources_vec & (~ $gantt->[0]->[2]);
   
    if (unpack("%32b*",$resources_to_add_vec) > 0){
        # We need to insert new resources on all hole
        my $g = 0;
        while ($g <= $#{$gantt}){
            # Add resources
            if ($gantt->[$g]->[1]->[$#{$gantt->[$g]->[1]}]->[0] == $Infinity){
                $gantt->[$g]->[1]->[$#{$gantt->[$g]->[1]}]->[1] |= $resources_to_add_vec;
            }else{
                push(@{$gantt->[$g]->[1]}, [$Infinity, $resources_vec]);
            }
            $g++;
        }
        # Keep already inserted resources in mind
        $gantt->[0]->[2] |= $resources_vec;
    }
}


# Inserts in the gantt new resource occupations
# args : gantt ref, start slot date, slot duration, resources bit vector
sub set_occupation($$$$){
    my ($gantt, $date, $duration, $resources_vec) = @_;

    # Feed vector with enough 0
    $resources_vec |= $gantt->[0]->[3];

    # If a resource was not initialized
    add_new_resources($gantt,$resources_vec); # If it is not yet done

    my $new_hole = [
                        $date + $duration + 1,
                        []
                    ];
    
    my $g = 0;
    while (($g <= $#{$gantt}) and ($gantt->[$g]->[0] <= $new_hole->[0])){
        my $slot_deleted = 0;
        # Look at all holes that are before the end of the occupation
        if (($#{$gantt->[$g]->[1]} >= 0) and ($gantt->[$g]->[1]->[$#{$gantt->[$g]->[1]}]->[0] >= $date)){
            # Look at holes with a biggest slot >= $date
            my $h = 0;
            my $slot_date_here = 0;
            while ($h <= $#{$gantt->[$g]->[1]}){
                # Look at all slots
                $slot_date_here = 1 if ($gantt->[$g]->[1]->[$h]->[0] == $date);
                if ($gantt->[$g]->[1]->[$h]->[0] > $date){
                    # This slot ends after $date
                    
                    #print("-->[1]\n ".($date - $gantt->[$g]->[0])." -- $gantt->[0]->[4]\n<--[1]\n");
                    
                    if (($gantt->[$g]->[0] < $date) and ($slot_date_here == 0) and ($date - $gantt->[$g]->[0] > $gantt->[0]->[4])){
                        # We must create a smaller slot (hole start time < $date)
                        splice(@{$gantt->[$g]->[1]}, $h, 0, [ $date , $gantt->[$g]->[1]->[$h]->[1] ]);
                        $h++;   # Go to the slot that we were on it before the splice
                        $slot_date_here = 1;

                        #print("-->[2]\n "); pretty_print($gantt); print("<--[2]\n");

                    }
                    # Add new slots in the new hole
                    if (($new_hole->[0] < $gantt->[$g]->[1]->[$h]->[0]) and ($gantt->[$g]->[1]->[$h]->[0] - $new_hole->[0] > $gantt->[0]->[4])){
                        # copy slot in the new hole if needed
                        my $slot = 0;
                        while (($slot <= $#{$new_hole->[1]}) and ($new_hole->[1]->[$slot]->[0] < $gantt->[$g]->[1]->[$h]->[0])){
                            # Find right index in the sorted slot array
                            $slot++;
                        }
                        if ($slot <= $#{$new_hole->[1]}){
                            if ($new_hole->[1]->[$slot]->[0] == $gantt->[$g]->[1]->[$h]->[0]){
                                # If the slot already exists
                                $new_hole->[1]->[$slot]->[1] |= $gantt->[$g]->[1]->[$h]->[1];
                            }else{
                                # Insert the new slot
                                splice(@{$new_hole->[1]}, $slot, 0, [$gantt->[$g]->[1]->[$h]->[0], $gantt->[$g]->[1]->[$h]->[1]]);
                            }
                        }elsif ($new_hole->[0] < $gantt->[$g]->[1]->[$h]->[0]){
                            # There is no slot so we create one
                            push(@{$new_hole->[1]}, [ $gantt->[$g]->[1]->[$h]->[0], $gantt->[$g]->[1]->[$h]->[1] ]);
                            
                            #print("-->[3]\n Add new hole $new_hole->[0]: $gantt->[$g]->[1]->[$h]->[0]:\n".Dumper($new_hole)."\n -->[3]\n");
                        
                        }
                    }
                    # Remove new occupied resources from the current slot
                    $gantt->[$g]->[1]->[$h]->[1] &= (~ $resources_vec) ;
                    if (unpack("%32b*",$gantt->[$g]->[1]->[$h]->[1]) == 0){
                        # There is no free resource on this slot so we delete it
                        
                        #print("-->[4]\n Delete slot: $gantt->[$g]->[0],$gantt->[$g]->[1]->[$h]->[0] \n<--[4]\n");
                        
                        splice(@{$gantt->[$g]->[1]}, $h, 1);
                        $h--;
                        $slot_deleted = 1;

                        #print("-->[5]\n "); pretty_print($gantt); print("<--[5]\n");

                    }elsif ($h > 0){
                        # check if this is the same hole than the previous one
                        my $tmp_vec = $gantt->[$g]->[1]->[$h-1]->[1] ^ $gantt->[$g]->[1]->[$h]->[1];
                        if (unpack("%32b*",$tmp_vec) == 0){
                            splice(@{$gantt->[$g]->[1]}, $h-1, 1);
                            $h--;
                        }
                    }
                }
                # Go to the next slot
                $h++;
            }
        }
        if (($slot_deleted == 1) and ($#{$gantt->[$g]->[1]} < 0)){
            # There is no free slot on the current hole so we delete it
            splice(@{$gantt}, $g, 1);
            $g--;
        }elsif($g > 0){
            # Test if there is a same hole
            my $different = 0;

            #print("-->[6]\n ");pretty_print($gantt);print("<--[6]\n");
            #print("-->[7]\nG-1=$gantt->[$g - 1]->[0]       G=$gantt->[$g]->[0] \n<--[7]\n");
            
            if ($#{$gantt->[$g - 1]->[1]} != $#{$gantt->[$g]->[1]}){
                $different = 1;
            }
            my $tmp_h = 0;
            while (($different == 0) and (defined($gantt->[$g]->[1]->[$tmp_h]))){
                if ($gantt->[$g - 1]->[1]->[$tmp_h]->[0] != $gantt->[$g]->[1]->[$tmp_h]->[0]){
                    $different = 1;
                }else{
                    my $tmp_vec = $gantt->[$g - 1]->[1]->[$tmp_h]->[1] ^ $gantt->[$g]->[1]->[$tmp_h]->[1];
                    if (unpack("%32b*",$tmp_vec) != 0){
                        $different = 1;
                    }
                }
                $tmp_h++;
            }
            if ($different == 0){
                
                #print("-->[8]\n Delete Hole: $gantt->[$g]->[0] \n-->[8]\n");
                
                splice(@{$gantt}, $g, 1);
                $g--;
            }
        }
        # Go to the next hole
        $g++;
    }
    
    #print("-->[9]\n "); pretty_print($gantt); print("<--[9]\n");
    
    if ($#{$new_hole->[1]} >= 0){
        # Add the new hole
        if (($g > 0) and ($g - 1 <= $#{$gantt}) and ($gantt->[$g - 1]->[0] == $new_hole->[0])){
            # Verify if the hole does not already exist
            splice(@{$gantt}, $g - 1, 1, $new_hole);
        }else{
            splice(@{$gantt}, $g, 0, $new_hole);
        }
    }
}

# Find the first hole in the data structure that can fit the given slot
sub find_hole($$$){
    my ($gantt, $begin_date, $duration) = @_;

    my $end_date = $begin_date + $duration;
    my $g = 0;
    while (($g < $#{$gantt}) and (($gantt->[$g+1]->[0] <= $begin_date) or (($g <= $#{$gantt}) and ($gantt->[$g]->[0] < $begin_date) and ($gantt->[$g]->[1]->[$#{$gantt->[$g]->[1]}]->[0] < $end_date)))){
        $g++
    }

    return($g);
}

# Returns the vector of the maximum free resources at the given date for the given duration
sub get_free_resources($$$){
    my ($gantt, $begin_date, $duration) = @_;
    
    my $end_date = $begin_date + $duration;
    my $hole_index = 0;
    # search the nearest hole
    while (($hole_index <= $#{$gantt}) and ($gantt->[$hole_index]->[0] < $begin_date) and
            (($gantt->[$hole_index]->[1]->[$#{$gantt->[$hole_index]->[1]}]->[0] < $end_date) or 
                (($hole_index + 1 <= $#{$gantt}) and $gantt->[$hole_index + 1]->[0] < $begin_date))){
        $hole_index++;
    }
    return($gantt->[0]->[4]) if ($hole_index > $#{$gantt});
    
    my $h = 0;
    while (($h <= $#{$gantt->[$hole_index]->[1]}) and ($gantt->[$hole_index]->[1]->[$h]->[0] < $end_date)){
        $h++;
    }
    return($gantt->[$hole_index]->[1]->[$h]->[1]);
}


# Take a list of resource trees and find a hole that fit
# args : gantt ref, initial time from which the search will begin, job duration, list of resource trees
sub find_first_hole($$$$$$$$$$$$){
    my ($gantt,
        $initial_time,
        $duration,
        $tree_description_list,
        $timeout,
        $job_queue,
        $job_project,
        $job_types_arrayref,
        $job_user,
        $gantt_quotas,
        $accounting,
        $only_default_type_resource_vec) = @_;

    # $tree_description_list->[0]  --> First resource group corresponding tree
    # $tree_description_list->[1]  --> Second resource group corresponding tree
    # ...

    # Test if all groups are populated
    my $comment = "no_matching_slot";
    my $return_infinity = 0;
    my $g = 0;
    while (($return_infinity == 0) and ($g <= $#$tree_description_list)){
        if (!defined($tree_description_list->[$g])){
            $return_infinity = 1;
        }
        $g++;
    }
    return ($Infinity, $comment, ()) if ($return_infinity > 0);

    my @result_tree_list = ();
    my $end_loop = 0;
    my $current_time = $initial_time;
    my $timeout_initial_time = time();
    # begin research at the first potential hole
    my $current_hole_index = find_hole($gantt, $initial_time, $duration);
    my $h = 0;
    while ($end_loop == 0){
        # Go to a right hole
        while (($current_hole_index <= $#{$gantt}) and
                (($gantt->[$current_hole_index]->[0] + $duration > $gantt->[$current_hole_index]->[1]->[$h]->[0]) or
                   (($initial_time > $gantt->[$current_hole_index]->[0]) and
                        ($initial_time + $duration > $gantt->[$current_hole_index]->[1]->[$h]->[0])))){
            while (($h <= $#{$gantt->[$current_hole_index]->[1]}) and
                    (($gantt->[$current_hole_index]->[0] + $duration > $gantt->[$current_hole_index]->[1]->[$h]->[0]) or
                        (($initial_time > $gantt->[$current_hole_index]->[0]) and
                        ($initial_time + $duration > $gantt->[$current_hole_index]->[1]->[$h]->[0])))){
                $h++;
            }
            if ($h > $#{$gantt->[$current_hole_index]->[1]}){
                # in this hole no slot fits so we must search in the next hole
                $h = 0;
                $current_hole_index++;
            }
        }
        if ($current_hole_index > $#{$gantt}){
            # no hole fits
            $current_time = $Infinity;
            @result_tree_list = ();
            $end_loop = 1;
        }else{
            #print("Treate hole $current_hole_index, $h : $gantt->[$current_hole_index]->[0] --> $gantt->[$current_hole_index]->[1]->[$h]->[0]\n");
            $current_time = $gantt->[$current_hole_index]->[0] if ($initial_time < $gantt->[$current_hole_index]->[0]);
            #Check all trees
            my $i = 0;
            # Initiate already used resources with the empty vector
            my $already_occupied_resources_vec = $gantt->[0]->[3]; 
            my $accounted_used_resources_vec = '';
            my $tmp_leafs_vec = '';
            my $tmp_leafs_hashref = {};
            my $tree_clone;
            do{
                $already_occupied_resources_vec |= $tmp_leafs_vec;
                # clone the tree, so we can work on it without damage
                $tree_clone = OAR::Schedulers::ResourceTree::clone($tree_description_list->[$i]);
                my $tree_clone_bck = $tree_clone;
                
                #print(Dumper($tree_clone));
                $tree_clone = OAR::Schedulers::ResourceTree::delete_tree_nodes_with_not_enough_resources_and_unnecessary_subtrees(
                                    $tree_clone,
                                    $gantt->[$current_hole_index]->[1]->[$h]->[1] & ~ $already_occupied_resources_vec);
                
                ## QUOTAS
                # $current_time : start date of the hole
                # $gantt->[$current_hole_index]->[1]->[$h]->[0] : stop date of the hole
                if (defined($tree_clone)){
                    # Keep in mind the number of resources used by previous groups of the job
                    ($tmp_leafs_vec, $tmp_leafs_hashref) = OAR::Schedulers::ResourceTree::get_tree_leafs_vec($tree_clone);
                    $accounted_used_resources_vec |= $tmp_leafs_vec;
                    my $gantt_next_hole_date_start = $Infinity;
                    $gantt_next_hole_date_start = $gantt->[$current_hole_index+1]->[0] if ($current_hole_index < $#{$gantt});
                    ($current_time,$comment) = OAR::Schedulers::QuotaStorage::check_quotas(
                                                    $accounting,
                                                    $gantt_quotas,
                                                    $current_time,
                                                    $gantt->[$current_hole_index]->[1]->[$h]->[0],
                                                    $gantt_next_hole_date_start,
                                                    $duration,
                                                    $job_queue,$job_project,$job_types_arrayref,$job_user,
                                                    unpack("%32b*", $accounted_used_resources_vec & $only_default_type_resource_vec)
                                                                                           );
                    if (($current_time + $duration >= $gantt->[$current_hole_index]->[1]->[$h]->[0])
                        or (($current_hole_index < $#{$gantt}) and ($gantt->[$current_hole_index+1]->[0] <= $current_time))
                       ){
                        $tree_clone = undef;
                        # Free memory
                        OAR::Schedulers::ResourceTree::destroy($tree_clone_bck);
                    }
                }else{
                    # Free memory
                    OAR::Schedulers::ResourceTree::destroy($tree_clone_bck);
                }
                ## QUOTAS
                my @tmpa = keys(%{$tmp_leafs_hashref});
                $result_tree_list[$i] = [$tree_clone, $tmp_leafs_vec, \@tmpa];
                $i ++;
            }while(defined($tree_clone) && ($i <= $#$tree_description_list));
            if (defined($tree_clone)){
                # We find the first hole
                $end_loop = 1;
            }else{
                # Free memory
                foreach my $t (@{result_tree_list}){
                    OAR::Schedulers::ResourceTree::destroy($t->[0]);
                }
                # Go to the next slot
                if (($h >= $#{$gantt->[$current_hole_index]->[1]})
                     or (($current_hole_index < $#{$gantt}) and ($gantt->[$current_hole_index+1]->[0] <= $current_time))
                   ){
                    $h = 0;
                    $current_hole_index++;
                }else{
                    $h++;
                }
            }
        }
        # Check timeout
        if (($current_hole_index <= $#{$gantt})
            and (((time() - $timeout_initial_time) >= $timeout)
                 or (($gantt->[$current_hole_index]->[0] == $gantt->[0]->[5]->[0])
                     and ($gantt->[$current_hole_index]->[1]->[$h]->[0] >= $gantt->[0]->[5]->[1]))
                 or ($gantt->[$current_hole_index]->[0] > $gantt->[0]->[5]->[0]))
            and ($gantt->[$current_hole_index]->[0] > $initial_time)
           ){
            if (($gantt->[0]->[5]->[0] == $gantt->[$current_hole_index]->[0]) and
                ($gantt->[0]->[5]->[1] > $gantt->[$current_hole_index]->[1]->[$h]->[0])){
                $gantt->[0]->[5]->[1] = $gantt->[$current_hole_index]->[1]->[$h]->[0];
            }elsif ($gantt->[0]->[5]->[0] > $gantt->[$current_hole_index]->[0]){
                $gantt->[0]->[5]->[0] = $gantt->[$current_hole_index]->[0];
                $gantt->[0]->[5]->[1] = $gantt->[$current_hole_index]->[1]->[$h]->[0];
            }
            #print("TTTTTTT $gantt->[0]->[5]->[0] $gantt->[0]->[5]->[1] -- $gantt->[$current_hole_index]->[0] $gantt->[$current_hole_index]->[1]->[$h]->[0]\n");
            $current_time = $Infinity;
            @result_tree_list = ();
            $comment = "TIMEOUT";
            $end_loop = 1;
        }
    }

    return($current_time, $comment, \@result_tree_list);
}

