using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Globalization;
using Microsoft.Research.DynamicDataDisplay.Common.Auxiliary;

namespace Microsoft.Research.DynamicDataDisplay.Charts.NewAxis
{
	public class DateTimeTicksProvider : DateTimeTicksProviderBase
	{
		private static readonly Dictionary<DifferenceIn, ITicksProvider<DateTime>> providers =
			new Dictionary<DifferenceIn, ITicksProvider<DateTime>>();

		static DateTimeTicksProvider()
		{
			providers.Add(DifferenceIn.Year, new YearProvider());
			providers.Add(DifferenceIn.Month, new MonthProvider());
			providers.Add(DifferenceIn.Day, new DayProvider());
			providers.Add(DifferenceIn.Hour, new HourProvider());
			providers.Add(DifferenceIn.Minute, new MinuteProvider());
			providers.Add(DifferenceIn.Second, new SecondProvider());
		}

		private DifferenceIn diff;
		/// <summary>
		/// Gets the ticks.
		/// </summary>
		/// <param name="range">The range.</param>
		/// <param name="ticksCount">The ticks count.</param>
		/// <returns></returns>
		public override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
		{
			Verify.Is(ticksCount > 0);

			DateTime start = range.Min;
			DateTime end = range.Max;
			TimeSpan length = end - start;

			diff = GetDifference(length);

			TicksInfo<DateTime> res = new TicksInfo<DateTime> { Info = diff };
			if (providers.ContainsKey(diff))
			{
				ITicksInfo<DateTime> result = providers[diff].GetTicks(range, ticksCount);
				DateTime[] mayorTicks = result.Ticks;

				res.Ticks = mayorTicks;

				DifferenceIn lowerDiff = DifferenceIn.Year;
				// todo разобраться с minor ticks
				bool lowerDiffExists = TryGetLowerDiff(diff, out lowerDiff);
				if (lowerDiffExists && providers.ContainsKey(lowerDiff))
				{
					var minorTicks = result.Ticks.GetPairs().Select(r => ((IMinorTicksProvider<DateTime>)providers[lowerDiff]).CreateTicks(r)).
						SelectMany(m => m).ToArray();

					res.MinorTicks = minorTicks;
				}
				return res;
			}


			DateTime newStart = RoundDown(start, diff);
			DateTime newEnd = RoundUp(end, diff);

			DebugVerify.Is(newStart <= start);

			List<DateTime> resultTicks = new List<DateTime>();
			DateTime dt = newStart;
			do
			{
				resultTicks.Add(dt);
				dt = Shift(dt, diff);
			} while (dt <= newEnd);

			while (resultTicks.Count > ticksCount)
			{
				var res2 = resultTicks;
				resultTicks = res2.Where((date, i) => i % 2 == 0).ToList();
			}

			res.Ticks = resultTicks.ToArray();

			return res;
		}

		/// <summary>
		/// Tries the get lower diff.
		/// </summary>
		/// <param name="diff">The diff.</param>
		/// <param name="lowerDiff">The lower diff.</param>
		/// <returns></returns>
		private static bool TryGetLowerDiff(DifferenceIn diff, out DifferenceIn lowerDiff)
		{
			lowerDiff = diff;

			int code = (int)diff;
			bool res = code > 0;
			if (res)
			{
				lowerDiff = (DifferenceIn)(code - 1);
			}
			return res;
		}

		/// <summary>
		/// Decreases the tick count.
		/// </summary>
		/// <param name="tickCount">The tick count.</param>
		/// <returns></returns>
		public override int DecreaseTickCount(int tickCount)
		{
			if (providers.ContainsKey(diff))
				return providers[diff].DecreaseTickCount(tickCount);

			int res = tickCount / 2;
			if (res < 2) res = 2;
			return res;
		}

		/// <summary>
		/// Increases the tick count.
		/// </summary>
		/// <param name="tickCount">The tick count.</param>
		/// <returns></returns>
		public override int IncreaseTickCount(int tickCount)
		{
			DebugVerify.Is(tickCount < 2000);

			if (providers.ContainsKey(diff))
				return providers[diff].IncreaseTickCount(tickCount);

			return tickCount * 2;
		}
	}

	public enum DifferenceIn
	{
		Year = 7,
		Month = 6,
		Day = 5,
		Hour = 4,
		Minute = 3,
		Second = 2,
		Millisecond = 1
	}

	internal static class DateTimeArrayExt
	{
		[Obsolete("Works wrongly", true)]
		internal static DateTime[] Clip(this DateTime[] array, DateTime start, DateTime end)
		{
			if (start > end)
			{
				DateTime temp = start;
				start = end;
				end = temp;
			}

			int startIndex = array.GetIndex(start);
			int endIndex = array.GetIndex(end) + 1;
			DateTime[] res = new DateTime[endIndex - startIndex];
			Array.Copy(array, startIndex, res, 0, res.Length);

			return res;
		}

		internal static int GetIndex(this DateTime[] array, DateTime value)
		{
			for (int i = 0; i < array.Length - 1; i++)
			{
				if (array[i] <= value && value < array[i + 1])
					return i;
			}

			return array.Length - 1;
		}
	}

	internal abstract class DatePeriodTicksProvider : DateTimeTicksProviderBase, IMinorTicksProvider<DateTime>
	{
		protected DatePeriodTicksProvider()
		{
			tickCounts = GetTickCountsCore();
			difference = GetDifferenceCore();
		}

		protected DifferenceIn difference;
		protected abstract DifferenceIn GetDifferenceCore();

