/*
 * 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::Window;
autoload Client::Link;
autoload Client::Update;
autoload Client::Userlist;
autoload Client::Messages;
autoload Client::Games;
autoload Client::Host;
autoload ParseArgs;
autoload RR;
autoload Cairo;
autoload Nichrome;
autoload Nichrome::Button;
autoload Nichrome::Box;
autoload Nichrome::Label;
autoload Nichrome::Toggle;
autoload Nichrome::Textline;
autoload Nichrome::RRboard;
autoload Nichrome::Solid;
autoload Nichrome::Message;
autoload Mutex;

extend namespace Client {

	public namespace Main {
		import Nichrome;
		import RRboard;
		import Box;
		import RR;
		import Userlist;

		int	port = RR::Port;

		string	font = Widget::default_font;

		Mutex::mutex	board_lock = Mutex::new();

		Link::link_t	link;
		bool watch = false;

		ParseArgs::argdesc argd = {
			.args = {
				{ .var = { .arg_string = &Host::host },
				  .abbr = 'h',
				  .name = "host",
				  .expr_name = "hostname",
				  .desc = "server hostname"},
				{ .var = { .arg_int = &port },
				  .abbr = 'p',
				  .name = "port",
				  .expr_name = "port",
				  .desc = "server port" },
				{ .var = { .arg_string = &Host::name },
				  .abbr = 'n',
				  .name = "name",
				  .expr_name = "username",
				  .desc = "user name" },
				{ .var = { .arg_string = &Games::current_game },
				  .abbr = 'g',
				  .name = "game",
				  .expr_name = "gamename",
				  .desc = "proposed game name" },
				{ .var = { .arg_string = &font },
				  .abbr = 'f',
				  .name = "font",
				  .expr_name = "font name",
				  .desc = "default font name" },
				{ .var = { .arg_flag = &watch },
				  .abbr = 'w',
				  .name = "watch",
				  .desc = "watch game" },
			},
			.unknown = &(int user_argind),
		};

		&nichrome_t		ui;
		&Label::label_t		game_name;
		&Label::label_t		turn;
		&Label::label_t		state;

		/* New/Bid mode items */
		&Box::box_t		bid_box;
		&Button::button_t	bid;
		&Textline::textline_t	bid_value;
		&Button::button_t	revoke;
		&Button::button_t	abandon;
		&Button::button_t	nobid;

		/* Show mode items */
		&Box::box_t		show_box;
		&Label::label_t		moves;
		&Button::button_t	undo;
		&Button::button_t	reset;
		&Button::button_t	pass;

		/* Done mode items */
		&Box::box_t		done_box;
		&Button::button_t	next_turn;

		&Button::button_t	select_game;
		&Button::button_t	quit;
		&rrboard_widget_t	rrboard;
		&userlist_t		userlist;
		RR::GameState		current_state;
		&Messages::messages_t	messages;
		&Nichrome::widget_t	desired_focus;

		void update_board(string board) {
			twixt (Mutex::acquire(board_lock); Mutex::release(board_lock))
				Update::update(&(rrboard.board), board);
		}

		void update_robot(Color color, int x, int y) {
			twixt (Mutex::acquire(board_lock); Mutex::release(board_lock))
				Update::update_robot(&(rrboard.board), color, x, y);
		}

		void update_target(Color color, Shape shape) {
			twixt (Mutex::acquire(board_lock); Mutex::release(board_lock))
				Update::update_target(&(rrboard.board), color, shape);
		}

		int	current_moves = 0;

		void update_moves(int new_moves) {
			current_moves = new_moves;
			Label::relabel(&moves, sprintf("Moves: %d", new_moves));
		}

		int 	current_turn = 0;

		void update_game(string new_game) {
			Games::current_game = new_game;
			Label::relabel(&game_name, sprintf("Game: %s", Games::current_game));
		}

		void update_turn(int new_turn) {
			current_turn = new_turn;
			Label::relabel(&turn, sprintf("Turn: %d", new_turn));
		}

		void clear_bid() {
			Textline::set_text(&bid_value, "");
		}
			
		bool messages_active() {
			if (is_uninit(&(&ui.key_focus)))
				return false;
			return &ui.key_focus == messages.input;
		}

		void set_focus(&Nichrome::widget_t focus, bool force) {
			&desired_focus = &focus;
			if (!messages_active() || force)
				Nichrome::set_key_focus(&ui, &focus);
		}

		void update_state(string new_state) {
			Label::relabel(&state, sprintf("State: %s", new_state));
			Nichrome::suspend_draw(&ui);
			current_state = RR::game_state(new_state);
			Box::set_active(&bid_box, false);
			Box::set_active(&done_box, false);
			Box::set_active(&show_box, false);
			union switch (current_state){
			case NEW:
				RRboard::hide_timer(&rrboard);
				clear_bid();
				Userlist::clear_bid(&userlist);
				Userlist::clear_modes(&userlist);
				/* fall through ... */
			case BID:
				set_focus(&bid_value, false);
				Box::set_active(&bid_box, true);
				Userlist::clear_showing(&userlist);
				break;
			case SHOW:
				RRboard::stop_timer(&rrboard);
				set_focus(&rrboard, false);
				update_moves(0);
				Box::set_active(&show_box, true);
				break;
			case DONE:
				RRboard::hide_timer(&rrboard);
				set_focus(&rrboard, false);
				Box::set_active(&done_box, true);
				Userlist::clear_showing(&userlist);
				Userlist::clear_modes(&userlist);
				break;
			}
			Nichrome::release_draw(&ui);
		}

		void add_user(string name) {
			Link::message_t u = Link::command(link, "USERINFO %s\n", name);
			if (u->reply[0] == "ERROR")
				return;
			Userlist::add(&userlist, name,
				      RR::boolean(u->reply[2]),
				      string_to_integer(u->reply[3]),
				      string_to_integer(u->reply[4]),
				      string_to_integer(u->reply[5]));
		}

		void handle_notice(thread main) {
			for (;;) {
				try {
					Link::message_t notice = Link::notice(link);
				} catch Link::link_closing() {
					break;
				}
				switch (notice->reply[1]) {

				/* Global game notices */
				case "BOARD":
					update_board(notice->reply[2]);
					break;
				case "GAMESTATE":
					update_state (notice->reply[2]);
					break;
				case "TURN":
					update_target(RR::color(notice->reply[2]),
						      RR::shape(notice->reply[3]));
					update_turn(current_turn + 1);
					break;
				case "GAMEOVER":
					/* start new game */
					clear_score(&userlist);
					Userlist::update(&userlist);
					update_turn(0);
					break;
				case "JOIN":
				case "WATCH":
					if (dim(notice->reply) > 3)
						if (!is_uninit(&Games::current_game) && notice->reply[3] != Games::current_game)
							break;
					add_user(notice->reply[2]);
					break;
				case "PART":
					if (dim(notice->reply) > 3)
						if (!is_uninit(&Games::current_game) && notice->reply[3] != Games::current_game)
							break;
					Userlist::remove(&userlist, notice->reply[2]);
					break;
				case "MESSAGE":
					Messages::add(&messages, notice->reply[2],
						      notice->reply[3]);
					break;

				/* Bid notices */
				case "BID":
					Userlist::bid(&userlist, notice->reply[2],
						      string_to_integer(notice->reply[3]));
					break;
				case "REVOKE":
					Userlist::bid(&userlist, notice->reply[2], 0);
					break;
				case "TIMER":
					RRboard::set_timer(&rrboard,
							   string_to_integer(notice->reply[2]));
					break;
				case "ABANDON":
					Userlist::set_mode(&userlist, notice->reply[2], "abandon");
					break;
				case "NOBID":
					Userlist::set_mode(&userlist, notice->reply[2], "done");
					break;


				/* Solving notices */
				case "ACTIVE":
					Userlist::showing(&userlist, notice->reply[2], true);
					break;
				case "MOVE":
					update_moves(string_to_integer(notice->reply[2]));
					break;
				case "UNDO":
					update_moves(max(current_moves - 1, 0));
					break;
				case "RESET":
					update_moves(0);
					break;
				case "POSITION":
					update_robot(RR::color(notice->reply[2]),
						     string_to_integer(notice->reply[3]),
						     string_to_integer(notice->reply[4]));
					break;
				case "SCORE":
					Userlist::score(&userlist, notice->reply[2],
							string_to_integer(notice->reply[3]));
					break;
				}
				Nichrome::redraw(&ui);
				Games::handle_notice(notice);
			}
			Thread::send_signal(main, 0);
		}

		void move_callback(RR::Color color, RR::Direction dir) {
			Link::command(link, "MOVE %C %D\n", color, dir);
		}

		void do_undo() {
			Link::command(link, "UNDO\n");
		}

		void do_bid() {
			Link::command(link, "BID %s\n", bid_value.text);
			Textline::set_text(&bid_value, "");
		}

		void do_revoke() {
			Link::command(link, "REVOKE\n");
		}

		void do_abandon() {
			Link::command(link, "ABANDON\n");
		}

		void do_no_bid() {
			Link::command(link, "NOBID\n");
		}

		void do_pass() {
			Link::command(link, "PASS\n");
		}

		void go_message() {
			Messages::set_focus(&messages);
		}

		void done_message() {
			set_focus(&desired_focus, true);
		}
		
		void do_turn () {
			Link::command(link, "TURN\n");
		}

		void do_pass () {
			Link::command(link, "PASS\n");
		}

		void do_reset() {
			Link::command(link, "RESET\n");
		}

		void message_done(*Messages::messages_t m, string message) {
			Link::command(link, "MESSAGE %s\n", message);
			done_message();
		}
			
		bool global_key(&key_event_t key) {
			if (key.type != key_type_t.press)
				return false;

			if (messages_active())
				return false;

			switch (key.key) {
			case "m": case "slash":	go_message(); return true;
			}
			enum switch (current_state) {
			case NEW:
			case BID:
				switch (key.key) {
				case "b": case "Return": do_bid(); return true;
				case "r": do_revoke(); return true;
				case "a": do_abandon(); return true;
				case "d": do_no_bid(); return true;
				}
				break;
			case SHOW:
				switch (key.key) {
				case "p": case "P":
					do_pass();
					return true;
				case "BackSpace": case "u": case "U":
					do_undo();
					return true;
				}
				break;
			case DONE:
				switch (key.key) {
				case "Return": case "t": case "T":
					do_turn(); return true;
				}
				break;
			}
			return false;
		}

		public void do_quit() {
			Link::close(link);
			ui.running = false;
			RRboard::stop_timer(&rrboard);
			exit(0);
		}

		public void do_join(string game, bool watch) {
			Userlist::clear(&userlist);

			Games::current_game = game;

			Link::message_t r = Link::command(link, "%s %s\n", watch ? "WATCH" : "JOIN", game);

			if (r->reply[0] == "ERROR") {
				if (r->reply[1] == "NOGAME") {
					Link::message_t r = Link::command(link, "NEW %s\n", game);
					if (r->reply[0] == "ERROR") {
						printf ("Cannot create game %s: %s\n", game, r->reply[1]);
						do_quit();
					}
				} else {
					printf ("Cannot join game %s: %s\n", game, r->reply[1]);
				}
			}

			update_game(game);

			Link::message_t r = Link::command(link, "PLAYERS %s\n", game);

			if (r->reply[0] == "ERROR") {
				printf ("Cannot enumerate players: %s\n", r->reply[1]);
				do_quit();
			}

			for (int i = 1; i < dim(r->reply); i += 2)
				add_user(r->reply[i]);

			Link::message_t r = Link::command(link, "GAMEINFO %s\n", game);

			if (r->reply[0] == "ERROR") {
				printf ("Cannot get game info for %s: %s\n", game, r->reply[1]);
				do_quit();
			}
			update_turn(string_to_integer(r->reply[1]));

			update_state(r->reply[4]);

			if (current_state == RR::GameState.SHOW)
				Userlist::showing(&userlist, r->reply[7], true);

			Link::message_t r = Link::command(link, "SHOW\n");

			if (r->reply[0] == "ERROR") {
				printf ("Cannot show board: %s\n", r->reply[1]);
				do_quit();
			}

			update_board(r->reply[1]);
		}

		public void select_game_callback(string game, bool watch) {
			do_join (game, watch);
			Nichrome::redraw(&ui);
		}

		public void do_select_game() {
			Games::start(link, select_game_callback);
		}

		public void do_select_host() {
			if (!Host::select())
				exit(0);
		}

		public void main () {
			ParseArgs::parseargs(&argd, &argv);

			if (!is_uninit(&font))
				Widget::default_font = font;

			&ui = Nichrome::new("Ricochet Robots", board_width, board_height);
			Nichrome::hide(&ui);
			Nichrome::set_global_key(&ui, global_key);
			&rrboard = RRboard::new(&ui, move_callback);
			&userlist = Userlist::new(&ui);
			&messages = Messages::new(&ui, 20, message_done, Host::name);

			&select_game = Button::new(&ui, "Switch Game",
						   void func (&widget_t w, bool state) {
							   if (state)
								   do_select_game();
						   });

			&quit = Button::new(&ui, "Quit",
					    void func (&widget_t w, bool state) {
						    do_quit();
					    });

			&game_name = Label::new(&ui, "game");

			&turn = Label::new(&ui, "turn");

			&state = Label::new(&ui, "state");

			/* Bid mode items */
			&bid = Button::new(&ui, "Bid",
					   void func (&widget_t w, bool state) {
						   if (state)
							   do_bid();
					   });

			&bid_value = Textline::new(&ui, 6);
			clear_bid();

			&revoke = Button::new(&ui, "Revoke",
					      void func (&widget_t w, bool state) {
						      if (state)
							      do_revoke();
					      });

			&abandon = Button::new(&ui, "Abandon",
					      void func (&widget_t w, bool state) {
						      if (state)
							      do_abandon();
					      });

			&nobid = Button::new(&ui, "Done Bidding",
					      void func (&widget_t w, bool state) {
						      if (state)
							      do_no_bid();
					      });

			&bid_box = Box::new(Box::dir_t.horizontal,
					    Box::widget_item(&bid, 0),
					    Box::widget_item(&bid_value, 0),
					    Box::widget_item(&revoke, 0),
					    Box::widget_item(&abandon, 0),
					    Box::widget_item(&nobid, 0));

			/* Show mode items */
			&moves = Label::new(&ui, "moves");

			&undo = Button::new(&ui, "Undo Move",
					    void func (&widget_t w, bool state) {
						    if (state)
							    do_undo();
					    });

			&reset = Button::new(&ui, "Reset Robots",
					     void func (&widget_t w, bool state) {
						     if (state)
							     do_reset();
					     });
			&pass = Button::new(&ui, "Pass",
					    void func (&widget_t w, bool state) {
						    if (state)
							    do_pass();
					    });
			&show_box = Box::new(Box::dir_t.horizontal,
					     Box::widget_item(&moves, 0),
					     Box::widget_item(&undo, 0),
					     Box::widget_item(&reset, 0),
					     Box::widget_item(&pass, 0));

			&next_turn = Button::new(&ui, "Turn",
						 void func (&widget_t w, bool state) {
							 if (state)
								 do_turn();
						 });

			&done_box = Box::new(Box::dir_t.horizontal,
					     Box::widget_item(&next_turn, 0));

			&box_t			top = Box::new (Box::dir_t.horizontal,
								Box::widget_item(&game_name, 0),
								Box::widget_item(&turn, 0),
								Box::widget_item(&state, 0),
								Box::box_item(&show_box),
								Box::box_item(&bid_box),
								Box::box_item(&done_box),
								Box::glue_item(1),
								Box::widget_item(&select_game, 0),
								Box::widget_item(&quit, 0));
			&box_t			rbox = Box::new (Box::dir_t.vertical,
								 Box::box_item(userlist.box),
								 Box::widget_item(Solid::new(&ui, 1, 1),
										  1, 0),
								 Box::box_item(messages.vbox));
			&box_t			hbox = Box::new (Box::dir_t.horizontal,
								 Box::widget_item(&rrboard, 1, 1, 1),
								 Box::box_item(&rbox));

			&box_t			vbox = Box::new (Box::dir_t.vertical,
								 Box::box_item(&top),
								 Box::box_item(&hbox));

			set_box(&ui, &vbox);

			for (;;) {
				if (is_uninit(&Host::host))
					do_select_host();

				try {
					for (;;) {
						try {
							link = Link::new(Host::host, port);

							Link::message_t r = Link::command(link, "helo %s\n", Host::name);

							if (r->reply[0] == "ERROR")
								raise Link::link_error(sprintf("%s %s", r->reply[1], Host::name));
							break;
						} catch Link::link_error(string reason) {
							Nichrome::Message::new("Connection failed",
									       sprintf ("host \"%s\": %s", Host::host, reason));
						}
						do_select_host();
					}

					thread	me = Thread::current();
					fork handle_notice(me);

					Userlist::set_link(&userlist, link);

					if (is_uninit(&Games::current_game)) {
						do_select_game();
						Games::wait();
					}

					if (is_uninit(&Games::current_game))
						do_quit();

					Nichrome::show(&ui);

					main_loop(&ui);
				} catch Thread::signal(int sig) {
					if (sig != 0)
						raise Thread::signal(sig);
					make_uninit(&Host::host);
					Userlist::clear_link(&userlist);
					Nichrome::hide(&ui);
				}
			}
		}
	}
}

