using System;
using System.IO;
using System.Runtime.Remoting.Messaging;

namespace FSpot {
	public delegate void AreaUpdatedHandler (object sender, AreaUpdatedArgs area);
	public delegate void AreaPreparedHandler (object sender, AreaPreparedArgs args);

	public class AreaPreparedArgs : System.EventArgs {
		bool reduced_resolution;

		public bool ReducedResolution {
			get { return reduced_resolution; }
		}
	
		public AreaPreparedArgs (bool reduced_resolution)
		{
			this.reduced_resolution = reduced_resolution;
		}
	}

	public class AreaUpdatedArgs : System.EventArgs {
		Gdk.Rectangle area;

		public Gdk.Rectangle Area { 
			get { return area; }
		}

		public AreaUpdatedArgs (Gdk.Rectangle area)
		{
			this.area = area;
		}
	}

	public class AsyncPixbufLoader : System.IDisposable {
		StreamWrapper stream;
		Gdk.PixbufLoader loader;		
		Uri uri;
		bool area_prepared = false;
		bool done_reading = false;
		Gdk.Pixbuf pixbuf;
		Gdk.Pixbuf thumb;
		PixbufOrientation orientation;

		private Gdk.AreaUpdatedHandler au;
		private System.EventHandler ap;
		private System.EventHandler ev;

		//byte [] buffer = new byte [8192];
		byte [] buffer = new byte [1 << 15];

		public event AreaUpdatedHandler AreaUpdated;
		public event AreaPreparedHandler AreaPrepared;
		public event System.EventHandler Done;

		// If the photo we just loaded has an out of date
		// thumbnail save a new one
		bool validate_thumbnail = true;

		// Limit pass control back to the main loop after
		// chunk_timeout miliseconds.
		int  chunk_timeout = 75;

		Delay delay;
		IAsyncResult result;

		Gdk.Rectangle damage;

		public AsyncPixbufLoader ()
		{
			delay = new Delay (0, new GLib.IdleHandler (AsyncRead));
			ap = new System.EventHandler (HandleAreaPrepared);
			au = new Gdk.AreaUpdatedHandler (HandleAreaUpdated);
			ev = new System.EventHandler (HandleClosed);
		}
		
		public bool Loading
		{
			get { return ! done_reading; }
		}

		public bool Prepared
		{
			get { return area_prepared; }
		}

		public Gdk.Pixbuf Pixbuf {
			get { return pixbuf; }
		}

		public Gdk.PixbufLoader Loader {
			get { return loader; }
		}
		
		private void FileLoad (ImageFile img)
		{
			pixbuf = img.Load ();
			done_reading = true;
			if (Done != null)
				Done (this, System.EventArgs.Empty);
		}

		public void Load (Uri uri)
		{
			this.uri = uri;

			delay.Stop ();

			if (!done_reading)
				Close ();

			done_reading = false;
			area_prepared = false;
			damage = Gdk.Rectangle.Zero;

			using (ImageFile img = ImageFile.Create (uri)) {
				orientation = Accelerometer.GetViewOrientation (img.Orientation);
			
				try {
					PixbufOrientation thumb_orientation = Accelerometer.GetViewOrientation (PixbufOrientation.TopLeft);
					thumb = new Gdk.Pixbuf (ThumbnailGenerator.ThumbnailPath (uri));
					thumb = PixbufUtils.TransformOrientation (thumb, thumb_orientation);
				} catch (System.Exception e) {
					//FSpot.ThumbnailGenerator.Default.Request (uri.ToString (), 0, 256, 256);	
					if (!(e is GLib.GException)) 
						System.Console.WriteLine (e.ToString ());
				}

				System.IO.Stream nstream = img.PixbufStream ();
				if (nstream == null) {
					FileLoad (img);
					return;
				} else
					stream = new StreamWrapper (nstream);

				loader = new Gdk.PixbufLoader ();
				loader.AreaPrepared += ap;
				loader.AreaUpdated += au;
				loader.Closed += ev;

				if (AreaPrepared != null && thumb != null) {
					pixbuf = thumb;
					AreaPrepared (this, new AreaPreparedArgs (true));
				}

				ThumbnailGenerator.Default.PushBlock ();
				//AsyncIORead (null);
				if (nstream is IOChannel) {
					((IOChannel)nstream).DataReady += IOChannelRead;
				} else
					delay.Start ();
			}

		}			

