/*
 * 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 Nichrome;
autoload Nichrome::Box;
autoload Nichrome::Label;
autoload Nichrome::Solid;
autoload Mutex;

extend namespace Client {
	public namespace Userlist {

		import Nichrome;

		typedef struct {
			string		name;
			*Label::label_t	name_label;
			bool		playing;
			int		games;
			*Label::label_t	games_label;
			int		score;
			*Label::label_t	score_label;
			int		bid;
			*Label::label_t	bid_label;
			string		mode;
			*Label::label_t	mode_label;
		} user_t;

		typedef union {
			*user_t		user;
			void		none;
		} user_or_none_t;

		typedef struct {
			*Label::label_t		label;
			int			width;
		} column_t;

		public typedef struct {
			*nichrome_t		ui;
			*Box::box_t		box;
			column_t		name;
			column_t		games;
			column_t		score;
			column_t		bid;
			column_t		mode;
			(*user_t)[...]		names;
			user_or_none_t		showing;
			string			font;
			string			bold_font;
			string			italic_font;
			Link::link_t		link;
			Mutex::mutex		lock;
		} userlist_t;

		bool user_greater(*user_t a, *user_t b)  = a->name > b->name;

		string games_string(int games) {
			return sprintf("%d", games);
		}

		string score_string(bool playing, int score) {
			if (playing)
				return sprintf("%d", score);
			return "";
		}

		string bid_string(bool playing, int bid) {
			if (playing)
				return sprintf("%d", bid);
			return "";
		}

		void forname(*userlist_t ul, string name, void (*user_t, int u) doit) {
			twixt(Mutex::acquire(ul->lock); Mutex::release(ul->lock)) {
				for (int u = 0; u < dim(ul->names); u++)
					if (ul->names[u]->name == name) {
						doit (ul->names[u], u);
						break;
					}
			}
		}

		void foreach(*userlist_t ul, void (*user_t, int u) doit) {
			twixt(Mutex::acquire(ul->lock); Mutex::release(ul->lock)) {
				for (int u = 0; u < dim(ul->names); u++)
					doit (ul->names[u], u);
			}
		}

		public void score(*userlist_t ul, string name, int score) {
			forname(ul, name, void func (*user_t user, int u) {
					user->score = score;
					Label::relabel(user->score_label, score_string(user->playing, user->score));
				});
		}

		void update_user(*userlist_t ul, *user_t user) {
			Link::message_t	r = Link::command(ul->link, "USERINFO %s\n", user->name);
			if (r->reply[0] == "ERROR")
				return;
			user->playing = RR::boolean(r->reply[2]);
			user->games = string_to_integer(r->reply[3]);
			Label::relabel(user->games_label, games_string(user->games));
			user->score = string_to_integer(r->reply[4]);
			Label::relabel(user->score_label, score_string(user->playing, user->score));
			user->bid = string_to_integer(r->reply[5]);
			Label::relabel(user->bid_label, bid_string(user->playing, user->bid));
		}

		public void bid(*userlist_t ul, string name, int bid) {
			forname(ul, name, void func (*user_t user, int u) {
					user->bid = bid;
					Label::relabel(user->bid_label, bid_string(user->playing, user->bid));
				});
		}

		public void clear_bid(*userlist_t ul) {
			foreach(ul, void func (*user_t user, int u) {
					user->bid = 0;
					Label::relabel(user->bid_label, bid_string(user->playing, user->bid));
				});
		}

		public void clear_score(*userlist_t ul) {
			foreach(ul, void func (*user_t user, int u) {
					user->score = 0;
					Label::relabel(user->score_label, score_string(user->playing, user->score));
				});
		}

		void set_font(*user_t user, string font) {
			user->name_label->font = font;
			user->games_label->font = font;
			user->score_label->font = font;
			user->bid_label->font = font;
			user->mode_label->font = font;
		}

		void set_showing (*userlist_t ul, *user_t user, bool showing) {
			if (showing) {
				if (ul->showing != (user_or_none_t.user) user) {
					union switch (ul->showing) {
					case user old:
						set_font(old, ul->font);
						break;
					default:
					}
					ul->showing = (user_or_none_t.user) user;
					set_font(user, ul->bold_font);
				}
			} else {
				if (ul->showing == (user_or_none_t.user) user) {
					ul->showing = user_or_none_t.none;
					set_font(user, ul->font);
				}
			}
		}

		public void showing(*userlist_t ul, string name, bool showing) {
			forname(ul, name, void func (*user_t user, int u) {
					set_showing (ul, user, showing);
				});
		}

		public void clear_showing(*userlist_t ul) {
			twixt(Mutex::acquire(ul->lock); Mutex::release(ul->lock)) {
				union switch (ul->showing) {
				case user u:
					set_showing (ul, u, false);
					break;
				default:
				}
			}
		}

		public void clear_modes(*userlist_t ul) {
			foreach (ul, void func (*user_t user, int u) {
					Label::relabel(user->mode_label, "");
				});
		}

		public void set_mode(*userlist_t ul, string name, string mode) {
			forname (ul, name, void func (*user_t user, int u) {
					user->mode = mode;
					Label::relabel(user->mode_label, user->mode);
				});
		}

		public void update(*userlist_t ul) {
			foreach (ul, void func (*user_t user, int u) {
					update_user(ul, user);
				});
		}

		bool user_gt(*user_t a, *user_t b) {
			if (!a->playing && b->playing)
				return true;
			if (a->playing && !b->playing)
				return false;
			return a->name > b->name;
		}

		public void add(*userlist_t ul, string name, bool playing, int games, int score, int bid) {
			twixt(Mutex::acquire(ul->lock); Mutex::release(ul->lock)) {
				for (int u = 0; u < dim (ul->names); u++)
					if (ul->names[u]->name == name)
						return;

				*user_t user = &(user_t) {
					.name = name,
					.name_label = Label::new(ul->ui, name),
					.playing = playing,
					.games = games,
					.games_label = Label::new(ul->ui, games_string(games)),
					.score = score,
					.score_label = Label::new(ul->ui, score_string(playing, score)),
					.bid = bid,
					.bid_label = Label::new(ul->ui, bid_string(playing, bid)),
					.mode = "",
					.mode_label = Label::new(ul->ui, ""),
				};

				string font = ul->font;
				if (!playing)
					font = ul->italic_font;
				user->name_label->font = font;
				user->name_label->justify = Label::justify_t.left;
				user->games_label->font = font;
				user->score_label->font = font;
				user->bid_label->font = font;
				user->mode_label->font = font;

				int u = 0;
				for (; u < dim (ul->names); u++)
					if (user_gt (ul->names[u], user))
						break;

				Box::suspend(ul->box);
				Box::insert_row(ul->box, u+2);
				Box::add_row(ul->box, 0, u+2,
					     Box::widget_item(user->name_label, 0, 0),
					     Box::widget_item(user->games_label, 0, 0),
					     Box::widget_item(user->score_label, 0, 0),
					     Box::widget_item(user->bid_label, 0, 0),
					     Box::widget_item(user->mode_label, 0, 0));
				Box::release(ul->box);

				for (int t = dim(ul->names); t > u; t--)
					ul->names[t] = ul->names[t-1];

				ul->names[u] = user;
			}
		}

		public void remove(*userlist_t ul, string name) {
			forname(ul, name, void func (*user_t user, int u) {
					Box::delete_row(ul->box, u + 2);
					for (; u < dim(ul->names) - 1; u++)
						ul->names[u] = ul->names[u+1];
					setdim(ul->names, dim(ul->names) - 1);
				});
		}

		public void clear(*userlist_t ul) {
			twixt(Mutex::acquire(ul->lock); Mutex::release(ul->lock)) {
				Box::clear(ul->box, 0, 2, 0, dim(ul->names));
				setdim(ul->names, 0);
			}
		}

		protected void set_link(*userlist_t ul, Link::link_t link) {
			ul->link = link;
		}

		protected void clear_link(*userlist_t ul) {
			make_uninit(&ul->link);
		}

		protected void init (*userlist_t ul,
				     *nichrome_t ui) {
			ul->ui = ui;
			ul->font = Widget::default_font;
			ul->bold_font = sprintf("%s:bold", Widget::default_font);
			ul->italic_font = sprintf("%s:italic", Widget::default_font);

			ul->names = ((*user_t)[...]) {};
			ul->lock = Mutex::new();
			Cairo::cairo_t	cr = Nichrome::cairo(ui);
			Cairo::set_font(cr, ul->font);
			Cairo::text_extents_t x = Cairo::text_extents(cr, "x");
			real x_width = x.x_advance;
			void init_column(*column_t column, string label, int width) {
				column->label = Label::new(ui, label);
				column->label->font = ul->bold_font;
				column->width = ceil(width * x_width);
			}
			init_column(&ul->name, "Player", 32);
			ul->name.label->justify = Label::justify_t.left;
			init_column(&ul->games, "Games", 3);
			init_column(&ul->score, "Score", 2);
			init_column(&ul->bid, "Bid", 3);
			init_column(&ul->mode, "State", 12);
			ul->box = Box::new(Box::dir_t.horizontal,
					   Box::widget_item(ul->name.label, 0),
					   Box::widget_item(ul->games.label, 0),
					   Box::widget_item(ul->score.label, 0),
					   Box::widget_item(ul->bid.label, 0),
					   Box::widget_item(ul->mode.label, 0));
			Box::add_row(ul->box, 0, 1,
				     Box::widget_span_item(Solid::new(ui, 1, 1),
							   100, 1, 1, 0));
			Box::add_row(ul->box, 0, 2,
				     Box::glue_item(ul->name.width, 0, 0, 0),
				     Box::glue_item(ul->games.width, 0, 0, 0),
				     Box::glue_item(ul->score.width, 0, 0, 0),
				     Box::glue_item(ul->bid.width, 0, 0, 0),
				     Box::glue_item(ul->mode.width, 0, 0, 0));

			ul->showing = user_or_none_t.none;
		}

		public *userlist_t new (*nichrome_t ui) {
			*userlist_t	ul = &(userlist_t) {};
			init(ul, ui);
			return ul;
		}
	}
}
