using Gdk;
using GLib;
using Gtk;
using GtkSharp;
using System;
using Mono.Unix;
using FSpot.Xmp;

namespace FSpot {
public class PhotoView : EventBox {
	FSpot.Delay description_delay; 

	private bool has_selection = false;
	private FSpot.PhotoImageView photo_view;
	private ScrolledWindow photo_view_scrolled;
	private EventBox background;

	private Widgets.TagView tag_view;
	
	private Gtk.ToolButton display_next_button, display_previous_button;
	private Label count_label;
	private Entry description_entry;

	private Gtk.ToolButton crop_button;
	private Gtk.ToolButton redeye_button;
	private Gtk.ToolButton color_button;	
	private Gtk.ToolButton desaturate_button;
	private Gtk.ToolButton sepia_button;

	private OptionMenu constraints_option_menu;
	private int selection_constraint_ratio_idx;
	private uint restore_scrollbars_idle_id;

	private System.Collections.Hashtable constraint_table = new System.Collections.Hashtable ();

	// Public events.

	public delegate void PhotoChangedHandler (PhotoView me);
	public event PhotoChangedHandler PhotoChanged;

	public delegate void UpdateStartedHandler (PhotoView view);
	public event UpdateStartedHandler UpdateStarted;

	public delegate void UpdateFinishedHandler (PhotoView view);
	public event UpdateFinishedHandler UpdateFinished;

	// Selection constraints.
	private const string CONSTRAINT_RATIO_IDX_KEY = "FEditModeManager::constraint_idx";

	private struct SelectionConstraint {
		public string Label;
		public double XyRatio;
		
		public SelectionConstraint (string label, double ratio)
		{
			Label = label;
			XyRatio = ratio;
		}
	}

	private static SelectionConstraint [] constraints = {
		new SelectionConstraint (Catalog.GetString ("No Constraint"), 0.0),
		new SelectionConstraint (Catalog.GetString ("4 x 3 (Book)"), 4.0 / 3.0),
		new SelectionConstraint (Catalog.GetString ("4 x 6 (Postcard)"), 6.0 / 4.0),
		new SelectionConstraint (Catalog.GetString ("5 x 7 (L, 2L)"), 7.0 / 5.0),
		new SelectionConstraint (Catalog.GetString ("8 x 10"), 10.0 / 8.0),
//		new SelectionConstraint (Catalog.GetString ("4 x 3 Portrait (Book)"), 3.0 / 4.0),
//		new SelectionConstraint (Catalog.GetString ("4 x 6 Portrait (Postcard)"), 4.0 / 6.0),
//		new SelectionConstraint (Catalog.GetString ("5 x 7 Portrait (L, 2L)"), 5.0 / 7.0),
//		new SelectionConstraint (Catalog.GetString ("8 x 10 Portrait"), 8.0 / 10.0),
		new SelectionConstraint (Catalog.GetString ("Square"), 1.0)
	};

	public FSpot.PhotoImageView View {
		get { return photo_view; }
	}

	new public FSpot.BrowsablePointer Item {
		get { return photo_view.Item; }
	}


	private IBrowsableCollection query;
	public IBrowsableCollection Query {
		get { return query; }
		set { query = value; }
	}

	public double Zoom {
		get { return photo_view.Zoom; }
		set { photo_view.Zoom = value; }
	}
	
	public double NormalizedZoom {
		get { return photo_view.NormalizedZoom; }
		set { photo_view.NormalizedZoom = value; }
	}

	private void HandleSelectionConstraintOptionMenuActivated (object sender, EventArgs args)
	{
		selection_constraint_ratio_idx = (int) constraint_table [sender];
		photo_view.SelectionXyRatio = constraints [selection_constraint_ratio_idx].XyRatio;
	}

	public void Reload ()
	{
		photo_view.Reload ();
	}

