using System;
using FSpot.Editors;

namespace FSpot {
	public enum ProgressType {
		None,
		Async,
		Full
	}

	public class PhotoImageView : ImageView {
		public delegate void PhotoChangedHandler (PhotoImageView view);
		public event PhotoChangedHandler PhotoChanged;
		
		protected BrowsablePointer item;
		protected FSpot.Loupe loupe;
		protected FSpot.Loupe sharpener;
		ProgressType load_async = ProgressType.Full;
		bool progressive_display;
		public GdkGlx.Context Glx;
		private Editor editor;

		public PhotoImageView (IBrowsableCollection query) : this (new BrowsablePointer (query, -1))
		{
		}

		public PhotoImageView (BrowsablePointer item)
		{
			loader = new FSpot.AsyncPixbufLoader ();
			loader.AreaUpdated += HandlePixbufAreaUpdated;
			loader.AreaPrepared += HandlePixbufPrepared;
			loader.Done += HandleDone;

			Accelerometer.OrientationChanged += HandleOrientationChanged;

			HandleRealized (null, null);

			this.SizeAllocated += HandleSizeAllocated;
			this.KeyPressEvent += HandleKeyPressEvent;
			//this.Realized += HandleRealized;
			this.Unrealized += HandleUnrealized;
			this.ScrollEvent += HandleScrollEvent;
			this.item = item;
			item.Changed += PhotoItemChanged;
			this.Destroyed += HandleDestroyed;
			this.SetTransparentColor (this.Style.BaseColors [(int)Gtk.StateType.Normal]);
		}
		
		protected override void OnStyleSet (Gtk.Style previous)
		{
			this.SetTransparentColor (this.Style.Backgrounds [(int)Gtk.StateType.Normal]);
		}

		new public BrowsablePointer Item {
			get {
				return item;
			}
		}

		private IBrowsableCollection query;
		public IBrowsableCollection Query {
			get {
				return item.Collection;
			}
#if false
			set {
				if (query != null) {
					query.Changed -= HandleQueryChanged;
					query.ItemsChanged -= HandleQueryItemsChanged;
				}

				query = value;
				query.Changed += HandleQueryChanged;
				query.ItemsChanged += HandleQueryItemsChanged;
			}
#endif
		}

		public Editor Editor {
			get { return editor; }
			set {
				value.Done += HandleEditorDone;

				if (editor != null)
					editor.Destroy ();
				
				editor = value;
			}
		}

		private void HandleEditorDone (object sender, EventArgs args)
		{
			Editor old = sender as Editor;

			old.Done -= HandleEditorDone;
				
			if (old == editor)
				editor = null;
		}


		public Loupe Loupe {
			get { return loupe; }
		}

		public Gdk.Pixbuf CompletePixbuf ()
		{
			loader.LoadToDone ();
			return this.Pixbuf;
		}

		public void HandleOrientationChanged (object sender)
		{
			Reload ();
		}

		public void Reload ()
		{
			if (!Item.IsValid)
				return;
			
			PhotoItemChanged (Item, null);
		}

		[GLib.ConnectBefore]
		private void HandleRealized (object sender, EventArgs args)
		{
			int [] attr = new int [] {
				(int) GdkGlx.GlxAttribute.Rgba,
				(int) GdkGlx.GlxAttribute.DepthSize, 16,
				(int) GdkGlx.GlxAttribute.DoubleBuffer,
				(int) GdkGlx.GlxAttribute.None
			};

			try {
				Glx = new GdkGlx.Context (Screen, attr);
				Colormap = Glx.GetColormap ();
			} catch (GdkGlx.GlxException e) {
				Console.WriteLine ("Error initializing the OpenGL context:{1} {0}", e, Environment.NewLine);
			}
		}

		private void HandleUnrealized (object sender, EventArgs args)
		{
			if (Glx != null)
				Glx.Destroy ();
		}

		// Display.
		private void HandlePixbufAreaUpdated (object sender, AreaUpdatedArgs args)
		{
			if (!ShowProgress)
				return;

			Gdk.Rectangle area = this.ImageCoordsToWindow (args.Area);
			this.QueueDrawArea (area.X, area.Y, area.Width, area.Height);
		}
		

		private void HandlePixbufPrepared (object sender, AreaPreparedArgs args)
		{
			if (!ShowProgress)
				return;

			Gdk.Pixbuf prev = this.Pixbuf;
			Gdk.Pixbuf next = loader.Pixbuf;

			this.Pixbuf = next;
			if (prev != null)
				prev.Dispose ();

			UpdateMinZoom ();
			this.ZoomFit (args.ReducedResolution);
		}

