// Copyright (c) AlphaSierraPapa for the SharpDevelop Team (for details please see \doc\copyright.txt)
// This code is distributed under the GNU LGPL (for details please see \doc\license.txt)

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Windows;
using System.ComponentModel;
using System.Collections.ObjectModel;
using System.Windows.Controls;
using System.Collections.Specialized;
using System.Windows.Input;

namespace ICSharpCode.TreeView
{
	public partial class SharpTreeNode : INotifyPropertyChanged
	{
		SharpTreeNodeCollection modelChildren;
		internal SharpTreeNode modelParent;
		bool isVisible = true;
		
		#region Main
		
		public SharpTreeNode()
		{
		}
		
		public SharpTreeNodeCollection Children {
			get {
				if (modelChildren == null)
					modelChildren = new SharpTreeNodeCollection(this);
				return modelChildren;
			}
		}
		
		public SharpTreeNode Parent {
			get { return modelParent; }
		}
		
		public virtual object Text
		{
			get { return null; }
		}
		
		public virtual object Icon
		{
			get { return null; }
		}
		
		public virtual object ToolTip
		{
			get { return null; }
		}
		
		public int Level
		{
			get { return Parent != null ? Parent.Level + 1 : 0; }
		}
		
		public bool IsRoot
		{
			get { return Parent == null; }
		}
		
		bool isHidden;
		
		public bool IsHidden
		{
			get { return isHidden; }
			set {
				if (isHidden != value) {
					isHidden = value;
					RaisePropertyChanged("IsHidden");
				}
			}
		}
		
		bool isSelected;
		
		public bool IsSelected
		{
			get { return isSelected; }
			set {
				if (isSelected != value) {
					isSelected = value;
					RaisePropertyChanged("IsSelected");
				}
			}
		}
		
		public virtual ContextMenu GetContextMenu()
		{
			return null;
		}
		
		#endregion
		
		#region OnChildrenChanged
		internal void OnChildrenChanged(NotifyCollectionChangedEventArgs e)
		{
			if (e.OldItems != null) {
				foreach (SharpTreeNode node in e.OldItems) {
					Debug.Assert(node.modelParent == this);
					node.modelParent = null;
				}
			}
			if (e.NewItems != null) {
				foreach (SharpTreeNode node in e.NewItems) {
					Debug.Assert(node.modelParent == null);
					node.modelParent = this;
				}
			}	
			
			RaisePropertyChanged("ShowExpander");
			RaiseIsLastChangedIfNeeded(e);
		}
		#endregion
		
		#region Expanding / LazyLoading
		
		public virtual object ExpandedIcon
		{
			get { return Icon; }
		}
		
		public virtual bool ShowExpander
		{
			get { return Children.Count > 0 || LazyLoading; }
		}
		
		bool isExpanded;
		
		public bool IsExpanded
		{
			get { return isExpanded; }
			set
			{
				if (isExpanded != value) {
					isExpanded = value;
					if (isExpanded) {
						EnsureLazyChildren();
					}
					RaisePropertyChanged("IsExpanded");
				}
			}
		}
		
		bool lazyLoading;
		
		public bool LazyLoading
		{
			get { return lazyLoading; }
			set
			{
				lazyLoading = value;
				if (lazyLoading) {
					IsExpanded = false;
				}
				RaisePropertyChanged("LazyLoading");
				RaisePropertyChanged("ShowExpander");
			}
		}
		
		public virtual bool ShowIcon
		{
			get { return Icon != null; }
		}
		
		protected virtual void LoadChildren()
		{
			throw new NotSupportedException(GetType().Name + " does not support lazy loading");
		}
		
		/// <summary>
		/// Ensures the children were initialized (loads children if lazy loading is enabled)
		/// </summary>
		public void EnsureLazyChildren()
		{
			if (LazyLoading) {
				LazyLoading = false;
				LoadChildren();
			}
		}
		
		#endregion
		
		#region Ancestors / Descendants
		
		public IEnumerable<SharpTreeNode> Descendants()
		{
			return TreeTraversal.PreOrder(this.Children, n => n.Children);
		}
		
		public IEnumerable<SharpTreeNode> DescendantsAndSelf()
		{
			return TreeTraversal.PreOrder(this, n => n.Children);
		}
		
		public IEnumerable<SharpTreeNode> ExpandedDescendants()
		{
			return TreeTraversal.PreOrder(this.Children, n => n.IsExpanded ? n.Children : null);
		}
		
		public IEnumerable<SharpTreeNode> ExpandedDescendantsAndSelf()
		{
			return TreeTraversal.PreOrder(this, n => n.IsExpanded ? n.Children : null);
		}
		
		public IEnumerable<SharpTreeNode> Ancestors()
		{
			var node = this;
			while (node.Parent != null) {
				yield return node.Parent;
				node = node.Parent;
			}
		}
		
