using System;
using System.Collections;
using Mono.Posix;
using Gtk;

namespace Tomboy
{
	public class NetworkSyncWindow : Window
	{
		NoteManager manager;
		NetworkData data;

		Gtk.AccelGroup accel_group;
		Gtk.Button close_button;
		Gtk.Button sync_button;
		Gtk.Button keep_button;
		Gtk.ScrolledWindow matches_window;
		Gtk.VBox content_vbox;

		Gtk.TreeView tree;
		Gtk.ListStore store;

		static Gdk.Pixbuf recent_icon;
		static Gdk.Pixbuf stock_notes;

		Note cur_lnote, cur_rnote;

		private enum Columns {
			ICON = 0,
			TITLE = 1,
			LOCAL_DATE = 2,
			REMOTE_DATE = 3,
			ACTION_KEEP = 4,
			ACTION_GET = 5,
			LOCAL_NOTE = 6,
			REMOTE_NOTE = 7,
			REMOTE_DATETIME = 8
                }


		static NetworkSyncWindow ()
		{
                        // FIXME: Get a better recent notes window icon
                        recent_icon = new Gdk.Pixbuf (null, "stock_notes.png");
                        stock_notes = GuiUtils.GetMiniIcon ("stock_notes.png");
		}

		public NetworkSyncWindow (NoteManager manager, NetworkData data)
			: base (Catalog.GetString ("Network Synchronization"))
		{ 
			this.manager = manager;
			this.data = data;
			this.Icon = recent_icon;
			this.DefaultWidth = 500;

			// For Escape (Close)
			accel_group = new Gtk.AccelGroup ();
			AddAccelGroup (accel_group);

			Gtk.Image image = new Gtk.Image (Gtk.Stock.SortAscending, 
							 Gtk.IconSize.Dialog);

			Gtk.Label label = new Gtk.Label (Catalog.GetString (
				"<b>Note Overview</b> ...\n" +
				"Select action for each note."));
			label.UseMarkup = true;
			label.Wrap = true;

			Gtk.HBox hbox = new Gtk.HBox (false, 2);
			hbox.BorderWidth = 8;
			hbox.PackStart (image, false, false, 4);
			hbox.PackStart (label, false, false, 0);
			hbox.ShowAll ();

			MakeSyncTree ();
			tree.Show ();

			// List all the current notes
			UpdateResults ();

			// Update on changes to notes
			manager.NoteDeleted += OnNotesChanged;
			manager.NoteAdded += OnNotesChanged;
			manager.NoteRenamed += OnNoteRenamed;
			
			matches_window = new Gtk.ScrolledWindow ();
			matches_window.ShadowType = Gtk.ShadowType.In;

			// Reign in the window size if there are notes with long
			// names, or a lot of notes...

			Gtk.Requisition tree_req = tree.SizeRequest ();
			if (tree_req.Height > 420)
				matches_window.HeightRequest = 420;
			else
				matches_window.VscrollbarPolicy = Gtk.PolicyType.Never;

			if (tree_req.Width < 280)
				matches_window.WidthRequest = 280;
			else
				matches_window.HscrollbarPolicy = Gtk.PolicyType.Never;

			matches_window.Add (tree);
			matches_window.Show ();

			keep_button = new Gtk.Button ();
			keep_button.Label = Catalog.GetString("Keep All");
			keep_button.Clicked += OnKeepClicked;
			keep_button.Show ();

			sync_button = new Gtk.Button ();
			sync_button.Label = Catalog.GetString("Sync");
			sync_button.Clicked += OnSyncClicked;
			sync_button.Show ();

			Separator separator = new Gtk.VSeparator ();
			separator.Show ();


			close_button = new Gtk.Button (Gtk.Stock.Close);
			close_button.Clicked += CloseClicked;
			close_button.AddAccelerator ("activate",
						     accel_group,
						     (uint) Gdk.Key.Escape, 
						     0,
						     Gtk.AccelFlags.Visible);
			close_button.Show ();

			Gtk.HButtonBox button_box = new Gtk.HButtonBox ();
			button_box.Layout = Gtk.ButtonBoxStyle.End;
			button_box.Spacing = 8;
			button_box.PackStart (keep_button);
			button_box.PackStart (sync_button);
			button_box.PackStart (separator);
			button_box.PackEnd (close_button);
			button_box.Show ();

			content_vbox = new Gtk.VBox (false, 8);
			content_vbox.BorderWidth = 6;
			content_vbox.PackStart (hbox, false, false, 0);
			content_vbox.PackStart (matches_window);
			content_vbox.PackStart (button_box, false, false, 0);
			content_vbox.Show ();

			this.Add (content_vbox);
		}

