//
// DockBarItem.cs
//
// Author:
//   Lluis Sanchez Gual
//

//
// Copyright (C) 2007 Novell, Inc (http://www.novell.com)
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
// 
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
// 
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//


using System;
using Gtk;
using Pinta.Core;

namespace MonoDevelop.Components.Docking
{
	class DockBarItem: EventBox
	{
		DockBar bar;
		DockItem it;
		Box box;
		Label label;
		Alignment mainBox;
		AutoHideBox autoShowFrame;
		AutoHideBox hiddenFrame;
		uint autoShowTimeout = uint.MaxValue;
		uint autoHideTimeout = uint.MaxValue;
		int size;
		Gdk.Size lastFrameSize;
		
		public DockBarItem (DockBar bar, DockItem it, int size)
		{
			Events = Events | Gdk.EventMask.EnterNotifyMask | Gdk.EventMask.LeaveNotifyMask;
			this.size = size;
			this.bar = bar;
			this.it = it;
			VisibleWindow = false;
			UpdateTab ();
			lastFrameSize = bar.Frame.Allocation.Size;
			bar.Frame.SizeAllocated += HandleBarFrameSizeAllocated;
		}

		void HandleBarFrameSizeAllocated (object o, SizeAllocatedArgs args)
		{
			if (!lastFrameSize.Equals (args.Allocation.Size)) {
				lastFrameSize = args.Allocation.Size;
				if (autoShowFrame != null)
					bar.Frame.UpdateSize (bar, autoShowFrame);
			}
		}
		
		protected override void OnDestroyed ()
		{
			base.OnDestroyed ();
			bar.Frame.SizeAllocated -= HandleBarFrameSizeAllocated;
		}
		
		
		public void Close ()
		{
			UnscheduleAutoShow ();
			UnscheduleAutoHide ();
			AutoHide (false);
			bar.RemoveItem (this);
			Destroy ();
		}
		
		public int Size {
			get { return size; }
			set { size = value; }
		}
		
		public void UpdateTab ()
		{
			if (Child != null) {
				Widget w = Child;
				Remove (w);
				w.Destroy ();
			}
			
			mainBox = new Alignment (0,0,1,1);
			if (bar.Orientation == Gtk.Orientation.Horizontal) {
				box = new HBox ();
				mainBox.LeftPadding = mainBox.RightPadding = 2;
			}
			else {
				box = new VBox ();
				mainBox.TopPadding = mainBox.BottomPadding = 2;
			}
			
			Gtk.Widget customLabel = null;
			if (it.DockLabelProvider != null)
				customLabel = it.DockLabelProvider.CreateLabel (bar.Orientation);
			
			if (customLabel != null) {
				customLabel.ShowAll ();
				box.PackStart (customLabel, true, true, 0);
			}
			else {
				if (it.Icon != null)
					box.PackStart (new Gtk.Image (it.Icon), false, false, 0);
					
				if (!string.IsNullOrEmpty (it.Label)) {
					label = new Gtk.Label (it.Label);
					label.UseMarkup = true;
					if (bar.Orientation == Gtk.Orientation.Vertical)
						label.Angle = 270;
					box.PackStart (label, true, true, 0);
				} else
					label = null;
			}

			box.BorderWidth = 2;
			box.Spacing = 2;
			mainBox.Add (box);
			mainBox.ShowAll ();
			Add (mainBox);
			SetNormalColor ();
		}
		
		public MonoDevelop.Components.Docking.DockItem DockItem {
			get {
				return it;
			}
		}
		
		protected override void OnHidden ()
		{
			base.OnHidden ();
			UnscheduleAutoShow ();
			UnscheduleAutoHide ();
			AutoHide (false);
		}

