using Gtk;
using System;
using System.IO;
using FSpot;
using SemWeb;
using Mono.Unix;


// FIXME TODO: We want to use something like EClippedLabel here throughout so it handles small sizes
// gracefully using ellipsis.

public class InfoBox : VBox {
	Delay update_delay;

	private IBrowsableItem photo;
	public IBrowsableItem Photo {
		set {
			photo = value;
			update_delay.Start ();
		}

		get {
			return photo;
		}
	}

	public delegate void VersionIdChangedHandler (InfoBox info_box, uint version_id);
	public event VersionIdChangedHandler VersionIdChanged;


	// Widgetry.

	private Label name_label;
	private Label date_label;
	private Label size_label;
	private Label exposure_info_label;
	private OptionMenu version_option_menu;

	private void HandleVersionIdChanged (PhotoVersionMenu menu)
	{
		if (VersionIdChanged != null)
			VersionIdChanged (this, menu.VersionId);
	}

	private Label CreateRightAlignedLabel (string text)
	{
		Label label = new Label ();
		label.UseMarkup = true;
		label.Markup = text;
		label.Xalign = 1;

		return label;
	}

	const int TABLE_XPADDING = 3;
	const int TABLE_YPADDING = 3;
	static private Label AttachLabel (Table table, int row_num, Widget entry)
	{
		Label label = new Label (String.Empty);
		label.Xalign = 0;
		label.Selectable = true;
		label.Ellipsize = Pango.EllipsizeMode.End;
		label.Show ();

		table.Attach (label, 1, 2, (uint) row_num, (uint) row_num + 1,
			      AttachOptions.Expand | AttachOptions.Fill, AttachOptions.Expand | AttachOptions.Fill,
			      (uint) entry.Style.XThickness + TABLE_XPADDING, (uint) entry.Style.YThickness);

		return label;
	}

	private string default_exposure_string;
	private Label exposure_name_label;
	private void SetupWidgets ()
	{
		Table table = new Table (5, 2, false);
		table.BorderWidth = 0;

		string name_pre = "<b>";
		string name_post = "</b>";
		table.Attach (CreateRightAlignedLabel (name_pre + Catalog.GetString ("name") + name_post), 0, 1, 0, 1,
			      AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);
		table.Attach (CreateRightAlignedLabel (name_pre + Catalog.GetString ("version") + name_post), 0, 1, 1, 2,
			      AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);
		table.Attach (CreateRightAlignedLabel (name_pre + Catalog.GetString ("date") + name_post + Environment.NewLine), 0, 1, 2, 3,
			      AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);
		table.Attach (CreateRightAlignedLabel (name_pre + Catalog.GetString ("size") + name_post), 0, 1, 3, 4,
			      AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);
		default_exposure_string = name_pre + Catalog.GetString ("exposure") + name_post;
		exposure_name_label = CreateRightAlignedLabel (default_exposure_string);
		table.Attach (exposure_name_label, 0, 1, 4, 5,
			      AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);

		name_label = new Label ();
		name_label.Ellipsize = Pango.EllipsizeMode.Middle;
		name_label.Justify = Gtk.Justification.Left;
		name_label.Selectable = true;
		name_label.Xalign = 0;
		table.Attach (name_label, 1, 2, 0, 1,
			      AttachOptions.Fill | AttachOptions.Expand, AttachOptions.Fill,
			      3, 0);
		
		date_label = AttachLabel (table, 2, name_label);
		size_label = AttachLabel (table, 3, name_label);
		exposure_info_label = AttachLabel (table, 4, name_label);

		version_option_menu = new OptionMenu ();
		table.Attach (version_option_menu, 1, 2, 1, 2, AttachOptions.Fill, AttachOptions.Fill, TABLE_XPADDING, TABLE_YPADDING);

		date_label.Text = Environment.NewLine;
		exposure_info_label.Text = Environment.NewLine;

		table.ShowAll ();

		Add (table);
	}

	private void Clear ()
	{
		name_label.Sensitive = false;

		version_option_menu.Sensitive = false;
		version_option_menu.Menu = new Menu ();	// GTK doesn't like NULL here although that's what we want.

		name_label.Text = String.Empty;
		date_label.Text = Environment.NewLine;
		size_label.Text = String.Empty;
		exposure_info_label.Text = Environment.NewLine;
	}

	private class ImageInfo : StatementSink {
		string width;
		string height;
		string aperture;
		string fnumber;
		string exposure;
		string iso_speed;
		bool add = true;
		Resource iso_anon;

		MemoryStore store;
		
#if USE_EXIF_DATE
		DateTime date;
#endif
		public ImageInfo (ImageFile img) 
		{
			// FIXME We use the memory store to hold the anonymous statements
			// as they are added so that we can query for them later to 
			// resolve anonymous nodes.
			store = new MemoryStore ();

			if (img == null) 
				return;

			if (img is StatementSource) {
				SemWeb.StatementSource source = (SemWeb.StatementSource)img;
				source.Select (this);

				// If we couldn't find the ISO speed because of the ordering
				// search the memory store for the values
				if (iso_speed == null && iso_anon != null) {
					add = false;
					store.Select (this);
				}
			}

			if (img is JpegFile) {
				int real_width;
				int real_height;

				JpegUtils.GetSize (img.Uri.LocalPath, out real_width, out real_height);
				width = real_width.ToString ();
				height = real_height.ToString ();
			}
#if USE_EXIF_DATE
			date = img.Date.ToLocalTime ();
#endif
		}

