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

namespace EvoBoat.EvoAlg.WWO
{
    /// <summary>
    /// General framework of water wave optimization
    /// </summary>
    public abstract class WWOAlg<P, S, R, e> : EAlg<P, S, R, e>
        where P : IProblem<S, R>
        where S : VecSolution<R, e>, new()
        where R : IVector<e>, ICloneable<R>
    {
        public double Alpha { get; set; }
        public int HeightMax { get; set; }
        public int KLocal { get; set; }

        protected double[] wavelengths, waveheights;
        public double[] WaveLengths { get { return wavelengths; } }
        public double[] WaveHeights { get { return waveheights; } }

        public GaussRandom gRand { get; set; }

        public WWOAlg() : base()
        {
            Name = "WWO";
            gRand = new GaussRandom((int)DateTime.Now.Ticks);
        }

        public WWOAlg(P problem, int seed = 0) : base(problem, seed)
        {
            Name = "WWO";
            if (seed == 0)
                gRand = new GaussRandom((int)DateTime.Now.Ticks);
            else
                gRand = new GaussRandom(seed);
        }

        public override void SetParameters()
        {
            Size = 10;
            Alpha = 1.0026;
            HeightMax = 12;
            KLocal = 12;
        }

        public override void InitPop()
        {
            base.InitPop();
            wavelengths = new double[size];
            waveheights = new double[size];
            for (int i = 0; i < size; i++)
            {
                wavelengths[i] = 0.5;
                waveheights[i] = HeightMax;
            }
        }

        public override void Calculate()
        {
            double den = (worst.Fit - best.Fit + epsilon);
            for (int i = 0; i < pop.Length; i++)
                wavelengths[i] *= Math.Pow(Alpha, -(worst.Fit - pop[i].Fit + epsilon) / den);
        }

        public abstract double MoveP(S sol, int index, int d);

        public virtual double MoveB(S sol, int index, int d)
        {
            return MoveP(sol, index, d);
        }

        public virtual S Propagate(S sol, int index)
        {
            S sol1 = new S() { Content = sol.Content.Clone() };
            for (int d = 0; d < Problem.Dimension; d++)
                MoveP(sol1, index, d);
            this.Evaluate(sol1);
            return sol1;
        }

        public abstract S Reflect(S sol, int index);

        public virtual S Break(S sol, int index, int times)
        {
            if (times >= Problem.Dimension / 2)
                times = Problem.Dimension / 2;
            int[] sels = RandomSelectKIndicies(Problem.Dimension, times);
            foreach (int d in sels)
                MoveB(sol, index, d);
            return sol;
        }

        public override void Evolve()
        {
            for (int i = 0; i < pop.Length; i++)
            {
                S sol = this.Propagate(pop[i], i);
                if (sol.Fit < pop[i].Fit)
                {
                    pop[i] = sol;
                    waveheights[i] = HeightMax;
                    if (sol.Fit < best.Fit)
                    {
                        wavelengths[i] /= (best.Fit + epsilon);
                        sol = Break(sol, i, (sol.Fit > 0) ? (int)(KLocal * (best.Fit + epsilon) / (sol.Fit + epsilon)) : Problem.Dimension / 2);
                        //sol = Break(sol, i, KLocal);
                        best = sol;
                        iBest = i;
                        wavelengths[i] *= (best.Fit + epsilon);
                    }
                }
                else if (!Object.ReferenceEquals(pop[i], best) && --waveheights[i] == 0)
                {
                    wavelengths[i] /= (pop[i].Fit + epsilon);
                    pop[i] = this.Reflect(pop[i], i);
                    wavelengths[i] *= (pop[i].Fit + epsilon);
                    waveheights[i] = HeightMax;
                    if (pop[i].Fit < best.Fit)
                    {
                        best = pop[i];
                        iBest = i;
                    }
                }
            }
            this.UpdateWorst();
            iter++;
        }
    }
}
