using System;
using System.Collections;
using System.Collections.Generic;
using Mono.Unix;
using System.Runtime.InteropServices;

namespace Tomboy
{
	public class NoteMenuItem : Gtk.ImageMenuItem
	{
		Note note;
		Gtk.Image pin_img;
		bool pinned;
		bool inhibit_activate;

		static Gdk.Pixbuf note_icon;
		static Gdk.Pixbuf pinup;
		static Gdk.Pixbuf pinup_active;
		static Gdk.Pixbuf pindown;

		static NoteMenuItem ()
		{
			// Cache this since we use it a lot.
			note_icon = GuiUtils.GetIcon ("note", 16);
			pinup = GuiUtils.GetIcon ("pin-up", 16);
			pinup_active = GuiUtils.GetIcon ("pin-active", 16);
			pindown = GuiUtils.GetIcon ("pin-down", 16);
		}

		public NoteMenuItem (Note note, bool show_pin)
			: base (GetDisplayName(note))
		{
			this.note = note;
			Image = new Gtk.Image (note_icon);

			if (show_pin) {
				Gtk.HBox box = new Gtk.HBox (false, 0);
				Gtk.Widget child = Child;
				Remove (child);
				box.PackStart (child, true, true, 0);
				Add (box);
				box.Show();

				pinned = note.IsPinned;
				pin_img = new Gtk.Image(pinned ? pindown : pinup);
				pin_img.Show();
				box.PackStart (pin_img, false, false, 0);
			}
		}

		static string FormatForLabel (string name)
		{
			// Replace underscores ("_") with double-underscores ("__")
			// so Note menuitems are not created with mnemonics.
			return name.Replace ("_", "__");
		}

		static string GetDisplayName (Note note)
		{
			string display_name = note.Title;
			int max_length = 100;

			if (note.IsNew) {
				string new_string = Catalog.GetString(" (new)");
				max_length -= new_string.Length;
				display_name = Ellipsify (display_name, max_length)
					+ new_string;
			} else {
				display_name = Ellipsify (display_name, max_length);
			}

			return FormatForLabel (display_name);
		}

		static string Ellipsify (string str, int max)
		{
			if(str.Length > max)
				return str.Substring(0, max - 1) + "...";
			return str;
		}

		protected override void OnActivated ()
		{
			if (!inhibit_activate) {
				if (note != null)
					note.Window.Present ();
			}
		}

		protected override bool OnButtonPressEvent (Gdk.EventButton ev)
		{
			if (pin_img != null &&
			                ev.X >= pin_img.Allocation.X &&
			                ev.X < pin_img.Allocation.X + pin_img.Allocation.Width) {
				pinned = note.IsPinned = !pinned;
				pin_img.Pixbuf = pinned ? pindown : pinup;
				inhibit_activate = true;
				return true;
			}
			return base.OnButtonPressEvent (ev);
		}

		protected override bool OnButtonReleaseEvent (Gdk.EventButton ev)
		{
			if (inhibit_activate) {
				inhibit_activate = false;
				return true;
			}
			return base.OnButtonReleaseEvent (ev);
		}

		protected override bool OnMotionNotifyEvent (Gdk.EventMotion ev)
		{
			if (!pinned && pin_img != null) {
				if (ev.X >= pin_img.Allocation.X &&
				                ev.X < pin_img.Allocation.X + pin_img.Allocation.Width) {
					if (pin_img.Pixbuf != pinup_active)
						pin_img.Pixbuf = pinup_active;
				} else if (pin_img.Pixbuf != pinup) {
					pin_img.Pixbuf = pinup;
				}
			}
			return base.OnMotionNotifyEvent (ev);
		}

		protected override bool OnLeaveNotifyEvent (Gdk.EventCrossing ev)
		{
			if (!pinned && pin_img != null) {
				pin_img.Pixbuf = pinup;
			}
			return base.OnLeaveNotifyEvent (ev);
		}
	}
	
	
	public class TomboyTrayIcon : Gtk.StatusIcon
	{
		TomboyTray tray;
		TomboyGConfXKeybinder keybinder;