	private OptionMenu CreateConstraintsOptionMenu ()
	{
		Menu menu = new Menu ();

		int i = 0;
		foreach (SelectionConstraint c in constraints) {
			MenuItem menu_item = new MenuItem (c.Label);
			menu_item.Show ();
			constraint_table [menu_item] = i;
			menu_item.Activated += new EventHandler (HandleSelectionConstraintOptionMenuActivated);

			menu.Append (menu_item);
			i ++;
		}

		constraints_option_menu = new OptionMenu ();
		constraints_option_menu.Menu = menu;

		return constraints_option_menu;
	}

	private void ItemChanged (BrowsablePointer item, BrowsablePointerChangedArgs args)
	{
		
	}

	private void UpdateButtonSensitivity ()
	{
		bool valid = photo_view.Item.IsValid;
		bool prev = valid && Item.Index > 0;
		bool next = valid && Item.Index < query.Count - 1;

		if (valid) {
			Gnome.Vfs.Uri vfs = new Gnome.Vfs.Uri (photo_view.Item.Current.DefaultVersionUri.ToString ());
			valid = vfs.Scheme == "file";
		}

		display_previous_button.Sensitive = prev;
		display_next_button.Sensitive = next;

		if (valid && has_selection) {
			crop_button.SetTooltip (tips, Catalog.GetString ("Crop photo to selected area"), String.Empty);
			redeye_button.SetTooltip (tips, Catalog.GetString ("Remove redeye from selected area"), String.Empty);
		} else {
			crop_button.SetTooltip (tips, Catalog.GetString ("Select an area to crop"), null);
			redeye_button.SetTooltip (tips, Catalog.GetString ("Select an area to remove redeye"), null);
		}
		
		crop_button.Sensitive = valid;
		redeye_button.Sensitive = valid;
		color_button.Sensitive = valid;
		desaturate_button.Sensitive = valid;
		sepia_button.Sensitive = valid;
	}

	private void UpdateCountLabel ()
	{
		if (query == null)
			count_label.Text = String.Empty;
		else {
			if (query.Count == 0)
				count_label.Text = String.Format ("{0} of {1}", 0, 0);
			else 
				count_label.Text = String.Format ("{0} of {1}", Item.Index + 1, Query.Count);
		}
	}

	private void UpdateDescriptionEntry ()
	{
		description_entry.Changed -= HandleDescriptionChanged;
		if (Item.IsValid) {
			if (description_entry.Sensitive == false)
				description_entry.Sensitive = true;

			string desc = Item.Current.Description;
			if (description_entry.Text != desc) {
				description_entry.Text = desc == null ? String.Empty : desc;
			}
		} else {
			description_entry.Sensitive = false;
			description_entry.Text = String.Empty;
		}

		if (description_entry.Text != String.Empty)
			tips.SetTip (description_entry, description_entry.Text, description_entry.Text);
		else
			tips.SetTip (description_entry, null, null);

		description_entry.Changed += HandleDescriptionChanged;
	}    

	private void Update ()
	{
		if (UpdateStarted != null)
			UpdateStarted (this);

		UpdateButtonSensitivity ();
		UpdateCountLabel ();
		UpdateDescriptionEntry ();

		if (UpdateFinished != null)
			UpdateFinished (this);
	}

	public void ZoomIn ()
	{
		photo_view.ZoomIn ();
	}
	
	public void ZoomOut ()
	{
		photo_view.ZoomOut ();
	}

	// Event handlers.
	private void HandleButtonPressEvent (object sender, ButtonPressEventArgs args)
	{
		if (args.Event.Type == EventType.ButtonPress
		    && args.Event.Button == 3) {
			PhotoPopup popup = new PhotoPopup ();
			popup.Activate (this.Toplevel, args.Event);
		}
	}

	protected override bool OnPopupMenu ()
	{
		PhotoPopup popup = new PhotoPopup ();
		popup.Activate (this.Toplevel);
		return true;
	}

	private void HandleDisplayNextButtonClicked (object sender, EventArgs args)
	{
		View.Item.MoveNext ();
	}

	private void HandleDisplayPreviousButtonClicked (object sender, EventArgs args)
	{
		View.Item.MovePrevious ();
	}


