using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;

using miew.Enumerable;
using miew.Memory;

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/// <summary>
/// IHotValue(T) : a push-forward computation mesh
/// </summary>
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

namespace miew.Mesh
{
	using Math = System.Math;
	using String = System.String;

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// 
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public interface IHotValueListener<T>
	{
		void Recalculate();

		/// <summary>
		/// This convenience method should be implemented like so:
		/// public void ListenTo(IHotValue(T) bv) { bv.AddListener(this); }
		/// </summary>
		void ListenTo(IHotValue<T> bv);

		/// <summary>
		/// This convenience method should be implemented like so:
		/// public void StopListeningTo(IHotValue<T> bv) { bv.RemoveListener(this); }
		/// </summary>
		void StopListeningTo(IHotValue<T> bv);
	};

	public interface IHotValue<T>
	{
		void AddListener(IHotValueListener<T> bv);
		void RemoveListener(IHotValueListener<T> bv);
		T Value { get; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// An IHotValue(T) instance which supports a value of T that never changes.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class HotValueConst<T> : IHotValue<T>
	{
		public static HotValueConst<T> Default = new HotValueConst<T>(default(T));
#if DEBUG
		readonly
#endif
 T val;

		public HotValueConst(T value) { this.val = value; }

		public T Value { get { return val; } }

		/// Never have to notify, so there is no point in keeping track of listeners
		public void AddListener(IHotValueListener<T> bv) { }
		public void RemoveListener(IHotValueListener<T> bv) { }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Abstract implementation helper for an IHotValue(T) which needs to notify subscribers of changes to its
	/// value because that value is subject to change (i.e. perhaps because the IHotValue(T) is itself a subscriber 
	/// to other HotValues although this is not a specific requirement here)
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class HotValueNotifier<T> : IHotValue<T>
		where T : IEquatable<T>
	{
		List<WeakReference<IHotValueListener<T>>> listeners = null;

		public void AddListener(IHotValueListener<T> hv)
		{
			if (hv == null)
				return;
			listeners = listeners ?? new List<WeakReference<IHotValueListener<T>>>();
			listeners.Add(new WeakReference<IHotValueListener<T>>(hv));
		}
		public void RemoveListener(IHotValueListener<T> hv)
		{
			if (listeners == null || hv == null)
				return;
			for (int i = 0; i < listeners.Count; i++)
			{
				if (hv == listeners[i].Target)
				{
					listeners.RemoveAt(i);
					return;
				}
			}
		}
		public void Notify()
		{
			if (listeners == null)
				return;
			for (int i = 0; i < listeners.Count; )
			{
				IHotValueListener<T> bv = listeners[i].Target;
				if (bv == null)
					listeners.RemoveAt(i);
				else
				{
					bv.Recalculate();
					i++;
				}
			}
		}

		public abstract T Value { get; }
	};


	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Implementation of an IHotValue(T) that can (only) be explicitly changed. Such manual changes trigger notifications 
	/// to this object's subscribers.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class HotValueStatic<T> : HotValueNotifier<T>
		where T : IEquatable<T>
	{
		T val;

		public HotValueStatic(T initial_value)
		{
			this.val = initial_value;
		}

		public override T Value { get { return val; } }

		public void SetValue(T value)
		{
			if (!value.Equals(this.val))
			{
				this.val = value;
				Notify();
			}
		}
	}

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Implementation of an IHotValue(T) repeater which allows the same notification targets to be maintained when the source 
	/// IHotValueTarget(T) is replaced. To change the source, call the 'ChangeSource' method. As an optimization, the last
	/// reported value of the source is cached and if the value of the new source is equal the previous value, notifications 
	/// are not sent.
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public class HotValueDynamic<T> : HotValueNotifier<T>, IHotValueListener<T>
		where T : IEquatable<T>
	{
		public HotValueDynamic(IHotValue<T> source)
		{
			this.src = source;
			this.val = src == null ? default(T) : src.Value;
			ListenTo(src);
		}

		IHotValue<T> src;
		T val;

		public override T Value { get { return src.Value; } }

		public void Recalculate()
		{
			T new_val = src == null ? default(T) : src.Value;
			if (!new_val.Equals(val))
			{
				val = new_val;
				Notify();
			}
		}

		public void ListenTo(IHotValue<T> hv) { hv.AddListener(this); }

		public void StopListeningTo(IHotValue<T> hv) { hv.RemoveListener(this); }

		public IHotValue<T> Source
		{
			get { return src; }
			set
			{
				StopListeningTo(src);
				src = value;
				ListenTo(src);
				Recalculate();
			}
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Abstract implementation helper for a HotValueCalc(S,T) instance:
	/// (a.) maintains a single cached value of type 'T' which is automatically computed based on zero or more IHotValue(S) 
	/// input values, and
	/// (b.) itself implements IHotValue(T) allowing it to be used in an heterotypical automatic calculation mesh.
	/// Derived classes must override the 'Calculate' function to implement their cutomized calculation.
	/// </summary>
	/// <remarks>
	/// Changes to any of the input values automatically trigger recalculation and notification of this object's subscribers. 
	/// The number of IHotValue(S) input sources is set upon construction and cannot be modified, but these arguments can be 
	/// replaced by calling 'ChangeArg' 
	/// </remarks>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class HotValueCalc<S, T> : HotValueNotifier<T>, IHotValueListener<S>
		where T : IEquatable<T>
	{
		T val;
		readonly IHotValue<S>[] args;
		static bool f_double;

		static HotValueCalc()
		{
			f_double = default(T).GetType() == typeof(Double);
		}

		public HotValueCalc(params IHotValue<S>[] args)
			: this(true, args)
		{
		}

		public HotValueCalc(bool f_calc, params IHotValue<S>[] args)
		{
			this.args = args;
			foreach (IHotValue<S> bv in args)
				ListenTo(bv);

			/// calculate and store the initial value, bypassing the check for vacuous value setting.
			/// also skip broadcasting since there can be no subscribers initially
			if (f_calc)
				this.val = Calculate();
		}

		public void ListenTo(IHotValue<S> bv) { bv.AddListener(this); }

		public void StopListeningTo(IHotValue<S> bv) { bv.RemoveListener(this); }

		public override T Value { get { return val; } }

		public void Recalculate()
		{
			T value = Calculate();
			//Debug.WriteLine(value);
			//Debug.WriteLine();
			//	if (new StackTrace().FrameCount > 200)
			//	Debugger.Break();
			//Debug.WriteLine(new StackTrace().FrameCount);

			if (f_double)
			{
				if (Math.Abs(Convert.ToDouble(this.val) - Convert.ToDouble(value)) < .00001)
					return;
			}

			if (!value.Equals(this.val))
			{
				this.val = value;
				Notify();
			}
		}

		protected IHotValue<S>[] Args { get { return args; } }
		protected S Arg0 { get { return args[0] == null ? default(S) : args[0].Value; } }
		protected S Arg1 { get { return args[1] == null ? default(S) : args[1].Value; } }
		protected S Arg2 { get { return args[2] == null ? default(S) : args[2].Value; } }
		protected S Arg3 { get { return args[3] == null ? default(S) : args[3].Value; } }
		protected S Arg4 { get { return args[4] == null ? default(S) : args[4].Value; } }
		protected S Arg5 { get { return args[5] == null ? default(S) : args[5].Value; } }

		public void ChangeArg(IHotValue<S> old_arg, IHotValue<S> new_arg)
		{
			if (old_arg == null || new_arg == null)
				throw new ArgumentException("Cannot be null.");

			int ix = System.Array.IndexOf<IHotValue<S>>(args, old_arg);
			if (ix == -1)
				throw new ArgumentException("The argument to replace was not found.");

			if (old_arg == new_arg)
				return;
			do
			{
				StopListeningTo(old_arg);
				args[ix] = new_arg;
				ListenTo(new_arg);
			}
			while ((ix = System.Array.IndexOf<IHotValue<S>>(args, old_arg, ix)) != -1);
			Recalculate();
		}

		protected abstract T Calculate();
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// Abstract implementation helper for calculated IHotValue(T) values where the input type is the same as the type
	/// of the calculation output
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	public abstract class HotValueCalc<T> : HotValueCalc<T, T>
		where T : IEquatable<T>
	{
		public HotValueCalc(params IHotValue<T>[] args)
			: base(true, args)
		{
		}

		public HotValueCalc(bool f_calc, params IHotValue<T>[] args)
			: base(f_calc, args)
		{
		}
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// IHvDouble - supporting a mesh of double-precision calculations
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public interface IHvDouble : IHotValue<Double> { }


	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdConst : HotValueConst<Double>, IHvDouble
	{
		public HvdConst(Double value)
			: base(value)
		{ }

		public override string ToString() { return String.Format("{0:X8} Constant = {1}", GetHashCode(), Value); }

		public static HvdConst NegativeInfinity = new HvdConst(double.NegativeInfinity);
		public static HvdConst PositiveInfinity = new HvdConst(double.PositiveInfinity);
		public static HvdConst Zero = new HvdConst(0);
		public static HvdConst NaN = new HvdConst(double.NaN);
	};

	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdStatic : HotValueStatic<Double>, IHvDouble
	{
		public HvdStatic(Double value)
			: base(value)
		{ }

		public override string ToString() { return String.Format("{0:X8} Static = {1}", GetHashCode(), Value); }
	};

	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdDynamic : HotValueDynamic<Double>, IHvDouble
	{
		public HvdDynamic(IHvDouble bv_arg)
			: base(bv_arg)
		{ }

		public override string ToString() { return String.Format("{0:X8} Dynamic = {1}", GetHashCode(), Value); }
	};


	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdAdd : HotValueCalc<Double>, IHvDouble
	{
		public HvdAdd(params IHvDouble[] args)
			: base(args)
		{ }

		protected override Double Calculate() { return Args.Sum(x => x.Value); }
		public override string ToString() { return String.Format("{0:X8} {1} + {2} = {3}", GetHashCode(), Arg0, Arg1, Value); }
	};

	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdAddConst : HotValueCalc<Double>, IHvDouble
	{
		public HvdAddConst(Double dVal, params IHvDouble[] args)
			: base(false, args)
		{
			this.dVal = dVal;
			Recalculate();
		}

		Double dVal;

		protected override Double Calculate() { return dVal + Args.Sum(x => x.Value); }
		public override string ToString()
		{
			return String.Format("{0:X8} /{1}/ + {2} = {3}", GetHashCode(), dVal, Args.Select(x => x.Value.ToString()).StringJoin(" + "), Value);
		}
	};

	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdMax : HotValueCalc<Double>, IHvDouble
	{
		public HvdMax(IHvDouble bv0, IHvDouble bv1)
			: base(bv0, bv1)
		{ }

		protected override Double Calculate() { return Math.Max(Arg0, Arg1); }
		public override string ToString() { return String.Format("{0:X8} Max({1},{2}) = {3}", GetHashCode(), Arg0, Arg1, Value); }
	};

	public class HvdMultiMax : HotValueCalc<Double>, IHvDouble
	{
		public HvdMultiMax(params IHvDouble[] args)
			: base(args)
		{ }

		protected override Double Calculate() { return Args.Max(x => x.Value); }
	};

	[DebuggerDisplay("{ToString(),nq}")]
	public class HvdMin : HotValueCalc<Double>, IHvDouble
	{
		public HvdMin(IHvDouble bv0, IHvDouble bv1)
			: base(bv0, bv1)
		{ }

		protected override Double Calculate() { return Math.Min(Arg0, Arg1); }
		public override string ToString() { return String.Format("{0:X8} Min({1},{2}) = {3}", GetHashCode(), Arg0, Arg1, Value); }
	};

	public class HvdMultiMin : HotValueCalc<Double>, IHvDouble
	{
		public HvdMultiMin(params IHvDouble[] args)
			: base(args)
		{ }

		protected override Double Calculate() { return Args.Min(x => x.Value); }
	};

	public class HvdAverage : HotValueCalc<Double>, IHvDouble
	{
		public HvdAverage(params IHvDouble[] args)
			: base(args)
		{ }

		protected override Double Calculate() { return Args.Average(x => x.Value); }
	};

	public class HvdAddHalf : HotValueCalc<Double>, IHvDouble
	{
		public HvdAddHalf(IHvDouble bv0, IHvDouble bv1)
			: base(bv0, bv1)
		{ }

		protected override Double Calculate() { return Arg0 + (Arg1 / 2); }
	};

	public class HvdSubHalf : HotValueCalc<Double>, IHvDouble
	{
		public HvdSubHalf(IHvDouble bv0, IHvDouble bv1)
			: base(bv0, bv1)
		{ }

		protected override Double Calculate() { return Arg0 - (Arg1 / 2); }
	};

	public class HvdCenterNode : HotValueCalc<Double>, IHvDouble
	{
		public HvdCenterNode(IHvDouble bv0, IHvDouble bv1, IHvDouble width)
			: base(bv0, bv1, width)
		{ }

		protected override Double Calculate() { return Math.Abs(Arg1 - Arg0) / 2 - Arg2 / 2; }
	};

	public class HvdHalfSpan : HotValueCalc<Double>, IHvDouble
	{
		public HvdHalfSpan(IHvDouble bv0, IHvDouble bv1)
			: base(bv0, bv1)
		{ }

		protected override Double Calculate() { return Math.Abs(Arg1 - Arg0) / 2; }
	};

	public class HvdAddHalfSpan : HotValueCalc<Double>, IHvDouble
	{
		public HvdAddHalfSpan(IHvDouble bv0, IHvDouble bv1, IHvDouble bv2)
			: base(bv0, bv1, bv2)
		{ }

		protected override Double Calculate() { return Arg0 + (Arg2 - Arg1) / 2; }
	};

	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
	/// <summary>
	/// IHvInterval - supporting a mesh of calculations involving intervals
	/// </summary>
	///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

	public interface IHvInterval : IHotValue<IHvInterval>
	{
		IHvDouble HvdX1 { get; }
		IHvDouble HvdX2 { get; }
		IHvDouble HvdLength { get; }
		IHvDouble HvdCenter { get; }

		Double X1 { get; }
		Double X2 { get; }
		Double Length { get; }
		Double Center { get; }
	};

	public class HvIntervalBuilder : IHotValueListener<Double>, IHvInterval, IHotValueListener<IHvInterval>
	{
		public HvIntervalBuilder()
		{
			x1 = new HvdDynamic(null);
			x2 = new HvdDynamic(null);

			x1.AddListener(this);
			x2.AddListener(this);
		}

		List<WeakReference<IHotValueListener<IHvInterval>>> listeners = null;
		HvdDynamic x1 = null;
		HvdDynamic x2 = null;
		HvdStatic length = null;
		HvdStatic center = null;

		public IHvDouble HvdX1
		{
			get { return x1; }
		}
		public IHvDouble HvdX2
		{
			get { return x2; }
		}
		public IHvDouble HvdLength
		{
			get { return length = length ?? new HvdStatic(Length); }
		}
		public IHvDouble HvdCenter
		{
			get { return center = center ?? new HvdStatic(Center); }
		}

		public Double X1 { get { return x1.Value; } }
		public Double X2 { get { return x2.Value; } }
		public Double Length { get { return x2.Value - x1.Value; } }
		public Double Center { get { return x1.Value + (x2.Value - x1.Value) / 2.0; } }


		void IHotValueListener<Double>.Recalculate()
		{
			Notify();
		}

		void IHotValueListener<IHvInterval>.Recalculate()
		{
			Notify();
		}

		void IHotValueListener<Double>.ListenTo(IHotValue<Double> bv) { bv.AddListener(this); }
		void IHotValueListener<IHvInterval>.ListenTo(IHotValue<IHvInterval> bv) { bv.AddListener(this); }
		void IHotValueListener<Double>.StopListeningTo(IHotValue<Double> bv) { bv.RemoveListener(this); }
		void IHotValueListener<IHvInterval>.StopListeningTo(IHotValue<IHvInterval> bv) { bv.AddListener(this); }

		public void AddListener(IHotValueListener<IHvInterval> hv)
		{
			if (hv == null)
				return;
			listeners = listeners ?? new List<WeakReference<IHotValueListener<IHvInterval>>>();
			listeners.Add(new WeakReference<IHotValueListener<IHvInterval>>(hv));
		}
		public void RemoveListener(IHotValueListener<IHvInterval> hv)
		{
			if (listeners == null || hv == null)
				return;
			for (int i = 0; i < listeners.Count; i++)
			{
				if (hv == listeners[i].Target)
				{
					listeners.RemoveAt(i);
					return;
				}
			}
		}

		public void Notify()
		{
			if (listeners == null)
				return;
			for (int i = 0; i < listeners.Count; )
			{
				IHotValueListener<IHvInterval> bv = listeners[i].Target;
				if (bv == null)
					listeners.RemoveAt(i);
				else
				{
					bv.Recalculate();
					i++;
				}
			}
		}

		public IHvInterval Value
		{
			get { return this; }
		}
	};
}