using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;


namespace FormulaEditor
{

	/// <summary>
	/// Tree of object formula
	/// </summary>
	public class ObjectFormulaTree : ICloneable
	{
		/// <summary>
		/// Children formulas
		/// </summary>
		protected List<ObjectFormulaTree> children = new List<ObjectFormulaTree>();

		/// <summary>
		/// Operation of this formula
		/// </summary>
		protected IObjectOperation operation;

		/// <summary>
		/// Temporary variables
		/// </summary>
		protected object[] y;

		/// <summary>
		/// Creator of formulas
		/// </summary>
		static protected IFormulaObjectCreator creator;

		/// <summary>
		/// Constructor
		/// </summary>
		protected ObjectFormulaTree()
		{
		}

		/// <summary>
		/// Constructor
		/// </summary>
		/// <param name="operation">Tree operation</param>
		/// <param name="children">Children trees</param>
		public ObjectFormulaTree(IObjectOperation operation, List<ObjectFormulaTree> children)
		{
			if (operation is ICloneable)
			{
				ICloneable c = operation as ICloneable;
				this.operation = c.Clone() as IObjectOperation;
			}
			else
			{
				this.operation = operation;
			}
			if (operation.Arity != 0)
			{
				if (children == null)
				{
					this.children = children;
				}
				if (children.Count != operation.Arity)
				{
					this.children = children;
				}
			}
			this.children = children;
			y = new object[operation.Arity];
		}

		/// <summary>
		/// Consructor from formula
		/// </summary>
		/// <param name="formula">The formula</param>
		/// <param name="creator">The formula object creator</param>
		public ObjectFormulaTree(MathFormula formula, IFormulaObjectCreator creator)
		{
			init(formula, creator);
		}

		/// <summary>
		/// Consructor from formula
		/// </summary>
		/// <param name="formula">The formula</param>
		public ObjectFormulaTree(MathFormula formula) : this(formula, creator)
		{
		}

		public object Clone()
		{
			ObjectFormulaTree t = new ObjectFormulaTree();
			if (operation is ICloneable)
			{
				ICloneable c = operation as ICloneable;
				t.operation = c.Clone() as IObjectOperation;
			}
			else
			{
				t.operation = operation;
			}
			foreach (ObjectFormulaTree tree in children)
			{
				t.children.Add(tree.Clone() as ObjectFormulaTree);
			}
			if (t.children.Count != t.operation.Arity)
			{
				y = y;
			}
			if (y != null)
			{
				t.y = new object[y.Length];
			}
			return t;
		}


		public static IFormulaObjectCreator Creator
		{
			set
			{
				creator = value;
			}
		}




		private void init(MathFormula formula, IFormulaObjectCreator creator)
		{
			if (creator.IsBra(formula[0]) & creator.IsKet(formula[formula.Count - 1]))
			{
				if (formula[formula.Count - 1].Count >= 0)
				{
					if(formula.Count > 2)
					{
						int m = 1;
						for (int i = 1; i < formula.Count - 1; i++)
						{
							if (creator.IsBra(formula[i]))
							{
								++m;
							}
							else if (creator.IsKet(formula[i]))
							{
								--m;
							}
							if (m == 0)
							{
								goto calc;
							}
						}
						MathFormula f = new MathFormula(formula, 1, formula.Count - 2);
						init(f, creator);
						return;
					}
				}
			}
			calc:
				if (processMultiOperation(formula, creator))
				{
					return;
				}
			if (processBinary(formula, creator))
			{
				return;
			}
			if (processOperation(formula, creator))
			{
				return;
			}
		}

		/// <summary>
		/// Result of calculation
		/// </summary>
		public object Result
		{
			get
			{
                if (operation is OptionalOperation)
                {
                    object o = this[0].Result;
                    if (o == null)
                    {
                        return null;
                    }
                    bool b = (bool)o;
                    if (b)
                    {
                        return this[1].Result;
                    }
                    return this[2].Result;
                }
				for (int i = 0; i < operation.Arity; i++)
				{
                    object o = this[i].Result;
                    if (o == null | o is DBNull)
                    {
                        return null;
                    }
					y[i] = o;
				}
				return operation[y];
			}
		}

		/// <summary>
		/// Access to i - th child
		/// </summary>
		public ObjectFormulaTree this[int i]
		{
			get
			{
				if (children.Count <= i | i < 0 | children == null)
				{
					return null;
				}
				return children[i] as ObjectFormulaTree;
			}
		}

