/***
* Name: Mondrian City
* Author: Arnaud Grignard, Tri Nguyen-Huu and Patrick Taillandier 
* Description: An abstract mobilty Model represented in a Mondrian World. 
* Tags: art, interaction, mobility
***/

model Mondrian_City


global{
	
	float weight_mobility1;
	float weight_mobility2;
	float weight_mobility3;
	int population_level;

	
	float spacing <- 0.75;
	float line_width <- 0.65;
	bool dynamical_width <- true;
	
	float building_scale <- 0.65; 
	bool show_cells <- false;
	bool show_building <- true;
	bool show_road <- true;
	bool show_agent <- true;
		 
	int grid_height <- 6;
	int grid_width <- 6;
	float environment_height <- 5000.0;
	float environment_width <- 5000.0;
	int global_people_size <-50;
			
	float computed_line_width;
	float road_width;
	float block_size;
	
	bool on_modification_cells <- false update: show_cells != show_cells_prev;
	bool show_cells_prev <- show_cells update: show_cells ;
	bool on_modification_bds <- false update: false;	
		
	map max_traffic_per_mode <- ["mobility1"::50, "mobility2"::50, "walk"::50];
	map mode_order <- ["mobility1"::0, "mobility2"::1, "walk"::2]; 
	map color_per_mode <- ["mobility1"::rgb(52,152,219), "mobility2"::rgb(192,57,43), "walk"::rgb(161,196,90), "mobility3"::#magenta];
	map shape_per_mode <- ["mobility1"::circle(global_people_size*0.225), "mobility2"::circle(global_people_size*0.21), "walk"::circle(global_people_size*0.2), "mobility3"::circle(global_people_size*0.21)];
	
	map offsets <- ["mobility1"::{0,0}, "mobility2"::{0,0}, "walk"::{0,0}];
	map> colormap_per_mode <- ["mobility1"::[rgb(107,213,225),rgb(255,217,142),rgb(255,182,119),rgb(255,131,100),rgb(192,57,43)], "mobility2"::[rgb(107,213,225),rgb(255,217,142),rgb(255,182,119),rgb(255,131,100),rgb(192,57,43)], "walk"::[rgb(107,213,225),rgb(255,217,142),rgb(255,182,119),rgb(255,131,100),rgb(192,57,43)]];
	map color_per_id <- ["residentialS"::#blue,"residentialM"::#white,"residentialL"::#cyan,"officeS"::#yellow,"officeM"::#red,"officeL"::#green];
	map nb_people_per_size <- ["S"::10.0, "M"::50.0, "L"::100.0];
	map proba_choose_per_size <- ["S"::0.1, "M"::0.5, "L"::1.0];
	map> id_to_building_type <- [1::["residential","S"],2::["residential","M"],3::["residential","L"],4::["office","S"],5::["office","M"],6::["office","L"]];
		
	float weight_mobility1_prev <- weight_mobility1;
	float weight_mobility2_prev <- weight_mobility2;
	float weight_mobility3_prev <- weight_mobility3;
	
	list residentials;
	map offices;

	map graph_per_mode;
	float road_capacity <- 10.0;
	geometry shape<-rectangle(environment_width, environment_height);
	float step <- sqrt(shape.area) /2000.0 ;
	
	map> speed_per_mobility <- ["mobility1"::[20.0,40.0], "mobility2"::[5.0,15.0], "walk"::[3.0,7.0], "mobility3"::[15.0,30.0]];
	
	init {
		list lines;
		float cell_w <- first(cell).shape.width;
		float cell_h <- first(cell).shape.height;
		loop i from: 0 to: grid_width {
			lines << line([{i*cell_w,0}, {i*cell_w,environment_height}]);
		}
		loop i from: 0 to: grid_height {
			lines << line([{0, i*cell_h}, {environment_width,i*cell_h}]);
		}
		create road from: split_lines(lines) {
			create road with: [shape:: line(reverse(shape.points))];
		}
		do update_graphs;
		block_size <- min([first(cell).shape.width,first(cell).shape.height]);
	}
	
	action update_graphs {
		loop mode over: ["walk", "mobility1", "mobility2"] {
			graph_per_mode[mode] <- directed(as_edge_graph(road where (mode in each.allowed_mobility)));
		}
	}
	
	reflex update_mobility  {
		if(weight_mobility1_prev != weight_mobility1) or (weight_mobility2_prev != weight_mobility2) or (weight_mobility3_prev != weight_mobility3) {
			ask people {
				know_mobility3 <- flip(weight_mobility3);
				has_mobility1 <- flip(weight_mobility1);
				has_mobility2 <- flip(weight_mobility2);
				
				do choose_mobility;
				do mobility;
			}
		}
		weight_mobility1_prev <- weight_mobility1;
		weight_mobility2_prev <- weight_mobility2;
		weight_mobility3_prev <-weight_mobility3;
		
	}
		
	reflex randomGridUpdate when:every(1000#cycle){
		do randomGrid;
	} 
		
	reflex compute_traffic_density{
		ask road {traffic_density <- ["mobility1"::[0::0,1::0], "mobility2"::[0::0,1::0], "walk"::[0::0,1::0], "mobility3"::[0::0,1::0]];}

		ask people{
			if current_path != nil and current_path.edges != nil{
				ask list(current_path.edges){
					traffic_density[myself.mobility_mode][myself.heading_index]  <- traffic_density[myself.mobility_mode][myself.heading_index] + 1;
				}
			}
		}
	}
	
	reflex precalculate_display_variables{
		road_width <- block_size * 2/3 * (1-building_scale);
		computed_line_width <- line_width * road_width/10;
		loop t over: mode_order.keys{
			offsets[t] <- {0.5*road_width*spacing*(mode_order[t]+0.5)/(length(mode_order)-0.5),0.5*road_width*spacing*(mode_order[t]+0.5)/(length(mode_order)-0.5)};
		}		
	}
		
	action manage_road{
		road selected_road <- first(road overlapping (circle(sqrt(shape.area)/100.0) at_location #user_location));
		if (selected_road != nil) {
			bool with_mobility1 <- "mobility1" in selected_road.allowed_mobility;
			bool with_mobility2 <- "mobility2" in selected_road.allowed_mobility;
			bool with_pedestrian <- "walk" in selected_road.allowed_mobility;
			map input_values <- user_input_dialog([enter("mobility1 allowed",with_mobility1),enter("mobility2 allowed",with_mobility2),enter("pedestrian allowed",with_pedestrian)]);
			if (with_mobility1 != input_values["mobility1 allowed"]) {
				if (with_mobility1) {selected_road.allowed_mobility >> "mobility1";}
				else {selected_road.allowed_mobility << "mobility1";}
				
			}
			if (with_mobility2 != input_values["mobility2 allowed"]) {
				if (with_mobility2) {selected_road.allowed_mobility >> "mobility2";}
				else {selected_road.allowed_mobility << "mobility2";}
			}
			if (with_pedestrian != input_values["pedestrian allowed"]) {
				if (with_pedestrian) {selected_road.allowed_mobility >> "walk";}
				else {selected_road.allowed_mobility << "walk";}
			}
			point pt1 <- first(selected_road.shape.points);
			point pt2 <- last(selected_road.shape.points);
			road reverse_road <- road first_with ((first(each.shape.points) = pt2) and (last(each.shape.points) = pt1));
			if (reverse_road != nil) {
				reverse_road.allowed_mobility <-  selected_road.allowed_mobility;
			}
			do update_graphs;
		}	
	}
	
	action createCell(int id, int x, int y){
		list types <- id_to_building_type[id];
		string type <- types[0];
		string size <- types[1];
		cell current_cell <- cell[x,y];
		bool new_building <- true;
		if (current_cell.my_building != nil) {
			building build <- current_cell.my_building;
			new_building <- (build.type != type) or (build.size != size);
		}
		if (new_building) {
			if (type = "residential") {
				ask current_cell {do new_residential(size);}
			} else if (type = "office") {
				ask current_cell {do new_office(size);}
			}
		}
	} 
	
   action randomGrid{
   	int id;
   	loop i from: 0 to: 5 {
		loop j from: 0 to: 5 {
		    if (flip(0.5)){
		        id <- 1+rnd(5);	
		    }else{
		    	id<--1;
		    }			
			if (id > 0) {
             do createCell(id, j, i);
			}
			cell current_cell <- cell[j,i];
			current_cell.is_active <- id<0?false:true;
			if (id<=0){					
				ask current_cell{ do erase_building;}
			}
		}
	}
   }		
}


species building {
	string size <- "S" among: ["S", "M", "L"];
	string type <- "residential" among: ["residential", "office"];
	list inhabitants;
	rgb color;
	geometry bounds;

	action initialize(cell the_cell, string the_type, string the_size) {
		the_cell.my_building <- self;
		type <- the_type;
		size <- the_size;
		do define_color;
		shape <- the_cell.shape;
		if (type = "residential") {residentials << self;}
		else if (type = "office") {
			offices[self] <- proba_choose_per_size[size];
		}
		bounds <- the_cell.shape + 0.5 - shape;
			
	}
	
	reflex populate when: (type = "residential"){
		int pop <- int(population_level/100 * nb_people_per_size[size]);
		if length(inhabitants) < pop{
			create people number: 1 with: [location::any_location_in(bounds)] {
				origin <- myself;
				origin.inhabitants << self;
				
				do reinit_destination;
			}
		}
		if length(inhabitants) > pop{
			people tmp <- one_of(inhabitants);
			inhabitants >- tmp;
			ask tmp {do die;}
		}
	}
	
	action remove {
		if (type = "office") {
			offices[] >- self;
			ask people {
				do reinit_destination;
			}
		} else {
			ask inhabitants {
				do die;
			}
		}
		cell(location).my_building <- nil;
		do die;
		
	}
	action define_color {
		color <- color_per_id[type+size];
	}
	aspect default {
		if show_building {draw shape scaled_by building_scale*1.1 wireframe:true color: color;}
	}
}

species road {
	int nb_people;
	map> traffic_density <- ["mobility1"::[0::0,1::0], "mobility2"::[0::0,1::0], "walk"::[0::0,1::0], "mobility3"::[0::0,1::0]];
	rgb color <- rnd_color(255);
	list allowed_mobility <- ["walk","mobility2","mobility1"];

	init {
	}
	
	int total_traffic{
		return sum(traffic_density.keys collect(sum(traffic_density[each])));
	}
	
	
	int total_traffic_per_mode(string m){
		return sum(traffic_density[m]);
	}
	
	
	rgb color_map(rgb c, float scale){
		return rgb(255+scale * (c.red - 255),255+scale * (c.green - 255),255+scale * (c.blue - 255));
	}

	aspect default {
		if(show_road){
			loop t over: mode_order.keys{
					float scale <- min([1,traffic_density[t][0] / max_traffic_per_mode[t]]);	
					if dynamical_width{
						if scale > 0 {draw shape + computed_line_width * scale color: color_per_mode[t] at: self.location+offsets[t];}
						scale <- min([1,traffic_density[t][1] / max_traffic_per_mode[t]]);	
						if scale > 0 {draw shape + computed_line_width * scale color: color_per_mode[t] at: self.location-offsets[t];}
					}else{
						if scale > 0 {draw shape + computed_line_width color: color_map(color_per_mode[t],scale) at: self.location+offsets[t];}
						scale <- min([1,traffic_density[t][1] / max_traffic_per_mode[t]]);	
						if scale > 0 {draw shape + computed_line_width color: color_map(color_per_mode[t],scale) at: self.location-offsets[t];}
					}
				}
			
		}
				

	}
}

species people skills: [moving]{

	int heading_index <- 0;
	string mobility_mode <- "walk"; 
	float display_size <-sqrt(world.shape.area)* 0.01;
	building origin;
	building dest;
	bool to_destination <- true;
	point target;
	bool know_mobility3 <- false;
	bool has_mobility1 <- flip(weight_mobility1);
	bool has_mobility2 <- flip(weight_mobility2);
	float max_dist_walk <- 1000.0;
	float max_dist_mobility2 <- 3000.0;
	float max_dist_mobility3 <- 5000.0;
	action choose_mobility {
		if (origin != nil and dest != nil) {
			float dist <- manhattan_distance(origin.location, dest.location);
			if (dist <= max_dist_walk ) {
					mobility_mode <- "walk";
			} else if (has_mobility2 and dist <= max_dist_mobility2 ) {
					mobility_mode <- "mobility2";
			} else if (know_mobility3 and (dist <= max_dist_mobility3 )) {
					mobility_mode <- "mobility3";
			} else if has_mobility1 {
					mobility_mode <- "mobility1";
			} else {
					mobility_mode <- "walk";
			}
		speed <- rnd(speed_per_mobility[mobility_mode][0],speed_per_mobility[mobility_mode][1]) #km/#h;
		}
	}
	
	float manhattan_distance (point p1, point p2) {
		return abs(p1.x - p2.x) + abs(p1.y - p2.y);
	}
	reflex update_heading_index{
		if (mod(heading+90,360) < 135) or (mod(heading+90,360) > 315){
						heading_index <- 0;
					} else{
						heading_index <- 1;
					}
	}
	action reinit_destination {
		dest <- empty(offices) ? nil : offices.keys[rnd_choice(offices.values)];
		target <- nil;
	}
	
	action mobility {
		do unregister;
		do goto target: target on: graph_per_mode[(mobility_mode = "mobility3") ? "mobility2" : mobility_mode] recompute_path: false ;
		do register;
	}
	action update_target {
		if (to_destination) {target <- any_location_in(dest);}//centroid(dest);}
		else {target <- any_location_in(origin);}//centroid(origin);}
		do choose_mobility;
		do mobility;
	}
	
	action register {
		if ((mobility_mode = "mobility1") and current_edge != nil) {
			road(current_edge).nb_people <- road(current_edge).nb_people + 1;
		}
	}
	action unregister {
		if ((mobility_mode = "mobility1") and current_edge != nil) {
			road(current_edge).nb_people <- road(current_edge).nb_people - 1;
		}
	}

	reflex move when: dest != nil{
		if (target = nil) {
			do update_target;
		}
		do mobility;
		if (target = location) {
			target <- nil;
			to_destination <- not to_destination;
			do update_target;
		}
	}
	
	reflex wander when: dest = nil and origin != nil {
		do wander bounds: origin.bounds;
	}

	
	aspect default{
		if(show_agent){
		point offset <- {0,0};
		if self.current_edge != nil {
		  offset <- offsets[mobility_mode]*(heading_index > 0 ? (-1): 1);	
		}
		if (target != nil or dest = nil) {
			if(mobility_mode ="mobility1"){
			  draw copy(shape_per_mode[mobility_mode])  color: color_per_mode[mobility_mode] border:color_per_mode[mobility_mode] rotate:heading +90 at: location+offset;
			}else{
			  draw copy(shape_per_mode[mobility_mode])  color: color_per_mode[mobility_mode] rotate:heading +90 at: location+offset;	
			}
		}	
	  }
	}
}

grid cell width: grid_width height: grid_height { 
	building my_building;
	bool is_active <- true;
	action new_residential(string the_size) {
		if (my_building != nil and (my_building.type = "residential") and (my_building.size = the_size)) {
			return;
		} else {
			if (my_building != nil ) {ask my_building {do remove;}}
			create building returns: bds{
				do initialize(myself, "residential", the_size);
			}
		}
		
	}
	action new_office (string the_size) {
		if (my_building != nil and (my_building.type = "office") and (my_building.size = the_size)) {
			return;
		} else {
			if (my_building != nil) {ask my_building {do remove;}}
			create building returns: bds{
				do initialize(myself, "office",the_size);
			}
			ask people {
				do reinit_destination;
			}
		}
	}
	action erase_building {
		if (my_building != nil) {ask my_building {do remove;}}
	}
	
	aspect default{
		if show_cells {draw shape scaled_by (0.5) color: rgb(100,100,100) ;}
	}
}

experiment MondrianCity type: gui autorun: true{
	float minimum_cycle_duration <- 0.05;
	parameter "mobility1 level" var: weight_mobility1 min: 0.1 max: 1.0 step: 0.1 colors: [#gamablue] <-0.5;
	parameter "mobility2 level" var: weight_mobility2 min: 0.1 max: 1.0 step: 0.1 colors: [#gamablue] <-0.5;
	parameter "mobility3 level" var: weight_mobility3 min: 0.1 max: 1.0 step: 0.1 colors: [#gamablue] <-0.5;
	parameter "Population level" var: population_level min: 0 max: 100 step: 1 colors: [#gamablue] <-50;

	output synchronized:true{
		display map background:#black toolbar:false type:opengl  axes:false fullscreen:false{
			species cell aspect:default;
			species road ;
			species people;
			species building;
			event["a"] {show_agent<-!show_agent;}
			event["r"] {show_road<-!show_road;}
			event["b"] {show_building<-!show_building;}		  	
		}		
	}
}