package MpmasDistSharedFunctions;

use strict;
use warnings;
use Exporter;
#use Math::Random::MT::Auto qw/rand srand/ ???

use Storable qw(dclone);
use EmpiricalDF;
use Carp;
use POSIX;
use List::Util qw(max);
use Scalar::Util qw(looks_like_number);
use Data::Dumper qw(Dumper);
use Term::ReadKey;

use CtUtilities qw(cartesian is_in_list fisher_yates_shuffle determine_class_by_upperbound);
use IndexedTable;
use MpmasQLFunctionLanguage;
use MasMapAgent;
use MasMapRegion;
use EnhancedGrid;
use SimpleObject;
use SimpleObjectRegistry;


our @ISA = qw(Exporter);
our @EXPORT_OK = qw(prepare_icdf predraw_from_uniform draw_value_from_cdf do_assignment do_hhmember_assignment do_post_assignment evaluate_constraints divide_constraints set_verbosity);

sub do_assignment {
	###processing ASSIGN
	my $disinfo = shift;
	my $agent_to_assign_to = shift;
	my $curEndow = shift;
	my %loc2 = %{ shift(@_) };
	my $verbose = shift || 0;
	my ($starttag, $endtag) = set_verbosity($verbose);
	for my $i (0..$#{$disinfo->{ASSIGN}->{lines}}) {
		my ($field, $content) = split /\s*#=\s*/, $disinfo->{ASSIGN}->{lines}->[$i];
		my ($keys, $fors) = MpmasQLFunctionLanguage->convert_to_arrayrefs($field,\%loc2);
		if (defined $keys->[0]) {
			for my $j (0..$#{$keys}) {		
				my %locvars2  = %loc2;
				$locvars2{asset} = $fors->[$j];
				$agent_to_assign_to->add_to_attribute("num_assigned", 1);
				my $newassigned = SimpleObject->create(id => $agent_to_assign_to->get_attribute("num_assigned") );
				$newassigned->set_attribute("type", $keys->[$j]);
				$newassigned->set_attribute("size" , MpmasQLFunctionLanguage->convert($content,\%locvars2) || 0 );
				$agent_to_assign_to->get_attribute("assigned")->add_objects($newassigned);
				
				print $starttag.$agent_to_assign_to->id." receives ". $newassigned->get_attribute("size"). " of ". $keys->[$j].$endtag if $verbose;
			}
		}
		
	}
	
}
sub do_hhmember_assignment {
	###processing MEMBERS
	my $disinfo = shift;
	my $agent_to_assign_to = shift;
	my $curEndow = shift;
	my %loc2 = %{ shift(@_) };
	my $verbose = shift || 0;
	my ($starttag, $endtag) = set_verbosity($verbose);
	if (not defined $agent_to_assign_to->get_attribute("memberlist") ) {
					      $agent_to_assign_to->set_attribute("memberlist", SimpleObjectRegistry->create());
					      $agent_to_assign_to->set_attribute("num_members", 0);
	}
	for my $i (0..$#{$disinfo->{MEMBERS}->{lines}}) {
		my ($field, $content) = split /\s*#=\s*/, $disinfo->{MEMBERS}->{lines}->[$i];
		my ($keys, $fors) = MpmasQLFunctionLanguage->convert_to_arrayrefs($field,\%loc2);
		if (defined $keys->[0]) {
			for my $j (0..$#{$keys}) {		
				my %locvars2  = %loc2;
				$locvars2{member} = $fors->[$j];
				my $x = MpmasQLFunctionLanguage->convert($content,\%locvars2) || 0 ;
				if ($x > 0  ) {
					my $newassigned = SimpleObject->create(id => $agent_to_assign_to->get_attribute("num_members") );
					$newassigned->set_attribute("type", $keys->[$j]);
					$agent_to_assign_to->add_to_attribute("num_members", $x);
					$newassigned->set_attribute("number" , $x );
					$agent_to_assign_to->get_attribute("memberlist")->add_objects($newassigned);
					print $starttag.$agent_to_assign_to->id." receives ". $newassigned->get_attribute("number"). " of ". $keys->[$j].$endtag if $verbose;
				}
				

			}
		}
		
	}
	
}
sub do_post_assignment {
	###processing ON_ASSIGN
	my $disinfo = shift;
	my $agent_to_assign_to = shift;
	my $curEndow = shift;
	my %loc2 = %{ shift(@_) };
	my $verbose = shift || 0;
	my ($starttag, $endtag) = set_verbosity($verbose);
	for my $i (0..$#{$disinfo->{ON_ASSIGN}->{lines}}) {
		
		my ($field, $content) = split /\s*#=\s*/, $disinfo->{ON_ASSIGN}->{lines}->[$i];
		my ($keys, $fors) = MpmasQLFunctionLanguage->convert_to_arrayrefs($field,\%loc2);
		for my $j (0..$#{$keys}) {	
				
			my %locvars2  = %loc2;
			$locvars2{attribute} = $fors->[$j];
			if ($keys->[$j] =~ /(\w+)\.(\w+)/ ) {
				my $e = $1;
				my $f = $2;
				my $ref;
				if ($e eq "observation" and defined $curEndow) {
					$ref = $curEndow;
				}
				elsif ($e eq "agent") {
					$ref = $agent_to_assign_to;
				}
				my $amount = MpmasQLFunctionLanguage->convert($content,\%locvars2) || 0;
				if ($e eq "global") {
					MpmasQLFunctionLanguage->set_globvar($f, $amount);
				}
				else {
					$ref->set_attribute($f, $amount ) if (defined $ref);
				}
				#print "$e $f $amount\n" if $verbose;
			}
		}
		
	}
}