	        private void LoadToAreaPrepared ()
		{
			delay.Stop  ();
			while (!area_prepared && AsyncRead ())
				; //step
		}

		public void LoadToDone ()
		{
			delay.Stop ();
			while (!done_reading && AsyncRead ())
				; //step
		}

		private void Close () {
			ThumbnailGenerator.Default.PopBlock ();
				
			try {
				result = null;

				delay.Stop ();
				if (loader != null) { 
					loader.AreaPrepared -= ap;
					loader.AreaUpdated -= au;
					// this can throw exceptions
					loader.Close ();
				}
			} catch (System.Exception) {
				//System.Console.WriteLine (e.ToString ());
				if (pixbuf != null)
					pixbuf.Dispose ();

				pixbuf = null;
			} finally {
				if (loader != null) {
					loader.Closed -= ev;
					loader.Dispose ();
				}

				loader = null;

				if (stream != null) 
					stream.Close ();

				stream = null;
			}
		}

		private void UpdateListeners ()
		{
			Gdk.Rectangle area = damage;
			
			if (pixbuf != null && loader.Pixbuf != null && loader.Pixbuf != pixbuf && damage != Gdk.Rectangle.Zero)
				area = PixbufUtils.TransformAndCopy (loader.Pixbuf, pixbuf, orientation, damage);
			
			if (area.Width != 0 && area.Height != 0 && AreaUpdated != null)
				AreaUpdated (this, new AreaUpdatedArgs (area));

			//System.Console.WriteLine ("orig {0} tform {1}", damage.ToString (), area.ToString ());
			damage = Gdk.Rectangle.Zero;
		}

		private class StreamWrapper {
			private delegate int ReadDelegate (byte [] buffer, int offset, int count);
			System.IO.Stream stream;

			public System.IO.Stream Stream {
				get { return stream; }
			}

			public StreamWrapper (System.IO.Stream stream)
			{
				this.stream = stream;
			}

			public int Read (byte[] buffer, int offset, int count)
			{
				return stream.Read (buffer, offset, count);
			}

			public IAsyncResult BeginRead (byte [] buffer, int offset, int count, AsyncCallback cb, object state)
			{
				ReadDelegate del = new ReadDelegate (Read);
				return del.BeginInvoke (buffer, offset, count, cb, state);
			}
			
			public int EndRead (IAsyncResult result)
			{
				AsyncResult art = result as AsyncResult;
				ReadDelegate del = art.AsyncDelegate as ReadDelegate;
				int i = del.EndInvoke (result);
				return i;
			}
			
			public void Close ()
			{
				stream.Close ();
			}
		}

		private void IOChannelRead (object sender, DataReadEventArgs args)
		{
			//Console.WriteLine ("IO read {0}", args.Condition);

			if ( (System.IO.Stream)sender == stream.Stream)				
				args.Continue = AsyncRead ();
			else {
				args.Continue = false;
				stream.Close ();
			}

			return;
		}

		private bool AsyncIORead ()
		{
			try {
				if (result == null) {
					//System.Console.WriteLine ("start read");
					result = stream.BeginRead (buffer, 0, buffer.Length, AsyncIOEnd, stream);
				} else {
					//Console.WriteLine ("not done");
					UpdateListeners ();
				}
			} catch (System.Exception e) {
				System.Console.WriteLine ("In read got {0}", e);
			}
			
			if (done_reading)
				delay.Stop ();

			return ! done_reading;
		}

