/* 
 * ImageDisplay.cs
 *
 * Copyright 2007 Novell Inc.
 *
 * Author
 *   Larry Ewing <lewing@novell.com>
 *
 * See COPYING for license information.
 *
 */

using Gtk;
using Gdk;
using System;
using Cairo;
using FSpot;

namespace FSpot.Widgets {
	[Binding(Gdk.Key.Up, "Up")]
	[Binding(Gdk.Key.Down, "Down")]
	[Binding(Gdk.Key.Left, "TiltImage", 0.05)]
	[Binding(Gdk.Key.Right, "TiltImage", -0.05)] 
	[Binding(Gdk.Key.space, "Pan")]
	[Binding(Gdk.Key.Q, "Vingette")]
	[Binding(Gdk.Key.R, "RevealImage")]
	[Binding(Gdk.Key.P, "PushImage")]
	public class ImageDisplay : Gtk.EventBox {
		ImageInfo current;
		ImageInfo next;
		BrowsablePointer item;
		ITransition transition;
		IEffect effect;
		Delay delay;
		int index = 0;
		int block_size = 256;

		ITransition Transition {
			get { return transition; }
			set { 
				if (transition != null) 
					transition.Dispose ();

				transition = value;
				

				if (transition != null)
					delay.Start ();
				else 
					delay.Stop ();
			}
		}

		public ImageDisplay (BrowsablePointer item) 
		{
			this.item = item;
			CanFocus = true;
			current = new ImageInfo (item.Current.DefaultVersionUri);
			if (item.Collection.Count > item.Index + 1) {
				next = new ImageInfo (item.Collection [item.Index + 1].DefaultVersionUri);
			}
			delay = new Delay (30, new GLib.IdleHandler (DrawFrame));
		}

		protected override void OnDestroyed ()
		{
			if (current != null) {
				current.Dispose ();
				current = null;
			}

		        if (next != null) {
				next.Dispose ();
				next = null;
			}

			Transition = null;
			
			if (effect != null)
				effect.Dispose ();
			
			base.OnDestroyed ();
		}
	
		public bool Up ()
		{
			Console.WriteLine ("Up");
			Transition = new Dissolve (current, next);
			return true;
		}

		public bool Down ()
		{
			Console.WriteLine ("down");
			Transition = new Dissolve (next, current);
			return true;
		}

		public bool Vingette ()
		{
			SoftFocus f = effect as SoftFocus;
			
			if (f == null) {
				f = new SoftFocus (current);
				effect = f;
			}

			QueueDraw ();
			return true;
		}

		public bool TiltImage (double radians)
		{
			Tilt t = effect as Tilt;

			if (t == null) {
				t = new Tilt (current);
				effect = t;
			}

			t.Angle += radians;

			QueueDraw ();

			return true;
		}

		public bool Pan ()
		{
			Console.WriteLine ("space");
			Transition = new Wipe (current, next);
			return true;
		}
		
		public bool RevealImage ()
		{
			Console.WriteLine ("r");
			Transition = new Reveal (current, next);
			return true;
		}

		public bool PushImage ()
		{
			Console.WriteLine ("p");
			Transition = new Push (current, next);
			return true;
		}


		public bool DrawFrame ()
		{
			if (Transition != null)
				Transition.OnEvent (this);

			return true;
		}
		
		private static void SetClip (Context ctx, Gdk.Rectangle area) 
		{
			ctx.MoveTo (area.Left, area.Top);
			ctx.LineTo (area.Right, area.Top);
			ctx.LineTo (area.Right, area.Bottom);
			ctx.LineTo (area.Left, area.Bottom);
			
			ctx.ClosePath ();
			ctx.Clip ();
		}
		
		private static void SetClip (Context ctx, Region region)
		{
			foreach (Gdk.Rectangle area in region.GetRectangles ()) {
				ctx.MoveTo (area.Left, area.Top);
				ctx.LineTo (area.Right, area.Top);
				ctx.LineTo (area.Right, area.Bottom);
				ctx.LineTo (area.Left, area.Bottom);
					
				ctx.ClosePath ();
			}
			ctx.Clip ();
		}

		private void OnExpose (Context ctx, Region region)
		{
			SetClip (ctx, region);
			if (Transition != null) {
				bool done = false;
				foreach (Gdk.Rectangle area in GetRectangles (region)) {
					BlockProcessor proc = new BlockProcessor (area, block_size);
					Gdk.Rectangle subarea;
					while (proc.Step (out subarea)) {
						ctx.Save ();
						SetClip (ctx, subarea);
						done = ! Transition.OnExpose (ctx, Allocation);
						ctx.Restore ();
					}
				}
				if (done) {
					System.Console.WriteLine ("frames = {0}", Transition.Frames);
					Transition = null;
				}
			} else if (effect != null) {
				foreach (Gdk.Rectangle area in GetRectangles (region)) {
					BlockProcessor proc = new BlockProcessor (area, block_size);
					Gdk.Rectangle subarea;
					while (proc.Step (out subarea)) {
						ctx.Save ();
						SetClip (ctx, subarea);
						effect.OnExpose (ctx, Allocation);
						ctx.Restore ();
					}
				}
			} else {
				ctx.Operator = Operator.Source;
				SurfacePattern p = new SurfacePattern (current.Surface);
				p.Filter = Filter.Fast;
				SetClip (ctx, region);
				ctx.Matrix = current.Fill (Allocation);

				ctx.Source = p;
				ctx.Paint ();
				p.Destroy ();
			}
		}

		private static Gdk.Rectangle [] GetRectangles (Gdk.Region region)
		{
#if true
			return new Gdk.Rectangle [] { region.Clipbox };
#else
			return region.GetRectangles ();
#endif
		}

		protected override bool OnExposeEvent (EventExpose args)
		{
			bool double_buffer = false;
			base.OnExposeEvent (args);

			Context ctx = CairoUtils.CreateContext (GdkWindow);
			//Surface glitz = CairoUtils.CreateGlitzSurface (GdkWindow);
			//Context ctx = new Context (glitz);
			if (double_buffer) {
				ImageSurface cim = new ImageSurface (Format.RGB24, 
								     Allocation.Width, 
								     Allocation.Height);

				Context buffer = new Context (cim);
				OnExpose (buffer, args.Region);

				SurfacePattern sur = new SurfacePattern (cim);
				sur.Filter = Filter.Fast;
				ctx.Source = sur;
				SetClip (ctx, args.Region);

				ctx.Paint ();

				((IDisposable)buffer).Dispose ();
				((IDisposable)cim).Dispose ();
				sur.Destroy ();
			} else {
				OnExpose (ctx, args.Region);
			}

			//glitz.Flush ();
			((IDisposable)ctx).Dispose ();
			return true;
		}
	}
}

