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

namespace EvoBoat.EvoAlg.PSO
{
    /// <summary>
    /// Framework of particle swarm optimization
    /// </summary>
    public abstract class PSOAlg<P, S, R, e> : EAlg<P, S, R, e>
        where P : IProblem<S, R>
        where S : VecSolution<R, e>, ICloneable<S>, new()
        where R : IVector<e>, ICloneable<R>
    {
        protected double wInertia;
        public double WMax { get; set; }
        public double WMin { get; set; }
        public double CRate1 { get; set; }
        public double CRate2 { get; set; }

        protected Vector[] velocities;
        public Vector[] Velocities { get { return velocities; } }
        protected S[] pbests;
        public S[] PBests { get { return pbests; } }

        public PSOAlg() : base()
        {
            Name = "PSO";
        }

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

        public override void SetParameters()
        {
            wInertia = WMax = 0.9;
            WMin = 0.4;
            CRate1 = CRate2 = 2.0;
        }

        public override void InitPop()
        {
            base.InitPop();
            velocities = new Vector[size];
            pbests = new S[size];
            for (int i = 0; i < size; i++)
            {
                velocities[i] = new Vector(Problem.Dimension);
                Type t = pop[i].GetType();
                pbests[i] = ((ICloneable<S>)pop[i]).Clone();
            }
        }

        public override void Calculate()
        {
            if(nfes > 0)
                wInertia = WMax - (WMax - WMin) * ((double)nfe / nfes);
            else if(iters > 0)
                wInertia = WMax - (WMax - WMin) * ((double)iter / iters);
        }

        public abstract double Difference(e pos1, e pos2);
        public abstract e Increase(e pos, double v);
        public abstract void Repair(S sol, int index, int d, e oldv);

        public virtual void AdjustVelocity(S sol, int index)
        {
            for (int d = 0; d < Problem.Dimension; d++)
                velocities[index][d] = wInertia * velocities[index][d] + Rand.NextDouble() * CRate1 * Difference(pbests[index].Content[d], sol.Content[d]) + Rand.NextDouble() * CRate2 * Difference(best.Content[d], sol.Content[d]);       
        }

        public S Move(S sol, int index)
        {
            this.AdjustVelocity(sol, index);
            for (int d = 0; d < Problem.Dimension; d++)
            {
                e oldv = sol.Content[d];
                sol.Content[d] = Increase(sol.Content[d], velocities[index][d]);
                Repair(sol, index, d, oldv);                
            }
            this.Evaluate(sol);
            return sol;
        }


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

        public override void Reset()
        {
            base.Reset();
            wInertia = WMax;
        }
    }
}
