/**
 *  Author: Tri Nguyen-Huu
 *  Description: A generalized Lotka-Volterra model. 
 * The left column and upper row allow to add or remove animal species. 
 * Each animal species has a population that evolves by default independantly from others, according to a logistic law (with a carrying capacity).
 * The buttons in the matrix can be pressed in order to add interactions, "+" denoting a positive action from the upper species towards the left species, 
 * and a "-" denotes a negative interaction. A positive interaction of species "A" towards "B" means that a high density of species high increases the
 * density of species B (for example A is predated by B). A negative interaction of species "A" towards "B" means that a high density of species high 
 * decreases the density of species B (for example B is predated by A).
 * 
 * Common Lotka-Volterra interactions between two spices A and B ares:
 * mutalism: A -> B: +, B -> A: +
 * A predating B: A -> B: -, B -> A: +
 * competition: A -> B: -, B -> A: -
 */

model GeneralizedLotkaVolterra

global {
	int max_species <- 8;
	string dummy <- '';

	string language <- "english" among: ["french","english"];

	map>> animal_names <- map(
										"french"::[["Goé","land"],["Ga","zelle"],["Tama","noir"],["La","pin"],["Cou","cou"],["Ca","nard"],["Cha","mois"],["Ecu","reuil"],["Elé","phant"],
										["Droma","daire"],["Pé","lican"],["Sou","ris"],["Pou","let"],["Perro","quet"],["Rossi","gnol"],["Gre","nouille"],["Phaco","chère"],["Maque","reau"],
										["Sar","dine"],["Mou","ton"],["Ser","pent"],["Tor","tue"],["Pu","tois"]],
										"english"::[["Chee","tah"],["Gi","raffe"],["Ele","phant"],["Ra","bbit"],["Squi","rrel"],
										["Chame","leon"],["Bumble","bee"],["Bu","ffalo"],["Ze","bra"],
										["Rattle","snake"],["Bea","ver"],["Sala","mander"],["Hippo","potamus"],["Pa","rrot"],
										["Rhino","ceros"],["Kanga","roo"],["Leo","pard"],["Alli","gator"],
										["Go","rilla"],["Croco","dile"],["Platy","pus"],["Octo","pus"],["Porcu","pine"]]
										);


	list possible_type <-["neutral","positive","negative"];
	map type_color <- ["neutral"::rgb(240,240,240),"negative"::rgb(250,65,65),"positive"::rgb(150,217,100)];
	
	image_file arrow <- image_file("../../includes/arrow.png");
	
	list color_list <- list_with(max_species,rgb(0,0,0,0));
	list species_list <- list_with(max_species, nil);
	
	geometry shape<-square(1000);
	graph the_graph <- [] ;
	map,string> edge_type <- [];
	float hKR4 <- 0.01;
	
	init{
	//	create population_count;
		loop i from: 0 to: max_species-1{
			color_list[i] <- rgb(int(240/max_species*i),int(240/max_species*i),255);
		}
		create solver_and_scheduler;
	}
	
	reflex layout_graph {
		the_graph <- layout_circle(the_graph,rectangle(world.shape.width * 0.7, world.shape.height*0.7),false);
	}
	
	action activate_act {
		button selected_button <- first(button overlapping (circle(1) at_location #user_location));
		
		if ((selected_button.grid_x > 0) and (selected_button.grid_y = 0)){
			selected_button <- button[selected_button.grid_y,selected_button.grid_x];
		}
		selected_button.button_pressed <- true;
	}	
}


species animal{
	float t;
	float pop;
	float r;
	float k;
	
	list positive_species <-[];
	list negative_species <-[];
	map interaction_coef <- [];
	
	action change_type(animal ani, string type){
		remove ani from: positive_species;
		remove ani from: negative_species;
		remove key: ani from: interaction_coef;
		if type = "positive" { positive_species <+ ani;}
		if type = "negative" { negative_species <+ ani;}
		if type != "neutral" {interaction_coef <+ ani::(rnd(100)/100);}
	}
	

	equation dynamics simultaneously: [animal]{ 
		diff(pop,t) = r*pop * (1 - pop/k + sum((positive_species where (!dead(each))) collect(interaction_coef[each]*each.pop/k)) - sum((negative_species where (!dead(each))) collect(interaction_coef[each]*each.pop/k)));		
        }
        
 
	aspect default{
		draw circle(20) color: #blue;
		draw name anchor: #left_center at: location+{30,0,0} color: #black font:font("SansSerif", 13, #bold);
	}
}


species solver_and_scheduler{
	float t;
	float dummy;
	list pop <- list_with(max_species,0.0);
	
	
	equation dynamics simultaneously: [animal]{ 
		diff(dummy,t)=0;		
    }
    
    	
	reflex solveEquation {      
 
    	loop selected_button over: (button where each.button_pressed){
    		// action for buttons in left column or upper row
			if ((selected_button.grid_x = 0) or (selected_button.grid_y = 0)) and (selected_button.grid_y != selected_button.grid_x){
				if (selected_button.grid_x > 0) {selected_button <- button[selected_button.grid_y,selected_button.grid_x];}
				button opposite_button <- button[selected_button.grid_y,selected_button.grid_x];
				if (selected_button.active){
					// remove animal species and reset interactions
					animal species_to_be_removed <- species_list[selected_button.grid_y - 1];
					the_graph <- species_to_be_removed remove_node_from the_graph;
					ask button where ((each.grid_x = selected_button.grid_y) or (each.grid_y = selected_button.grid_y)) {self.type <- "neutral";}
					ask animal {
						remove species_to_be_removed from: self.positive_species;
						remove key: species_to_be_removed from: self.interaction_coef;
					}
					ask species_to_be_removed {do die;}
					species_list[selected_button.grid_y - 1] <- nil;
				}else{
					// add a new animal species
					create animal{
						name <- animal_names[language][rnd(length(animal_names[language])-1)][0]+animal_names[language][rnd(length(animal_names[language])-1)][1];
						species_list[selected_button.grid_y - 1] <- self;
						r <- rnd(100)/100;
						k <- 30.0+rnd(50);
						pop <- rnd(k);
						the_graph <- the_graph add_node species_list[selected_button.grid_y - 1];
					}
				}
				selected_button.active <- !selected_button.active;
				opposite_button.active <- !opposite_button.active;
				ask button where (each.grid_x > 0 and each.grid_y > 0){
					self.active <- (species_list[self.grid_x-1] != nil) and (species_list[self.grid_y-1] != nil);
				}
			}
				
			// action for buttons in the main matrix
			if (selected_button.active) and (selected_button.grid_x > 0) and (selected_button.grid_y > 0) and (selected_button.grid_x != selected_button.grid_y){
				string new_type <- possible_type[mod(possible_type index_of(selected_button.type)+1,length(possible_type))];
				selected_button.type <- new_type;
				ask species_list[selected_button.grid_y - 1] {do change_type(species_list[selected_button.grid_x - 1], new_type);}
				add edge(species_list[selected_button.grid_x - 1], species_list[selected_button.grid_y - 1]) to: the_graph;
				add (species_list[selected_button.grid_x - 1]::species_list[selected_button.grid_y - 1])::new_type to: edge_type;
			}
			selected_button.button_pressed <- false;
    	} 	
    solve dynamics method: "rk4" step_size:0.01;
    }

	reflex update_count{
		loop i from: 0 to: max_species-1{
			pop[i] <- (species_list[i] != nil)?species_list[i].pop:0;
		}
	}
	
	
}



grid button width:max_species+1 height:max_species+1 
{
	string type <- "neutral";
	bool active <- false;
	bool button_pressed <- false;
	
	aspect classic {
		if (grid_x = 0 and grid_y > 0) {
			draw rectangle(shape.width,shape.height * 0.8) at: location - {0.1*shape.width,0,0} color: active?color_list[grid_y - 1]:rgb(230,230,230) ;
			if (species_list[grid_y - 1] != nil)  {draw species_list[grid_y -1].name font:font("SansSerif", 13, #bold) anchor: #left_center at: location - {shape.width*0.48,0,0} color: #black;}

		} else if (grid_y = 0 and grid_x > 0) {
			draw rectangle(shape.width*0.8,shape.height) at: location - {0,0.1*shape.height,0} color: active?color_list[grid_x - 1]:rgb(230,230,230) ;
			if (species_list[grid_x - 1] != nil)  {draw species_list[grid_x -1].name font:font("SansSerif", 13, #bold) anchor: #left_center at: location - {0,shape.height*0.0,0} rotate: -90 color: #black;}
		} else if (grid_x = grid_y){
			if grid_x != 0 {draw rectangle(shape.width * 0.8,shape.height * 0.8) color: active?rgb(200,200,200):rgb(240,240,240) ;}
		} else {
			draw rectangle(shape.width * 0.8,shape.height * 0.8) color: active?type_color[type]:rgb(240,240,240) ;
		}
	}
	
	
		aspect modern {
		if (grid_x = 0 and grid_y > 0) {
			draw rectangle(shape.width,shape.height * 0.8) at: location - {0.1*shape.width,0,0} color: active?color_list[grid_y - 1]:rgb(230,230,230) ;
			if (species_list[grid_y - 1] != nil)  {
				draw species_list[grid_y -1].name font:font("SansSerif", 12, #bold) anchor: #center at: location - {shape.width*0.05,0,0} color: #white;
			}
			if !active {
				draw "?" font:font("Arial", 60, #bold)  at: location - {0.1*shape.width,0.06 * shape.height,0} anchor: #center color: #white;
			}
		} else if (grid_y = 0 and grid_x > 0) {
			draw rectangle(shape.width*0.8,shape.height) at: location - {0,0.1*shape.height,0} color: active?color_list[grid_x - 1]:rgb(230,230,230) ;
			if !active {
				draw "?" font:font("Arial", 60, #bold)  at: location - {0.0*shape.width, 0.1 * shape.height,0} anchor: #center color: #white;
			}
			if (species_list[grid_x - 1] != nil)  {draw species_list[grid_x -1].name font:font("SansSerif", 12, #bold) anchor: #center at: location + {0.2 * shape.width,-shape.height*0.25,0} rotate: -90 color: #white;}
		} else if (grid_x = grid_y){
			
			if grid_x != 0 {
				draw rectangle(shape.width * 0.8,shape.height * 0.8) color: #white  border: rgb(240,240,240);
			} else {
				draw arrow size: shape.width *0.7;
			}
		} else {
			if active{
				draw rectangle(shape.width * 0.8,shape.height * 0.8) color: type_color[type];	
				if type != "neutral" {draw rectangle(shape.width * 0.55,shape.height * 0.15) color: #white;}
				if type = "positive" {draw rectangle(shape.width * 0.15,shape.height * 0.55) color: #white;}
			}else{
				draw rectangle(shape.width * 0.8,shape.height * 0.8) color: #white  border: rgb(240,240,240);
			}
		}
	}
}





experiment simulation type: gui autorun: true  {
	float minimum_cycle_duration <- 0.1;
	parameter "Language for animal names" var: language category: "language";
 	text "Click on '?'s to add animal species, then click on grey squares to change among 3 types of interactions:
'+' means that the upper species has a positive impact on the left species (e.g. the left one eats the top one). 
'-' is for negative impact.
grey is neutral."
 		category: "Help";
 			
	output { 
		layout value: horizontal([0::50,vertical([1::50,2::50])::50]) tabs:true;
		display action_button name:"Interaction matrix" toolbar: false{
			species button aspect:modern ;
			event mouse_down action:activate_act;    
		}
		display LV name: "Time series" refresh: every(1#cycle) type: java2D toolbar: false{
			chart "Population size" type: series background: rgb('white') x_range: 200 x_tick_line_visible: false{
				loop i from: 0 to: max_species-1{
					data "Species "+i value: first(solver_and_scheduler).pop[i] color: (species_list[i] != nil)?color_list[i]:rgb(0,0,0,0) marker: false;
				}
			}
		}
		display "Interaction graph" type: java2D {
			graphics "edges" {
				loop edge over: the_graph.edges {
					float angle <- (pair(edge)).key towards (pair(edge)).value;
					point centre <- centroid(polyline([pair(edge).key.location,pair(edge).value.location]));
					draw geometry(edge) + 2 at: centre + {2.5*sin(angle),-2.5*cos(angle),0}color: edge_type[pair(edge)] = "negative"?#red:#green;
					draw triangle(20) rotate: angle + 90 at: centre + {12*cos(angle),12*sin(angle),0} color: edge_type[pair(edge)] = "negative"?#red:#green;
				}
 			}
 			species animal aspect: default;
		}
	
	}
}