		private void AsyncIOEnd (IAsyncResult iar)
		{
			//System.Console.WriteLine ("ioended");
			if (stream == (StreamWrapper)iar.AsyncState)
				Gtk.Application.Invoke (ReadDone);
		}
		
		public void ReadDone (object sender, System.EventArgs args)
		{
			if (result == null)
				return;

			int len = 0;
			try {
				len = stream.EndRead (result);
				//System.Console.WriteLine ("read {0} bytes", len);
				loader.Write (buffer, (ulong)len);
			} catch (System.ObjectDisposedException od) {
				System.Console.WriteLine ("error in endread {0}", od);
				//delay.Start ();
				len = -1;
			} catch (GLib.GException e) {
				System.Console.WriteLine (e.ToString ());
				pixbuf = null;
				len = -1;
			}
			result = null;

			if (len <= 0) {
				if (loader.Pixbuf == null) {
					if (pixbuf != null)
						pixbuf.Dispose ();
					
					pixbuf = null;
				}
				
				UpdateListeners ();
				done_reading = true;
				Close ();
				return;
			}
		}
	
		private bool AsyncRead () 
		{
#if false
			return AsyncIORead ();
#else	
			return NormalRead ();
#endif		
		}

		private bool NormalRead ()
		{
			System.DateTime start_time = System.DateTime.Now;
			System.TimeSpan span = start_time - start_time;

			do {
				span = System.DateTime.Now - start_time;

				int len;
				try {
					len = stream.Read (buffer, 0, buffer.Length);
					loader.Write (buffer, (ulong)len);
				} catch (Exception) {
					len = -1;
				}

				if (len <= 0) {
					if (loader.Pixbuf == null) {
						if (pixbuf != null)
							pixbuf.Dispose ();

						pixbuf = null;
					}

					UpdateListeners ();
					done_reading = true;
					Close ();
					return false;
				}
			} while (!done_reading && span.TotalMilliseconds <= chunk_timeout);

			UpdateListeners ();
			return true;
		}
		
		private void HandleAreaPrepared (object sender, System.EventArgs args)
		{
			pixbuf = PixbufUtils.TransformOrientation (loader.Pixbuf, orientation, false);

			if (thumb != null && pixbuf != null)
				thumb.Composite (pixbuf, 0, 0,
						 pixbuf.Width, pixbuf.Height,
						 0.0, 0.0,
						 pixbuf.Width/(double)thumb.Width, pixbuf.Height/(double)thumb.Height,
						 Gdk.InterpType.Bilinear, 0xff);
			
			if (thumb != null)
				if (!ThumbnailGenerator.ThumbnailIsValid (thumb, uri))
					FSpot.ThumbnailGenerator.Default.Request (uri, 0, 256, 256);

			area_prepared = true;			
			if (AreaUpdated != null)
				AreaPrepared (this, new AreaPreparedArgs (false));

			if (thumb != null)
				thumb.Dispose ();
			thumb = null;
		}

		private void HandleAreaUpdated (object sender, Gdk.AreaUpdatedArgs args)
		{
			Gdk.Rectangle area = new Gdk.Rectangle (args.X, args.Y, args.Width, args.Height);
			
			if (damage.Width == 0 || damage.Height == 0)
				damage = area;
			else 
				damage = area.Union (damage);
		}

		private void HandleClosed (object sender, System.EventArgs args) 
		{
			// FIXME This should probably queue the
			// thumbnail regeneration to a worker thread
			if (validate_thumbnail && done_reading && pixbuf != null) {
				PhotoLoader.ValidateThumbnail (uri, pixbuf);
			}

			if (Done != null)
				Done (this, System.EventArgs.Empty);
			
			ThumbnailGenerator.Default.PopBlock ();
		}

		public void Dispose ()
		{
			Close ();

			if (pixbuf != null)
				pixbuf.Dispose ();
		}
	}
}

	       