		public TomboyTrayIcon (NoteManager manager)
		{
			tray = new TomboyTray (manager, this);
			keybinder = new TomboyGConfXKeybinder (manager, tray);
			int panel_size = 22;
			Pixbuf = GuiUtils.GetIcon ("tomboy", panel_size);

			Tooltip = TomboyTrayUtils.GetToolTipText ();
		}
		
		public TomboyTray Tray
		{
			get {
				return tray;
			}
		}
		
		protected override void OnActivate()
		{
			ShowMenu (false);
		}
		
		protected override void OnPopupMenu (uint button, uint activate_time)
		{
			if (button == 3)
				GuiUtils.PopupMenu (MakeRightClickMenu (),
				                    null, 
				                    new Gtk.MenuPositionFunc (GetTrayMenuPosition));
				
		}
		
		public void ShowMenu (bool select_first_item)
		{
			TomboyTrayUtils.UpdateTomboyTrayMenu (tray, null);
			if (select_first_item)
				tray.TomboyTrayMenu.SelectFirst (false);

			
				
			GuiUtils.PopupMenu (tray.TomboyTrayMenu, null, 
				new Gtk.MenuPositionFunc (GetTrayMenuPosition));
		}
		
		public void GetTrayMenuPosition (Gtk.Menu menu,
		                             out int  x,
		                             out int  y,
		                             out bool push_in)
		{
			// some default values in case something goes wrong
			push_in = true;
			x = 0;
			y = 0;
			
			Gdk.Screen screen;
			Gdk.Rectangle area;
			Gtk.Orientation orientation;
			GetGeometry (out screen, out area, out orientation);
			
			x = area.X;
			y = area.Y;
			
			Gtk.Requisition menu_req = menu.SizeRequest ();
			if (y + menu_req.Height >= screen.Height)
				y -= menu_req.Height;
			else
				y += area.Height;
		}
		
		Gtk.Menu MakeRightClickMenu ()
		{
			Gtk.Menu menu = new Gtk.Menu ();

			Gtk.AccelGroup accel_group = new Gtk.AccelGroup ();
			menu.AccelGroup = accel_group;

			Gtk.ImageMenuItem item;

			item = new Gtk.ImageMenuItem (Catalog.GetString ("_Preferences"));
			item.Image = new Gtk.Image (Gtk.Stock.Preferences, Gtk.IconSize.Menu);
			item.Activated += ShowPreferences;
			menu.Append (item);

			item = new Gtk.ImageMenuItem (Catalog.GetString ("_Help"));
			item.Image = new Gtk.Image (Gtk.Stock.Help, Gtk.IconSize.Menu);
			item.Activated += ShowHelpContents;
			menu.Append (item);

			item = new Gtk.ImageMenuItem (Catalog.GetString ("_About Tomboy"));
			item.Image = new Gtk.Image (Gtk.Stock.About, Gtk.IconSize.Menu);
			item.Activated += ShowAbout;
			menu.Append (item);

			menu.Append (new Gtk.SeparatorMenuItem ());

			item = new Gtk.ImageMenuItem (Catalog.GetString ("_Quit"));
			item.Image = new Gtk.Image (Gtk.Stock.Quit, Gtk.IconSize.Menu);
			item.Activated += Quit;
			menu.Append (item);

			menu.ShowAll ();
			return menu;
		}

		void ShowPreferences (object sender, EventArgs args)
		{
			Tomboy.ActionManager ["ShowPreferencesAction"].Activate ();
		}

		void ShowHelpContents (object sender, EventArgs args)
		{
			Tomboy.ActionManager ["ShowHelpAction"].Activate ();
		}

		void ShowAbout (object sender, EventArgs args)
		{
			Tomboy.ActionManager ["ShowAboutAction"].Activate ();
		}