		/// <summary>
		/// Sets null arity objects
		/// </summary>
		/// <param name="x">The object to set</param>
		public void SetNullArityObjects(object x)
		{
			for (int i = 0; i < Count; i++)
			{
				this[i].SetNullArityObjects(x);
			}
			if (!(operation is INullArityOperation))
			{
				return;
			}
			INullArityOperation n = operation as INullArityOperation;
			n.Object = x;
		}

		/// <summary>
		/// Calculates result by argument
		/// </summary>
		/// <param name="x">The argument</param>
		/// <returns>The result</returns>
		public object GetResult(object x)
		{
			SetNullArityObjects(x);
			return Result;
		}

		/// <summary>
		/// Children count
		/// </summary>
		public int Count
		{
			get
			{
				if (children == null)
				{
					return 0;
				}
				return children.Count;
			}
		}
		
		/// <summary>
		/// Return type
		/// </summary>
		public object ReturnType
		{
			get
			{
				return operation.ReturnType;
			}
		}

		/// <summary>
		/// Tree root operation
		/// </summary>
		public IObjectOperation Operation
		{
			get
			{
				return operation;
			}
		}
		
		/// <summary>
		/// Processes binary operation
		/// </summary>
		/// <param name="formula">Input formula</param>
		/// <param name="creator">Creator of tree</param>
		protected bool processBinary(MathFormula formula, IFormulaObjectCreator creator)
		{
			for (int i = 0; i < creator.BinaryCount; i++)
			{
				IBinaryDetector detector = creator.GetBinaryDetector(i);
				ObjectFormulaTree tA = null;
				ObjectFormulaTree tB = null;
				if (detector.AssociationDirection == BinaryAssociationDirection.LeftRight)
				{
					int m = 0;
					for (int j = 0; j < formula.Count - 1; j++)
					{
						MathSymbol symbol = formula[j];
						if (creator.IsBra(symbol))
						{
							++m;
							continue;
						}
						if (creator.IsKet(symbol))
						{
							--m;
							continue;
						}
						if (m < 0)
						{
							//FormulaEditorPerformer.ThrowErrorException(FormulaTree.ERRORS[1]);
						}
						if (j == 0 | m != 0)
						{
							continue;
						}
						IBinaryAcceptor acceptor = detector.Detect(symbol);
						if (acceptor == null)
						{
							continue;
						}
						tA = new ObjectFormulaTree(new MathFormula(formula, 0, j - 1), creator);
						tB = new ObjectFormulaTree(new MathFormula(formula, j + 1, formula.Count - 1), creator);
						operation = acceptor.Accept(tA.ReturnType, tB.ReturnType);
						if (operation != null)
						{
							goto start;
						}
					}
				}
				else
				{
					int m = 0;
					for (int j = formula.Count - 1; j > 0; j--)
					{
						MathSymbol symbol = formula[j];
						if (creator.IsKet(symbol))
						{
							++m;
							continue;
						}
						if (creator.IsBra(symbol))
						{
							--m;
							continue;
						}
						if (m < 0)
						{
							//FormulaEditorPerformer.ThrowErrorException(FormulaTree.ERRORS[1]);
						}
						if (m != 0)
						{
							continue;
						}
						IBinaryAcceptor acceptor = detector.Detect(symbol);
						if (acceptor == null)
						{
							continue;
						}
						tA = new ObjectFormulaTree(new MathFormula(formula, 0, j - 1), creator);
						tB = new ObjectFormulaTree(new MathFormula(formula, j + 1, formula.Count - 1), creator);
						operation = acceptor.Accept(tA.ReturnType, tB.ReturnType);
						if (operation != null)
						{
							goto start;
						}
					}
				}
			start:
				if (operation != null)
				{
					children.Add(tA);
					children.Add(tB);
					y = new object[2];
					return true;
				}
			}
			return false;
		}

