import <h7>

// enable this macro for the benchmark
// #define BENCH

module ferry {
	constant int    BENCH_SAMPLES   = 100;
	constant double SIM_DURATION    = 24.0*60.0*7.0;
	constant double HARBOR_DISTANCE = 10.0;
	constant double FERRY_TIMEOUT   = 5.0;
	constant int    FERRY_CAPACITY  = 5;
	constant int    FERRY_COUNT     = 2;
	constant int    HARBOR_COUNT    = 4;
	
	random_variable ferry_cargo_len;
	random_variable ferry_load_time;
	random_variable car_wait_time;
	
	passive class Car(double duration) {
		double arrival_time = time;
		double load_duration = duration;
	}
	
	class Pier {
		rn_stream rng;
		set(Car) ranked FIFO landing_site;
		
		actions {
			double load_duration;
			forever {
				advance rv_expo(rng, 10.0);
				do load_duration = rv_normal(rng, 0.5, 0.2);
				while (load_duration < 0.0);
				place new Car(load_duration) into landing_site;
			}
		}
	}
	
	class Ferry(int _capacity, double _timeout, double _travel_time) {
		pointer(Car) cargo[_capacity];
		double timeout = _timeout;
		double travel_time = _travel_time;
		set(Pier) ranked FIFO piers;
		pointer(Pier) pier;
		pointer(Car) car;
		
		actions {
			double begin_loading, begin_waiting;
			int len = 0;
			int i;
			
			forever {
				for (pier = each Pier in piers) {
					// unload the cars
					for (i = 1; i <= len; ++i) {
						advance cargo[i]->load_duration;
					}
					
					len = 0;
					begin_loading = time;
					
					// wait until new cars arrive or a timeout occurs
					while (len < array_upper_bound(cargo, 1)) {
						begin_waiting = time;
						wait until pier->landing_site.size > 0
						        || time >= begin_waiting + timeout;
						car = first Car in pier->landing_site;
						
						if (car != NULL) {
							// a car arrived in time
							remove car from pier->landing_site;
							tabulate car_wait_time = time - car->arrival_time;
							advance car->load_duration;
							cargo[++len] = car;
						} else {
							// the timeout has been triggered
							break;
						}
					}
					
					tabulate ferry_load_time = time - begin_loading;
					tabulate ferry_cargo_len = len;
					
					// travel to the next harbor
					advance travel_time;
				}
			}
		}
	}
	
	procedure ferry(double duration, int ferries, int harbors) {
		pointer(Pier) ports[harbors];
		pointer(Ferry) ferry;
		pointer(Car) car;
		int i, j;
		
		// create all of the harbors
		for (i = 1; i <= harbors; ++i) {
			ports[i] = new Pier;
			activate ports[i];
		}
		
		// create all of the ferries
		for (i = 1; i <= ferries; ++i) {
			ferry = new Ferry(FERRY_CAPACITY, FERRY_TIMEOUT, HARBOR_DISTANCE);
			for (j = 1; j <= harbors; ++j)
				place ports[(i + j - 2) % harbors + 1] into ferry->piers;
			activate ferry;
		}
		
		// await the end of the simulation
		advance duration;
		
		// take cars into account that weren't picked up by a ferry
		for (i = 1; i <= harbors; ++i) {
			for (car = each Car in ports[i]->landing_site) {
				tabulate car_wait_time = time - car->arrival_time;
			}
		}
	}
	
	procedure main(int argc, string(*) argv[*]) {
		// interpret the optional command line arguments for simulation
		// duration and number of timed runs in this batch
		double sim_duration = SIM_DURATION;
		int bench_samples = BENCH_SAMPLES;
		if (argc > 1) {
			read string = argv[1] (sim_duration);
			if (argc > 2) {
			    read string = argv[2] (bench_samples);
			}
		}
		
		#ifdef BENCH
		random_variable samples;
		double real_start, real_end;
		int i;
		
		// remove the random variable from the system as to prevent resets on 'clear system'
		remove &samples from random_variable_set;
		
		for (i = 1; i <= bench_samples; ++i) {
			real_start = real_time();
			ferry(sim_duration, FERRY_COUNT, HARBOR_COUNT);
			real_end = real_time();
			tabulate samples = real_end - real_start;
			empty rn_stream_set;
			clear system;
		}
		
		print(sample_sum(samples)) "_.________";
		#else
		ferry(sim_duration, FERRY_COUNT, HARBOR_COUNT);
		report system;
		#endif
	}
}