	private void HandleRedEyeButtonClicked (object sender, EventArgs args)
	{
		ProcessImage (true);
	}
	
	private void HandleCropButtonClicked (object sender, EventArgs args)
	{
		ProcessImage (false);
	}

	private void ShowError (System.Exception e, Photo photo)
	{
		string msg = Catalog.GetString ("Error editing photo");
		string desc = String.Format (Catalog.GetString ("Received exception \"{0}\". Unable to save photo {1}"),
					     e.Message, photo.Name);
		
		HigMessageDialog md = new HigMessageDialog ((Gtk.Window)this.Toplevel, DialogFlags.DestroyWithParent, 
							    Gtk.MessageType.Error, ButtonsType.Ok, 
							    msg,
							    desc);
		md.Run ();
		md.Destroy ();
	}

	private void HandleSepiaButtonClicked (object sender, EventArgs args)
	{
		PhotoQuery pq = query as PhotoQuery;

		if (pq == null)
			return;

		try {
			FSpot.SepiaTone sepia = new FSpot.SepiaTone ((Photo)View.Item.Current);
			sepia.Pixbuf = View.CompletePixbuf ();
			sepia.Adjust ();
			pq.Commit (Item.Index);
		} catch (System.Exception e) {
			ShowError (e, (Photo)View.Item.Current); 
		}
	}

	private void HandleDesaturateButtonClicked (object sender, EventArgs args)
	{
		PhotoQuery pq = query as PhotoQuery;

		if (pq == null)
			return;

		try {
			FSpot.Desaturate desaturate = new FSpot.Desaturate ((Photo) View.Item.Current);
			desaturate.Pixbuf = View.CompletePixbuf ();
			desaturate.Adjust ();
			pq.Commit (Item.Index);
		} catch (System.Exception e) {
			ShowError (e, (Photo)View.Item.Current);
		}
	}

	// FIXME this design sucks, I'm just doing it this way while
	// I redesign the editing system.
	private void ProcessImage (bool redeye)
	{
		int x, y, width, height;
		if (! photo_view.GetSelection (out x, out y, out width, out height)) {
			string msg = Catalog.GetString ("No selection available");
			string desc = Catalog.GetString ("This tool requires an active selection. Please select a region of the photo and try the operation again");
			
			HigMessageDialog md = new HigMessageDialog ((Gtk.Window)this.Toplevel, DialogFlags.DestroyWithParent, 
								    Gtk.MessageType.Error, ButtonsType.Ok, 
								    msg,
								    desc);

			md.Run ();
			md.Destroy ();
			return;
		}		

		Photo photo = (Photo)Item.Current;
		try {
			Pixbuf original_pixbuf = photo_view.CompletePixbuf ();
			if (original_pixbuf == null) {
				return;
			}
			
			Pixbuf edited;
			if (redeye) {
				Gdk.Rectangle area = new Gdk.Rectangle (x, y, width, height);
				edited = PixbufUtils.RemoveRedeye (original_pixbuf, 
								   area,
								   (int) Preferences.Get (Preferences.EDIT_REDEYE_THRESHOLD));
			} else { // Crop (I told you it was ugly)
				edited = new Pixbuf (original_pixbuf.Colorspace, 
						     original_pixbuf.HasAlpha, original_pixbuf.BitsPerSample,
						     width, height);
				
				original_pixbuf.CopyArea (x, y, width, height, edited, 0, 0);
			}
			
			bool create_version = photo.DefaultVersionId == Photo.OriginalVersionId;
			photo.SaveVersion (edited, create_version);
			((PhotoQuery)query).Commit (Item.Index);

			// FIXME the fact that the selection doesn't go away is a bug in ImageView, it should
			// be fixed there.
			photo_view.Pixbuf = edited;
			original_pixbuf.Dispose ();
			photo_view.UnsetSelection ();

			photo_view.Fit = true;
			
			if (PhotoChanged != null)
				PhotoChanged (this);
		} catch (System.Exception e) {
			ShowError (e, photo);
		}
	}

