/*
 *  Copyright (C) 2008-2013 Christian Hergert <chris@dronelabs.com>,
 *                          Giulio Collura <random.cpp@gmail.com>,
 *                          Victor Eduardo <victoreduardm@gmail.com>,
 *                          ammonkey <am.monkeyd@gmail.com>
 *
 *  This program or library is free software; you can redistribute it
 *  and/or modify it under the terms of the GNU Lesser General Public
 *  License as published by the Free Software Foundation; either
 *  version 3 of the License, or (at your option) any later version.
 *
 *  This library 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
 *  Lesser General Public License for more details.
 *
 *  You should have received a copy of the GNU Lesser General
 *  Public License along with this library; if not, write to the
 *  Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA 02110-1301 USA.
 */

namespace FeedReader {

	/**
	 * This widget is a multiple option modal switch
	 *
	 * {{../../doc/images/ModeButton.png}}
	 */
	public class ModeButton : Gtk.Box {

		private class Item : Gtk.ToggleButton {
			public int index { get; construct; }
			public Item (int index) {
				Object (index: index);
				can_focus = false;
				add_events (Gdk.EventMask.SCROLL_MASK);
			}
		}

		public signal void mode_added (int index, Gtk.Widget widget);
		public signal void mode_removed (int index, Gtk.Widget widget);
		public signal void mode_changed (Gtk.Widget widget);

		/**
		 * Index of currently selected item.
		 */
		public int selected {
			get { return _selected; }
			set { set_active (value); }
		}

		/**
		 * Read-only length of current ModeButton
		 */
		public uint n_items {
			get { return item_map.size; }
		}

		private int _selected = -1;
		private Gee.HashMap<int, Item> item_map;
		private uint m_timeout_source_id = 0;

		/**
		 * Makes new ModeButton
		 */
		public ModeButton () {
			homogeneous = true;
			spacing = 0;
			can_focus = false;

			item_map = new Gee.HashMap<int, Item> ();

			var style = get_style_context ();
			style.add_class (Gtk.STYLE_CLASS_LINKED);
			style.add_class ("raised"); // needed for toolbars
		}

		/**
		 * Appends Pixbuf to ModeButton
		 *
		 * @param pixbuf Gdk.Pixbuf to append to ModeButton
		 */
		public int append_pixbuf (Gdk.Pixbuf pixbuf, string tooltip = "") {
			return append (new Gtk.Image.from_pixbuf (pixbuf), tooltip);
		}

		/**
		 * Appends text to ModeButton
		 *
		 * @param text text to append to ModeButton
		 * @return index of new item
		 */
		public int append_text (string text, string tooltip = "") {
			return append (new Gtk.Label(text), tooltip);
		}

		/**
		 * Appends icon to ModeButton
		 *
		 * @param icon_name name of icon to append
		 * @param size desired size of icon
		 * @return index of appended item
		 */
		public int append_icon (string icon_name, Gtk.IconSize size, string tooltip = "") {
			return append (new Gtk.Image.from_icon_name (icon_name, size), tooltip);
		}

		/**
		 * Appends given widget to ModeButton
		 *
		 * @param w widget to add to ModeButton
		 * @return index of new item
		 */
		public int append (Gtk.Widget w, string tooltip) {
			int index;
			for (index = item_map.size; item_map.has_key (index); index++);
			assert (item_map[index] == null);

			w.set_tooltip_text(tooltip);

			var item = new Item (index);
			item.scroll_event.connect (on_scroll_event);
			item.add (w);

			item.button_press_event.connect (() => {
				set_active (item.index);
				return true;
			});

			item_map[index] = item;

			add (item);
			item.show_all ();

			mode_added (index, w);

			return index;
		}

		/**
		 * Sets item of given index's activity
		 *
		 * @param new_active_index index of changed item
		 */
		public void set_active (int new_active_index, bool initSet = false) {
			return_if_fail (item_map.has_key (new_active_index));
			var new_item = item_map[new_active_index] as Item;

			if (new_item != null) {
				assert (new_item.index == new_active_index);
				new_item.set_active (true);

				if (_selected == new_active_index)
					return;

				// Unselect the previous item
				var old_item = item_map[_selected] as Item;
				if (old_item != null)
					old_item.set_active (false);

				_selected = new_active_index;

				if(!initSet)
				{
					if(m_timeout_source_id > 0)
					{
						GLib.Source.remove(m_timeout_source_id);
						m_timeout_source_id = 0;
					}

					m_timeout_source_id = GLib.Timeout.add(50, () => {
						mode_changed(new_item.get_child());
						m_timeout_source_id = 0;
						return false;
					});
				}
			}
		}

		/**
		 * Changes visibility of item of given index
		 *
		 * @param index index of item to be modified
		 * @param val value to change the visiblity to
		 */
		public void set_item_visible (int index, bool val) {
			return_if_fail (item_map.has_key (index));
			var item = item_map[index] as Item;

			if (item != null) {
				assert (item.index == index);
				item.no_show_all = !val;
				item.visible = val;
			}
		}

		/**
		 * Removes item at given index
		 *
		 * @param index index of item to remove
		 */
		public new void remove (int index) {
			return_if_fail (item_map.has_key (index));
			var item = item_map[index] as Item;

			if (item != null) {
				assert (item.index == index);
				item_map.unset (index);
				mode_removed (index, item.get_child ());
				item.destroy ();
			}
		}

		/**
		 * Clears all children
		 */
		public void clear_children () {
			foreach (weak Gtk.Widget button in get_children ()) {
				button.hide ();
				if (button.get_parent () != null)
					base.remove (button);
			}

			item_map.clear ();

			_selected = -1;
		}

		private bool on_scroll_event (Gtk.Widget widget, Gdk.EventScroll ev) {
			int offset;

			switch (ev.direction) {
				case Gdk.ScrollDirection.DOWN:
				case Gdk.ScrollDirection.RIGHT:
					offset = 1;
					break;
				case Gdk.ScrollDirection.UP:
				case Gdk.ScrollDirection.LEFT:
					offset = -1;
					break;
				default:
					return false;
			}

			// Try to find a valid item, since there could be invisible items in
			// the middle and those shouldn't be selected. We use the children list
			// instead of item_map because order matters here.
			var children = get_children ();
			uint n_children = children.length ();

			var selected_item = item_map[selected];
			if (selected_item == null)
				return false;

			int new_item = children.index (selected_item);
			if (new_item < 0)
				return false;

			do {
				new_item += offset;
				var item = children.nth_data (new_item) as Item;

				if (item != null && item.visible && item.sensitive) {
					selected = item.index;
					break;
				}
			} while (new_item >= 0 && new_item < n_children);

			return false;
		}
	}
}