		public IEnumerable<SharpTreeNode> AncestorsAndSelf()
		{
			yield return this;
			foreach (var node in Ancestors()) {
				yield return node;
			}
		}
		
		#endregion
		
		#region Editing
		
		public virtual bool IsEditable
		{
			get { return false; }
		}
		
		bool isEditing;
		
		public bool IsEditing
		{
			get { return isEditing; }
			set
			{
				if (isEditing != value) {
					isEditing = value;
					RaisePropertyChanged("IsEditing");
				}
			}
		}
		
		public virtual string LoadEditText()
		{
			return null;
		}
		
		public virtual bool SaveEditText(string value)
		{
			return true;
		}
		
		#endregion
		
		#region Checkboxes
		
		public virtual bool IsCheckable {
			get { return false; }
		}
		
		bool? isChecked;
		
		public bool? IsChecked {
			get { return isChecked; }
			set {
				SetIsChecked(value, true);
			}
		}
		
		void SetIsChecked(bool? value, bool update)
		{
			if (isChecked != value) {
				isChecked = value;
				
				if (update) {
					if (IsChecked != null) {
						foreach (var child in Descendants()) {
							if (child.IsCheckable) {
								child.SetIsChecked(IsChecked, false);
							}
						}
					}
					
					foreach (var parent in Ancestors()) {
						if (parent.IsCheckable) {
							if (!parent.TryValueForIsChecked(true)) {
								if (!parent.TryValueForIsChecked(false)) {
									parent.SetIsChecked(null, false);
								}
							}
						}
					}
				}
				
				RaisePropertyChanged("IsChecked");
			}
		}
		
		bool TryValueForIsChecked(bool? value)
		{
			if (Children.Where(n => n.IsCheckable).All(n => n.IsChecked == value)) {
				SetIsChecked(value, false);
				return true;
			}
			return false;
		}
		
		#endregion
		
		#region Cut / Copy / Paste / Delete
		
		public bool IsCut { get { return false; } }
		/*
			static List<SharpTreeNode> cuttedNodes = new List<SharpTreeNode>();
			static IDataObject cuttedData;
			static EventHandler requerySuggestedHandler; // for weak event
	
			static void StartCuttedDataWatcher()
			{
				requerySuggestedHandler = new EventHandler(CommandManager_RequerySuggested);
				CommandManager.RequerySuggested += requerySuggestedHandler;
			}
	
			static void CommandManager_RequerySuggested(object sender, EventArgs e)
			{
				if (cuttedData != null && !Clipboard.IsCurrent(cuttedData)) {
					ClearCuttedData();
				}
			}
	
			static void ClearCuttedData()
			{
				foreach (var node in cuttedNodes) {
					node.IsCut = false;
				}
				cuttedNodes.Clear();
				cuttedData = null;
			}
	
			//static public IEnumerable<SharpTreeNode> PurifyNodes(IEnumerable<SharpTreeNode> nodes)
			//{
			//    var list = nodes.ToList();
			//    var array = list.ToArray();
			//    foreach (var node1 in array) {
			//        foreach (var node2 in array) {
			//            if (node1.Descendants().Contains(node2)) {
			//                list.Remove(node2);
			//            }
			//        }
			//    }
			//    return list;
			//}
	
			bool isCut;
	
			public bool IsCut
			{
				get { return isCut; }
				private set
				{
					isCut = value;
					RaisePropertyChanged("IsCut");
				}
			}
	
			internal bool InternalCanCut()
			{
				return InternalCanCopy() && InternalCanDelete();
			}
	
			internal void InternalCut()
			{
				ClearCuttedData();
				cuttedData = Copy(ActiveNodesArray);
				Clipboard.SetDataObject(cuttedData);
	
				foreach (var node in ActiveNodes) {
					node.IsCut = true;
					cuttedNodes.Add(node);
				}
			}
	
			internal bool InternalCanCopy()
			{
				return CanCopy(ActiveNodesArray);
			}
	
			internal void InternalCopy()
			{
				Clipboard.SetDataObject(Copy(ActiveNodesArray));
			}
	
			internal bool InternalCanPaste()
			{
				return CanPaste(Clipboard.GetDataObject());
			}
	
			internal void InternalPaste()
			{
				Paste(Clipboard.GetDataObject());
	
				if (cuttedData != null) {
					DeleteCore(cuttedNodes.ToArray());
					ClearCuttedData();
				}
			}
		 */
		
		public virtual bool CanDelete()
		{
			return false;
		}
		
		public virtual void Delete()
		{
			throw new NotSupportedException(GetType().Name + " does not support deletion");
		}
		
		public virtual void DeleteCore()
		{
			throw new NotSupportedException(GetType().Name + " does not support deletion");
		}
		
		public virtual IDataObject Copy(SharpTreeNode[] nodes)
		{
			throw new NotSupportedException(GetType().Name + " does not support copy/paste or drag'n'drop");
		}
		