	private void HandleColorButtonClicked (object sender, EventArgs args) 
	{
		ColorDialog.CreateForView (photo_view);
	}	

	int description_photo;
	private bool CommitPendingChanges ()
	{
		if (description_delay.IsPending) {
			description_delay.Stop ();
			((PhotoQuery)query).Commit (description_photo);
		}
		return true;
	}

	private void HandleDescriptionChanged (object sender, EventArgs args) 
	{
		if (!Item.IsValid)
			return;
		
		((Photo)Item.Current).Description = description_entry.Text;
		
		if (description_delay.IsPending)
			if (description_photo == Item.Index)
				description_delay.Stop ();
			else
				CommitPendingChanges ();
		
		tips.SetTip (description_entry, description_entry.Text, "This is a tip");
		description_photo = Item.Index;
		description_delay.Start ();
	}

	private void HandlePhotoChanged (FSpot.PhotoImageView view)
	{
		if (query is PhotoQuery) {
			CommitPendingChanges ();
		}
		
		tag_view.Current = Item.Current;
		Update ();

		if (this.PhotoChanged != null)
			PhotoChanged (this);
	}

	private void HandleSelectionChanged ()
	{
		int x, y, width, height;
		bool old = has_selection;
		has_selection = photo_view.GetSelection (out x, out y, out width, out height);
	
		if (has_selection != old)
			UpdateButtonSensitivity ();
	}

	private void HandleDestroy (object sender, System.EventArgs args)
	{
		CommitPendingChanges ();
	}

	Gtk.Tooltips tips = new Gtk.Tooltips ();