		void Quit (object sender, EventArgs args)
		{
			Tomboy.ActionManager ["QuitTomboyAction"].Activate ();
		}

	}
	
	public class TomboyTray
	{
		NoteManager manager;
		TomboyTrayIcon tray_icon = null;
		TomboyAppletEventBox applet_event_box = null;
		bool menu_added = false;
		List<Gtk.MenuItem> recent_notes = new List<Gtk.MenuItem> ();
		Gtk.Menu tray_menu;
		
		protected TomboyTray (NoteManager manager)
		{
			this.manager = manager;
			
			tray_menu = MakeTrayNotesMenu ();
			tray_menu.Hidden += MenuHidden;
		}
		
		public TomboyTray (NoteManager manager, TomboyTrayIcon tray_icon)
			: this (manager)
		{
			this.tray_icon = tray_icon;
		}
		
		public TomboyTray (NoteManager manager, TomboyAppletEventBox applet_event_box)
			: this (manager)
		{
			this.applet_event_box = applet_event_box;
		}
		
		public void ShowMenu (bool select_first_item)
		{
			if (applet_event_box != null)
				applet_event_box.ShowMenu (select_first_item);
			else if (tray_icon != null)
				tray_icon.ShowMenu (select_first_item);
		}
		
		Gtk.Menu MakeTrayNotesMenu ()
		{
			Gtk.Menu menu =
			        Tomboy.ActionManager.GetWidget ("/TrayIconMenu") as Gtk.Menu;

			bool enable_keybindings = (bool)
			                          Preferences.Get (Preferences.ENABLE_KEYBINDINGS);
			if (enable_keybindings) {
				// Create New Note Keybinding
				Gtk.MenuItem item =
				        Tomboy.ActionManager.GetWidget (
				                "/TrayIconMenu/TrayNewNotePlaceholder/TrayNewNote") as Gtk.MenuItem;
				if (item != null)
					GConfKeybindingToAccel.AddAccelerator (
					        item,
					        Preferences.KEYBINDING_CREATE_NEW_NOTE);

				// Show Search All Notes Keybinding
				item =
				        Tomboy.ActionManager.GetWidget (
				                "/TrayIconMenu/ShowSearchAllNotes") as Gtk.MenuItem;
				if (item != null)
					GConfKeybindingToAccel.AddAccelerator (
					        item,
					        Preferences.KEYBINDING_CREATE_NEW_NOTE);

				// Open Start Here Keybinding
				item =
				        Tomboy.ActionManager.GetWidget (
				                "/TrayIconMenu/OpenStartHereNote") as Gtk.MenuItem;
				if (item != null)
					GConfKeybindingToAccel.AddAccelerator (
					        item,
					        Preferences.KEYBINDING_OPEN_RECENT_CHANGES);
			}

			return menu;
		}
		
		void MenuHidden (object sender, EventArgs args)
		{
			// Remove the old dynamic items
			RemoveRecentlyChangedNotes ();
		}
		
		void RemoveRecentlyChangedNotes ()
		{
			foreach (Gtk.Widget item in recent_notes) {
				tray_menu.Remove (item);
			}

			recent_notes.Clear ();
		}
		