		public bool Add (SemWeb.Statement stmt)
		{
			if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("tiff:ImageWidth")) {
				if (width == null)
					width = ((SemWeb.Literal)stmt.Object).Value;
				} else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("tiff:ImageLength")) {
				if (height == null)
					height = ((SemWeb.Literal)stmt.Object).Value;
			} else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:PixelXDimension"))
				width = ((SemWeb.Literal)stmt.Object).Value;						      
			else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:PixelYDimension"))
				height = ((SemWeb.Literal)stmt.Object).Value;
			else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:ExposureTime"))
				exposure = ((SemWeb.Literal)stmt.Object).Value;
			else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:ApertureValue"))
				aperture = ((SemWeb.Literal)stmt.Object).Value;
			else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:FNumber"))
				fnumber = ((SemWeb.Literal)stmt.Object).Value;
			else if (stmt.Predicate == MetadataStore.Namespaces.Resolve ("exif:ISOSpeedRatings"))
				iso_anon = stmt.Object;
			else if (stmt.Subject == iso_anon && stmt.Predicate == MetadataStore.Namespaces.Resolve ("rdf:li"))
				iso_speed = ((SemWeb.Literal)stmt.Object).Value;
			else if (add && stmt.Subject.Uri == null)
				store.Add (stmt);

			if (width == null || height == null || exposure == null || aperture == null || iso_speed == null)
				return true;
			else
				return false;
		}

		public string ExposureInfo {
			get {
				string info = String.Empty;

				if  (fnumber != null && fnumber != String.Empty) {
					FSpot.Tiff.Rational rat = new FSpot.Tiff.Rational (fnumber);
					info += String.Format ("f/{0:.0} ", rat.Value);
				} else if (aperture != null && aperture != String.Empty) {
					// Convert from APEX to fnumber
					FSpot.Tiff.Rational rat = new FSpot.Tiff.Rational (aperture);
					info += String.Format ("f/{0:.0} ", Math.Pow (2, rat.Value / 2));
				}

				if (exposure != null && exposure != String.Empty)
					info += exposure + " sec ";

				if (iso_speed != null && iso_speed != String.Empty)
					info += Environment.NewLine + "ISO " + iso_speed;
				
				if (info == String.Empty)
					return Catalog.GetString ("(None)");
				
				return info;
			}
		}

		public string Dimensions {
			get {
				if (width != null && height != null)
					return String.Format ("{0}x{1}", width, height);
				else 
					return Catalog.GetString ("(Unknown)");
			}
		}
#if USE_EXIF_DATE
		public string Date {
			get {
				if (date > DateTime.MinValue && date < DateTime.MaxValue)
					return date.ToShortDateString () + Environment.NewLine + date.ToShortTimeString ();
				else 
					return Catalog.GetString ("(Unknown)");
			}
		}
#endif
	}
		

	public bool Update ()
	{
		ImageInfo info;

		if (photo == null) {
			Clear ();
			return false;
		}
		
		name_label.Text = photo.Name != null ? System.Uri.UnescapeDataString(photo.Name) : String.Empty;
		try {
			//using (new Timer ("building info")) {
				using (ImageFile img = ImageFile.Create (photo.DefaultVersionUri))
				{
					info = new ImageInfo (img);
				}
				//}
		} catch (System.Exception e) {
			System.Console.WriteLine (e);
			info = new ImageInfo (null);			
		}


		name_label.Sensitive = true;
		exposure_info_label.Text = info.ExposureInfo;
		if (exposure_info_label.Text.IndexOf (Environment.NewLine) != -1)
			exposure_name_label.Markup = default_exposure_string + Environment.NewLine;
		else
			exposure_name_label.Markup = default_exposure_string;

		size_label.Text = info.Dimensions;
#if USE_EXIF_DATE
		date_label.Text = info.Date;
#else
		DateTime local_time = photo.Time.ToLocalTime ();
		date_label.Text = String.Format ("{0}{2}{1}",
			local_time.ToShortDateString (),
			local_time.ToShortTimeString (),
			Environment.NewLine
		);
#endif
		

		Photo p = photo as Photo;
		if (p != null) {
			version_option_menu.Visible = true;
			version_option_menu.Sensitive = true;
			PhotoVersionMenu menu = new PhotoVersionMenu (p);
			menu.VersionIdChanged += new PhotoVersionMenu.VersionIdChangedHandler (HandleVersionIdChanged);
			menu.WidthRequest = version_option_menu.Allocation.Width;
			version_option_menu.Menu = menu;
			
			uint i = 0;
			foreach (uint version_id in p.VersionIds) {
				if (version_id == p.DefaultVersionId) {
					// FIXME GTK# why not just .History = i ?
					version_option_menu.SetHistory (i);
					break;
				}
				i++;
			}
		} else {
			version_option_menu.Visible = false;
			version_option_menu.Sensitive = false;
			version_option_menu.Menu = null;
		}


		return false;
	}


	// Constructor.

	public InfoBox () : base (false, 0)
	{
		SetupWidgets ();
		update_delay = new Delay (Update);
		update_delay.Start ();

		BorderWidth = 2;
	}
}