		void MakeSyncTree ()
		{
			Type [] types = 
				new Type [] {
					typeof (Gdk.Pixbuf), // icon
					typeof (string),     // title
					typeof (string),     // local change date
					typeof (string),     // remote change date
					typeof (bool),       // check keep
					typeof (bool),       // check get
					typeof (Note),       // note
					typeof (Note),       // note
					typeof (string),     // real date for remote note
				};

			store = new Gtk.ListStore (types);
			store.SetSortFunc ((int)Columns.LOCAL_DATE,
					   new Gtk.TreeIterCompareFunc (CompareLocalDates),
					   IntPtr.Zero, 
					   null);
			store.SetSortFunc ((int)Columns.REMOTE_DATE,
					   new Gtk.TreeIterCompareFunc (CompareRemoteDates),
					   IntPtr.Zero, 
					   null);
			//store.SetSortColumnId ((int)Columns.LOCAL_DATE, Gtk.SortType.Descending);

			tree = new Gtk.TreeView (store);
			tree.HeadersVisible = true;
			tree.RulesHint = true;
			tree.RowActivated += OnRowActivated;

			Gtk.CellRenderer renderer;

			// Column Icon+Title
			Gtk.TreeViewColumn title = new Gtk.TreeViewColumn ();
			title.Title = "Note";
			title.Sizing = Gtk.TreeViewColumnSizing.Autosize;
			title.Resizable = true;
			
			renderer = new Gtk.CellRendererPixbuf ();
			title.PackStart (renderer, false);
			title.AddAttribute (renderer, "pixbuf", (int)Columns.ICON);

			renderer = new Gtk.CellRendererText ();
			title.PackStart (renderer, true);
			title.AddAttribute (renderer, "text", (int)Columns.TITLE);
			title.SortColumnId = (int)Columns.TITLE;

			tree.AppendColumn (title);

			// Column Local Date
			Gtk.TreeViewColumn change = MakeDateColumn (
				Catalog.GetString ("Local Last Changed"),
				(int)Columns.LOCAL_DATE
				);
			tree.AppendColumn (change);

			// Column Remote Date
			change = MakeDateColumn (
				Catalog.GetString ("Remote Last Changed"),
				(int)Columns.REMOTE_DATE
				);
			tree.AppendColumn (change);

			// Action RadioButtons
			change = new Gtk.TreeViewColumn ();
			CellRendererToggle toggle = new CellRendererToggle();
			toggle.Activatable = true;
			toggle.Radio = true;
			toggle.Toggled += OnToggleKeep;
			tree.AppendColumn ("Keep", toggle, "active", (int)Columns.ACTION_KEEP);

			toggle = new CellRendererToggle();
			toggle.Activatable = true;
			toggle.Radio = true;
			toggle.Toggled += OnToggleGet;
			tree.AppendColumn ("Get", toggle, "active", (int)Columns.ACTION_GET);
		}

		private Gtk.TreeViewColumn MakeDateColumn (string title, int column)
		{
			Gtk.TreeViewColumn change = new Gtk.TreeViewColumn ();
			change.Title = title;
			change.Sizing = Gtk.TreeViewColumnSizing.Autosize;
			change.Resizable = true;

			Gtk.CellRenderer renderer = new Gtk.CellRendererText ();
			renderer.Data ["xalign"] = 1.0;
			change.PackStart (renderer, false);
			change.AddAttribute (renderer, "text", column);
			change.SortColumnId = column;

			return change;
		}