		protected override bool OnExposeEvent (Gdk.EventExpose evnt)
		{
			if (State == StateType.Prelight) {
				int w = Allocation.Width, h = Allocation.Height;
				double x=Allocation.Left, y=Allocation.Top, r=3;
				x += 0.5; y += 0.5; h -=1; w -= 1;
				
				using (Cairo.Context ctx = Gdk.CairoHelper.Create (GdkWindow)) {
					HslColor c = new HslColor (Style.Background (Gtk.StateType.Normal));
					HslColor c1 = c;
					HslColor c2 = c;
					if (State != StateType.Prelight) {
						c1.L *= 0.8;
						c2.L *= 0.95;
					} else {
						c1.L *= 1.1;
						c2.L *= 1;
					}
					Cairo.Gradient pat;
					switch (bar.Position) {
						case PositionType.Top: pat = new Cairo.LinearGradient (x, y, x, y+h); break;
						case PositionType.Bottom: pat = new Cairo.LinearGradient (x, y, x, y+h); break;
						case PositionType.Left: pat = new Cairo.LinearGradient (x+w, y, x, y); break;
						default: pat = new Cairo.LinearGradient (x, y, x+w, y); break;
					}
					pat.AddColorStop (0, c1);
					pat.AddColorStop (1, c2);
					ctx.NewPath ();
					ctx.Arc (x+r, y+r, r, 180 * (Math.PI / 180), 270 * (Math.PI / 180));
					ctx.LineTo (x+w-r, y);
					ctx.Arc (x+w-r, y+r, r, 270 * (Math.PI / 180), 360 * (Math.PI / 180));
					ctx.LineTo (x+w, y+h);
					ctx.LineTo (x, y+h);
					ctx.ClosePath ();
					ctx.SetSource (pat);
					ctx.FillPreserve ();
					c1 = c;
					c1.L *= 0.7;
					ctx.LineWidth = 1;
					ctx.SetSourceColor (c1);
					ctx.Stroke ();
					
					// Inner line
					ctx.NewPath ();
					ctx.Arc (x+r+1, y+r+1, r, 180 * (Math.PI / 180), 270 * (Math.PI / 180));
					ctx.LineTo (x+w-r-1, y+1);
					ctx.Arc (x+w-r-1, y+r+1, r, 270 * (Math.PI / 180), 360 * (Math.PI / 180));
					ctx.LineTo (x+w-1, y+h-1);
					ctx.LineTo (x+1, y+h-1);
					ctx.ClosePath ();
					c1 = c;
					//c1.L *= 0.9;
					ctx.LineWidth = 1;
					ctx.SetSourceColor (c1);
					ctx.Stroke ();
				}
			}
		
			bool res = base.OnExposeEvent (evnt);
			return res;
		}

		public void Present (bool giveFocus)
		{
			AutoShow ();
			if (giveFocus) {
				GLib.Timeout.Add (200, delegate {
					// Using a small delay because AutoShow uses an animation and setting focus may
					// not work until the item is visible
					it.SetFocus ();
					ScheduleAutoHide (false);
					return false;
				});
			}
		}

		void AutoShow ()
		{
			UnscheduleAutoHide ();
			if (autoShowFrame == null) {
				if (hiddenFrame != null)
					bar.Frame.AutoHide (it, hiddenFrame, false);
				autoShowFrame = bar.Frame.AutoShow (it, bar, size);
				autoShowFrame.EnterNotifyEvent += OnFrameEnter;
				autoShowFrame.LeaveNotifyEvent += OnFrameLeave;
				autoShowFrame.KeyPressEvent += OnFrameKeyPress;
				SetPrelight ();
			}
		}
		
		void AutoHide (bool animate)
		{
			UnscheduleAutoShow ();
			if (autoShowFrame != null) {
				size = autoShowFrame.Size;
				hiddenFrame = autoShowFrame;
				autoShowFrame.Hidden += delegate {
					hiddenFrame = null;
				};
				bar.Frame.AutoHide (it, autoShowFrame, animate);
				autoShowFrame.EnterNotifyEvent -= OnFrameEnter;
				autoShowFrame.LeaveNotifyEvent -= OnFrameLeave;
				autoShowFrame.KeyPressEvent -= OnFrameKeyPress;
				autoShowFrame = null;
				UnsetPrelight ();
			}
		}
		
