using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using EvoBoat.EvoAlg.WWO;

namespace EvoBoat.EvoAlg.IntDivision
{
    public class IntDiv_WWOAlg<P,S> : COMB_WWOAlg<P, S, IntVector, int>
        where P : IntDivisionProblem<S>
        where S : VecSolution<IntVector, int>, new()
    {
        public static Func<EAlg<P, S, IntVector, int>, S, int, S, S> ReflectFunc = (alg, sol, index, exSol) =>
        {
            int[] arr = new int[alg.Problem.Dimension];
            int d, sum = 0;
            for (d = 0; d < alg.Problem.Dimension; d++)
            {
                arr[d] = (alg.Best.Content[d] + sol.Content[d]) / 2;
                sum += arr[d];
            }
            while(sum> alg.Problem.Total)
            {
                d = alg.Rand.Next(alg.Problem.Dimension);
                while (arr[d] == 0)
                    d = alg.Rand.Next(alg.Problem.Dimension);
                arr[d]--;
                sum--;
            }
            while (sum < alg.Problem.Total)
            {
                arr[alg.Rand.Next(alg.Problem.Dimension)]++;
                sum++;
            }
            S sol1 = new S() { Content = arr };
            alg.Evaluate(sol1);
            return sol1;
        };

        protected int beta;
        public int BetaMax { get; set; }
        public int BetaMin { get; set; }

        public IntDiv_WWOAlg() : base()
        { }

        public IntDiv_WWOAlg(P problem, int seed = 0) : base(problem, seed)
        {
            this.Factory = new IntDivisionSolutionFactory<P, S>(problem);
        }

        public override void SetFactory()
        {
            if (Problem == null)
                throw new ArgumentNullException("Please assign the problem instance before set the factory");
            this.Factory = new IntDivisionSolutionFactory<P, S>(Problem);
        }

        public override void SetParameters()
        {
            base.SetParameters();
            if (KLocal >= Problem.Dimension)
                KLocal = Problem.Dimension - 1;
            BetaMax = (int)Math.Floor(Problem.Avg / 4);
            BetaMin = 1;
        }
        
        public override double MoveP(S sol, int index, int d)
        {
            int dep, oldv = sol.Content[d];
            if (sol.Content[d] > 0)
            {
                dep = Rand.Next(1, sol.Content[d] + 1);
                if (sol.Content[d] > Problem.Total / 2 || Rand.Next() % 2 == 0)
                    dep = -dep;
            }
            else
                dep = Rand.Next(1, (int)Math.Round(Problem.Avg * 2) + 1);
            sol.Content[d] += dep;
            Problem.RepairAt(this, sol, d, sol.Content[d] - oldv);
            return 0;
        }

        public override double MoveB(S sol, int index, int d)
        {
            if (Rand.Next() % 2 == 0 && sol.Content[d] > beta)
            {
                int d1 = RandomSelectIndex(Problem.Dimension, d);
                sol.Content[d] -= beta;
                sol.Content[d1] += beta;
                if (Evaluate(sol) > sol.Fit)
                {
                    sol.Content[d] += beta;
                    sol.Content[d1] -= beta;
                }
            }
            else
            {
                int maxchange = Problem.Total - sol.Content[d];
                int change = (beta <= maxchange) ? beta : maxchange;
                sol.Content[d] += change;
                int[] changes = Problem.RepairAt(this, sol, d, change);
                if (Evaluate(sol) > sol.Fit)
                {
                    sol.Content[d] -= change;
                    for (int d1 = 0; d1 < Problem.Dimension; d1++)
                        sol.Content[d1] -= changes[d1];
                }
            }
            return 0;
        }

        public override S Reflect(S sol, int index)
        {
            return ReflectFunc(this, sol, index, best);
        }
    }
}