		public void AddRecentlyChangedNotes ()
		{
			int min_size = (int) Preferences.Get (Preferences.MENU_NOTE_COUNT);
			int max_size = 18;
			int list_size = 0;
			bool menuOpensUpward = MenuOpensUpward ();
			NoteMenuItem item;

			// Assume menu opens downward, move common items to top of menu
			Gtk.MenuItem newNoteItem = Tomboy.ActionManager.GetWidget (
			                                   "/TrayIconMenu/TrayNewNotePlaceholder/TrayNewNote") as Gtk.MenuItem;
			Gtk.MenuItem searchNotesItem = Tomboy.ActionManager.GetWidget (
			                                       "/TrayIconMenu/ShowSearchAllNotes") as Gtk.MenuItem;
			tray_menu.ReorderChild (newNoteItem, 0);
			int insertion_point = 1; // If menu opens downward
			
			// Find all child widgets under the TrayNewNotePlaceholder
			// element.  Make sure those added by add-ins are
			// properly accounted for and reordered.
			List<Gtk.Widget> newNotePlaceholderWidgets = new List<Gtk.Widget> ();
			IList<Gtk.Widget> allChildWidgets =
				Tomboy.ActionManager.GetPlaceholderChildren ("/TrayIconMenu/TrayNewNotePlaceholder");
			foreach (Gtk.Widget child in allChildWidgets) {
				if (child is Gtk.MenuItem &&
				    child != newNoteItem) {
					newNotePlaceholderWidgets.Add (child);
					tray_menu.ReorderChild (child, insertion_point);
					insertion_point++;
				}
			}
			
			tray_menu.ReorderChild (searchNotesItem, insertion_point);
			insertion_point++;

			DateTime days_ago = DateTime.Today.AddDays (-3);
			
			// Prevent template notes from appearing in the menu
			Tag template_tag = TagManager.GetOrCreateSystemTag (TagManager.TemplateNoteSystemTag);

			// List the most recently changed notes, any currently
			// opened notes, and any pinned notes...
			foreach (Note note in manager.Notes) {
				if (note.IsSpecial)
					continue;
				
				// Skip template notes
				if (note.ContainsTag (template_tag))
					continue;

				bool show = false;

				// Test for note.IsPinned first so that all of the pinned notes
				// are guaranteed to be included regardless of the size of the
				// list.
				if (note.IsPinned) {
					show = true;
				} else if ((note.IsOpened && note.Window.IsMapped) ||
				                note.ChangeDate > days_ago ||
				                list_size < min_size) {
					if (list_size <= max_size)
						show = true;
				}

				if (show) {
					item = new NoteMenuItem (note, true);
					// Add this widget to the menu (+insertion_point to add after new+search+...)
					tray_menu.Insert (item, list_size + insertion_point);
					// Keep track of this item so we can remove it later
					recent_notes.Add (item);

					list_size++;
				}
			}

			Note start = manager.FindByUri (NoteManager.StartNoteUri);
			if (start != null) {
				item = new NoteMenuItem (start, false);
				if (menuOpensUpward)
					tray_menu.Insert (item, list_size + insertion_point);
				else
					tray_menu.Insert (item, insertion_point);
				recent_notes.Add (item);

				list_size++;

				bool enable_keybindings = (bool)
				                          Preferences.Get (Preferences.ENABLE_KEYBINDINGS);
				if (enable_keybindings)
					GConfKeybindingToAccel.AddAccelerator (
					        item,
					        Preferences.KEYBINDING_OPEN_START_HERE);
			}


			// FIXME: Rearrange this stuff to have less wasteful reordering
			if (menuOpensUpward) {
				// Relocate common items to bottom of menu
				insertion_point -= 1;
				tray_menu.ReorderChild (searchNotesItem, list_size + insertion_point);
				foreach (Gtk.Widget widget in newNotePlaceholderWidgets)
					tray_menu.ReorderChild (widget, list_size + insertion_point);
				tray_menu.ReorderChild (newNoteItem, list_size + insertion_point);
				insertion_point = list_size;
			}

			Gtk.SeparatorMenuItem separator = new Gtk.SeparatorMenuItem ();
			tray_menu.Insert (separator, insertion_point);
			recent_notes.Add (separator);
		}
		
		public bool MenuOpensUpward ()
		{
			bool open_upwards = false;
			int val = 0;
			Gdk.Screen screen = null;
			
			if (applet_event_box != null) {
				int x, y;
				applet_event_box.GdkWindow.GetOrigin (out x, out y);
				val = y;
				screen = applet_event_box.Screen;
			} else if (tray_icon != null) {
				Gdk.Rectangle area;
				Gtk.Orientation orientation;
				tray_icon.GetGeometry(out screen, out area, out orientation);
				val = area.Y;
			}
			
			Gtk.Requisition menu_req = tray_menu.SizeRequest ();
			if (val + menu_req.Height >= screen.Height)
				open_upwards = true;

			return open_upwards;
		}

