using System;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Collections;
using System.Collections.Generic;
using System.Xml;


using CategoryTheory;
using DiagramUI;
using FormulaEditor;
using GeneralLinearMethod;
using AnalyticPolynom;

namespace DataPerformer
{
	[Serializable()]
	public class Series : ISerializable, ICategoryObject, 
		IArgumentSelection, IMeasure, IMeasurements, IStructuredSelectionCollection,
		IUnary, IObjectOperation, IOperationAcceptor, IComments
	{
		
		#region Fields

		private static readonly Double a = 0;
		private readonly string[] var = {"x"};
		private readonly double eps = 1e-9;
		static public readonly string[] HasEqualStepString = new string[] {"This series has equal steps",
																			  "This series has no equal steps"};
		protected List<double[]> points = new List<double[]>();
		protected string x = "";
		protected string y = "";
		public double[] parameter = new double[2];
		private object obj;
		private double step = 0;
		protected int[] pointStart = new int[2];
		protected int[] pointFinish = new int[2];
		private ArrayList comments = new ArrayList();
		private object type = new object[] {a, a};
		private object[] meaX = new object[2];
		private object[] meaY = new object[2];
		private DataPerformer.MeasureParameter[] measureParameter = new MeasureParameter[2];
		private IMeasure measureY;
		private IStructuredSelection[] selections;

		#endregion

		#region Constructors

		public Series()
		{
			initialize();
			selections = new IStructuredSelection[]{new XSelection(this), this};
		}

		/// <summary>
		/// Deserialization constructor
		/// </summary>
		/// <param name="info">Serialization info</param>
		/// <param name="context">Streaming context</param>
		protected Series(SerializationInfo info, StreamingContext context)
		{
            try
            {
                points = info.GetValue("Points", typeof(List<double[]>)) as List<double[]>;
            }
            catch (Exception)
            {
                ArrayList p = info.GetValue("Points", typeof(ArrayList)) as ArrayList;
                for (int i = 0; i < p.Count; i++)
                {
                    double[] d = p[i] as double[];
                    points.Add(d);
                }
            }
            try
            {
                comments = (ArrayList)info.GetValue("Comments", typeof(ArrayList));
            }
            catch (Exception)
            {
            }

            Post();
		}

		#endregion

		#region ISerializable Members

		/// <summary>
		/// ISerializable interface implementation
		/// </summary>
		/// <param name="info">Serialization info</param>
		/// <param name="context">Streaming context</param>
		public virtual void GetObjectData(SerializationInfo info, StreamingContext context)
		{
			info.AddValue("Points", points);
			info.AddValue("Comments", comments);
		}

		#endregion

		#region IStructuredSelectionCollection Members

		int IStructuredSelectionCollection.Count
		{
			get
			{
				return 2;
			}
		}

		IStructuredSelection IStructuredSelectionCollection.this[int i]
		{
			get
			{
				return selections[i];
			}
		}

		#endregion

		#region IMeasurements Members

		IMeasure DataPerformer.IMeasurements.this[int n]
		{
			get
			{
				if (n == 0)
				{
					return this;
				}
				return measureY;
			}
		}

		public void UpdateMeasurements()
		{
		}

		public string SourceName
		{
			get
			{
				IObjectLabel l = obj as IObjectLabel;
				return l.Name;
			}
		}

		public bool IsUpdated
		{
			get
			{
				return true;
			}
			set
			{
			}
		}

		int IMeasurements.Count
		{
			get
			{
				return 2;
			}
		}


		#endregion

		#region IMeasure Members

		public DataPerformer.MeasureParameter Parameter
		{
			get
			{
				return measureParameter[0];
			}
		}

		public DataPerformer.MeasureParameter Derivation
		{
			get
			{
				return null;
			}
		}

		public string Name
		{
			get
			{
				return "X";
			}
		}



		public double Factor
		{
			get
			{
				return 1;
			}
		}

		public object Type
		{
			get
			{
				initMea();
				return type;
			}
		}

		#endregion

		#region IXmlCreator Members
		
		/// <summary>
		/// Creates correspond xml
		/// </summary>
		/// <param name="doc">document to create element</param>
		/// <returns>The created element</returns>
		public XmlElement CreateXml(XmlDocument doc)
		{
			XmlElement el = doc.CreateElement("Plot");
			XmlAttribute nx = doc.CreateAttribute("X");
			nx.Value = X;
			el.Attributes.Append(nx);
			XmlAttribute ny = doc.CreateAttribute("Y");
			ny.Value = Y;
			el.Attributes.Append(ny);
			foreach(double[] p in points)
			{
				XmlElement ep = doc.CreateElement("PlotPoint");
				XmlAttribute ax = doc.CreateAttribute("PlotX");
				ax.Value = "" + p[0];
				ep.Attributes.Append(ax);
				XmlAttribute ay = doc.CreateAttribute("PlotY");
				ay.Value = "" + p[1];
				ep.Attributes.Append(ay);
				el.AppendChild(ep);
			}
			return el;
		}

		#endregion

		#region ICategoryObject members
		/// <summary>
		/// The category of this object
		/// </summary>
		public ICategory Category
		{
			get
			{
				return null;
			}
		}

		/// <summary>
		/// The identical arrow of this object
		/// </summary>
		public ICategoryArrow Id
		{
			get
			{
				return null;
			}
		}

		#endregion

		#region IAssociatedObject members

		/// <summary>
		/// Associated object
		/// </summary>
		public object Object
		{
			get
			{
				return obj;
			}
			set
			{
				obj = value;
			}
		}

		#endregion

		#region IObjectOperation Members

		object IObjectOperation.this[object[] x]
		{
			get
			{
				double a = (double) x[0];
				return GetValue(a);
			}
		}

		public object ReturnType
		{
			get
			{
				return a;
			}
		}

		public int Arity
		{
			get
			{
				return 1;
			}
		}

		public bool IsPowered
		{
			get
			{
				return true;
			}
		}

		#endregion

		#region IOperationAcceptor Members

		public IObjectOperation Accept(object type)
		{
			if (type.Equals(a))
			{
				return this;
			}
			return null;
		}

		#endregion

		#region IUnary Members

		/// <summary>
		/// Gets value of function
		/// </summary>
		/// <param name="x">Argument</param>
		/// <returns></returns>
		public double GetValue(double x)
		{
			return this[x][0];
		}

		/// <summary>
		/// Gets derivation of function
		/// </summary>
		/// <param name="x">Argument</param>
		/// <returns></returns>
		public double GetDerivation(double x)
		{
			return this[x][1];
		}

		#endregion


        public List<double[]> Points
        {
            get { return points; }
        }


        protected void Post()
        {
            measureParameter[0] = new DataPerformer.MeasureParameter(parX);
            measureParameter[1] = new DataPerformer.MeasureParameter(parY);
            measureY = YMeasure.getMeasure(this);
            selections = new IStructuredSelection[] { new XSelection(this), this };
            checkEqualStep();

        }


		/// <summary>
		/// Calculates synchronized selection
		/// </summary>
		/// <param name="selection">The etalon selection</param>
		/// <returns>Synchronized selection</returns>
		public IArgumentSelection SynchronizedSelection(IArgumentSelection selection)
		{
			if (!(selection is Series))
			{
				throw new Exception("Incompatible selections");
			}
			Series etalon = selection as Series;
			Series s = new Series();
			for (int i = 0; i < etalon.PointsCount; i++)
			{
				double x = etalon[i, 0];
				double a = this[0, 0];
				if (x < a)
				{
					s.AddXY(x, this[0, 1]);
					continue;
				}
				double b = this[PointsCount - 1, 0];
				if (x > b)
				{
					s.AddXY(x, this[PointsCount - 1, 1]);
					continue;
				}
				s.AddXY(x, this[x][1]);
			}
			return s;
		}

		public string HasEqualSteps
		{
			get
			{
				if (step == 0)
				{
					return PureDesktop.GetResourceString(HasEqualStepString[1]);
				}
				return PureDesktop.GetResourceString(HasEqualStepString[0]);
			}
		}


		#region IStructuredSelection Members

		/// <summary>
		/// Dimension of data
		/// </summary>
		public int DataDimension
		{
			get
			{
				return points.Count;
			}
		}

		/// <summary>
		/// Access to n - th element
		/// </summary>
		double? IStructuredSelection.this[int n]
		{
			get
			{
				return this[n, 1];
			}
		}

		/// <summary>
		/// Weight of n - th element
		/// </summary>
		/// <param name="n">Element number</param>
		/// <returns>The weight</returns>
		public double GetWeight(int n)
		{
			return 1;
		}

		/// <summary>
		/// Aprior weight of n - th element
		/// </summary>
		/// <param name="n">Element number</param>
		/// <returns>The weight</returns>
		public double GetApriorWeight(int n)
		{
			return 1;
		}

		/// <summary>
		/// Tolerance of it - th element
		/// </summary>
		/// <param name="n">Element number</param>
		/// <returns>Tolerance</returns>
		public int GetTolerance(int n)
		{
			return 1;
		}

		/// <summary>
		/// Sets tolerance of n - th element
		/// </summary>
		/// <param name="n">Element number</param>
		/// <param name="tolerance">Tolerance to set</param>
		public void SetTolerance(int n, int tolerance)
		{
		}

		/// <summary>
		/// The "is fixed amount" sign
		/// </summary>
		public bool HasFixedAmount
		{
			get
			{
				return true;
			}
		}

		string IStructuredSelection.Name
		{
			get
			{
				return "Y";
			}
		}



		/// <summary>
		/// Count of points
		/// </summary>
		public int PointsCount
		{
			get
			{
				return points.Count;
			}
		}
		
		/// <summary>
		/// Free variables
		/// </summary>
		public string[] Variables
		{
			get
			{
				return var;
			}
		}
		/// <summary>
		/// Dimension of output vector
		/// </summary>
		public int VectorDimension
		{
			get
			{
				return 1;
			}
		}

		#endregion


		/// <summary>
		/// Gets value of variable
		/// </summary>
		double IArgumentSelection.this[int i, string str]
		{
			get
			{
				return this[i, 0];
			}
		}

		#region Specific Members

		/// <summary>
		/// Copies from another series
		/// </summary>
		/// <param name="s">Pattern series</param>
		public void CopyFrom(Series s)
		{
			points.Clear();
			for (int i = 0; i < s.Count; i++)
			{
				AddXY(s[i, 0], s[i, 1]);
			}
			step = 0;
			checkEqualStep();
		}

		/// <summary>
		/// Gets or sets comments
		/// </summary>
		public ArrayList Comments
		{
			get
			{
				return comments;
			}
			set
			{
				if (value != null)
				{
					comments.Clear();
					foreach (ICloneable c in value)
						comments.Add(c.Clone());
				}
			}
		}


		/// <summary>
		/// Adds point
		/// </summary>
		/// <param name="x">x - coordinate</param>
		/// <param name="y">y - coordinate</param>
		public void AddXY(double x, double y)
		{
			points.Add(new double[]{x, y});
		}

		/// <summary>
		/// Access to i - th point (j = 0 access to x - coordinate, j = 1 access to y coordinate)
		/// </summary>
		public double this[int i, int j]
		{
			get
			{ 
				double[] x = points[i] as double[];
				return x[j];
			}
		}

		/// <summary>
		/// Clears itself
		/// </summary>
		public virtual void Clear()
		{
			points.Clear();
		}

		/// <summary>
		/// Count of points
		/// </summary>
		public int Count
		{
			get
			{
				return points.Count;
			}
		}

		/// <summary>
		/// Size of this series
		/// </summary>
		public double[,] Size
		{
			get
			{
				double[,] size = new double[2, 2];
				for (int i = 0; i < points.Count; i++)
				{
					double[] x = (double[])points[i];
					if (i == 0)
					{
						size[0, 0] = x[0];
						size[0, 1] = x[1];
						size[1, 0] = x[0];
						size[1, 1] = x[1];
						continue;
					}
					for (int j = 0; j < 2; j++)
					{
						if (x[j] < size[0, j])
						{
							size[0, j] = x[j];
						}
						if (x[j] > size[1, j])
						{
							size[1, j] = x[j];
						}
					}
				}
				return size;
			}
		}

		/// <summary>
		/// Creates correspond xml
		/// </summary>
		/// <param name="doc">document to create element</param>
		/// <returns>The created element</returns>
		public XmlElement CreateXmlPoint(int n, XmlDocument doc)
		{
			XmlElement el = doc.CreateElement("CommonPlotPoint");
			XmlAttribute nx = doc.CreateAttribute("NX");
			nx.Value = X;
			el.Attributes.Append(nx);
			XmlAttribute ny = doc.CreateAttribute("NY");
			ny.Value = Y;
			el.Attributes.Append(ny);
			double[] p = points[n] as double[];
			XmlAttribute x = doc.CreateAttribute("X");
			x.Value = p[0] + "";
			el.Attributes.Append(x);
			XmlAttribute y = doc.CreateAttribute("Y");
			y.Value = p[1] + "";
			el.Attributes.Append(y);
			return el;
		}






		/// <summary>
		/// Access to value of function and its derivation
		/// </summary>
		public double[] this[double x]
		{
			get
			{
				if (this[0, 0] > x)
				{
					//throw new Exception("Argument too small");
					parameter[0] = this[0, 1];
					parameter[1] = 0;
					return parameter;
				}
				if (this[Count - 1, 0] < x)
				{
					//throw new Exception("Argument too large");
					parameter[0] = this[Count - 1, 1];
					parameter[1] = 0;
					return parameter;
				}
				if (step != 0)
				{
					int i = (int)(Math.Floor((x - this[0, 0]) / step));
					if (i == Count - 1)
					{
						--i;
					}
					double x1 = this[i, 0];
					double x2 = this[i + 1, 0];
					double y1 = this[i, 1];
					double y2 = this[i + 1, 1];
					parameter[1] = (y2 - y1) / (x2 - x1);
					parameter[0] = y1 + parameter[1] * (x - x1);
					return parameter;
				}
				for (int i = 1; i < Count; i++)
				{
					double x2 = this[i, 0];
					if (x2 > x)
					{
						double x1 = this[i - 1, 0];
						double y1 = this[i - 1, 1];
						double y2 = this[i, 1];
						parameter[1] = (y2 - y1) / (x2 - x1);
						parameter[0] = y1 + parameter[1] * (x - x1);
						break;
					}
				}
				return parameter;
			}
		}




		public string X
		{
			get
			{
				return x;
			}
			set
			{
				x = value;
			}
		}

		public string Y
		{
			get
			{
				return y;
			}
			set
			{
				y = value;
			}
		}

		private object parX()
		{
			initMea();
			return meaX;
		}

		private object parY()
		{
			initMea();
			return meaY;
		}

		private void initMea()
		{
			if (meaX.Length == points.Count)
			{
				return;
			}
			meaX = new object[points.Count];
			meaY = new object[meaX.Length];
			type = new object[meaX.Length];
			for (int i = 0; i < meaX.Length; i++)
			{
				meaX[i] = this[i, 0];
				meaY[i] = this[i, 1];
			}
            Double a = 0;
            type = new ArrayReturnType(a, new int[] { points.Count });

		}



		/// <summary>
		/// Checks whether series has equal step
		/// </summary>
		private void checkEqualStep()
		{
			if (points.Count < 2)
			{
				return;
			}
			double s =  0;
			double t = 0;
			for (int i = 0; i < points.Count; i++)
			{
				double[] p = points[i] as double[];
				if (i == 1)
				{
					s = p[0] - t;
				}
				if (i > 1)
				{
					if (Math.Abs(s - (p[0] - t)) > (eps * Math.Abs(s)))
					{
						return;
					}
				}
				t = p[0];
			}
			step = s;
		}

		/// <summary>
		/// Initialization
		/// </summary>
		private void initialize()
		{
			measureParameter[0] = new DataPerformer.MeasureParameter(parX);
			measureParameter[1] = new DataPerformer.MeasureParameter(parY);
			measureY = YMeasure.getMeasure(this);
			points = new List<double[]>();
			pointStart = new int[2];
			pointFinish = new int[2];
		}

		#endregion

		class YMeasure : IMeasure
		{
			private Series s;

			public static IMeasure getMeasure(Series s)
			{
				return new YMeasure(s);
			}

			YMeasure(Series s)
			{
				this.s = s;
			}
			#region IMeasure Members

			public DataPerformer.MeasureParameter Parameter
			{
				get
				{
					return s.measureParameter[1];
				}
			}

			public DataPerformer.MeasureParameter Derivation
			{
				get
				{
					return null;
				}
			}

			public string Name
			{
				get
				{
					return "Y";
				}
			}

			public double Factor
			{
				get
				{
					return 1;
				}
			}

			public object Type
			{
				get
				{
					return s.Type;
				}
			}

			#endregion
		}

		class XSelection : IStructuredSelection
		{
			private Series s;

			#region IStructuredSelection Members

			public XSelection(Series s)
			{
				this.s = s;
			}

			/// <summary>
			/// Dimension of data
			/// </summary>
			public int DataDimension
			{
				get
				{
					return s.points.Count;
				}
			}

			/// <summary>
			/// Access to n - th element
			/// </summary>
			double? IStructuredSelection.this[int n]
			{
				get
				{
					return s[n, 0];
				}
			}

			/// <summary>
			/// Weight of n - th element
			/// </summary>
			/// <param name="n">Element number</param>
			/// <returns>The weight</returns>
			public double GetWeight(int n)
			{
				return 1;
			}

			/// <summary>
			/// Aprior weight of n - th element
			/// </summary>
			/// <param name="n">Element number</param>
			/// <returns>The weight</returns>
			public double GetApriorWeight(int n)
			{
				return 1;
			}

			/// <summary>
			/// Tolerance of it - th element
			/// </summary>
			/// <param name="n">Element number</param>
			/// <returns>Tolerance</returns>
			public int GetTolerance(int n)
			{
				return 1;
			}

			/// <summary>
			/// Sets tolerance of n - th element
			/// </summary>
			/// <param name="n">Element number</param>
			/// <param name="tolerance">Tolerance to set</param>
			public void SetTolerance(int n, int tolerance)
			{
			}

			/// <summary>
			/// The "is fixed amount" sign
			/// </summary>
			public bool HasFixedAmount
			{
				get
				{
					return true;
				}
			}

			/// <summary>
			/// Count of points
			/// </summary>
			public int PointsCount
			{
				get
				{
					return s.points.Count;
				}
			}
		
			/// <summary>
			/// Free variables
			/// </summary>
			public string[] Variables
			{
				get
				{
					return s.var;
				}
			}
			/// <summary>
			/// Dimension of output vector
			/// </summary>
			public int VectorDimension
			{
				get
				{
					return 1;
				}
			}

			/// <summary>
			/// Gets value of variable
			/// </summary>
			/*double IArgumentSelection.this[int i, string str]
			{
				get
				{
					return this[i, 0];
				}
			}*/




			public string Name
			{
				get
				{
					return "X";
				}
			}

			#endregion
		}
	}

}
