#!/usr/bin/perl -w

# bank office simulation

use strict;
use 5.026;
use feature qw(state);

my %server = (   # minutes/client, average
    teller1 => 4,
    teller2 => 4.5,
    teller3 => 5,
    desk1 => 10,
    desk2 => 12,
    desk3 => 15,
    desk4 => 10,
);

my %dispatcher = (   # percentaje server type
    teller => 0.7,
    desk => 0.3,
);

my $demand = 60;    # average clients/hour. No dogs allowed.

# Previously, analysis if there is stable/unstable condition

my ($capt, $capd);
for my $i (keys %server){
    if ($i =~ /teller/){
	$capt += 60/$server{$i};
    }else{
	$capd += 60/$server{$i};
    }
}
my $flag=0;
if ($capt < $demand*$dispatcher{teller}){
    print "UNSTABLE: add tellers\n";
    $flag=1;
}
if ($capd < $demand*$dispatcher{desk}){
    print "UNSTABLE: add desks\n";
    $flag=2;
}
if ($flag==0) {
    print "STABLE.\n";
}else{
    warn "Should not continue!";
}

# done

my $seats = 20;      # waiting capacity

my (%status, %busy, %finish, %sched, %uniq, %stats);
my ($expellt, $expelld)=(0,0);
my @queue;
my @queueTeller=();
my @queueDesk=();
my ($sumqT, $sumqD, $sumTTT, $sumTTD, $clientsT, $clientsD);

for my $i (keys %server){
    $status{$i} = 0;
    $busy{$i} = 0;
    $finish{$i} = 0;
    $stats{$i} = 0;
}
for my $j (keys %dispatcher){
    $sched{$j} = 0;
}

arraivals();
for my $i (@queue){
    queues($i);
    tasks();
    clear();
}
report();
exit 0;



sub arraivals {
    state $t_entrie = 0;
    return if ($t_entrie > 7*60);      # doors closed after 7 hours
    while ($t_entrie < 7*60){
	$t_entrie += randexp(60/$demand,(60/$demand)/6);   # simulate time of entries with exponential (mu 1, sigma 1/6)
#    print "$t_entrie\n";
	$uniq{$t_entrie} =0;
	push @queue, $t_entrie;
    }
}

sub queues {
    my $i = $queue[shift];
    if (defined $i && $i >0){
	if ($uniq{$i}==0){
	    if (rand() < $dispatcher{teller}){
		$sched{teller}++;
		if ($sched{teller} + $sched{desk} > $seats){ 
		    $expellt++;
		    $sched{teller}--;
		    $queue[$i] =0;
		}else{
		    push (@queueTeller, $i) if (++$uniq{$i} <=1) ;
		}
	    }else{
		$sched{desk}++;
		if ($sched{teller} + $sched{desk} > $seats){  
		    $expelld++;
		    $sched{desk}--;
		    $queue[$i] =0;
		}else{
		    push (@queueDesk, $i) if (++$uniq{$i} <=1) ;
		}
	    }
	}
    }elsif (defined $i){
	queues($i+1);
    }
}

sub tasks {
    for my $i (0..$#queueTeller){
	next if $queueTeller[$i] ==0;
	last if $sched{teller} <=0;
	for my $j (keys %server) {
	    if ( $j =~ /teller/ && $status{$j} == 0){
		$status{$j} = 1;
		$busy{$j} = randexp($server{$j},$server{$j}/6);
		$finish{$j} = $queueTeller[$i] + $busy{$j};
		$stats{$j} += $busy{$j};
		$sumqT += $queueTeller[-1]-$queueTeller[$i];
		$sumTTT += $busy{$j};
		$clientsT++;
		$sched{teller}--;		    
		$queueTeller[$i] =0;
		last;
	    }	
	}
    }
    for my $i (0..$#queueDesk){
	next if $queueDesk[$i] ==0;
	last if $sched{desk} <=0;
	for my $j (keys %server) {
	    if ( $j =~ /desk/ && $status{$j} == 0){
		$status{$j} = 1;
		$busy{$j} = randexp($server{$j},$server{$j}/6);
		$finish{$j} = $queueDesk[$i] + $busy{$j};
		$stats{$j} += $busy{$j};
		$sumqD += $queueDesk[-1]-$queueDesk[$i];
		$sumTTD += $busy{$j};
		$clientsD++;
		$sched{desk}--;
		$queueDesk[$i] =0;
		last;
	    }	
	}
    }
}

sub clear {
    for my $i (keys %server){
	if (defined $queueTeller[-1]){
	    if ($finish{$i} <= $queueTeller[-1] && $i =~ /teller/){
		$status{$i}=0;
	    }
	}
	if (defined $queueDesk[-1]){
	    if ($finish{$i} <= $queueDesk[-1] && $i =~ /desk/){
		$status{$i}=0;
	    }       
	}
    }
}

sub report {
    for my $i (sort keys %server){
	print "$i -> ", $stats{$i}/$queue[-1], "\n";
    }
    print "Expelled of tellers = $expellt\n";
    print "Expelled of desks = $expelld\n";
    print "Mean of queue tellers = ", $sumqT/$clientsT, "\n";
    print "Mean of queue desks = ", $sumqD/$clientsD, "\n";
    $sumTTT += $sumqT;
    $sumTTD += $sumqD;
    print "Total time tellers = ", $sumTTT/$clientsT, "\n";
    print "Total time desks = " , $sumTTD/$clientsD, "\n";
    print "Total number of clients = ", $clientsT+$clientsD, "\n";
    print "Final situation:\n";
    print "$sched{teller} persons in tellers queue.\n";
    print "$sched{desk} persons in desks queue.\n";
  
}

sub rtime {
    return 2 * $_[0] * rand;
}

sub randexp2 {
    my $lambda = $_[0];
    return -(1/$lambda)*(log(rand()));
}



sub randexp {
    my $mu = shift;
    my $sigma = shift;
    # Generate the random variables by inversion method.
    my $r = $mu + $sigma * log rand;     # originally -sigma
    if ($r > 0){
	return $r;
    }else{
	return 0;
    }
}

__END__
  
