using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace EvoBoat.EvoAlg
{
    using Excel = Microsoft.Office.Interop.Excel;

    public class AlgTestData<P,S,R,e>
        where P : IProblem<S, R>
        where S : class, ISolution<R>, new()
        where R : IVector<e>
    {
        EAlg<P, S, R, e> alg;
        public EAlg<P, S, R, e> Algorithm
        {
            get { return alg; }
        }

        object[,] data;
        public object[,] Data
        {
            get { return data; }
        }

        double median, meanBest, std;
        public double Median
        {
            get { return median; }
        }
        public double MeanBest
        {
            get { return meanBest; }
        }
        public double Std
        {
            get { return std; }
        }

        public override string ToString()
        {
            StringBuilder sb1 = new StringBuilder();
            sb1.AppendLine(alg.Name);
            for (int i = 0; i < data.GetLength(0); i++)
            {
                for (int j = 0; j < data.GetLength(1); j++)
                {
                    sb1.Append(data[i, j]);
                    sb1.Append(' ');
                }
                if (i > 0)
                {
                    sb1.Remove(sb1.Length - 1, 1);
                    sb1.AppendLine("ms");
                }
                else
                    sb1.AppendLine();
            }
            sb1.Append("Median:");
            sb1.Append(median.ToString());
            sb1.Append(" Mean best:");
            sb1.Append(meanBest);
            sb1.Append(" Std:");
            sb1.AppendLine(std.ToString());
            return sb1.ToString();
        }

        public void WriteExcel(Excel.Worksheet sheet)
        {
            sheet.get_Range("A1").Value2 = alg.Name;
            int rows = data.GetLength(0), cols = data.GetLength(1);
            string col, col1;
            if (cols < 26)
            {
                col = ((char)('A' + cols)).ToString();
                col1 = ((char)('A' + cols - 1)).ToString();
            }
            else
            {
                col = string.Format("{0}{1}", (char)('A' + (cols + 1) / 26 - 1), (char)('A' + (cols + 1) % 26 - 1));
                if (cols % 26 > 1)
                {
                    col1 = string.Format("{0}{1}", (char)('A' + (cols + 1) / 26 - 1), (char)('A' + (cols + 1) % 26 - 2));
                }
                else
                {
                    if ((cols + 1) / 26 > 1)
                    {
                        col1 = string.Format("{0}Z", (char)('A' + (cols + 1) / 26 - 2));
                    }
                    else
                    {
                        col1 = "Z";
                    }
                }
            }
            sheet.get_Range("A2").Value2 = "r1";
            sheet.get_Range("A2").AutoFill(sheet.get_Range("A2:A" + rows));
            string sRange = string.Format("B1:{1}{0}", rows, col);
            sheet.get_Range(sRange).Value2 = data;
            //median
            sheet.get_Range("A" + (rows + 1)).Value2 = "median";
            sheet.get_Range("B" + (rows + 1)).Formula = string.Format("=MEDIAN(B2:B{0})", rows);
            sRange = string.Format("B{0}:{1}{0}", rows + 1, col);
            sheet.get_Range(sRange).FillRight();
            //mean
            sheet.get_Range("A" + (rows + 2)).Value2 = "mean";
            sheet.get_Range("B" + (rows + 2)).Formula = string.Format("=AVERAGE(B2:B{0})", rows);
            sRange = string.Format("B{0}:{1}{0}", rows + 2, col);
            sheet.get_Range(sRange).FillRight();
            //std
            sRange = string.Format("{1}{0}", rows + 3, col1);
            if (double.Parse(sheet.Application.Version) <= 12.0)
                sheet.get_Range(sRange).Formula = string.Format("=STDEV({1}2:{1}{0})", rows, col1);
            else
                sheet.get_Range(sRange).Formula = string.Format("=STDEV.S({1}2:{1}{0})", rows, col1);
            //Max/Min
            sRange = string.Format("{1}{0}", rows + 4, col1);
            sheet.get_Range(sRange).Formula = string.Format("=MAX({1}2:{1}{0})", rows, col1);
            sRange = string.Format("{1}{0}", rows + 5, col1);
            sheet.get_Range(sRange).Formula = string.Format("=MIN({1}2:{1}{0})", rows, col1);

            //style
            sRange = string.Format("{0}:{0}", rows + 1);
            sheet.get_Range(sRange).Font.Bold = true;
            sRange = string.Format("{2}{0}:{2}{1}", rows + 1, rows + 5, col1);
            sheet.get_Range(sRange).NumberFormat = "0.00E+00";
        }

        public static AlgTestData<P,S,R,e> Get(EAlg<P, S, R, e> alg, P problem, SolutionFactory<P, S, R> factory, int runs, int upper, int interval = 10, bool byNfe = false)
        {
            AlgTestData<P, S, R, e> atd = new AlgTestData<P, S, R, e>();
            atd.alg = alg;
            //alg.Reset();
            alg.Problem = problem;
            alg.Factory = factory;
            double[] results = new double[runs];
            long time;
            S sol;
            if (interval == 0)
            {
                atd.data = new object[runs + 1, 2];
                atd.data[0, 0] = upper;
                for (int i = 0; i < runs; i++)
                {
                    alg.SetParameters();
                    if (!byNfe)
                        sol = alg.Solve(upper, out time);
                    else
                        sol = alg.SolveF(upper, out time);
                    results[i] = sol.Fit;

                    atd.data[i + 1, 0] = results[i];
                    atd.data[i + 1, 1] = alg.Nfe;
                    atd.meanBest += results[i];
                    alg.Reset();
                }
            }
            else
            {
                atd.data = new object[runs + 1, upper / interval + 2];
                List<double> data;
                for (int j = 0; j < atd.data.GetLength(1) - 1; j++)
                    atd.data[0, j] = j * interval;
                for (int i = 0; i < runs; i++)
                {
                    alg.SetParameters();
                    if (!byNfe)
                        sol = alg.SolveAndWatch(upper, out time, out data, interval);
                    else
                        sol = alg.SolveFAndWatch(upper, 1.0E-8, out time, out data, interval);
                    results[i] = sol.Fit;
                    atd.meanBest += results[i];
                    for (int j = 0; j < data.Count; j++)
                        atd.data[i + 1, j] = data[j];
                    atd.data[i + 1, data.Count] = time;
                    alg.Reset();
                }
            }
            atd.meanBest /= runs;
            for (int i = 0; i < runs; i++)
            {
                atd.std += (results[i] - atd.meanBest) * (results[i] - atd.meanBest);
            }
            atd.std = Math.Sqrt(atd.std / (runs - 1));
            results = results.OrderBy(v => v).ToArray();
            if (runs % 2 == 0)
            {
                atd.median = (results[runs / 2] + results[runs / 2 - 1]) / 2;
            }
            else
            {
                atd.median = results[(runs - 1) / 2];
            }
            return atd;
        }
    }
}