		protected abstract int[] GetTickCountsCore();
		protected int[] tickCounts = { };

		public sealed override int DecreaseTickCount(int ticksCount)
		{
			if (ticksCount > tickCounts[0]) return tickCounts[0];

			for (int i = 0; i < tickCounts.Length; i++)
				if (ticksCount > tickCounts[i])
					return tickCounts[i];

			return tickCounts.Last();
		}

		public sealed override int IncreaseTickCount(int ticksCount)
		{
			if (ticksCount >= tickCounts[0]) return tickCounts[0];

			for (int i = tickCounts.Length - 1; i >= 0; i--)
				if (ticksCount < tickCounts[i])
					return tickCounts[i];

			return tickCounts.Last();
		}

		protected abstract int GetSpecificValue(DateTime start, DateTime dt);
		protected abstract DateTime GetStart(DateTime start, int value, int step);
		protected abstract bool IsMinDate(DateTime dt);
		protected abstract DateTime AddStep(DateTime dt, int step);

		public sealed override ITicksInfo<DateTime> GetTicks(Range<DateTime> range, int ticksCount)
		{
			DateTime start = range.Min;
			DateTime end = range.Max;
			TimeSpan length = end - start;

			bool isPositive = length.Ticks > 0;
			DifferenceIn diff = difference;

			DateTime newStart = isPositive ? RoundDown(start, diff) : SafelyRoundUp(start);
			DateTime newEnd = isPositive ? SafelyRoundUp(end) : RoundDown(end, diff);

			RoundingInfo bounds = RoundHelper.CreateRoundedRange(GetSpecificValue(newStart, newStart), GetSpecificValue(newStart, newEnd));

			int delta = (int)(bounds.Max - bounds.Min);
			if (delta == 0)
				return new TicksInfo<DateTime> { Ticks = new DateTime[] { newStart } };

			int step = delta / ticksCount;

			if (step == 0) step = 1;

			DateTime tick = GetStart(newStart, (int)bounds.Min, step);
			bool isMinDateTime = IsMinDate(tick) && step != 1;
			if (isMinDateTime)
				step--;

			List<DateTime> ticks = new List<DateTime>();
			DateTime finishTick = AddStep(range.Max, step);
			while (tick < finishTick)
			{
				ticks.Add(tick);
				tick = AddStep(tick, step);
				if (isMinDateTime)
				{
					isMinDateTime = false;
					step++;
				}
			}

			TicksInfo<DateTime> res = new TicksInfo<DateTime> { Ticks = ticks.ToArray(), Info = diff };
			return res;
		}

		private DateTime SafelyRoundUp(DateTime dt)
		{
			if (AddStep(dt, 1) == DateTime.MaxValue)
				return DateTime.MaxValue;

			return RoundUp(dt, difference);
		}

		#region IMinorTicksProvider<DateTime> Members

		public MinorTickInfo<DateTime>[] CreateTicks(Range<DateTime> range)
		{
			int tickCount = tickCounts[1];
			ITicksInfo<DateTime> ticks = GetTicks(range, tickCount);

			MinorTickInfo<DateTime>[] res = ticks.Ticks.
				Select(dt => new MinorTickInfo<DateTime>(0.5, dt)).ToArray();

			return res;
		}

		#endregion
	}

	internal class YearProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Year;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 20, 10, 5, 4, 2, 1 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return dt.Year;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			int year = start.Year;
			int newYear = (year / step) * step;
			if (newYear == 0) newYear = 1;

			return new DateTime(newYear, 1, 1);
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return dt.Year == DateTime.MinValue.Year;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			if (dt.Year + step > DateTime.MaxValue.Year)
				return DateTime.MaxValue;

			return dt.AddYears(step);
		}
	}

	internal class MonthProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Month;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 12, 6, 4, 3, 2, 1 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return dt.Month + (dt.Year - start.Year) * 12;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			return new DateTime(start.Year, 1, 1);
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return dt.Month == DateTime.MinValue.Month;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			return dt.AddMonths(step);
		}
	}

	internal class DayProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Day;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 30, 15, 10, 5, 2, 1 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return (dt - start).Days;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			return start.Date;
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return dt.Day == 1;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			return dt.AddDays(step);
		}
	}

	internal class HourProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Hour;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 24, 12, 6, 4, 3, 2, 1 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return (dt - start).Hours;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			return start.Date;//.AddHours(start.Hour);
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return false;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			return dt.AddHours(step);
		}
	}

	internal class MinuteProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Minute;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return (dt - start).Minutes;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			return start.Date.AddHours(start.Hour);
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return false;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			return dt.AddMinutes(step);
		}
	}

	internal class SecondProvider : DatePeriodTicksProvider
	{
		protected override DifferenceIn GetDifferenceCore()
		{
			return DifferenceIn.Second;
		}

		protected override int[] GetTickCountsCore()
		{
			return new int[] { 60, 30, 20, 15, 10, 5, 4, 3, 2 };
		}

		protected override int GetSpecificValue(DateTime start, DateTime dt)
		{
			return (dt - start).Seconds;
		}

		protected override DateTime GetStart(DateTime start, int value, int step)
		{
			return start.Date.AddHours(start.Hour).AddMinutes(start.Minute);
		}

		protected override bool IsMinDate(DateTime dt)
		{
			return false;
		}

		protected override DateTime AddStep(DateTime dt, int step)
		{
			return dt.AddSeconds(step);
		}
	}
}
