/*
 * Copyright © 2012 Keith Packard <keithp@keithp.com>
 *
 * 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; version 2 of the License.
 *
 * 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, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
 */

autoload Client;
autoload Client::Link;
autoload Client::Util;
autoload Nichrome;
autoload Nichrome::Box;
autoload Nichrome::Label;
autoload Nichrome::Button;
autoload Nichrome::Solid;
autoload Nichrome::Textline;
autoload Sort;

extend namespace Client {
	public namespace Games {
		import Nichrome;
		import Util;

		public typedef games_t;

		typedef struct {
			string		name;
			string[*]	players;
		} game_t;

		public string current_game;
		
		protected typedef void (string game, bool watch) callback_t;

		public typedef struct {
			*nichrome_t		ui;
			*Box::box_t		box;
			*Box::box_t		game_box;
			*Label::label_t		title;
			*Button::button_t	close;
			*Textline::textline_t	game_name;
			Link::link_t		link;
			callback_t		callback;
			game_t[*]		games;
		} games_t;

		Mutex::mutex	games_mutex = Mutex::new();
		bool		games_running = false;
		*games_t	global_games;
		thread		global_games_thread;

		void stop(*games_t games) {
			twixt(Mutex::acquire(games_mutex); Mutex::release(games_mutex)) {
				games_running = false;
				Nichrome::destroy(games->ui);
				make_uninit(&global_games);
			}
		}

		int rows(*game_t game) {
			return max(1, dim(game->players)) + 1;
		}

		void find_game(*games_t games, string name, void (*game_t game, int row) doit) {
			int row = 2;
			for (int g = 0; g < dim(games->games); g++) {
				if (games->games[g].name == name)
					doit(&games->games[g], row);
				row += rows (&games->games[g]);
			}
		}

		
		void add_dispose_game(*games_t games, *game_t game, int row) {
			void dispose_game(&widget_t w, bool state) {
				static string name = game->name;
				if (state)
					Link::command(games->link,
						      "DISPOSE %s\n", name);
			}
			Box::add_widget(games->game_box, 1, row, Button::new(games->ui, "Dispose Game",
									     dispose_game), 0);
		}

		void remove_user(*games_t games, string game, string name) {
			find_game(games, game, void func (*game_t game, int row) {
					int p;
					for (p = 0; p < dim (game->players); p++)
						if (game->players[p] == name)
							break;
					if (p == dim(game->players))
						return;
					for (int q = p; q < dim(game->players) - 1; q++)
						game->players[q] = game->players[q+1];
					setdim(game->players, dim(game->players) - 1);
					int r = row + p;
					if (dim (game->players) == 0) {
						add_dispose_game(games, game, r);
					} else {
						Box::delete_row(games->game_box, r);
					}
					Nichrome::redraw(games->ui);
				});
		}

		void add_user(*games_t games, string game, string name) {
			find_game(games, game, void func (*game_t game, int row) {
					int p;
					for (p = 0; p < dim (game->players); p++)
						if (game->players[p] > name)
							break;
					for (int q = dim(game->players); q > p; q--)
						game->players[q] = game->players[q-1];
					int r = row + p;
					game->players[p] = name;
					*widget_t w = Util::new_left(games->ui, name);
					if (dim (game->players) > 1) {
						if (p == 0) {
							Box::insert_row(games->game_box, r+1);
							Box::add_widget(games->game_box, 1, r+1,
									new_left(games->ui, game->players[1]));
							Box::add_glue(games->game_box, 0, r+1, 1);
							Box::add_glue(games->game_box, 2, r+1, 1);
							Box::add_glue(games->game_box, 3, r+1, 1);
						} else {
							Box::insert_row(games->game_box, r);
							Box::add_glue(games->game_box, 0, r, 1);
							Box::add_glue(games->game_box, 2, r, 1);
							Box::add_glue(games->game_box, 3, r, 1);
						}
					}
					Box::add_widget(games->game_box, 1, r, w, 0);
					Nichrome::redraw(games->ui);
				});
		}

		Box::item_t separator(*nichrome_t ui) {
			return Box::widget_span_item(Solid::new(ui, 1, 1), 100, 1, 1, 0);
		}

		int add_game(*games_t games, string name) {
			Link::message_t players = Link::command(games->link, "PLAYERS %s\n", name);
			if (dim(players->reply) == 0 || players->reply[0] != "PLAYERS")
				return -1;
			int nplayers = (dim(players->reply) - 1) // 2;

			int g;
			int r = 2;
			for (g = 0; g < dim(games->games); g++) {
				if (name < games->games[g].name)
					break;
				r += rows(&games->games[g]);
			}
			for (int h = dim(games->games); h > g; h--) {
				games->games[h] = games->games[h-1];
			}
			games->games[g] = (game_t) {
				.name = name,
				.players = (string[...]) {}
			};
			Box::insert_row(games->game_box, r);
			Box::add_widget(games->game_box, 0, r, new_left(games->ui, games->games[g].name), 0);
			if (!is_uninit(&current_game) && current_game == name) {
				Box::add_widget(games->game_box, 2, r, Label::new(games->ui, ""), 0);
				Box::add_widget(games->game_box, 3, r, Label::new(games->ui, ""), 0);
			} else {
				Box::add_widget(games->game_box, 2, r, Button::new(games->ui, "Join Game",
										   void func (&widget_t w, bool state) {
											   if (state) {
												   games->callback(name, false);
												   stop(games);
											   }
										   }), 0);
				Box::add_widget(games->game_box, 3, r, Button::new(games->ui, "Watch Game",
										   void func (&widget_t w, bool state) {
											   if (state) {
												   games->callback(name, true);
												   stop(games);
											   }
										   }), 0);
			}
			add_dispose_game(games, &games->games[g], r);
			r++;
			Box::insert_row(games->game_box, r);
			Box::add(games->game_box, 0, r, separator(games->ui));
			for (int p = 0; p < nplayers; p++)
				add_user(games, name, players->reply[1+2*p]);
			return g;
		}

		void remove_game(*games_t games, string name) {
			int	row = 2;
			for (int g = 0; g < dim(games->games); g++) {
				int	rows = Games::rows(&games->games[g]);
				if (games->games[g].name == name) {
					for (int h = g; h < dim(games->games) - 1; h++)
						games->games[h] = games->games[h+1];
					setdim(games->games, dim(games->games) - 1);
					for (int r = 0; r < rows; r++)
						Box::delete_row(games->game_box, row);
					break;
				}
				row += rows;
			}
		}

		void get_games(*games_t games) {
			Link::message_t	r = Link::command(games->link, "GAMES\n");
			games->games = (game_t[...]) {};
			if (dim(r->reply) == 0 || r->reply[0] != "GAMES")
				return;
			for (int i = 1; i < dim(r->reply); i++)
				add_game(games, r->reply[i]);
		}

		public *games_t new(Link::link_t link, callback_t callback) {
			*games_t	games = &(games_t) {};
			games->link = link;
			games->ui = Nichrome::new("Ricochet Robots Games", 100, 100);
			games->title = new_bold(games->ui, "Games", Label::justify_t.center);
			games->close = Button::new(games->ui, "Close",
						  void func (&widget_t w, bool state) {
							  if (state)
								  stop(games);
						  });
			games->game_box = Box::new(Box::dir_t.horizontal,
						   Box::widget_item(new_bold(games->ui, "Game Name", Label::justify_t.left), 0),
						   Box::widget_item(new_bold(games->ui, "Players", Label::justify_t.left), 0),
						   Box::glue_item(1));

			Box::add_row(games->game_box, 0, 1, separator(games->ui));
			int r = 2;
			games->game_name = Textline::new(games->ui, 40);
			Box::add_widget(games->game_box, 0, r, games->game_name, 0);
			Box::add_widget(games->game_box, 1, r, Label::new(games->ui, ""), 0);
			Box::add_widget(games->game_box, 2, r, Button::new(games->ui, "New Game",
									   void func (&widget_t w, bool state) {
										   if (state) {
											   callback(games->game_name->text, true);
											   stop(games);
										   }
									   }), 0);
			Box::add_widget(games->game_box, 3, r, Label::new(games->ui, ""), 0);
			get_games(games);

			games->box = Box::new(Box::dir_t.vertical,
					      Box::widget_item(games->title, 0),
					      Box::box_item(games->game_box),
					      Box::box_item(Box::new(Box::dir_t.horizontal,
								     Box::glue_item(1),
								     Box::widget_item(games->close, 0))),
					      Box::glue_item(1));
			games->link = link;
			games->callback = callback;
			set_box(games->ui, games->box);
			set_key_focus(games->ui, games->game_name);
			return games;
		}

		public void run(*games_t games) {
			main_loop(games->ui);
		}

		public void handle_notice(Link::message_t notice) {
			twixt(Mutex::acquire(games_mutex); Mutex::release(games_mutex)) {
				if (!games_running)
					return;
				switch (notice->reply[1]) {
				case "GAME":
					add_game(global_games, notice->reply[2]);
					Nichrome::redraw(global_games->ui);
					break;
				case "DISPOSE":
					remove_game(global_games, notice->reply[2]);
					Nichrome::redraw(global_games->ui);
					break;
				case "WATCH":
				case "JOIN":
					add_user(global_games, notice->reply[3], notice->reply[2]);
					break;
				case "PART":
					printf ("Remove user %s from %s\n", notice->reply[3], notice->reply[2]);
					remove_user(global_games, notice->reply[3], notice->reply[2]);
					break;
				}
			}
		}

		public void start(Link::link_t link, callback_t callback) {
			twixt(Mutex::acquire(games_mutex); Mutex::release(games_mutex)) {
				if (!games_running) {
					games_running = true;
					global_games = new(link, callback);
					global_games_thread = fork run (global_games);
				}
			}
		}

		public void wait() {
			if (games_running)
				Thread::join (global_games_thread);
		}
	}
}
	
