using System;
using System.Collections.Generic;
using System.Linq;
using LeafWeb.Core.Entities;
using MathNet.Numerics;

namespace LeafWeb.Core.Utility
{
	public class TimeInProgressEstimater
	{
		private readonly double[] _xData;
		private readonly double[] _yData;

		public TimeInProgressEstimater(IEnumerable<ILeafInput> leafInputs)
		{
			var xData = new List<double>();
			var yData = new List<double>();
			foreach (var leafInput in leafInputs)
			{
				xData.Add(leafInput.InputFiles.Count);
				yData.Add(ConvertFromTimeSpan(leafInput.TimeInProgress));
			}
			_xData = xData.ToArray();
			_yData = yData.ToArray();
		}

		private static Func<double, double> Positivize => i => i <= 0 ? 1 : i;

		private IEnumerable<double> Div => _xData.Zip(_yData, (x, y) => y/x);

		private Func<double, double> AverageFitFunc => i => Div.Average()*i;

		private Func<double, double> LineFitFunc => Fit.LineFunc(_xData, _yData);

		private Func<double, double> FitFunc => i => (Positivize(LineFitFunc(i)) + AverageFitFunc(i))/2;

		public TimeSpan EstimateTimeInProgress(ILeafInput leafInput)
		{
			return EstimateTimeInProgress(leafInput.InputFiles.Count);
		}

		public TimeSpan EstimateTimeInProgress(int inputFileCount)
		{
			var estimate = FitFunc(inputFileCount);
			if (estimate <= 0)
				estimate = 1;
			return ConvertToTimeSpan(estimate);
		}

		private static double ConvertFromTimeSpan(TimeSpan timeSpan)
		{
			return timeSpan.TotalSeconds;
		}

		private static TimeSpan ConvertToTimeSpan(double estimate)
		{
			return TimeSpan.FromSeconds(ConvertToInt32Guarded(estimate));
		}

		private static Func<double, int> ConvertToInt32Guarded => d =>
		{
			try
			{
				return Convert.ToInt32(d);
			}
			catch (OverflowException)
			{
				return int.MaxValue;
			}
		};
	}
}
