using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Windows.Forms;
using System.ComponentModel;

namespace ScintillaNet
{
	[TypeConverterAttribute(typeof(System.ComponentModel.ExpandableObjectConverter))]
	public class DropMarkers : ScintillaHelperBase
	{
		internal DropMarkers(Scintilla scintilla) : base(scintilla) { }

		private Stack<DropMarker> _markerStack = new Stack<DropMarker>();
		private static Dictionary<string, Stack<DropMarker>> _sharedStack = new Dictionary<string, Stack<DropMarker>>();

		internal bool ShouldSerialize()
		{
			return ShouldSerializeSharedStackName();
		}

		#region SharedStackName
		private string _sharedStackName = string.Empty;
		public string SharedStackName
		{
			get
			{
				return _sharedStackName;
			}
			set
			{
				if (value == null)
					value = string.Empty;

				if (_sharedStackName == value)
					return;

				
				if (value == string.Empty)
				{
					//	If we had a shared stack name but are now clearing it
					//	we need to create our own private DropMarkerStack again
					_markerStack = new Stack<DropMarker>();

					//	If this was the last subscriber of a shared stack
					//	remove the name to free up resources
					if (_sharedStack.ContainsKey(_sharedStackName) && _sharedStack[_sharedStackName].Count == 1)
						_sharedStack.Remove(_sharedStackName);
				}
				else
				{
					//	We're using one of the shared stacks. Of course if it hasn't 
					//	already been registered with the list we need to create it.
					if (!_sharedStack.ContainsKey(_sharedStackName))
						_sharedStack[_sharedStackName] = new Stack<DropMarker>();

					_markerStack = _sharedStack[_sharedStackName];
				}

				_sharedStackName = value;
			}
		}

		private bool ShouldSerializeSharedStackName()
		{
			return _sharedStackName != string.Empty;
		}

		private void ResetSharedStackName()
		{
			_sharedStackName = string.Empty;
		} 
		#endregion

		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public Stack<DropMarker> MarkerStack
		{
			get
			{
				return _markerStack;
			}

			//	That's right kids you can actually provide your own MarkerStack. This
			//	is really useful for MDI applications where you want a single master
			//	MarkerStack that will automatically switch documents (a la CodeRush).
			//	Of course you can let the control do this for you automatically by 
			//	setting the SharedStackName property of multiple instances.
			set
            {
            	_markerStack = value;
            }
		}

		private DropMarkerList _allDocumentDropMarkers = new DropMarkerList();
		[Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
		public DropMarkerList AllDocumentDropMarkers
		{
			get
			{
				return _allDocumentDropMarkers;
			}
			set
			{
				_allDocumentDropMarkers = value;
			}
		}

		public DropMarker Drop()
		{
			return Drop(NativeScintilla.GetCurrentPos());
		}

		public DropMarker Drop(int position)
		{
			DropMarker dm = new DropMarker(position, position, getCurrentTopOffset(), Scintilla);
			_allDocumentDropMarkers.Add(dm);
			_markerStack.Push(dm);
			Scintilla.ManagedRanges.Add(dm);

			//	Force the Drop Marker to paint
			Scintilla.Invalidate(dm.GetClientRectangle());
			return dm;
		}

		public void Collect()
		{
			while (_markerStack.Count > 0)
			{
				DropMarker dm = _markerStack.Pop();
				
				//	If the Drop Marker was deleted in the document by
				//	a user action it will be disposed but not removed
				//	from the marker stack. In this case just pretend
				//	like it doesn't exist and go on to the next one
				if (dm.IsDisposed)
					continue;

				//	The MarkerCollection fires a cancellable event.
				//	If it is canclled the Collect() method will return
				//	false. In this case we need to push the marker back
				//	on the stack so that it will still be collected in
				//	the future.
				if (!dm.Collect())
					_markerStack.Push(dm);

				return;
			}
		}

		private int getCurrentTopOffset()
		{
			return -1;
		}

	}

	public class DropMarkerList : System.Collections.ObjectModel.KeyedCollection<Guid, DropMarker>
	{
		protected override Guid GetKeyForItem(DropMarker item)
		{
			return item.Key;
		}
	}

	public class DropMarker : ManagedRange
	{
		private int _topOffset;
		public int TopOffset
		{
			get
			{
				return _topOffset;
			}
			set
			{
				_topOffset = value;
			}
		}

		private Guid _key = Guid.NewGuid();
		public Guid Key
		{
			get
			{
				return _key;
			}
			set
			{
				_key = value;
			}
		}


		public DropMarker(int start, int end, int topOffset, Scintilla scintilla)
			: base(start, end, scintilla)
		{
			base.Start		= start;
			base.End		= end;
			this._topOffset = topOffset;
		}

		public override void Change(int newStart, int newEnd)
		{
			Invalidate();			
			//	This actually changes Start and End
			base.Change(newStart, newEnd);
		}

		public void Invalidate()
		{
			if(Scintilla != null && Start > 0)
			{
				//	Invalidate the old Marker Location so that we don't get "Ghosts"
				Scintilla.Invalidate(GetClientRectangle());
			}
		}

		//	Drop Markers are points, not a spanned range. Though this could change in the future.
		public override bool IsPoint
		{
			get
			{
				return Start == End;
			}
		}

		protected internal override void Paint(Graphics g)
		{
			base.Paint(g);

			if (IsDisposed)
				return;

			int x = NativeScintilla.PointXFromPosition(Start);
			int y = NativeScintilla.PointYFromPosition(Start) + NativeScintilla.TextHeight(0) - 2;

			//	Draw a red Triangle with a dark red border at the marker position
			g.FillPolygon(Brushes.Red, new Point[] { new Point(x-2, y+4), new Point(x, y), new Point(x+2, y+4) });
			g.DrawPolygon(Pens.DarkRed, new Point[] { new Point(x-2, y+4), new Point(x, y), new Point(x+2, y+4) });
		}

		public bool Collect()
		{
			return Collect(true);
		}

		internal bool Collect(bool dispose)
		{
			DropMarkerCollectEventArgs e = new DropMarkerCollectEventArgs(this);
			Scintilla.OnDropMarkerCollect(e);

			if (e.Cancel)
				return false;

			GotoStart();

			if (dispose)
				Dispose();

			return true;
		}

		public override void Dispose()
		{
			Scintilla.DropMarkers.AllDocumentDropMarkers.Remove(this);
			Invalidate();
			base.Dispose();
		}		

		public Rectangle GetClientRectangle()
		{
			int x = NativeScintilla.PointXFromPosition(Start);
			int y = NativeScintilla.PointYFromPosition(Start) + NativeScintilla.TextHeight(0) - 2;

			//	Invalidate the old Marker Location so that we don't get "Ghosts"
			return new Rectangle(x - 2, y, 5, 5);
		}
	}
}