sub set_verbosity {
	my $level = shift;
	my $starttag;
	my $endtag;
	if ($level == 0) {
	    $starttag = "";
	    $endtag = "";
	}
	elsif ($level == 1) {
		$starttag = "\e[K \r";	
		$endtag = "\r";
	}	
	elsif ($level >= 2) {
		$starttag = "\e[K \r";	
		$endtag = "\n";
	}	
	return ($starttag, $endtag);
}

sub evaluate_constraints {
	## Processing constraints
	# 0: not specfific
	# 1: only observation specific
	# 2: only agent specific
	# 3: agent and observation specific
	my $constraints = shift;
	my $fixed_or_dynamic = shift; #arrayref
	my $specificity = shift; #arrayref
	my %loc2 = %{ shift(@_) };
	my $verbose = shift || 0;
	my ($starttag, $endtag) = set_verbosity($verbose);
	foreach my $f (@$fixed_or_dynamic) {
		foreach my $s (@$specificity) {
			for my $i (0..$#{$constraints->{$f}->{$s}}) {
				#print "Evaluating constraint $i for Agent $agent_to_assign_to\n" if $verbose;
				return 0 unless (MpmasQLFunctionLanguage->convert($constraints->{$f}->{$s}->[$i], \%loc2 , 1));				
			}
		}
	}
	return 1;
}
sub divide_constraints {
	my $disinfo = shift;
	my $constraints;
	
	
	for my $j (0..$#{$disinfo->{FIXED_CONSTRAINTS}->{lines}}) {
		my $i = $disinfo->{FIXED_CONSTRAINTS}->{lines}->[$j];
		my $s = 0;
		$s += 1 if ($i =~ /#this\s*\(\s*observation/  );
		$s += 2 if ($i =~ /#this\s*\(\s*agent/ );
		my $t = MpmasQLFunctionLanguage->translate_to_perl($i);
		push @{$constraints->{fixed}->{$s}} , $t;								
	}
	for my $j (0..$#{$disinfo->{DYNAMIC_CONSTRAINTS}->{lines}}) {
		my $i = $disinfo->{DYNAMIC_CONSTRAINTS}->{lines}->[$j];
		my $s = 0;
		$s += 1 if ($i =~ /#this\s*\(\s*observation/  );
		$s += 2 if ($i =~ /#this\s*\(\s*agent/ );
		my $t = MpmasQLFunctionLanguage->translate_to_perl($i);
		push @{$constraints->{dynamic}->{$s}} , $t;				
	}
	return $constraints;
	# fixed /dynamic
	# 0: not specfific
	# 1: only observation specific
	# 2: only agent specific
	# 3: agent and observation specific
}
sub prepare_icdf {
	my $curEndow = $_[0];
	my %hash;
	my $df = $curEndow->get_attribute("CDF_ftype") || "cdf";
	my $interpol = $curEndow->get_attribute("CDF_interpolation") || "linear";
	
	my $options = {};
	$options->{discrete} = $curEndow->get_attribute("CDF_discrete") || 0;
	my $icdf;
	
	if ($df =~ /^freq/ or $df eq "pdf") {
			      my %prehash;
			      for my $att ($curEndow->attribute_list) {
				      next unless (($att =~ /^CDF_bin_for_value_([\d.]+)/) or ($att =~ /^CDF_freq_for_value_([\d.]+)/) or ($att =~ /^CDF_prob_for_value_([\d.]+)/));
				      my $value = $1;
				      my $freq = $curEndow->get_attribute($att);
				      print STDERR "Inconsistent frequency $freq found: ".$curEndow->get_attribute("id") ." CDF_bin_for_value_$value\n" and exit(1) if ($freq < 0);
				      $prehash{$value} = 0 unless exists $prehash{$value} ;
				      $prehash{$value} += $freq;
			      }
			      #map {print "$_\t$prehash{$_}\n"} sort {$a <=> $b} keys %prehash;
			      my @v = sort {$a <=> $b} keys %prehash;
			      my @p = map {$prehash{$_} } @v;
			      #security check to ensure function is strictly increasing for Hermite interpolation
			      #Dropping all zero frequencies for the highest numbers.
			      while ( $p[$#p] <= 0) {
				  pop @p;
				  pop @v;
			      }
			      $icdf = EmpiricalDF->new(\@p, \@v, "freq", $interpol,$options);
	}
	elsif ($df eq "cdf") { 
	
		my $hundred_found = 0;
		for my $att ($curEndow->attribute_list) {
			next unless ($att =~ /^CDF_bin_for_value_([\d.]+)/);
			my $value = $1;
			my $ubound = $curEndow->get_attribute($att) / 100;
			$hundred_found = 1 if ($ubound == 1);
			print STDERR "Inconsistent upper bound $ubound found: ".$curEndow->get_attribute("id") ." CDF_bin_for_value_$value\n" and exit(1) if ($ubound < 0 or $ubound > 100);
			$hash{$ubound} = $value;
		}
		print STDERR "Highest upper bound for CDF of ".$curEndow->get_attribute("id")." is not 100\n" and exit(1) unless ($hundred_found);
		
		my @p = sort {$a <=> $b} keys %hash;
		my @v = map {$hash{$_} } @p;
		$icdf = EmpiricalDF->new(\@p, \@v, "qf", $interpol, $options);
		
	}
	elsif ($df eq "qf" or $df eq "icdf") { 
	
		my $hundred_found = 0;
		for my $att ($curEndow->attribute_list) {
			next unless ($att =~ /^CDF_value_for_bin_([\d.]+)/);
			my  $ubound = $1 / 100;
			my $value = $curEndow->get_attribute($att) ;
			$hundred_found = 1 if ($ubound == 1);
			print STDERR "Inconsistent upper bound $ubound found: ".$curEndow->get_attribute("id") ." CDF_value_for_bin_$value\n" and exit(1) if ($ubound < 0 or $ubound > 100);
			$hash{$ubound} = $value;
		}
		print STDERR "Highest upper bound for CDF of ".$curEndow->get_attribute("id")." is not 100\n" and exit(1) unless ($hundred_found);
		
		my @p = sort {$a <=> $b} keys %hash;
		my @v = map {$hash{$_} } @p;
		$icdf = EmpiricalDF->new(\@p, \@v, "qf", $interpol, $options);
		
	}
	else {
	    print STDERR "Error: Unknown type of distribution function: $df\n";
	    exit(1);
	}
	$curEndow->set_attribute("CDF_cdf",$icdf);

}
sub predraw_from_uniform {
	my ($potentials,$lbatt, $ubatt) = @_;
	my @plevels;
	if (defined $lbatt and  defined $ubatt) {
	    @plevels = map { 
			    my $lb = $_->get_attribute($lbatt)  || 0; 
			    my $ub = $_->get_attribute($ubatt)  || 100;   
			    $lb/100  + rand(1) * ($ub -$lb) /100;
			  } @$potentials;
	}	
	else {
	    @plevels = map (rand(1), @$potentials) ;
	}
	return \@plevels;
}
sub draw_value_from_cdf {
	my $cdf = shift; #hash reference - keys = ubounds - values = size to assign
	my $lb = shift ;
	my $ub = shift ;
	$lb = 0 if (not defined $lb or $lb < 0);
	$ub = 1 if(not defined $ub or $ub > 1);
	my $draw = $lb + rand(1) * ($ub - $lb);
	return $cdf->qf($draw);
	
	
}
1;