		void ScheduleAutoShow ()
		{
			UnscheduleAutoHide ();
			if (autoShowTimeout == uint.MaxValue) {
				autoShowTimeout = GLib.Timeout.Add (bar.Frame.AutoShowDelay, delegate {
					autoShowTimeout = uint.MaxValue;
					AutoShow ();
					return false;
				});
			}
		}
		
		void ScheduleAutoHide (bool cancelAutoShow)
		{
			ScheduleAutoHide (cancelAutoShow, false);
		}
		
		void ScheduleAutoHide (bool cancelAutoShow, bool force)
		{
			if (cancelAutoShow)
				UnscheduleAutoShow ();
			if (force)
				it.Widget.FocusChild = null;
			if (autoHideTimeout == uint.MaxValue) {
				autoHideTimeout = GLib.Timeout.Add (force ? 0 : bar.Frame.AutoHideDelay, delegate {
					// Don't hide the item if it has the focus. Try again later.
					if (it.Widget.FocusChild != null && !force)
						return true;
					// Don't hide the item if the mouse pointer is still inside the window. Try again later.
					int px, py;
					it.Widget.GetPointer (out px, out py);
					if (it.Widget.Visible && it.Widget.IsRealized && it.Widget.Allocation.Contains (px, py) && !force)
						return true;
					autoHideTimeout = uint.MaxValue;
					AutoHide (true);
					return false;
				});
			}
		}
		
		void UnscheduleAutoShow ()
		{
			if (autoShowTimeout != uint.MaxValue) {
				GLib.Source.Remove (autoShowTimeout);
				autoShowTimeout = uint.MaxValue;
			}
		}
		
		void UnscheduleAutoHide ()
		{
			if (autoHideTimeout != uint.MaxValue) {
				GLib.Source.Remove (autoHideTimeout);
				autoHideTimeout = uint.MaxValue;
			}
		}
		
		protected override bool OnEnterNotifyEvent (Gdk.EventCrossing evnt)
		{
			ScheduleAutoShow ();
			SetPrelight ();
			return base.OnEnterNotifyEvent (evnt);
		}
		
		protected override bool OnLeaveNotifyEvent (Gdk.EventCrossing evnt)
		{
			ScheduleAutoHide (true);
			if (autoShowFrame == null)
				UnsetPrelight ();
			return base.OnLeaveNotifyEvent (evnt);
		}
		
		void SetPrelight ()
		{
			if (State != StateType.Prelight) {
				State = StateType.Prelight;
				if (label != null)
					label.ModifyFg (StateType.Normal, Style.Foreground (Gtk.StateType.Normal));
			}
		}
		
		void UnsetPrelight ()
		{
			if (State == StateType.Prelight) {
				State = StateType.Normal;
				SetNormalColor ();
			}
		}
		
		protected override void OnRealized ()
		{
			base.OnRealized();
			SetNormalColor ();
		}

		
		void SetNormalColor ()
		{
			if (label != null) {
				HslColor c = Style.Background (Gtk.StateType.Normal);
				c.L *= 0.4;
				label.ModifyFg (StateType.Normal, c);
			}
		}
		
		void OnFrameEnter (object s, Gtk.EnterNotifyEventArgs args)
		{
			AutoShow ();
		}

		void OnFrameKeyPress (object s, Gtk.KeyPressEventArgs args)
		{
			if (args.Event.Key == Gdk.Key.Escape)
				ScheduleAutoHide (true, true);
		}
		
		void OnFrameLeave (object s, Gtk.LeaveNotifyEventArgs args)
		{
			if (args.Event.Detail != Gdk.NotifyType.Inferior)
				ScheduleAutoHide (true);
		}
		
		protected override bool OnButtonPressEvent (Gdk.EventButton evnt)
		{
			if (evnt.Button == 1) {
				if (evnt.Type == Gdk.EventType.TwoButtonPress)
					it.Status = DockItemStatus.Dockable;
				else
					AutoShow ();
			}
			else if (evnt.Button == 3)
				it.ShowDockPopupMenu (evnt.Time);
			return base.OnButtonPressEvent (evnt);
		}
	}
}