		/*
			public virtual bool CanCopy(SharpTreeNode[] nodes)
			{
				return false;
			}
	
			public virtual bool CanPaste(IDataObject data)
			{
				return false;
			}
	
			public virtual void Paste(IDataObject data)
			{
				EnsureLazyChildren();
				Drop(data, Children.Count, DropEffect.Copy);
			}
		 */
		#endregion
		
		#region Drag and Drop
		/*
			internal bool InternalCanDrag()
			{
				return CanDrag(ActiveNodesArray);
			}
	
			internal void InternalDrag(DependencyObject dragSource)
			{
				DragDrop.DoDragDrop(dragSource, Copy(ActiveNodesArray), DragDropEffects.All);
			}
	
			internal bool InternalCanDrop(DragEventArgs e, int index)
			{
				var finalEffect = GetFinalEffect(e, index);
				e.Effects = GetDragDropEffects(finalEffect);
				return finalEffect != DropEffect.None;
			}
	
			internal void InternalDrop(DragEventArgs e, int index)
			{
				if (LazyLoading) {
					EnsureLazyChildren();
					index = Children.Count;
				}
	
				var finalEffect = GetFinalEffect(e, index);
				Drop(e.Data, index, finalEffect);
	
				if (finalEffect == DropEffect.Move) {
					foreach (SharpTreeNode node in ActiveNodesArray)
						node.DeleteCore();
				}
			}
	
			DropEffect GetFinalEffect(DragEventArgs e, int index)
			{
				var requestedEffect = GetDropEffect(e);
				var result = CanDrop(e.Data, requestedEffect);
				if (result == DropEffect.Move) {
					if (!ActiveNodesArray.All(n => n.CanDelete())) {
						return DropEffect.None;
					}
				}
				return result;
			}
	
			static DropEffect GetDropEffect(DragEventArgs e)
			{
				if (e.Data != null) {
					var all = DragDropKeyStates.ControlKey | DragDropKeyStates.ShiftKey | DragDropKeyStates.AltKey;
	
					if ((e.KeyStates & all) == DragDropKeyStates.ControlKey) {
						return DropEffect.Copy;
					}
					if ((e.KeyStates & all) == DragDropKeyStates.AltKey) {
						return DropEffect.Link;
					}
					if ((e.KeyStates & all) == (DragDropKeyStates.ControlKey | DragDropKeyStates.ShiftKey)) {
						return DropEffect.Link;
					}
					return DropEffect.Move;
				}
				return DropEffect.None;
			}
	
			static DragDropEffects GetDragDropEffects(DropEffect effect)
			{
				switch (effect) {
					case DropEffect.Copy:
						return DragDropEffects.Copy;
					case DropEffect.Link:
						return DragDropEffects.Link;
					case DropEffect.Move:
						return DragDropEffects.Move;
				}
				return DragDropEffects.None;
			}
	
			public virtual bool CanDrag(SharpTreeNode[] nodes)
			{
				return false;
			}
	
			public virtual DropEffect CanDrop(IDataObject data, DropEffect requestedEffect)
			{
				return DropEffect.None;
			}
	
			public virtual void Drop(IDataObject data, int index, DropEffect finalEffect)
			{
				throw new NotSupportedException(GetType().Name + " does not support Drop()");
			}
		 */
		#endregion
		
		#region IsLast (for TreeView lines)
		
		public bool IsLast
		{
			get
			{
				return Parent == null ||
					Parent.Children[Parent.Children.Count - 1] == this;
			}
		}
		
		void RaiseIsLastChangedIfNeeded(NotifyCollectionChangedEventArgs e)
		{
			switch (e.Action) {
				case NotifyCollectionChangedAction.Add:
					if (e.NewStartingIndex == Children.Count - 1) {
						if (Children.Count > 1) {
							Children[Children.Count - 2].RaisePropertyChanged("IsLast");
						}
						Children[Children.Count - 1].RaisePropertyChanged("IsLast");
					}
					break;
				case NotifyCollectionChangedAction.Remove:
					if (e.OldStartingIndex == Children.Count) {
						if (Children.Count > 0) {
							Children[Children.Count - 1].RaisePropertyChanged("IsLast");
						}
					}
					break;
			}
		}
		
		#endregion
		
		#region INotifyPropertyChanged Members
		
		public event PropertyChangedEventHandler PropertyChanged;
		
		public void RaisePropertyChanged(string name)
		{
			if (PropertyChanged != null) {
				PropertyChanged(this, new PropertyChangedEventArgs(name));
			}
		}
		
		#endregion
		
		/// <summary>
		/// Gets called when the item is double-clicked.
		/// </summary>
		public virtual void ActivateItem(RoutedEventArgs e)
		{
		}
		
		public override string ToString()
		{
			// used for keyboard navigation
			object text = this.Text;
			return text != null ? text.ToString() : string.Empty;
		}
	}
}