	public PhotoView (IBrowsableCollection query)
		: base ()
	{
		this.query = query;

		description_delay = new FSpot.Delay (1000, new GLib.IdleHandler (CommitPendingChanges));
		this.Destroyed += HandleDestroy;

		Name = "ImageContainer";
		Box vbox = new VBox (false, 6);
		Add (vbox);

	        background = new EventBox ();
		Frame frame = new Frame ();
		background.Add (frame);

		frame.ShadowType = ShadowType.In;
		vbox.PackStart (background, true, true, 0);
		
		Box inner_vbox = new VBox (false , 2);

		frame.Add (inner_vbox);
		
		photo_view = new FSpot.PhotoImageView (query);
		photo_view.PhotoChanged += HandlePhotoChanged;
		photo_view.SelectionChanged += HandleSelectionChanged;

		photo_view_scrolled = new ScrolledWindow (null, null);

		photo_view_scrolled.SetPolicy (PolicyType.Automatic, PolicyType.Automatic);
		photo_view_scrolled.ShadowType = ShadowType.None;
		photo_view_scrolled.Add (photo_view);
		photo_view_scrolled.ButtonPressEvent += HandleButtonPressEvent;
		photo_view.AddEvents ((int) EventMask.KeyPressMask);
		inner_vbox.PackStart (photo_view_scrolled, true, true, 0);
		
		HBox inner_hbox = new HBox (false, 2);
		//inner_hbox.BorderWidth = 6;

		tag_view = new Widgets.TagView ();
		inner_hbox.PackStart (tag_view, false, true, 0);
		SetColors ();

		Label comment = new Label (Catalog.GetString ("Comment:"));
		inner_hbox.PackStart (comment, false, false, 0);
		description_entry = new Entry ();
		inner_hbox.PackStart (description_entry, true, true, 0);
		description_entry.Changed += HandleDescriptionChanged;
		
		inner_vbox.PackStart (inner_hbox, false, true, 0);

		Toolbar toolbar = new Toolbar ();
		toolbar.IconSize = IconSize.SmallToolbar;
		toolbar.ToolbarStyle = ToolbarStyle.Icons;
		vbox.PackStart (toolbar, false, true, 0);

		ToolItem constraints_menu = new ToolItem ();
		constraints_menu.Child = CreateConstraintsOptionMenu ();
		toolbar.Insert (constraints_menu, -1);	
		constraints_menu.SetTooltip (tips, Catalog.GetString ("Constrain the aspect ratio of the selection"), String.Empty);

		crop_button = GtkUtil.ToolButtonFromTheme ("crop", Catalog.GetString ("Crop"), false);
		toolbar.Insert (crop_button, -1);
		crop_button.Clicked += new EventHandler (HandleCropButtonClicked);

		redeye_button = GtkUtil.ToolButtonFromTheme ("red-eye-remove", Catalog.GetString ("Reduce Red-Eye"), false);
		toolbar.Insert (redeye_button, -1);
		redeye_button.Clicked += new EventHandler (HandleRedEyeButtonClicked);

		color_button = GtkUtil.ToolButtonFromTheme ("adjust-colors", Catalog.GetString ("Adjust Colors"), false);
		toolbar.Insert (color_button, -1);
		color_button.SetTooltip (tips, Catalog.GetString ("Adjust the photo colors"), String.Empty);
		color_button.Clicked += new EventHandler (HandleColorButtonClicked);

		desaturate_button = GtkUtil.ToolButtonFromTheme ("color-desaturate", Catalog.GetString ("Desaturate"), false);
		toolbar.Insert (desaturate_button, -1);
		desaturate_button.SetTooltip (tips, Catalog.GetString ("Convert the photo to black and white"), String.Empty);
		desaturate_button.Clicked += HandleDesaturateButtonClicked;

		sepia_button = GtkUtil.ToolButtonFromTheme ("color-sepia", Catalog.GetString ("Sepia Tone"), false);
		toolbar.Insert (sepia_button, -1);
		sepia_button.SetTooltip (tips, Catalog.GetString ("Convert the photo to sepia tones"), String.Empty);
		sepia_button.Clicked += HandleSepiaButtonClicked;

		ItemAction straighten = new TiltEditorAction (photo_view);
		ToolButton straighten_btn = straighten.CreateToolItem () as ToolButton;
		straighten_btn.SetTooltip (tips, straighten.Tooltip, String.Empty);
		toolbar.Insert (straighten_btn, -1);
		
		ItemAction softfocus = new SoftFocusEditorAction (photo_view);
		ToolButton softfocus_btn = softfocus.CreateToolItem () as ToolButton;
		softfocus_btn.SetTooltip (tips, softfocus.Tooltip, String.Empty);
		toolbar.Insert (softfocus_btn, -1);

		ItemAction autocolor = new AutoColor (photo_view.Item);
		ToolButton autocolor_btn = autocolor.CreateToolItem () as ToolButton;
		autocolor_btn.SetTooltip (tips, autocolor.Tooltip, String.Empty);
		toolbar.Insert (autocolor_btn, -1);

		SeparatorToolItem white_space = new SeparatorToolItem ();
		white_space.Draw = false;
		white_space.Expand = true;
		toolbar.Insert (white_space, -1);

		ToolItem label_item = new ToolItem ();
		count_label = new Label (String.Empty);
		label_item.Child = count_label;
		toolbar.Insert (label_item, -1);

		display_previous_button = new ToolButton (Stock.GoBack);
		toolbar.Insert (display_previous_button, -1);
		display_previous_button.SetTooltip (tips, Catalog.GetString ("Previous photo"), String.Empty);
		display_previous_button.Clicked += new EventHandler (HandleDisplayPreviousButtonClicked);

		display_next_button = new ToolButton (Stock.GoForward);
		toolbar.Insert (display_next_button, -1);
		display_next_button.SetTooltip (tips, Catalog.GetString ("Next photo"), String.Empty);
		display_next_button.Clicked += new EventHandler (HandleDisplayNextButtonClicked);

		UpdateButtonSensitivity ();

		vbox.ShowAll ();

		Realized += delegate (object o, EventArgs e) {SetColors ();};
	}

	private void SetColors ()
	{
		FSpot.Global.ModifyColors (tag_view);
		FSpot.Global.ModifyColors (photo_view);
		FSpot.Global.ModifyColors (background);
		FSpot.Global.ModifyColors (photo_view_scrolled);
	}

	protected override void OnStyleSet (Style previous)
	{
		SetColors ();
	}
}
}