		void UpdateResults ()
		{
			store.Clear ();
			Hashtable lookup = new Hashtable ();

			foreach (NetworkData.SerNote ser_note in data.Notes ()) {
				// create remote note
				Guid guid = Guid.NewGuid ();
			 	string tmp_file = System.IO.Path.Combine (manager.NetworkDir, 
						guid.ToString () + ".note");
				Note note = new Note ("[Remote] "+ser_note.title, tmp_file, manager);
				note.XmlContent = ser_note.xml;
				manager.PluginManager.LoadPluginsForNote (note);

				// find matching local note
				lookup.Add (ser_note.title, 1);

				Gtk.TreeIter iter = store.Append ();
				store.SetValue (iter, (int)Columns.ICON, stock_notes);
				store.SetValue (iter, (int)Columns.TITLE, note.Title);
				store.SetValue (iter, (int)Columns.REMOTE_NOTE, note);
				store.SetValue (iter, (int)Columns.REMOTE_DATE, 
						PrettyPrintDate (ser_note.date));
				store.SetValue (iter, (int)Columns.REMOTE_DATETIME, ser_note.date.ToString());
				store.SetValue (iter, (int)Columns.ACTION_GET, true);

				Note local_note = manager.Find (ser_note.title);
				if (local_note != null)
				{
					store.SetValue (iter, (int)Columns.TITLE, local_note.Title);
					store.SetValue (iter, (int)Columns.LOCAL_DATE, 
							PrettyPrintDate (local_note.ChangeDate));
					store.SetValue (iter, (int)Columns.LOCAL_NOTE, local_note);
					if (local_note.ChangeDate  > ser_note.date)
					{
						store.SetValue (iter, (int)Columns.ACTION_GET, false);
						store.SetValue (iter, (int)Columns.ACTION_KEEP, true);
					}
				}
			}

			foreach (Note note in manager.Notes) {
				if (lookup.Contains (note.Title))
					continue;
				Gtk.TreeIter iter = store.Append ();
				store.SetValue (iter, (int)Columns.ICON, stock_notes);
				store.SetValue (iter, (int)Columns.TITLE, note.Title);
				store.SetValue (iter, (int)Columns.LOCAL_DATE, 
						PrettyPrintDate (note.ChangeDate));
				store.SetValue (iter, (int)Columns.REMOTE_DATETIME, DateTime.MinValue.ToString());
				store.SetValue (iter, (int)Columns.LOCAL_NOTE, note);
				store.SetValue (iter, (int)Columns.ACTION_KEEP, true);
			}
		}

		void OnKeepClicked (object sender, EventArgs args)
		{
			TreeIter iter;
			if (store.GetIterFirst (out iter))
				do {
					store.SetValue (iter, (int)Columns.ACTION_GET, false);
					store.SetValue (iter, (int)Columns.ACTION_KEEP, true);
				} while (store.IterNext (ref iter));
		}

		void OnSyncClicked (object sender, EventArgs args)
		{
			TreeIter iter;
			if (store.GetIterFirst (out iter))
				do {
					if ((bool) store.GetValue (iter, (int)Columns.ACTION_KEEP))
						continue;

					Note local_note = (Note) store.GetValue (iter, (int)Columns.LOCAL_NOTE);
					Note remote_note = (Note) store.GetValue (iter, (int)Columns.REMOTE_NOTE);

					if (remote_note == null)
						continue;

					if (local_note == null) 
					{
						// create new note
						string title = remote_note.Title;
						title.Replace ("[Remote] ","");
						manager.Create (title, remote_note.XmlContent);
					} else
						// update local note
						local_note.XmlContent = remote_note.XmlContent;

				} while (store.IterNext (ref iter));
		}

		void OnNotesChanged (object sender, Note changed)
		{
			UpdateResults ();
		}

		void OnNoteRenamed (Note note, string old_title)
		{
			UpdateResults ();
		}

		void OnToggleKeep (object o, ToggledArgs args) {
			Toggle (o, args, (int)Columns.ACTION_KEEP);
		}

		void OnToggleGet (object o, ToggledArgs args) {
			TreeIter iter;
			if (store.GetIter (out iter, new TreePath (args.Path))) {
				Note note = (Note) store.GetValue (iter, (int)Columns.REMOTE_NOTE);
				if (note != null)
					Toggle (o, args, (int)Columns.ACTION_GET);
			}
		}

