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

namespace EvoBoat.EvoAlg.BBO
{
    public abstract class EBOAlg<P, S, R, e> : LocalBBOAlg<P, S, R, e>
        where P : IProblem<S, R>
        where S : VecSolution<R, e>, new()
        where R : IVector<e>, ICloneable<R>
    {
        public double Immaturity { get; set; }

        public EBOAlg() : base()
        {
            Name = "EBO";
        }

        public EBOAlg(P problem, int seed = 0) : base(problem, seed)
        {
            Name = "EBO";
        }

        public override void SetParameters()
        {
            Immaturity = 0.7;
        }

        public override void Calculate()
        {
            base.Calculate();
            if(nfes > 0)
                Immaturity = 0.7 - 0.3 * nfe / nfes;
            else if(iters > 0)
                Immaturity = 0.7 - 0.3 * iter / iters;
        }

        public abstract e Blend(S sol, int d, S sol1, S sol2);
        public abstract void Repair(S sol, int index, int d, e oldv);

        public override S Migrate(S sol, int index)
        {
            S sol1 = new S() { Content = sol.Content.Clone() }, solNb, solFar;
            int j = Rand.Next(Problem.Dimension), sel;
            for (int d = 0; d < Problem.Dimension; d++)
            {
                sel = RouletteSelectNeighbor((s, i) => immrs[i], index);
                solNb = pop[sel];
                if (Rand.NextDouble() < immrs[index] || j == d) //global migration 
                {
                    e olde = sol1.Content[d];
                    if (Rand.NextDouble() < Immaturity) //global migration
                    {
                        Tuple<int, int> ind = RandomSelectTwoIndicies(pop.Length, index, sel);
                        solFar = (pop[ind.Item1].Fit < pop[ind.Item2].Fit) ? pop[ind.Item1] : pop[ind.Item2];
                        //hFar = this.SelectNonNeighbor(h);
                        if (solFar.Fit < solNb.Fit)
                            sol1.Content[d] = Blend(sol, d, solFar, solNb);
                        else
                            sol1.Content[d] = Blend(sol, d, solNb, solFar);
                    }
                    else //local migration
                        sol1.Content[d] = Blend(sol, d, sol, solNb);
                    Repair(sol1, index, d, olde);
                }
            }
            this.Evaluate(sol1);
            return sol1;
        }

        public override S Mutate(S sol, int index)
        {
            throw new NotImplementedException();
        }

        public override void Evolve()
        {
            for (int i = 0; i < pop.Length; i++)
            {
                S sol = this.Migrate(pop[i], i);
                if (sol.Fit < pop[i].Fit)
                {
                    pop[i] = sol;
                    if (pop[i].Fit < best.Fit)
                        best = pop[i];
                }
            }
            iter++;
        }
    }
}
