namespace HeuristicLab.Problems.ProgramSynthesis {
  using System.Collections.Generic;

  using HeuristicLab.Common;
  using HeuristicLab.Core;
  using HeuristicLab.Data;
  using HeuristicLab.Parameters;
  using HeuristicLab.Persistence.Default.CompositeSerializers.Storable;

  [StorableClass]
  public class PushConfiguration : PushConfigurationBase, IReadOnlyPushConfiguration {
    private const string INSTRUCTIONS_PARAMETER_NAME = "Instructions";
    private const string INSTRUCTIONS_PARAMETER_DESCRIPTION = "Enables/Disables Instructions";
    private const string EVAL_PUSH_LIMIT_PARAMETER_NAME = "EvalPushLimit";
    private const string EVAL_PUSH_LIMIT_PARAMETER_DESCRIPTION = "This is the maximum allowed number of \"executions\" in a single top-level call to the interpreter. The execution of a single Push instruction counts as one execution, as does the processing of a single literal, as does the descent into one layer of parentheses (that is, the processing of the \"(\" counts as one execution).";
    private const string MIN_PROGRAM_LENGTH = "MinProgramLength";
    private const string MIN_PROGRAM_LENGTH_PARAMETER_DESCRIPTION = "This is the minium size of an item on the CODE/EXEC stack, expressed as a number of points. A point is an instruction, a literal, or a pair of parentheses.";
    private const string MAX_PROGRAM_LENGTH = "MaxProgramLength";
    private const string MAX_PROGRAM_LENGTH_PARAMETER_DESCRIPTION = "This is the maximum size of an item on the CODE/EXEC stack, expressed as a number of points. A point is an instruction, a literal, or a pair of parentheses.";
    private const string TOP_LEVEL_PUSH_CODE_PARAMETER_NAME = "TopLevelPushCode";
    private const string TOP_LEVEL_PUSH_CODE_PARAMETER_DESCRIPTION = "When TRUE (which is the default), code passed to the top level of the interpreter will be pushed onto the CODE stack prior to execution.";
    private const string TOP_LEVEL_POP_CODE_PARAMETER_NAME = "TopLevelPopCode";
    private const string TOP_LEVEL_POP_CODE_PARAMETER_DESCRIPTION = "When TRUE, the CODE stack will be popped at the end of top level calls to the interpreter. The default is FALSE.";
    private const string MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_NAME = "MaxPointsInRandomInstruction";
    private const string MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_DESCRIPTION = "MaxPointsInRandomInstruction";
    private const string ERC_OPTIONS_PARAMETER_NAME = "ERC options";
    private const string MAX_STRING_LENGTH_PARAMETER_NAME = "Max. string length of string literals";
    private const string MAX_DEPTH_PARAMETER_NAME = "Max. depth of a Push program";
    private const string MAX_CLOSE_PARAMETER_NAME = "Max. close";
    private const string MAX_PARENTHESES_CLOSE_PARAMETER_DESCRIPTION = "Specifies how many sub programs are max. closed if open during the recursive translation of an individual to a push program. Value is exclusive.";
    private const string CLOSE_BIAS_LEVEL_PARAMETER_NAME = "Close bias level";
    private const string PARENTHESES_CLOSE_BIAS_LEVEL_PARAMETER_DESCRIPTION = "Specifies how strong a random value between 0 .. 'Max. parentheses close' is biased towards 0. In other words, this parameter controls the length of sub programs.";
    private const string MAX_VECTOR_LENGTH_PARAMETER_NAME = "Max. vector length";
    private const string TOP_LEVEL_PUSH_INPUT_ARGUMENTS = "TopLevelPushInputArguments";

    public PushConfiguration() {
      Parameters = new ParameterCollection();
      InitParameters();

      FloatStringFormat = "R";
    }

    public PushConfiguration(PushConfiguration origin, Cloner cloner) : base(origin, cloner) {
      Parameters = cloner.Clone(origin.Parameters);
      FloatStringFormat = origin.FloatStringFormat;
    }

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

    [StorableConstructor]
    protected PushConfiguration(bool deserialize) { }

    [StorableHook(HookType.AfterDeserialization)]
    // ReSharper disable once UnusedMember.Local
    private void AfterDeserialization() {
      InitParameters();
    }

    [Storable]
    public ParameterCollection Parameters { get; private set; }

    [Storable]
    public string FloatStringFormat { get; set; }

    private void InitParameters() {
      if (!Parameters.ContainsKey(INSTRUCTIONS_PARAMETER_NAME))
        Parameters.Add(new ValueParameter<IExpressionsConfiguration>(
          INSTRUCTIONS_PARAMETER_NAME,
          INSTRUCTIONS_PARAMETER_DESCRIPTION,
          this));

      if (!Parameters.ContainsKey(ERC_OPTIONS_PARAMETER_NAME))
        Parameters.Add(new ValueParameter<ErcOptions>(ERC_OPTIONS_PARAMETER_NAME));

      if (!Parameters.ContainsKey(MAX_VECTOR_LENGTH_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_VECTOR_LENGTH_PARAMETER_NAME,
          new IntValue(500)) { Hidden = true });

      if (!Parameters.ContainsKey(EVAL_PUSH_LIMIT_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          EVAL_PUSH_LIMIT_PARAMETER_NAME,
          EVAL_PUSH_LIMIT_PARAMETER_DESCRIPTION,
          new IntValue(1000)));

      if (!Parameters.ContainsKey(MAX_PROGRAM_LENGTH))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_PROGRAM_LENGTH,
          MAX_PROGRAM_LENGTH_PARAMETER_DESCRIPTION,
          new IntValue(200)));