		private void HandleDone (object sender, System.EventArgs args)
		{
			// FIXME the error hander here needs to provide proper information and we should
			// pass the state and the write exception in the args
			Gdk.Pixbuf prev = this.Pixbuf;
			if (loader.Pixbuf == null) {
				System.Exception ex = null;

				// FIXME in some cases the image passes completely through the
				// pixbuf loader without properly loading... I'm not sure what to do about this other
				// than try to load the image one last time.
				this.Pixbuf = null;
				if (!loader.Loading) {
					try {
						System.Console.WriteLine ("Falling back to file loader");

						this.Pixbuf = FSpot.PhotoLoader.Load (item.Collection, 
										      item.Index);
					} catch (System.Exception e) {
						if (!(e is GLib.GException))
							System.Console.WriteLine (e.ToString ());

						ex = e;
					}
				}

				if (this.Pixbuf == null) {
					LoadErrorImage (ex);
				} else {
					UpdateMinZoom ();
					this.ZoomFit ();
				}
			} else {
				this.Pixbuf = loader.Pixbuf;

				if (!loader.Prepared || !ShowProgress) {
					UpdateMinZoom ();
					this.ZoomFit ();
				}
			}

			progressive_display = true;

			if (prev != this.Pixbuf && prev != null)
				prev.Dispose ();
		}
		
		private bool ShowProgress {
			get {
				return !(load_async != ProgressType.Full || !progressive_display);
			}
		}

		private bool fit = true;
		public bool Fit {
			get {
				return (Zoom == MIN_ZOOM);
			}
			set {
				if (!fit && value)
					ZoomFit ();
				
				fit = value;
			}
		}


		public double Zoom {
			get {
				double x, y;
				this.GetZoom (out x, out y);
				return x;
			}
			
			set {
				//Console.WriteLine ("Setting zoom to {0}, MIN = {1}", value, MIN_ZOOM);
				value = System.Math.Min (value, MAX_ZOOM);
				value = System.Math.Max (value, MIN_ZOOM);

				double zoom = Zoom;
				if (value == zoom)
					return;

				if (System.Math.Abs (zoom - value) < System.Double.Epsilon)
					return;

				if (value == MIN_ZOOM)
					this.Fit = true;
				else {
					this.Fit = false;
					this.SetZoom (value, value);
				}
			}
		}
		
		// Zoom scaled between 0.0 and 1.0
		public double NormalizedZoom {
			get {
				return (Zoom - MIN_ZOOM) / (MAX_ZOOM - MIN_ZOOM);
			}
			set {
				Zoom = (value * (MAX_ZOOM - MIN_ZOOM)) + MIN_ZOOM;
			}
		}
		
		private void HandleSizeAllocated (object sender, Gtk.SizeAllocatedArgs args)
		{
			if (fit)
				ZoomFit ();
		}

		FSpot.AsyncPixbufLoader loader;

		private void LoadErrorImage (System.Exception e)
		{
			// FIXME we should check the exception type and do something
			// like offer the user a chance to locate the moved file and
			// update the db entry, but for now just set the error pixbuf
			
			Gdk.Pixbuf old = this.Pixbuf;
			this.Pixbuf = new Gdk.Pixbuf (PixbufUtils.ErrorPixbuf, 0, 0, 
						      PixbufUtils.ErrorPixbuf.Width, 
						      PixbufUtils.ErrorPixbuf.Height);
			if (old != null)
				old.Dispose ();
			
			UpdateMinZoom ();
			this.ZoomFit ();
		}

		private void PhotoItemChanged (BrowsablePointer item, BrowsablePointerChangedArgs args) 
		{
			System.Console.WriteLine ("item changed");
			// If it is just the position that changed fall out
			if (args != null && 
			    args.PreviousItem != null &&
			    Item.IsValid &&
			    (args.PreviousIndex != item.Index) &&
			    (this.Item.Current.DefaultVersionUri == args.PreviousItem.DefaultVersionUri))
				return;

			if (args != null &&
			    args.PreviousItem != null && 
			    Item.IsValid && 
			    Item.Current.DefaultVersionUri == args.PreviousItem.DefaultVersionUri &&
			    load_async == ProgressType.Full)
				progressive_display = false;

			if (load_async != ProgressType.None) {
				Gdk.Pixbuf old = this.Pixbuf;
				try {
					if (Item.IsValid) {
						System.Uri uri = Item.Current.DefaultVersionUri;
						loader.Load (uri);
					} else
						LoadErrorImage (null);

				} catch (System.Exception e) {
					System.Console.WriteLine (e.ToString ());
					LoadErrorImage (e);
				}
				if (old != null)
					old.Dispose ();
			} else {	
				Gdk.Pixbuf old = this.Pixbuf;
				this.Pixbuf = FSpot.PhotoLoader.Load (item.Collection, 
								      item.Index);
				if (old != null)
					old.Dispose ();

				UpdateMinZoom ();
				this.ZoomFit ();
			}
			
			this.UnsetSelection ();

			if (PhotoChanged != null)
				PhotoChanged (this);
		}
		
		public void ZoomIn ()
		{
			Zoom = Zoom * ZOOM_FACTOR;
		}
		
		public void ZoomOut ()
		{
			Zoom = Zoom / ZOOM_FACTOR;
		}
		