		void Toggle (object o, ToggledArgs args, int n) 
		{
			TreeIter iter;

			// toggle other radio buttons off
			if (store.GetIter (out iter, new TreePath (args.Path))) {
				store.SetValue (iter, (int)Columns.ACTION_GET, false);
				store.SetValue (iter, (int)Columns.ACTION_KEEP, false);
				store.SetValue (iter, n, true);
			}
		}

		string PrettyPrintDate (DateTime date)
		{
			DateTime now = DateTime.Now;
			string short_time = date.ToShortTimeString ();

			if (date.Year == now.Year) {
				if (date.DayOfYear == now.DayOfYear)
					return String.Format (Catalog.GetString ("Today, {0}"), 
							      short_time);
				else if (date.DayOfYear == now.DayOfYear - 1)
					return String.Format (Catalog.GetString ("Yesterday, {0}"),
							      short_time);
				else if (date.DayOfYear > now.DayOfYear - 6)
					return String.Format (Catalog.GetString ("{0} days ago, {1}"), 
							      now.DayOfYear - date.DayOfYear,
							      short_time);
				else
					return date.ToString (Catalog.GetString ("MMMM d, h:mm tt"));
			} else
				return date.ToString (Catalog.GetString ("MMMM d yyyy, h:mm tt"));
		}

		void CloseClicked (object sender, EventArgs args)
		{
			Hide ();
			Destroy ();
		}

		int CompareLocalDates (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b)
		{
			Note note_a = (Note) model.GetValue (a, (int)Columns.LOCAL_NOTE);
			Note note_b = (Note) model.GetValue (b, (int)Columns.LOCAL_NOTE);

			if (note_a == null || note_b == null)
				return -1;
			else
				return DateTime.Compare (note_a.ChangeDate, note_b.ChangeDate);
		}

		int CompareRemoteDates (Gtk.TreeModel model, Gtk.TreeIter a, Gtk.TreeIter b)
		{
			string str_a =	(string) model.GetValue (a, (int)Columns.REMOTE_DATETIME);
			string str_b = (string) model.GetValue (b, (int)Columns.REMOTE_DATETIME);
			if (str_a == null || str_b == null)
				return -1;
			DateTime date_a = DateTime.Parse (str_a);
			DateTime date_b = DateTime.Parse (str_b);
			return DateTime.Compare (date_a, date_b);
		}

		void OnRowActivated (object sender, Gtk.RowActivatedArgs args)
		{
			Gtk.TreeIter iter;
			if (!store.GetIter (out iter, args.Path)) 
				return;

			// Select if we want to open remote or local note
			// FIXME can't add buttons to treeview ...

			cur_lnote = (Note) store.GetValue (iter, (int)Columns.LOCAL_NOTE);
			cur_rnote = (Note) store.GetValue (iter, (int)Columns.REMOTE_NOTE);

			Gtk.Menu menu = new Gtk.Menu ();
			Gtk.ImageMenuItem item;

			item = new Gtk.ImageMenuItem (Catalog.GetString ("Show _Local Note"));
			item.Image = new Gtk.Image (Stock.Open, Gtk.IconSize.Menu);
			item.Activated += ShowLocalNote;
			menu.Append (item);

			item = new Gtk.ImageMenuItem (Catalog.GetString ("Show _Remote Note"));
			item.Image = new Gtk.Image (Stock.Refresh, Gtk.IconSize.Menu);
			item.Activated += ShowRemoteNote;
			menu.Append (item);

			item = new Gtk.ImageMenuItem (Catalog.GetString ("Show _Both Notes"));
			item.Image = new Gtk.Image (Stock.Refresh, Gtk.IconSize.Menu);
			item.Activated += ShowBothNotes;
			menu.Append (item);

			menu.ShowAll ();
			menu.Popup ();
			args.RetVal = true;
		}

		void ShowRemoteNote (object sender, EventArgs args)
		{
			if (cur_rnote != null)
				cur_rnote.Window.Show ();
		}

		void ShowLocalNote (object sender, EventArgs args)
		{
			if (cur_lnote != null)
				cur_lnote.Window.Present ();
		}
		void ShowBothNotes (object sender, EventArgs args)
		{
			ShowLocalNote (sender, args);
			ShowRemoteNote (sender, args);
		}
	}
}