      if (!Parameters.ContainsKey(MIN_PROGRAM_LENGTH))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MIN_PROGRAM_LENGTH,
          MIN_PROGRAM_LENGTH_PARAMETER_DESCRIPTION,
          new IntValue(0)) { Hidden = false });

      if (!Parameters.ContainsKey(MAX_CLOSE_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_CLOSE_PARAMETER_NAME,
          MAX_PARENTHESES_CLOSE_PARAMETER_DESCRIPTION,
          new IntValue(4)) { Hidden = false });

      if (!Parameters.ContainsKey(CLOSE_BIAS_LEVEL_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<DoubleValue>(
          CLOSE_BIAS_LEVEL_PARAMETER_NAME,
          PARENTHESES_CLOSE_BIAS_LEVEL_PARAMETER_DESCRIPTION,
          new DoubleValue(3)) { Hidden = false });

      if (!Parameters.ContainsKey(TOP_LEVEL_PUSH_CODE_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<BoolValue>(
          TOP_LEVEL_PUSH_CODE_PARAMETER_NAME,
          TOP_LEVEL_PUSH_CODE_PARAMETER_DESCRIPTION,
          new BoolValue(true)) { Hidden = true });

      if (!Parameters.ContainsKey(TOP_LEVEL_POP_CODE_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<BoolValue>(
          TOP_LEVEL_POP_CODE_PARAMETER_NAME,
          TOP_LEVEL_POP_CODE_PARAMETER_DESCRIPTION,
          new BoolValue(false)) { Hidden = true });

      if (!Parameters.ContainsKey(MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_NAME,
          MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_DESCRIPTION,
          new IntValue(50)) { Hidden = true });

      if (!Parameters.ContainsKey(MAX_STRING_LENGTH_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_STRING_LENGTH_PARAMETER_NAME,
          new IntValue(1000)) { Hidden = true });

      if (!Parameters.ContainsKey(MAX_DEPTH_PARAMETER_NAME))
        Parameters.Add(new FixedValueParameter<IntValue>(
          MAX_DEPTH_PARAMETER_NAME,
          new IntValue(1000)) { Hidden = true });

      if (!Parameters.ContainsKey(TOP_LEVEL_PUSH_INPUT_ARGUMENTS))
        Parameters.Add(new FixedValueParameter<BoolValue>(
          TOP_LEVEL_PUSH_INPUT_ARGUMENTS,
          new BoolValue(true)) { Hidden = false });
    }

    public IValueParameter<IExpressionsConfiguration> InstructionsParameter {
      get { return (IValueParameter<IExpressionsConfiguration>)Parameters[INSTRUCTIONS_PARAMETER_NAME]; }
    }

    public IExpressionsConfiguration Instructions {
      get { return InstructionsParameter.Value; }
      set { InstructionsParameter.Value = value; }
    }

    public IValueParameter<ErcOptions> ErcOptionsParameter {
      get { return (IValueParameter<ErcOptions>)Parameters[ERC_OPTIONS_PARAMETER_NAME]; }
    }

    public ErcOptions ErcOptions {
      get { return ErcOptionsParameter.Value; }
      set {
        ErcOptionsParameter.Value = value;
      }
    }

    IReadOnlyList<string> IReadOnlyExpressionsConfiguration.EnabledExpressions {
      get {
        return enabledExpressions;
      }
    }

    IReadOnlyErcOptions IReadOnlyPushConfiguration.ErcOptions {
      get {
        return ErcOptions;
      }
    }

    /// <summary>
    ///     This is the maximum allowed number of "executions" in a single top-level call to the interpreter.
    ///     The execution of a single Push instruction counts as one execution, as does the processing of a single literal,
    ///     as does the descent into one layer of parentheses (that is, the processing of the "(" counts as one execution).
    ///     When this limit is exceeded the interpreter aborts immediately, leaving its stacks in the states they were in prior
    ///     to the abort (so they may still be examined by a calling program). Whether or not this counts as an "abnormal"
    ///     termination
    ///     is up to the calling program.
    /// </summary>
    public IValueParameter<IntValue> EvalPushLimitParameter {
      get { return (IValueParameter<IntValue>)Parameters[EVAL_PUSH_LIMIT_PARAMETER_NAME]; }
    }

    public int EvalPushLimit {
      get { return EvalPushLimitParameter.Value.Value; }
      set { EvalPushLimitParameter.Value.Value = value; }
    }


    /// <summary>
    /// Determines the likelihood of smaller or bigger values.
    /// x greater than 1 means that result is biased towards min.
    /// x smaller than 1 means that result is biased towards max.
    /// </summary>
    public IValueParameter<DoubleValue> CloseBiasLevelParameter {
      get { return (IValueParameter<DoubleValue>)Parameters[CLOSE_BIAS_LEVEL_PARAMETER_NAME]; }
    }

    public double CloseBiasLevel {
      get { return CloseBiasLevelParameter.Value.Value; }
      set { CloseBiasLevelParameter.Value.Value = value; }
    }

    /// <summary>
    /// Determines the maximum of blocks which will be closed.
    /// </summary>
    public IValueParameter<IntValue> MaxCloseParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_CLOSE_PARAMETER_NAME]; }
    }

    public int MaxClose {
      get { return MaxCloseParameter.Value.Value; }
      set {
        MaxCloseParameter.Value.Value = value;
      }
    }

    /// <summary>
    /// This is the maximum of depth a push program can have. Expressions, which lead to exceed this limit are interpreted as NOOP.
    /// </summary>
    public IValueParameter<IntValue> MaxDepthParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_DEPTH_PARAMETER_NAME]; }
    }

    public int MaxDepth {
      get { return MaxDepthParameter.Value.Value; }
      set {
        MaxDepthParameter.Value.Value = value;
      }
    }

    public IValueParameter<IntValue> MinProgramLengthParameter {
      get { return (IValueParameter<IntValue>)Parameters[MIN_PROGRAM_LENGTH]; }
    }

    public int MinProgramLength {
      get { return MinProgramLengthParameter.Value.Value; }
      set {
        MinProgramLengthParameter.Value.Value = value;
      }
    }

    /// <summary>
    ///     This is the maximum size of an item on the CODE stack, expressed as a number of points.
    ///     A point is an instruction, a literal, or a pair of parentheses. Any instruction that would cause this limit to be
    ///     exceeded should instead act as a NOOP, leaving all stacks in the states that they were in before the execution of the
    ///     instruction.
    /// </summary>
    public IValueParameter<IntValue> MaxProgramLengthParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_PROGRAM_LENGTH]; }
    }

    public int MaxProgramLength {
      get { return MaxProgramLengthParameter.Value.Value; }
      set {
        MaxProgramLengthParameter.Value.Value = value;
      }
    }

    public IValueParameter<IntValue> MaxVectorLengthParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_VECTOR_LENGTH_PARAMETER_NAME]; }
    }

    public int MaxVectorLength {
      get { return MaxVectorLengthParameter.Value.Value; }
      set { MaxVectorLengthParameter.Value.Value = value; }
    }

    /// <summary>
    ///     The maximum number of points in an expression produced by the CODE.RAND instruction.
    /// </summary>
    public IValueParameter<IntValue> MaxPointsInRandomExpressionParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_POINTS_IN_RANDOM_INSTRUCTION_PARAMETER_NAME]; }
    }

    public int MaxPointsInRandomExpression {
      get { return MaxPointsInRandomExpressionParameter.Value.Value; }
      set {
        MaxPointsInRandomExpressionParameter.Value.Value = value;
      }
    }

    /// <summary>
    ///     When TRUE (which is the default), code passed to the top level of the interpreter
    ///     will be pushed onto the CODE stack prior to execution.
    /// </summary>
    public IValueParameter<BoolValue> TopLevelPushCodeParameter {
      get { return (IValueParameter<BoolValue>)Parameters[TOP_LEVEL_PUSH_CODE_PARAMETER_NAME]; }
    }

    public bool TopLevelPushCode {
      get { return TopLevelPushCodeParameter.Value.Value; }
      set {
        TopLevelPushCodeParameter.Value.Value = value;
      }
    }

    /// <summary>
    ///     When TRUE, the CODE stack will be popped at the end of top level calls to the interpreter. The default is FALSE.
    /// </summary>
    public IValueParameter<BoolValue> TopLevelPopCodeParameter {
      get { return (IValueParameter<BoolValue>)Parameters[TOP_LEVEL_POP_CODE_PARAMETER_NAME]; }
    }

    public bool TopLevelPopCode {
      get { return TopLevelPopCodeParameter.Value.Value; }
      set {
        TopLevelPopCodeParameter.Value.Value = value;
      }
    }

    public IValueParameter<IntValue> MaxStringLengthParameter {
      get { return (IValueParameter<IntValue>)Parameters[MAX_STRING_LENGTH_PARAMETER_NAME]; }
    }

    public int MaxStringLength {
      get { return MaxStringLengthParameter.Value.Value; }
      set { MaxStringLengthParameter.Value.Value = value; }
    }

    public IValueParameter<BoolValue> TopLevelPushInputArgumentsParameter {
      get { return (IValueParameter<BoolValue>)Parameters[TOP_LEVEL_PUSH_INPUT_ARGUMENTS]; }
    }

    public bool TopLevelPushInputArguments {
      get { return TopLevelPushInputArgumentsParameter.Value.Value; }
      set { TopLevelPushInputArgumentsParameter.Value.Value = value; }
    }
  }
}