		bool upscale;
		private void ZoomFit ()
		{
			ZoomFit (upscale);
		}

		private void ZoomFit (bool upscale)
		{			
			Gdk.Pixbuf pixbuf = this.Pixbuf;
			Gtk.ScrolledWindow scrolled = this.Parent as Gtk.ScrolledWindow;
			this.upscale = upscale;
			
			if (pixbuf == null)
				return;

			int available_width = this.Allocation.Width;
			int available_height = this.Allocation.Height;
		
			double zoom_to_fit = ZoomUtils.FitToScale ((uint) available_width, 
								   (uint) available_height,
								   (uint) pixbuf.Width, 
								   (uint) pixbuf.Height, 
								   upscale);
			
			double image_zoom = zoom_to_fit;
			/*
			System.Console.WriteLine ("Zoom = {0}, {1}, {2}", image_zoom, 
						  available_width, 
						  available_height);
			*/

			if (scrolled != null)
				scrolled.SetPolicy (Gtk.PolicyType.Never, Gtk.PolicyType.Never);

			this.SetZoom (image_zoom, image_zoom);
			
			if (scrolled != null)
				scrolled.SetPolicy (Gtk.PolicyType.Automatic, Gtk.PolicyType.Automatic);
		}
		
		private void HandleLoupeDestroy (object sender, EventArgs args)
		{
			if (sender == loupe)
				loupe = null;

			if (sender == sharpener)
				sharpener = null;

		}

		[GLib.ConnectBefore]
		private void HandleKeyPressEvent (object sender, Gtk.KeyPressEventArgs args)
		{
			// FIXME I really need to figure out why overriding is not working
			// for any of the default handlers.
			args.RetVal = true;
		
			// Check for KeyPad arrow keys, which scroll the window when zoomed in
			// but should go to the next/previous photo when not zoomed (no scrollbars)
			if (this.Fit) {
				switch (args.Event.Key) {
				case Gdk.Key.Up:
				case Gdk.Key.Left:
				case Gdk.Key.KP_Up:
				case Gdk.Key.KP_Left:
					this.Item.MovePrevious ();
					return;
				case Gdk.Key.Down:
				case Gdk.Key.Right:
				case Gdk.Key.KP_Down:
				case Gdk.Key.KP_Right:
					this.Item.MoveNext ();
					return;
				}
			}

			switch (args.Event.Key) {
			case Gdk.Key.Up:
			case Gdk.Key.Left:
			case Gdk.Key.Page_Up:
			case Gdk.Key.KP_Page_Up:
				this.Item.MovePrevious ();
				break;
			case Gdk.Key.Home:
			case Gdk.Key.KP_Home:
				this.Item.Index = 0;
				break;
			case Gdk.Key.End:
			case Gdk.Key.KP_End:
				this.Item.Index = this.Query.Count - 1;
				break;
			case Gdk.Key.Down:
			case Gdk.Key.Right:
			case Gdk.Key.Page_Down:
			case Gdk.Key.KP_Page_Down:
			case Gdk.Key.space:
			case Gdk.Key.KP_Space:
				this.Item.MoveNext ();
				break;
			case Gdk.Key.Key_0:
			case Gdk.Key.KP_0:
				this.Fit = true;
				break;
			case Gdk.Key.Key_1:
			case Gdk.Key.KP_1:
				this.Zoom =  1.0;
				break;
			case Gdk.Key.Key_2:
			case Gdk.Key.KP_2:
				this.Zoom = 2.0;
				break;
			case Gdk.Key.minus:
			case Gdk.Key.KP_Subtract:
				ZoomOut ();
				break;
			case Gdk.Key.v:
				if (loupe == null) {
					loupe = new Loupe (this);
					loupe.Destroyed += HandleLoupeDestroy;
					loupe.Show ();
				} else {
					loupe.Destroy ();	
				}
				break;
			case Gdk.Key.e:
				Editor = new FSpot.Editors.SoftFocus (this);
				break;
			case Gdk.Key.equal:
			case Gdk.Key.plus:
			case Gdk.Key.KP_Add:
				ZoomIn ();
				break;
			default:
				args.RetVal = false;
				return;
			}

			return;
		}

		public void ShowSharpener ()
		{
			if (sharpener == null) {
				sharpener = new Sharpener (this);
				sharpener.Destroyed += HandleLoupeDestroy;
			}

			sharpener.Show ();	
		}
		
		[GLib.ConnectBefore]
		private void HandleScrollEvent (object sender, Gtk.ScrollEventArgs args)
		{
			//For right now we just disable fit mode and let the parent event handlers deal
			//with the real actions.
			this.Fit = false;
		}
		
		private void HandleDestroyed (object sender, System.EventArgs args)
		{
			//loader.AreaUpdated -= HandlePixbufAreaUpdated;
			//loader.AreaPrepared -= HandlePixbufPrepared;
			loader.Dispose ();
		}
	}
}
