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

namespace EvoBoat.EvoAlg.GA
{
    /// <summary>
    /// Algorithm framework of genetic algorithm
    /// </summary>
    public abstract class GA<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 MutationRate { get; set; }

        protected double[] pbs;

        public GA() : base()
        {
            Name = "GA";
        }

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

        public override void SetParameters()
        {
            MutationRate = 0.2;
        }

        public override void InitPop()
        {
            base.InitPop();
            pbs = new double[size];
        }

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

        public virtual Tuple<S, S> Crossover(S sol1, S sol2)
        {
            int n = sol1.Content.Length;
            S ch1 = Factory.CreateEmpty(), ch2 = Factory.CreateEmpty();
            for (int d = 0; d < n; d++)
            {
                if (Rand.Next() % 2 == 0)
                {
                    ch1.Content[d] = sol1.Content[d];
                    ch2.Content[d] = sol2.Content[d];
                }
                else {
                    ch1.Content[d] = sol2.Content[d];
                    ch2.Content[d] = sol1.Content[d];
                }
            }
            this.Evaluate(ch1);
            this.Evaluate(ch2);
            return new Tuple<S, S>(ch1, ch2);
        }

        //public SingleSolutionOperator<S> Mutate;
        public abstract S Mutate(S sol, int index);

        public override void Evolve()
        {
            S[] pop1 = new S[pop.Length];
            Tuple<int, int> sels;
            Tuple<S, S> children;
            int i = 0;
            while (i < pop1.Length)
            {
                sels = RouletteSelectTwo(pop, pbs);
                children = Crossover(pop[sels.Item1], pop[sels.Item2]);
                pop1[i] = children.Item1;
                if (pop1[i].Fit < best.Fit)
                    best = pop1[i];
                else if (pop[i] != best && Rand.NextDouble() < MutationRate)
                    this.Mutate(pop1[i], i);
                pop1[++i] = children.Item2;
                if (pop1[i].Fit < best.Fit)
                    best = pop1[i];
                else if (!object.ReferenceEquals(pop[i], best) && Rand.NextDouble() < MutationRate)
                    this.Mutate(pop1[i], i);
                ++i;
            }
            iter++;
            pop = pop1;
            this.UpdateWorst();
        }
    }
}