		public bool IsMenuAdded
		{
			get { return menu_added; }
			set { menu_added = value; }
		}

		public Gtk.Menu TomboyTrayMenu
		{
			get { return tray_menu; }
		}
		
		public NoteManager NoteManager
		{
			get { return manager; }
		}
	}
	
	public class TomboyTrayUtils
	{
	
		public static string GetToolTipText ()
		{
			string tip_text = Catalog.GetString ("Tomboy Notes");

			if ((bool) Preferences.Get (Preferences.ENABLE_KEYBINDINGS)) {
				string shortcut =
				        GConfKeybindingToAccel.GetShortcut (
				                Preferences.KEYBINDING_SHOW_NOTE_MENU);
				if (shortcut != null)
					tip_text += String.Format (" ({0})", shortcut);
			}
			
			return tip_text;
		}
		
		public static void UpdateTomboyTrayMenu (TomboyTray tray, Gtk.Widget parent)
		{
			if (!tray.IsMenuAdded) {
				if (parent != null)
					tray.TomboyTrayMenu.AttachToWidget (parent, GuiUtils.DetachMenu);
				tray.IsMenuAdded = true;
			}

			tray.AddRecentlyChangedNotes ();

			tray.TomboyTrayMenu.ShowAll ();
		}
	
	}

	//
	// This is a helper to take the XKeybinding string from GConf, and
	// convert it to a widget accelerator label, so note menu items can
	// display their global X keybinding.
	//
	// FIXME: It would be totally sweet to allow setting the accelerator
	// visually through the menuitem, and have the new value be stored in
	// GConf.
	//
	public class GConfKeybindingToAccel
	{
		static Gtk.AccelGroup accel_group;

		static GConfKeybindingToAccel ()
		{
			accel_group = new Gtk.AccelGroup ();
		}

		public static string GetShortcut (string gconf_path)
		{
			try {
				string binding = (string) Preferences.Get (gconf_path);
				if (binding == null ||
				                binding == String.Empty ||
				                binding == "disabled")
					return null;

				binding = binding.Replace ("<", "");
				binding = binding.Replace (">", "-");

				return binding;
			} catch {
			return null;
		}
	}

	[DllImport("libtomboy")]
		static extern bool egg_accelerator_parse_virtual (string keystring,
			                out uint keysym,
			                out uint virtual_mods);

		[DllImport("libtomboy")]
		static extern void egg_keymap_resolve_virtual_modifiers (
			        IntPtr keymap,
			        uint virtual_mods,
			        out Gdk.ModifierType real_mods);

		public static bool GetAccelKeys (string               gconf_path,
		                                 out uint             keyval,
		                                 out Gdk.ModifierType mods)
		{
			keyval = 0;
			mods = 0;

			try {
				string binding = (string) Preferences.Get (gconf_path);
				if (binding == null ||
				                binding == String.Empty ||
				                binding == "disabled")
					return false;

				uint virtual_mods = 0;
				if (!egg_accelerator_parse_virtual (binding,
				                                    out keyval,
				                                    out virtual_mods))
					return false;

				Gdk.Keymap keymap = Gdk.Keymap.Default;
				egg_keymap_resolve_virtual_modifiers (keymap.Handle,
				                                      virtual_mods,
				                                      out mods);

				return true;
			} catch {
			return false;
		}
	}

	public static void AddAccelerator (Gtk.MenuItem item, string gconf_path)
		{
			uint keyval;
			Gdk.ModifierType mods;

			if (GetAccelKeys (gconf_path, out keyval, out mods))
				item.AddAccelerator ("activate",
				                     accel_group,
				                     keyval,
				                     mods,
				                     Gtk.AccelFlags.Visible);
		}
	}
}
