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 that two parents produces one child
    /// </summary>
    public abstract class SGA<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 SGA() : base()
        {
            Name = "SGA";
        }

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

        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 S Crossover(S sol1, S sol2)
        {
            int n = sol1.Content.Length;
            S ch = Factory.CreateEmpty();
            for (int d = 0; d < n; d++)
            {
                if (Rand.Next() % 2 == 0)
                    ch.Content[d] = sol1.Content[d];
                else
                    ch.Content[d] = sol2.Content[d];
            }
            this.Evaluate(ch);
            return ch;
        }

        //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;            
            for (int i = 0; i < pop1.Length; i++)
            {
                sels = RouletteSelectTwo(pop, pbs);
                pop1[i] = Crossover(pop[sels.Item1], pop[sels.Item2]);
                if (pop1[i].Fit < best.Fit)
                    best = pop1[i];
                else if (pop[i] != best && Rand.NextDouble() < MutationRate)
                    this.Mutate(pop1[i], i);
            }
            pop = pop1;
            worst = pop[0];
            for (int i = 1; i < pop.Length; i++)
                if (pop[i].Fit > worst.Fit)
                    worst = pop[i];
            iter++;
        }
    }
}
