#region License Information
/* HeuristicLab
 * Copyright (C) 2002-2016 Heuristic and Evolutionary Algorithms Laboratory (HEAL)
 *
 * This file is part of HeuristicLab.
 *
 * HeuristicLab is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * HeuristicLab is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with HeuristicLab. If not, see <http://www.gnu.org/licenses/>.
 */
#endregion

using System;
using System.Drawing;
using System.Linq;
using HeuristicLab.Common;
using HeuristicLab.Core;
using HeuristicLab.Data;
using HeuristicLab.Encodings.PermutationEncoding;
using HeuristicLab.Parameters;
using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;
using HeuristicLab.PluginInfrastructure;
using HeuristicLab.Problems.Instances;
using HeuristicLab.Optimization;
using System.Collections.Generic;

namespace HeuristicLab.Problems.PFSP
{
    [Item("Permutation Flowshop Scheduling Problem (PFSP)", "Represents a Permutation Flowshop Scheduling Problem")]
    [Creatable(CreatableAttribute.Categories.CombinatorialProblems, Priority = 999)]
    [StorableClass]
    public sealed class PermutationFlowshopSchedulingProblem : SingleObjectiveHeuristicOptimizationProblem<IPFSPEvaluator, IPermutationCreator>, IProblemInstanceConsumer<FSSPData>, IProblemInstanceExporter<FSSPData>, IStorableContent
    {
        #region Default Instance
        private static readonly FSSPData DefaultInstance = new FSSPData()
        {
            Name = "Permutation Flowshop Scheduling Problem (PFSP)",
            Description = "The default instance of the PFSP problem in HeuristicLab",
            Jobs = 4,
            Machines = 4,
            BestKnownQuality = 328,
            ProcessingTimes = new double[,] {
                {1,1,2,3},
                {5,8,13,21},
                {34,55,89,144},
            }
        };
        #endregion

        public string Filename { get; set; }

        #region Parameter Properties
        public OptionalValueParameter<JobMatrix> BestKnownSolutionParameter
        {
            get { return (OptionalValueParameter<JobMatrix>)Parameters["BestKnownSolution"]; }
        }

        public OptionalValueParameter<JobMatrix> JobMatrixParameter
        {
            get { return (OptionalValueParameter<JobMatrix>)Parameters["JobMatrix"]; }
        }
        #endregion

        #region Properties
        public JobMatrix BestKnownSolution
        {
            get { return BestKnownSolutionParameter.Value; }
            set { BestKnownSolutionParameter.Value = value; }
        }
        public JobMatrix JobMatrix
        {
            get { return JobMatrixParameter.Value; }
            set { JobMatrixParameter.Value = value; }
        }
        #endregion

        // BackwardsCompatibility3.3
        #region Backwards compatible code, remove with 3.4
        [Obsolete]
        [Storable(Name = "operators")]
        private IEnumerable<IOperator> oldOperators
        {
            get { return null; }
            set
            {
                if (value != null && value.Any())
                    Operators.AddRange(value);
            }
        }
        #endregion

        [StorableConstructor]
        private PermutationFlowshopSchedulingProblem(bool deserializing) : base(deserializing) { }
        private PermutationFlowshopSchedulingProblem(PermutationFlowshopSchedulingProblem original, Cloner cloner)
          : base(original, cloner)
        {
            RegisterEventHandlers();
        }
        public PermutationFlowshopSchedulingProblem()
          : base(new AwesomeMakeSpanEvaluator(), new RandomPermutationCreator())
        {
            Parameters.Add(new OptionalValueParameter<JobMatrix>("BestKnownSolution", "The best known solution of this FSSP instance."));
            Parameters.Add(new OptionalValueParameter<JobMatrix>("JobMatrix", "The matrix which contains the jobs,machines and duration."));

            Load(DefaultInstance);
            EvaluatorParameter.GetsCollected = false;
            EvaluatorParameter.Hidden = true;

            Maximization.Value = false;
            MaximizationParameter.Hidden = true;

            SolutionCreator.PermutationParameter.ActualName = "TSPTour";
            Evaluator.QualityParameter.ActualName = "TSPTourLength";
            ParameterizeSolutionCreator();
            ParameterizeEvaluator();

            InitializeOperators();
            RegisterEventHandlers();
        }

        public override IDeepCloneable Clone(Cloner cloner)
        {
            return new PermutationFlowshopSchedulingProblem(this, cloner);
        }

        [StorableHook(HookType.AfterDeserialization)]
        private void AfterDeserialization()
        {
            RegisterEventHandlers();
        }

        private void RegisterEventHandlers()
        {
            SolutionCreator.PermutationParameter.ActualNameChanged += new EventHandler(SolutionCreator_PermutationParameter_ActualNameChanged);
            Evaluator.QualityParameter.ActualNameChanged += new EventHandler(Evaluator_QualityParameter_ActualNameChanged);
        }

        private void Evaluator_QualityParameter_ActualNameChanged(object sender, EventArgs e)
        {
            //Nothing to do  ParameterizeAnalyzers();
        }

        private void SolutionCreator_PermutationParameter_ActualNameChanged(object sender, EventArgs e)
        {
            ParameterizeEvaluator();
            //ParameterizeAnalyzers();
            ParameterizeOperators();
        }

