/*
 * $Id$
 *
 * Copyright © 2003 Keith Packard
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation, and that the name of Keith Packard not be used in
 * advertising or publicity pertaining to distribution of the software without
 * specific, written prior permission.  Keith Packard makes no
 * representations about the suitability of this software for any purpose.  It
 * is provided "as is" without express or implied warranty.
 *
 * KEITH PACKARD DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL KEITH PACKARD BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
 * PERFORMANCE OF THIS SOFTWARE.
 */

autoload RR
autoload RR::Send
autoload Server
autoload Server::Clients
autoload Server::Readreq
autoload Server::Show
autoload Server::Games

extend namespace Server {
    public namespace Dispatch {

	string function upper (string s)
	{
	    string  r = "";
	    for (int i = 0; i < String::length (s); i++)
		r = r + String::new (Ctype::toupper (s[i]));
	    return r;
	}

	void client_locked (file f, &Client c) {
	    void	respond (string fmt, poly args...) {
		Clients::client_send (&c, fmt, args...);
	    }

	    exception	closed ();

	    void assert_user ()
	    {
		if (c.user == User.none)
		    raise rr_error (Error.NONAMESET);
	    }

	    void assert_game ()
	    {
		if (c.game == GameRef.none)
		    raise rr_error (Error.NOTINGAME);
	    }

	    void print_client_score (&Client o) {
		Clients::print_client_score (&c, &o);
	    }

	    void print_client_games (&Client o) {
		Clients::print_client_games (&c, &o);
	    }

	    void print_client (&Client o) {
		Clients::print_client (&c, &o);
	    }

	    typedef struct {
		string	    command;
		string	    usage;
		string	    describe;
		void(string[])  f;
	    } Cmd;

	    string random_user () {
		const string	consonant = "bcdfghjklmnpqrstvwxz";
		const string	vowel = "aeiou";
		
		string randc (string a)
		{
		    int i = PRNG::randint (String::length (a));
		    return String::substr (a, i, 1);
		}
		string randthree ()
		{
		    return (randc (consonant) + 
			    randc (vowel) + 
			    randc (consonant));
		}

		return randthree () + randthree ();
	    }
	    
	    Cmd[] commands = {
		{	
		    command = "HELO", 
		    usage = "HELO <username>",
		    describe = 
		    "Sets the session username.  Must be used before\n"+
		    "any other command (aside from QUIT)\n",
		    f = void func (string[] w) {
			string username;
			if (dim (w) == 0) {
			    do
				username = random_user ();
			    while (Clients::find (username) != ClientRef.none);
			} else
			    username = w[0];
			if (Clients::find (username) != ClientRef.none)
			    raise rr_error (Error.INVALIDNAME);
			c.user.username = username;
			Sockets::sockaddr s = Sockets::getsockname (c.f);
			respond ("HELO %s %s %s %d\n",
				 server_id,
				 username,
				 Sockets::addr_to_string (s.addr),
				 s.port);
			Clients::server_send ("NOTICE USER %s\n", username);
			File::fprintf(stderr, "Client connect %s\n", username);
		    }
		},
		{
		    command = "WHO",
		    usage = "WHO",
		    describe =
		    "List connected users and the number of games they've won.\n",
		    f = void func (string[] w) {
			assert_user ();
			respond ("WHO");
			Clients::iterate (print_client_games);
			respond ("\n");
		    }
		},
		{
		    command = "GAMES",
		    usage = "GAMES",
		    describe =
		    "List available games.\n",
		    f = void func (string[] w) {
			assert_user ();
			respond ("GAMES");
			Games::iterate (void func(&Game g) {
			    respond (" %s", g.name);
			});
			respond ("\n");
		    }
		},
		{
		    command = "PLAYERS",
		    usage = "PLAYERS <game>",
		    describe =
		    "List players in the indicated game and their current score.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game = w[0];
			assert_user ();
			&Game g = &Games::find (game);
			respond ("PLAYERS");
			Games::iterate_client (&g, print_client_score, true, false);
			respond ("\n");
		    }
		},
		{
		    command = "WATCHERS",
		    usage = "WATCHERS <game>",
		    describe =
		    "List users watching the indicated game.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game = w[0];
			assert_user ();
			&Game	g = &Games::find (game);
			respond ("WATCHERS");
			Games::iterate_client (&g, print_client, false, true);
			respond ("\n");
		    }
		},
		{
		    command = "GAMEINFO",
		    usage = "GAMEINFO <game>",
		    describe =
		    "GAMEINFO <turn> <color> <shape> <state> <time> <bid> <active>\n"+
		    "\n"+
		    "<turn> is a number from 1 to 17 indicating the current turn\n"+
		    "<color> <shape> indicate the active piece\n"+
		    "<state> is one of:\n"+
		    "       new     Turn just started, no bids yet\n"+
		    "       bid     Bidding opened.  <time> indicates time remaining,\n"+
		    "               <bid> indicates the minimum bid\n"+
		    "       show    Bidding closed and solution being demonstrated\n"+
		    "               <active> indicates the person demonstrating\n"+
		    "       solved  Solution succesfully demonstrated that\n"+
		    "               is less than the active users bid. <active>\n"+
		    "               indicates the winner.\n"+
		    "<time> is valid only in BID state, else it's 0\n"+
		    "<bid> is valid in all but NEW state where it's 0\n"+
		    "<active> is valid in SHOW and SOLVED states, else it's \"\".\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game = w[0];
			assert_user ();
			&Game	g = &Games::find (game);

			int	    turn = 17 - dim (g.targets);

			int	    remain = 0;
			if (g.state == GameState.BID)
			{
			    remain = g.expire_time - time();
			    if (remain < 0) remain = 0;
			}

			int	    bid = 0;
			union switch (Games::lowest_bidder (&g)) {
			case none: break;
			case client c: bid = c.bid.bid.number; break;
			}

			string	    active = "";
			if (g.state == GameState.SHOW)
			    active = g.active.client.user.username;

			respond ("GAMEINFO %d %C %S %G %d %d %s\n",
				 turn, 
				 g.target.color,
				 g.target.shape,
				 g.state,
				 remain,
				 bid,
				 active);
		    }
		},
		{
		    command = "USERINFO",
		    usage = "USERINFO <username>",
		    describe =
		    "USERINFO <game> <playing> <games> <score> <bid>\n"+
		    "\n"+
		    "<game> is any currently associated game, else \"\".  If the user\n"+
		    "is not associated with any game, the remaining fields are\n"+
		    "false 0 0.\n"+
		    "\n"+
		    "<playing> is true if the user is playing and false if watching.\n"+
		    "\n"+
		    "<games> is the number of games won by this user\n"+
		    "\n"+
		    "<score> is a number from 0 to 17 indicating this players score\n"+
		    "\n"+
		    "<bid> is either \"0\" indicating no bid or a number indicating\n"+
		    "the users minimum bid.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string username = w[0];
			assert_user ();
			ClientRef	cr = Clients::find (username);
			union switch (cr) {
			case none:
			    raise rr_error (Error.NOUSER);
			case client c:
			    string  game = "";
			    bool    playing = false;
			    int	    games = c.games;
			    int	    score = 0;
			    int	    bid = 0;

			    union switch (c.game) {
			    case none:	break;
			    case game g:
				game = g.name;
				playing = c.playing;
				if (playing)
				{
				    score = c.score;
				    if (c.bid != Bid.none)
					bid = c.bid.bid.number;
				}
			    }
			    respond ("USERINFO %s %b %d %d %d\n",
				     game, playing, games, score, bid);
			}
		    }
		},
		{
		    command = "NEW",
		    usage = "NEW <game-suggestion>",
		    describe =
		    "Create new game.  <game-suggestion> will be used to construct\n"+
		    "the game name.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game=w[0];
			assert_user ();
			&Game g = &Games::new (game);
			Games::add_client (&g, &c, true);
			respond ("NEW %s\n", g.name);
		    }
		},
		{
		    command = "JOIN",
		    usage = "JOIN <game>",
		    describe =
		    "Join an existing game.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game=w[0];
			assert_user ();
			&Game g = &Games::find (game);
			Games::add_client (&g, &c, true);
			respond ("JOIN\n");
		    }
		},
		{
		    command = "WATCH",
		    usage = "WATCH <game>",
		    describe =
		    "Watch an existing game.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game=w[0];
			assert_user ();
			&Game g = &Games::find (game);
			Games::add_client (&g, &c, false);
			respond ("WATCH\n");
		    }
		},
		{
		    command = "DISPOSE",
		    usage = "DISPOSE <game>",
		    describe =
		    "Dispose an empty game.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			string game=w[0];
			assert_user ();
			&Game g = &Games::find (game);
			Games::dispose (&g);
			respond ("DISPOSE\n");
		    }
		},
		{
		    command = "SHOW",
		    usage = "SHOW",
		    describe =
		    "<game-board> is a quoted multi-line string containing an\n"+
		    "diagram of the game contents, (an array of cells). A single\n"+
		    "cell and its surrounding are indicated as:\n"+
		    "\n"+
		    " HHH \n"+
		    "VrcsV\n"+
		    " HHH\n"+
		    "\n"+
		    "H = ' ' or '='\n"+
		    "V = ' ' or '|'\n"+
		    "r = '.' or <robot-color>  (one of 'r', 'g', 'b', or 'y')\n"+
		    "c = '.' or <target-color> (one of 'r', 'g', 'b', or 'y')\n"+
		    "s = '.' or <target-shape> (one of 'c', 's', 'o', or 't')\n"+
		    "\n"+
		    "The goal robot and target (color and shape) are indicated with\n"+
		    "capital letters.\n"+
		    "\n"+
		    "For example:\n"+
		    "\n"+
		    "SHOW \"\n"+
		    " ===\n"+
		    "|R.. ... .gs\n"+
		    "\n"+
		    " byc|... .RT|\n"+
		    " ===    === \"\n"+
		    "\n"+
		    "R.. = 	Red robot (goal robot)\n"+
		    ".gs = 	Green square target\n"+
		    "byc = 	Blue robot on yellow circle target\n"+
		    ".RT =	Red triangle (goal target)\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			File::fprintf (f, "SHOW \"\n");
			Show::show (f, &c.game.game.board);
			File::fprintf (f, "\"\n");
		    }
		},
		{
		    command = "BID",
		    usage = "BID <number>",
		    describe =
		    "Make a bid on the current turn.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			int number = string_to_integer (w[0]);
			assert_user ();
			assert_game ();
			Games::bid (&c.game.game, &c, number);
			respond ("BID\n");
		    }
		},
		{
		    command = "REVOKE",
		    usage = "REVOKE",
		    describe =
		    "Revoke the current bid.  This makes it appear as if the user has\n"+
		    "never bid.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::revoke (&c.game.game, &c);
			respond ("REVOKE\n");
		    }
		},
		{
		    command = "ABANDON",
		    usage = "ABANDON",
		    describe =
		    "Give up trying to solve the current turn.  When all users\n"+
		    "ABANDON, TURN can be used to move to the next turn.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::abandon (&c.game.game, &c);
			respond ("ABANDON\n");
		    }
		},
		{
		    command = "NOBID",
		    usage = "NOBID",
		    describe =
		    "Give up trying to improve bids in the current turn.\n"+
		    "When all users NOBID, game moves to SHOW state.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::nobid (&c.game.game, &c);
			respond ("NOBID\n");
		    }
		},
		{
		    command = "MOVE",
		    usage = "MOVE <color> <dir1> <dir2> ...",
		    describe =
		    "Move the robot indicated by <color> in the indicated\n"+
		    "sequence of directions.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			Color col = color(w[0]);
			Direction[dim(w)-1] directions = { 
			    [i] = direction(w[i+1]) 
			};
			assert_user ();
			assert_game ();
			for (int i = 0; i < dim(directions); i++)
			    Games::move (&c.game.game, &c, col, directions[i]);
			respond ("MOVE %d\n", Games::count (&c.game.game));
		    }
		},
		{
		    command = "UNDO",
		    usage = "UNDO",
		    describe =
		    "Undo the last move.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::undo (&c.game.game, &c);
			respond ("UNDO\n");
		    }
		},
		{
		    command = "RESET",
		    usage = "RESET",
		    describe =
		    "Reset all robots back to their positions at the\n"+
		    "start of the turn.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::reset (&c.game.game, &c);
			respond ("RESET\n");
		    }
		},
		{
		    command = "TURN",
		    usage = "TURN",
		    describe =
		    "Advance the game to the next turn, (which will have a new\n"+
		    "target square). The robots will start in their final positions\n"+
		    "at the end of the first successfully demonstrated solution.\n"+
		    "\n"+
		    "TURN succeeds if one of the following is true:\n"+
		    "\n"+
		    "       + a solution has been demonstrated\n"+
		    "       + all players have passed\n"+
		    "       + all players have abandoned the turn\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::turn (&c.game.game, &c);
			respond ("TURN\n");
		    }
		},
		{
		    command = "PASS",
		    usage = "PASS",
		    describe =
		    "Give up attempting to demonstrate a solution.  Control passes\n"+
		    "to the next lowest bidder.  When all users have passed, TURN\n"+
		    "can be used to move to the next turn.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::pass (&c.game.game, &c);
			respond ("PASS\n");
		    }
		},
		{
		    command = "MESSAGE",
		    usage = "MESSAGE <text>",
		    describe =
		    "Broadcast <text> to all users.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			assert_user ();
			respond ("MESSAGE\n");
			string text = w[0];
			for (int i = 1; i < dim(w); i++)
			    text = text + " " + w[i];
			string u = (c.user == User.none ? "anonymous" :
				    c.user.username);
			Clients::server_send ("NOTICE MESSAGE %s %s\n", u, text);
		    }
		},
		{
		    command = "PART",
		    usage = "PART",
		    describe =
		    "Depart from the current game, but remain connected\n"+
		    "to the server.\n",
		    f = void func (string[] w) {
			assert_user ();
			assert_game ();
			Games::remove_client (&c);
			respond ("PART\n");
		    }
		},
		{
		    command = "QUIT",
		    usage = "QUIT",
		    describe =
		    "Disconnect from the server.\n",
		    f = void func (string[] w) {
			respond ("QUIT\n");
			raise closed();
		    }
		},
		{
		    command = "HELP",
		    usage = "HELP <command>",
		    describe =
		    "Provide help for <command>.\n",
		    f = void func (string[] w) {
		    	file	f = File::string_write ();
			void	print_help (&Cmd cmd, bool describe)
			{
			    File::fprintf (f, "%-12.12s %s\n", cmd.command, cmd.usage);
			    if (describe)
			    {
				File::fprintf (f, "\n%s\n", cmd.describe);
			    }
			}
			if (dim(w) > 0)
			{
			    for (int j = 0; j < dim(w); j++)
			    {
				string command = upper (w[j]);
				int i;
				for (i = 0; i < dim(commands); i++)
				    if (command == commands[i].command)
				    {
					print_help (&commands[i], true);
					break;
				    }
				if (i == dim(commands))
				    raise rr_error (Error.COMMAND);
			    }
			}
			else
			{
			    for (int i = 0; i < dim(commands); i++)
				print_help (&commands[i], false);
			}
			respond ("HELP %s\n", File::string_string(f));
		    }
		},
		{
		    command = "VERSION",
		    usage = "VERSION <client-version>",
		    describe =
		    "Negotiate protocol version.\n",
		    f = void func (string[] w) {
		    	if (dim (w) == 0)
			    raise rr_error (Error.SYNTAX);
			int version = min (string_to_integer (w[0],
							      server_version));
			c.version = version;
			respond ("VERSION %d\n", version);
		    }
		}
	    };

	    File::fprintf (stderr, "New client\n");

	    for (;;) {
		try {
		    File::flush (f);
		    string[]  words = Readreq::read (f);
		    
		    string  command = upper (words[0]);
		    string[dim(words)-1] args = { [i] = words[i+1] };
		    
		    if (words[0] == "") return;
		    int i;
		    for (i = 0; i < dim(commands); i++)
			if (command == commands[i].command)
			{
			    void f(string[*]);
			    try {
				f = commands[i].f;
			    } catch invalid_array_bounds (string s,
							  poly a, poly i) {
				raise rr_error (Error.SYNTAX);
			    }
			    f (args);
			    break;
			}
		    if (i == dim(commands))
			raise rr_error (Error.COMMAND);
		} catch rr_error (Error e) {
		    respond ("ERROR %E\n", e);
		} catch File::io_error (string msg, 
					File::error_type error,
					file f) {
		    File::fprintf (stderr, "%v: %s\n",
				   c.user, msg);
		    return;
		} catch File::io_eof (file f) {
		    return;
		} catch closed () {
		    return;
		}
	    }
	}

	void client_cleanup (&Client c) {
	    Games::remove_client (&c);
	    Clients::dispose (&c);
	}
	
	public void client (file f)
	{
	    twixt (lock (); unlock ())
		twixt ((&Client c = &Clients::new (f)), true;
		       client_cleanup (&c))
		    client_locked (f, &c);
	}
    }
}
