/*
 * Verrückte Turing-Maschinen — A turing complete game
 * Copyright (C) 2016-2018 kcf <vidofnir@folksprak.org>
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 */

namespace mod_vtm
	{
		
		export namespace mod_model
			{
				
				export namespace mod_game
					{
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export type type_game =
							{
								world : mod_world.type_world;
								token : lib_errormonade.type_errormonade<mod_token.type_token>;
								task : mod_task.type_task;
								testindex : lib_errormonade.type_errormonade<int>;
								mode : mod_mode.type_mode;
								listeners : {[event : string] : Array<(data ?: any)=>void>};
							}
						;
						

						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function create
							(
								task : mod_task.type_task
							)
							: type_game
							{
								let game : type_game =
									{
										"world": undefined,
										"token": undefined,
										"task": task,
										"testindex": undefined,
										"mode": undefined,
										"listeners":
											{
												"change_task": [],
												"change_world": [],
												"change_token": [],
												"change_mode": [],
											}
										,
									}
								;
								world_clear(game, false);
								reset(game, false);
								return game;
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function listen
							(
								game : type_game,
								event : string,
								procedure : (data ?: any)=>void
							)
							: void
							{
								if (event in game.listeners)
									{
										game.listeners[event].push(procedure);
									}
								else
									{
										const message : string = "kein Ereignis mit diesem Name";
										throw (new Error(message));
									}
							}
						

						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						function notify
							(
								game : type_game,
								event : string,
								data : any = {}
							) : void
							{
								if (game.listeners.hasOwnProperty(event))
									{
										game.listeners[event]
										.forEach
											(
												(procedure) =>
													{
														procedure(data);
													}
											)
										;
									}
								else
									{
										const message : string = "kein Ereignis mit diesem Name";
										throw (new Error(message));
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function reset
							(
								game : type_game,
								inform : boolean = true
							)
							: void
							{
								game.token = (lib_errormonade.create_nothing<mod_token.type_token>());
								game.testindex = (lib_errormonade.create_nothing<int>());
								game.mode = mod_mode.initial;
								if (inform)
									{
										notify(game, "change_token", {});
										notify(game, "change_mode", {});
									}
								else
									{
										// nothing tun
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function task_read
							(
								game : type_game
							)
							: mod_task.type_task
							{
								return game.task;
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function task_set
							(
								game : type_game,
								task : mod_task.type_task
							) : void
							{
								game.task = task;
								// game.world_clear();
								notify(game, "change_task", {});
								reset(game);
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function world_read
							(
								game : type_game
							)
							: mod_world.type_world
							{
								return game.world;
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function world_set
							(
								game : type_game,
								world : mod_world.type_world,
								inform : boolean = true
							)
							: void
							{
								game.world = world;
								if (inform)
									{
										notify(game, "change_world", {});
									}
								else
									{
										// nothing tun
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function world_clear
							(
								game : type_game,
								inform : boolean = true
							)
							: void
							{
								game.world = mod_world.blanko();
								if (inform)
									{
										notify(game, "change_world", {});
									}
								else
									{
										// nothing tun
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function token_read
							(
								game : type_game
							)
							: lib_errormonade.type_errormonade<mod_token.type_token>
							{
								return game.token;
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function mode_read
							(
								game : type_game
							)
							: mod_mode.type_mode
							{
								return game.mode;
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function world_tile_change
							(
								game : type_game,
								spot : mod_spot.type_spot,
								inverted : boolean = false
							)
							: void
							{
								if (! (game.mode === mod_mode.initial))
									{
										const message : string = "gesperrt";
									}
								else
									{
										mod_world.tile_change(game.world, spot, inverted);
										notify
											(
												game,
												"change_world",
												{
													"kind": "tile_change",
													"data":
														{
															"spot": spot,
															"inverted": inverted,
															"tile": mod_world.tile_get(game.world, spot),
														}
												}
											)
										;
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function world_tile_rotate
							(
								game : type_game,
								spot : mod_spot.type_spot,
								increment : int = +1
							)
							: void
							{
								if (! (game.mode === mod_mode.initial))
									{
										const message : string = "gesperrt";
									}
								else
									{
										mod_world.tile_rotate(game.world, spot, increment);
										notify(game, "change_world", {});
									}
							}
						
						
						/**
						 * @author kcf <vidofnir@folksprak.org>
						 */
						export function resume
							(
								game : type_game
							)
							: void
							{
								switch (game.mode)
									{
										case mod_mode.initial:
											{
												game.mode = mod_mode.uncertain;
												game.testindex = (lib_errormonade.create_just<int>(0));
												notify(game, "change_mode", {});
												break;
											}
										case mod_mode.uncertain:
											{
												if (! lib_errormonade.filled<mod_token.type_token>(game.token))
													{
														const test : mod_test.type_test = mod_task.tests(game.task)[lib_errormonade.read<int>(game.testindex)];
														const tape : Array<mod_symbol.type_symbol> = lib_list.copy<mod_symbol.type_symbol>(mod_test.input(test));
														const spot : mod_spot.type_spot = mod_world.generator_finden(game.world);
														game.token = (
															lib_errormonade.create_just<mod_token.type_token>
																(
																	mod_token.create
																		(
																			tape,
																			spot
																		)
																)
														);
													}
												else
													{
														const token : mod_token.type_token = lib_errormonade.read<mod_token.type_token>(game.token);
														const spot : mod_spot.type_spot = mod_token.spot_read(token);
														const actuator_ : lib_errormonade.type_errormonade<mod_actuator.type_actuator> = mod_world.tile_get(game.world, spot);
														const actuator : mod_actuator.type_actuator = (
															lib_errormonade.filled<mod_actuator.type_actuator>(actuator_)
															? lib_errormonade.read<mod_actuator.type_actuator>(actuator_)
															: mod_actuator.mod_rejector.create_extended()
														);
														mod_actuator.use(actuator, token);
														const state : mod_state.type_state = mod_token.state_read(token);
														if (state === mod_state.running)
															{
																// nothing tun
															}
														else if ((state === mod_state.accepted) || (state === mod_state.rejected))
															{
																const accepted : boolean = (state === mod_state.accepted);
																const output : Array<mod_symbol.type_symbol> = mod_token.tape_read(token);
																game.token = (lib_errormonade.create_nothing<mod_token.type_token>());
																let testindex : int = lib_errormonade.read<int>(game.testindex);
																const tests : Array<mod_test.type_test> = mod_task.tests(game.task);
																const test : mod_test.type_test = tests[testindex];
																if (! mod_test.pruefen(test, accepted, output))
																	{
																		game.mode = mod_mode.wrong;
																		notify(game, "change_mode", {});
																	}
																else
																	{
																		testindex += 1;
																		if (testindex < tests.length)
																			{
																				// nächsten Test auswählen
																				game.testindex = (lib_errormonade.create_just<int>(testindex));
																			}
																		else
																			{
																				// auf Modus "correct" change
																				game.testindex = (lib_errormonade.create_nothing<int>());
																				game.mode = mod_mode.correct;
																				notify(game, "change_mode", {});
																			}
																	}
															}
														else
															{
																const message : string = "unbehandelter Zustand";
																throw (new Error(message));
															}
													}
												notify(game, "change_token", {});
												break;
											}
										case mod_mode.wrong:
											{
												// do nothing
												break;
											}
										case mod_mode.correct:
											{
												// do nothing
												break;
											}
										default:
											{
												const message : string = "unbehandelter Modus";
												throw (new Error(message));
												break;
											}
									}
									
							}
							
					}
				
			}
		
	}