		/// <summary>
		/// Processes multi operation
		/// </summary>
		/// <param name="formula">Formula</param>
		/// <param name="creator">Formula creator</param>
		/// <returns>True if operation exists and false otherwise</returns>
		protected bool processMultiOperation(MathFormula formula, IFormulaObjectCreator creator)
		{
			for (int n = 0; n < creator.MultiOperationCount; n++)
			{
				IMultiOperationDetector detector = creator.GetMultiOperationDetector(n);
				int j = 0;
				int m = 0;
				int i = 0;
				//int opened = 0;
				int k = 0;
				List<ObjectFormulaTree> list = null;
				for(;i < formula.Count; i++)
				{
					MathSymbol symbol = formula[i];
					if (creator.IsBra(symbol))
					{
						++m;
						continue;
					}
					if (creator.IsKet(symbol))
					{
						--m;
						continue;
					}
					if (m != 0)
					{
						continue;
					}
					if (!detector.Detect(k, symbol))
					{
						continue;
					}
					if (list == null)
					{
						list = new List<ObjectFormulaTree>();
					}
					MathFormula f = new MathFormula(formula, j, i - 1);
					list.Add(new ObjectFormulaTree(f, creator));
					++k;
					j = i + 1;
					if (k == detector.Count)
					{
						f = new MathFormula(formula, j, formula.Count - 1);
						list.Add(new ObjectFormulaTree(f, creator));
						object[] types = new object[list.Count];
						int nOp = 0;
						foreach (ObjectFormulaTree tree in list)
						{
							types[nOp] = tree.ReturnType;
							++nOp;
						}
						IObjectOperation op = detector.Accept(types);
						if (op != null)
						{
							operation = op;
							children = list;
							y = new object[list.Count];
							return true;
						}
					}
				}
			}
			return false;
		}

		/// <summary>
		/// Processes ordinary operation
		/// </summary>
		/// <param name="formula">Formula</param>
		/// <param name="creator">Formula creator</param>
		/// <returns>True if operation exists and false otherwise</returns>
		protected bool processOperation(MathFormula formula, IFormulaObjectCreator creator)
		{
			for (int i = 0; i < creator.OperationCount; i++)
			{
				IOperationDetector detector = creator.GetDetector(i);
				IOperationAcceptor acceptor = detector.Detect(formula[0]);
				if (acceptor == null)
				{
					continue;
				}
				object type = null;
				ObjectFormulaTree tree = null;
				if (formula.Count > 1)
				{
					tree = new ObjectFormulaTree(new MathFormula(formula, 1, formula.Count - 1), creator);
					type = tree.ReturnType;
				}
				if (acceptor is IMultiVariableOperationAcceptor)
				{
					IMultiVariableOperationAcceptor ma = acceptor as IMultiVariableOperationAcceptor;
					IMultiVariableOperation multiOp = ma.AcceptOperation(formula[0]);
					MathSymbol s = formula[0];
					ObjectFormulaTree[] trees = null;
					object[] types = null;
					if (s.HasChildren)
					{
						int n = 0;
						for (int j = 0; j < s.Count; j++)
						{
							if (s[j] != null)
							{
								if (!s[j].IsEmpty)
								{
									++n;
								}
							}
						}
						trees = new ObjectFormulaTree[n];
						types = new object[n];
						n = 0;
						for (int j = 0; j < s.Count; j++)
						{
							if (s[j] != null)
							{
								if (!s[j].IsEmpty)
								{
									trees[j] = new ObjectFormulaTree(s[j], creator);
									types[j] = trees[j].ReturnType;
									++n;
								}
							}
						}
					}
					operation = multiOp.Accept(types);
					if (operation == null)
					{
						return false;
					}
					y = new object[types.Length];
					if (trees != null)
					{
						foreach (ObjectFormulaTree t in trees)
						{
							if (t != null)
							{
								children.Add(t);
							}
						}
					}
					return true;
				}
				IObjectOperation op = acceptor.Accept(type);
				if (op != null)
				{
					if (op.IsPowered)
					{
						if (formula.First.HasChildren)
						{
							ObjectFormulaTree pow = new ObjectFormulaTree(formula.First[0], creator);
							ObjectFormulaTree val = new ObjectFormulaTree();
							val.operation = op;
							val.y = new object[op.Arity];
							if (tree != null)
							{
								val.children.Add(tree);
							}
							IObjectOperation powOp = creator.GetPowerOperation(val.ReturnType, pow.ReturnType);
							if (powOp == null)
							{
								return false;
							}
							operation = powOp;
							children.Add(val);
							children.Add(pow);
							y = new object[2];
							return true;
						}
					}
					operation = op;
					if (tree != null)
					{
						children.Add(tree);
						y = new object[1];
					}
					return true;
				}			
			}
			return false;
		}

		/// <summary>
		/// Selects operations
		/// </summary>
		/// <param name="selector">Selector</param>
		/// <param name="list">Result list</param>
		private void selectOperations(IObjectSelector selector, ArrayList list)
		{
			object o = selector.Select(operation);
			if (o != null)
			{
				if (!list.Contains(o))
				{
					list.Add(o);
				}
			}
			for (int i = 0; i < Count; i++)
			{
				this[i].selectOperations(selector, list);
			}
		}
	}


}