package MasMapRegion;


use strict;
use warnings;
use SimpleObject;
use CtUtilities qw /round_to offset_of_cell_neighbor fisher_yates_shuffle/;
use Math::CDF qw/qnorm qbeta/;
our @ISA = qw(SimpleObject);
my $used = 0;

sub create {
	my $class = shift;
	my $id = shift;
	return $class->SUPER::create(id => $id);
}
sub option {
      return $_[0]->{options}->{$_[1]};
}
sub set_option {
      $_[0]->{options}->{$_[1]} = $_[2];
}
sub distribute_farmsteads {
	my $self = shift;
	my $fstpotmap = shift;
	my $suitable = shift;
	my $verbose = shift|| 0;
	
	unless (defined $self->farmsteadmap) {
		$self->create_blank_farmsteadmap( $fstpotmap);
		# make a table with all cells that may potentially harbour a farmstead
		$self->set_potential_farmsteads( $fstpotmap->report_cells_equal_to(@$suitable) );
	}
	else {
		# make a table with all cells that may potentially harbour a farmstead
		my $temp = $fstpotmap->report_cells_equal_to(@$suitable);
		my @arraypot;
		foreach (@$temp) {
			if ($self->farmsteadmap->get_cell($_->{x},$_->{y} ) == -1) {
				push @arraypot, $_;
			}
		}	
		$self->set_potential_farmsteads(\@arraypot  );
	}
	
	for my $i ( $self->agents() ) {
		my $num_free_farmsteads = $self->potential_farmsteads();
		return 0 if ($num_free_farmsteads <= 0);
		my $id = int rand($num_free_farmsteads);
		$self->assign_farmstead($i, $id, $verbose);
	}
	return 1;
}
sub potential_farmsteads {
	my $self = shift;
	my $id = shift;
	return defined ($id) ? $self->{fstsuittab}->[$id] : @{$self->{fstsuittab}};
}

sub set_potential_farmsteads {
	my $self = shift;
	my $arrayref = shift;
	$self->{fstsuittab} = $arrayref;
}