        #region Events
        private void ScheduleEvaluator_QualityParameter_ActualNameChanged(object sender, EventArgs eventArgs)
        {
            ParameterizeOperators();
        }

        private void SolutionCreator_SchedulingEncodingParameter_ActualNameChanged(object sender, EventArgs eventArgs)
        {
            ParameterizeOperators();
        }

        private void ScheduleDecoder_ScheduleParameter_ActualNameChanged(object sender, EventArgs eventArgs)
        {
            ParameterizeOperators();
        }
        #endregion

        #region Problem Instance Handling
        public void Load(FSSPData data)
        {

            BestKnownQuality = data.BestKnownQuality.HasValue ? new DoubleValue(data.BestKnownQuality.Value) : null;

            if (data.BestKnownSchedule != null)
            {
                int[] permut = data.BestKnownSchedule;
                //Clean up if the first index = 1
                if (!permut.Contains(0)) { permut = permut.Select(v => v - 1).ToArray(); }
                double bestKnownQuality = MakeSpanEvaluator.Apply(
                    new AwesomeMakeSpanEvaluator(),
                    new DoubleMatrix(data.ProcessingTimes),
                    new Permutation(PermutationTypes.Absolute, permut)
                );
                //BestKnownSolution = data.BestKnownSchedule;
                BestKnownQuality = new DoubleValue(bestKnownQuality);
            }
            Name = data.Name;
            Description = data.Description;
            JobMatrix = new JobMatrix(data.ProcessingTimes);
            ParameterizeSolutionCreator();
        }

        public FSSPData Export()
        {
            var result = new FSSPData
            {
                Name = Name,
                Description = Description,
                BestKnownQuality = BestKnownQuality.Value,
                Jobs = JobMatrix.Rows,
                Machines = JobMatrix.Columns
            };
            for (int i = 0; i < JobMatrix.Rows; i++)
            {
                for (int j = 0; j < JobMatrix.Columns; j++)
                {
                    result.ProcessingTimes[i, j] = JobMatrix[i, j];
                }
            }

            return result;
        }
        #endregion

        #region Helpers
        private void InitializeOperators()
        {
            var operators = new HashSet<IPermutationOperator>(new IPermutationOperator[] {
        new OrderCrossover2(),
        new InversionManipulator(),
        new StochasticInversionMultiMoveGenerator()
      }, new TypeEqualityComparer<IPermutationOperator>());
            foreach (var op in ApplicationManager.Manager.GetInstances<IPermutationOperator>())
                operators.Add(op);
            Operators.AddRange(operators);
            ParameterizeOperators();
            UpdateMoveEvaluators();
        }


        private void ParameterizeOperators()
        {
            foreach (IPermutationCrossover op in Operators.OfType<IPermutationCrossover>())
            {
                op.ParentsParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.ParentsParameter.Hidden = true;
                op.ChildParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.ChildParameter.Hidden = true;
            }
            foreach (IPermutationManipulator op in Operators.OfType<IPermutationManipulator>())
            {
                op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.PermutationParameter.Hidden = true;
            }
            foreach (IPermutationMoveOperator op in Operators.OfType<IPermutationMoveOperator>())
            {
                op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.PermutationParameter.Hidden = true;
            }
            foreach (IPermutationMultiNeighborhoodShakingOperator op in Operators.OfType<IPermutationMultiNeighborhoodShakingOperator>())
            {
                op.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.PermutationParameter.Hidden = true;
            }
            foreach (ISingleObjectiveImprovementOperator op in Operators.OfType<ISingleObjectiveImprovementOperator>())
            {
                op.SolutionParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.SolutionParameter.Hidden = true;
            }
            foreach (ISingleObjectivePathRelinker op in Operators.OfType<ISingleObjectivePathRelinker>())
            {
                op.ParentsParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                op.ParentsParameter.Hidden = true;
            }
            foreach (ISolutionSimilarityCalculator op in Operators.OfType<ISolutionSimilarityCalculator>())
            {
                op.SolutionVariableName = SolutionCreator.PermutationParameter.ActualName;
                op.QualityVariableName = Evaluator.QualityParameter.ActualName;
            }
        }
        #endregion

        private void UpdateMoveEvaluators()
        {
            ParameterizeOperators();
            OnOperatorsChanged();
        }

        private void ParameterizeSolutionCreator()
        {
            SolutionCreator.LengthParameter.Value = new IntValue(JobMatrix.Columns); //Jobs

            SolutionCreator.LengthParameter.Hidden = SolutionCreator.LengthParameter.Value != null;
            SolutionCreator.PermutationTypeParameter.Value = new PermutationType(PermutationTypes.RelativeUndirected);
            SolutionCreator.PermutationTypeParameter.Hidden = true;
        }
        private void ParameterizeEvaluator()
        {
            if (Evaluator is AwesomeMakeSpanEvaluator)
            {
                IMakespanEvaluator evaluator = (IMakespanEvaluator)Evaluator;
                evaluator.PermutationParameter.ActualName = SolutionCreator.PermutationParameter.ActualName;
                evaluator.PermutationParameter.Hidden = true;
            }
        }
    }
}