sub delete_potential_farmstead {
	my $self = shift;
	my $id = shift;
	my @fst = $self->potential_farmsteads();
	my @lower = @fst[0..($id -1)];
	my @upper = @fst[($id+1)..$#fst];
	push @lower, @upper;
	$self->set_potential_farmsteads(\@lower);
}
sub assign_farmstead {
	my $self = shift;
	my $agent = shift;
	my $id = shift;
	my $verbose = shift;
	my @fst = $self->potential_farmsteads();
	my $x = $fst[$id]->{x};
	my $y = $fst[$id]->{y};
	$agent->assign_farmstead($x,$y);
	print "Assigned farmstead $x,$y to agent ".$agent->id()."\n" if $verbose;
	$self->delete_potential_farmstead($id);
	$self->{fstmap}->set_cell($x,$y,$agent->id() );
	1;
}
sub create_blank_farmsteadmap {
	my $self = shift;
	my $fstpotmap = shift;
	my $fstmap = $fstpotmap->create_alike(-1);
	$self->{fstmap} = $fstmap;
	1;
}
sub farmsteadmap {
	$_[0]->{fstmap};
}

sub set_farmsteadmap {
	my $self = shift;
	my $mapref = shift;
	$self->{fstmap} = $mapref;
}
sub add_agents {
	my $self = shift;
	$self->create_agent_registry() if (not exists $self->{AgentRegistry});
	$self->{AgentRegistry}->add_objects(@_);
}
sub create_agent_registry {
	my $self = shift;
	$self->{AgentRegistry} = SimpleObjectRegistry->create();
}
sub agents {
	$_[0]->{AgentRegistry}->object_list();
}

sub get_agent_by_id {
	$_[0]->{AgentRegistry}->get_object_by_id($_[1]);
}
sub create_potential_propertymap {
	my ($self, $proppotmap, $suitable) = @_;
	my $propmap = $proppotmap->create_copy();
	my %reclasshash;
	foreach (@$suitable) {$reclasshash{$_} = -1};
	$propmap->reclassify(\%reclasshash, -9);
	#$propmap->save_as_ascii("test_reclass.txt");
	#declare each agents' farmsteads to be his property
	$propmap->overlay("update", $self->farmsteadmap() );
	#$propmap->save_as_ascii("test_fst.txt");
	#takeover the properties from the old propertymap
	$propmap->overlay("update", $self->propertymap() ) if (defined $self->propertymap);
	#$propmap->save_as_ascii("test_oldprop.txt");
	$propmap->create_polygonraster(-1);
	$self->set_propertymap($propmap);
	1;
}
sub propertymap {
	$_[0]->{propmap};
}

sub set_propertymap {
	my ($self, $mapref) = @_;
	$self->{propmap} = $mapref;
}


sub distribute_properties {
	my ($self, $potentialplots,$suitable,$avgplotsize,$att_to_distribute, $cellsize, $maxlevel, $verbose) = @_;
	$verbose = 0 if (not defined $verbose);
	$used++;
	#print "$avgplotsize,$att_to_distribute,$cellsize,$maxlevel\n ";
	$att_to_distribute = "farm_size" if (not defined  $att_to_distribute);
	$avgplotsize = 4 if (not defined $avgplotsize);
	$cellsize = 1 if (not defined $cellsize);
	$maxlevel = 20 if (not defined $maxlevel);
	
	
	$self->create_potential_propertymap($potentialplots, $suitable);
	
	my $finished = 0;
	my $avgplot = $avgplotsize / $cellsize;
	my $sigma = $avgplot *2;
	for my $curAgent ($self->agents()){
		my $tot = $curAgent->get_attribute($att_to_distribute) / $cellsize;
		$curAgent->set_attribute("notyetallocated", $tot);
		my $proptest = $curAgent->get_attribute("PROP_points");
		if (not defined $proptest) {
		    my $fpr = SimpleObjectRegistry->create();
		    my ($fst_x, $fst_y) = $curAgent->get_farmstead();
		    my $fpp = SimpleObject->create(x => $fst_x, y => $fst_y, max_chunk => -1);
		    $fpr->add_objects($fpp);
		    $curAgent->set_attribute("PROP_points",$fpr);
		}
		else {
		    foreach my $pp ($proptest->object_list) {
			  $pp->set_attribute("max_chunk" , -1);
		    }
		}
		
	}
	my $numAgents = $self->agents();
	my $numFinished = 0;
	my @agents_unfinished = $self->agents();
	my $cc = 0;		
	if ($self->option("DISTRIBUTE_ORDER") == 1) {
	    @agents_unfinished = sort {$b->get_attribute($att_to_distribute) <=> $a->get_attribute($att_to_distribute)} @agents_unfinished;

	}
	elsif ($self->option("DISTRIBUTE_ORDER") == 2) {
	    @agents_unfinished = sort {$a->get_attribute($att_to_distribute) <=> $b->get_attribute($att_to_distribute)} @agents_unfinished;

	}
	AGENTLOOP:  while (@agents_unfinished){
		my $curAgent = shift @agents_unfinished;
		next AGENTLOOP if ($curAgent->get_attribute("notyetallocated") <= 0);
		
		my ($id_largest, $biggestavailable) = $self->{propmap}->get_id_and_size_of_largest_polygon_of_value(-1);
		if (not defined $biggestavailable or $biggestavailable <= 0) {
		    print "Warning: Not all of the area could be distributed.\n";
		    $self->{propmap}->reclassify({-9 => -1},undef);
		    for my $curAgent ($self->agents()){
			  my $remainder = $curAgent->get_attribute("notyetallocated" ) * $cellsize;
			  $curAgent->set_attribute($att_to_distribute, $remainder);			  
		      }
		    return wantarray ? (0, \@agents_unfinished) :  0;
		}
		print "Allocation No.${used}_$cc\n" if $verbose;
		print "Unfinished agents: ". (scalar @agents_unfinished + 1)."\n" if $verbose;
		print "Finding plot for agent ".$curAgent->get_attribute("id")." \n" if $verbose;
		print "Area of agent still to be allocated: ".$curAgent->get_attribute("notyetallocated")."\n" if $verbose;
		print "Biggest available plot: $biggestavailable\n" if $verbose >= 2;
		
		
		
		#randomly determine a chunk size
		my $chunk;	
		RANDLOOP: while (1) {
			if ($curAgent->get_attribute("notyetallocated") == 1){
				$chunk = 1;
				last RANDLOOP;
			}
			if ($self->option("PLOT_PDF") == 1) {
			  $chunk= int (qnorm(rand()) * $sigma + $avgplot);
			}
			elsif ($self->option("PLOT_PDF") == 2) {
			  $chunk=  int rand ( $avgplot * 2); 
			}
			last RANDLOOP if ($chunk > 0 and $chunk <= $curAgent->get_attribute("notyetallocated") and $chunk <= $biggestavailable);
		}
		print "Plot size chosen: $chunk\n" if $verbose;

		#find a free polygon which is at least as big as the chunk, starting from the agents farmstead
				
		
		my @prop_point_list = $curAgent->get_attribute("PROP_points")->object_list;
		if ($verbose) {
		      print "Start points:\n";	
		      foreach my $startpoint (@prop_point_list) {
			      print $startpoint->get_attribute("x").", ".$startpoint->get_attribute("y")." (". $startpoint->get_attribute("max_chunk").")\n" if ($verbose >= 2);
		      }
		}

		my $nothingfound = 1;
		my ($x,$y);
		
		POLYGONSEARCH: while ($nothingfound and @prop_point_list) {
			my $startpoint = shift @prop_point_list;
			print "Check ". $startpoint->get_attribute("x").", ".$startpoint->get_attribute("y")." (". $startpoint->get_attribute("max_chunk")." > $chunk)\n" if ($verbose >= 2);
			if ($startpoint->get_attribute("max_chunk") == -1 or $chunk <= $startpoint->get_attribute("max_chunk")) {
				print "Start search from ". $startpoint->get_attribute("x").", ".$startpoint->get_attribute("y")." (". $startpoint->get_attribute("max_chunk")." > $chunk)\n" if ($verbose >= 2);
				for my $level (1..$maxlevel) {
				      my $numout = 0;
				      my @cellring = (0 .. 2**(2+$level) - 1);
				      my @shuffledcells = fisher_yates_shuffle (\@cellring);
				      
				      PSINNER: for my $n (@shuffledcells) {
					      my ($subx, $suby) = offset_of_cell_neighbor ($level, $n );
					      $x = $startpoint->get_attribute("x") + $subx;
					      $y = $startpoint->get_attribute("y") + $suby;
					      

					      if ($self->{propmap}->out_of_x_bounds($x) or $self->{propmap}->out_of_y_bounds($y)) {
						      print "$x, $y out of maps bounds!\n" if ($verbose >= 6);
						      $numout++;
						      next PSINNER;
					      };
					      next PSINNER unless ($self->{propmap}->get_cell($x,$y) == -1);
					      next PSINNER unless ($self->{propmap}->get_polygon_size_by_cell($x,$y) >= $chunk);
					      $nothingfound = 0;
					      last POLYGONSEARCH;
				      }
				#if nothing has been found yet and all cells of the cell ring lie out of map bounds stop searching, and report that this chunk size is too large
			      
				      if ($nothingfound and $numout == @cellring) {
					      print "No suitable polygon found in map (out of bounds, $numout, $#cellring)!\n" if $verbose;
					      $biggestavailable = $chunk -1;
					      next AGENTLOOP;
				      }
				}
				$startpoint->set_attribute("max_chunk", $chunk -1);
			}					
		}
		# if nothing has been found within 20 cells of any of the x,y combinations randomly choose any polygon large enough and start with its handle cell (workaround because of memory problems)
		if ($nothingfound) {
			print "No suitable polygon found within $maxlevel rings!\n" if $verbose;

			my $polyid = $self->{propmap}->get_id_of_random_polygon_of_value_and_larger_or_equal(-1, $chunk);
			unless (defined $polyid) {
				print "No suitable polygon found in map!\n" if $verbose;
				$biggestavailable = $chunk -1;
				next AGENTLOOP ;
			}
			($x,$y) = $self->{propmap}->get_handlecell_of_polygon_by_id($polyid);
			print "Workaround: Randomly selected suitable polygon $polyid \n" if $verbose;
			$nothingfound = 0;
		}

		my $selectedpoly = $self->{propmap}->get_polygon_id_by_cell($x,$y);
		print "Found Polygon $selectedpoly\n" if $verbose;
		print "Size: ". $self->{propmap}->get_size_of_polygon_id($selectedpoly) .", Value: ".$self->{propmap}->get_value_of_polygon_id($selectedpoly)."\n" if $verbose >= 2;
		# distribute a part of the polygon of size chunk to the agent
		my $cellstodistribute = $chunk;
		my @stack;
		#push start cell on stack
		my @cells_changed;
		#print "Distributing...\n" if $verbose;
		push @stack, {x => $x, y => $y, from => "start"};
		my ($curX,$curY);
		CELLDIST: while ($cellstodistribute and @stack) {
			
			my $c = pop @stack;
			$curX = $c->{x};
			$curY = $c->{y};
			my $from = $c->{from};
			#print "Test cell $curX, $curY\t" ;
			#print "Polygon: ". $self->{propmap}->get_polygon_id_by_cell($curX,$curY)."\t";
			#print "Value: ". $self->{propmap}->get_cell($curX, $curY)."\n";
			#check if cell is part of the polygon
			if (defined $self->{propmap}->get_polygon_id_by_cell($curX,$curY) and  $self->{propmap}->get_polygon_id_by_cell($curX,$curY) ==  $selectedpoly and $self->{propmap}->get_cell($curX, $curY) == -1) {
				#allocate the cell to the agent
				$self->{propmap}->set_cell($curX,$curY, $curAgent->id());
				$cellstodistribute -= 1;
				push @cells_changed, {x => $curX, y=> $curY};
				print "Allocated cell $curX, $curY to ".$curAgent->id()."\n" if $verbose;
				#push neighbouring cells on stack if not coming from there and not out of map bounds
				my @istack;
				push @istack, {x => $curX - 1, y=> $curY, from => "right"}   if (not $self->{propmap}->out_of_x_bounds($curX -1) and not $from eq "left");
				push @istack, {x => $curX + 1, y=> $curY, from => "left"}   if (not $self->{propmap}->out_of_x_bounds($curX +1) and not $from eq "right");
				push @istack, {x => $curX, y=> $curY - 1, from => "above"}   if (not $self->{propmap}->out_of_y_bounds($curY -1) and not $from eq "below");
				push @istack, {x => $curX, y=> $curY + 1, from => "below"}   if (not $self->{propmap}->out_of_y_bounds($curY +1) and not $from eq "above");
				my @shufstack =  fisher_yates_shuffle(\@istack) ; #permutate stack to avoid going always in one direction and thus having very narrow but long plots
				push @stack, @shufstack;
			}
			
		}
		undef @stack;
		my $allocated = $chunk - $cellstodistribute;
		print "Warning: Attributed less cells than expected from selected polygon [$selectedpoly, ${used}, $cc]!!!\n" if ($cellstodistribute > 0);
		my $np = SimpleObject->create(x => $curX, y=> $curY, max_chunk =>-1);
		$curAgent->get_attribute("PROP_points")->add_objects($np);
		#print "Refreshing polygon raster after allocation\n"if $verbose;
		#$self->{propmap}->save_as_ascii("test_pot1.txt");
		#$self->{propmap}->save_as_ascii("test_potpol1.txt","PolygonRaster");
		#$self->{propmap}->print_polygontable;
		#print "Cells changed:\n";
		#map {print "$_->{x}, $_->{y}\n" } @cells_changed;
		$self->{propmap}->update_polygonraster(\@cells_changed, $curAgent->id(), 1);
		
		$self->{propmap}->save_polygontable_as_ascii("./dbg/${used}_polyg_$cc.txt") if $verbose >= 3;
		$self->{propmap}->save_as_ascii("./dbg/${used}_propmap_$cc.txt") if $verbose >= 3;
		$self->{propmap}->save_as_ascii("./dbg/${used}_polygmap_$cc.txt","PolygonRaster") if $verbose >= 3;
		$curAgent->add_to_attribute("notyetallocated", -1* $allocated);
		push @agents_unfinished, $curAgent if ($curAgent->get_attribute("notyetallocated") >0 ); #enter agent at end of queue if still someting to distribute
		$cc++;
		print "\n" if $verbose;
	}

	$self->{propmap}->reclassify({-9 => -1},undef);
	for my $curAgent ($self->agents()){
	    my $remainder = $curAgent->get_attribute("notyetallocated" ) * $cellsize;
	    $curAgent->set_attribute($att_to_distribute, $remainder);			  
	}
	return wantarray ?  (1,undef) : 1;
}






1;