/*
 * Copyright © 2006-2026 Dynare Team
 *
 * This file is part of Dynare.
 *
 * Dynare 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.
 *
 * Dynare 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 Dynare.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef STATEMENT_HH
#define STATEMENT_HH

#include <map>
#include <optional>
#include <ostream>
#include <set>
#include <string>
#include <variant>

#include "SymbolList.hh"
#include "WarningConsolidation.hh"

// Helper constant for visitors
template<class>
inline constexpr bool always_false_v {false};

struct ModFileStructure
{
  //! Whether check is present
  bool check_present {false};
  //! Whether steady is present
  bool steady_present {false};
  //! Whether a perfect_foresight_setup/simul statement is present
  bool perfect_foresight_setup_present {false};
  //! Whether a perfect_foresight_solver/simul statement is present
  bool perfect_foresight_solver_present {false};
  //! Whether a perfect_foresight_with_expectation_errors_setup statement is present
  bool perfect_foresight_with_expectation_errors_setup_present {false};
  //! Whether a perfect_foresight_with_expectation_errors_solver statement is present
  bool perfect_foresight_with_expectation_errors_solver_present {false};
  //! Whether a stoch_simul statement is present
  bool stoch_simul_present {false};
  //! Whether an estimation statement is present
  bool estimation_present {false};
  //! Whether an osr statement is present
  bool osr_present {false};
  //! Whether an osr params statement is present
  bool osr_params_present {false};
  //! Whether an optim weight statement is present
  bool optim_weights_present {false};
  //! Whether a ramsey_model statement is present
  bool ramsey_model_present {false};
  //! Whether a discretionary_objective statement is present
  bool discretionary_policy_present {false};
  //! Whether a planner_objective statement is present
  bool planner_objective_present {false};
  //! Whether an extended_path statement is present
  bool extended_path_present {false};
  //! The hybrid order for extended_path (0 if not specified)
  int extended_path_hybrid_order {0};
  //! The stochastic order for extended_path (0 means deterministic)
  int extended_path_order {0};
  //! The value of the "order" option of stoch_simul, estimation, osr, ramsey_policy
  //! Derivation order
  /*! First initialized to zero. If user sets order option somewhere in the MOD file, it will be
   * equal to the maximum of order options. Otherwise will default to 2 */
  int order_option {0};
  //! Whether a bvar_density, bvar_forecast, sbvar, ms_sbvar statement is present
  bool bvar_present {false};
  //! Whether an svar_identification statement is present
  bool svar_identification_present {false};
  //! Whether an identification statement is present or the identification option of
  //! sensitivity statement is equal to one
  bool identification_present {false};
  //! The maximum of the “order” option in identification statements
  int identification_order {0};
  //! Whether a sensitivity statement is present
  bool sensitivity_present {false};
  //! Whether the option analytic_derivation is given to estimation
  bool estimation_analytic_derivation {false};
  //! Whether the option analytic_derivation is given to osr
  bool osr_analytic_derivation {false};
  //! Whether the option partial_information is given to stoch_simul/estimation/osr/ramsey_policy
  bool partial_information {false};
  //! Whether the "k_order_solver" option is used (explictly, or implicitly if order >= 3)
  bool k_order_solver {false};
  //! Whether an method_of_moments statement is present
  bool mom_estimation_present {false};
  //! Whether an GMM-option is present
  bool GMM_present {false};
  //! Whether an analytic_standard_errors-option is present
  bool analytic_standard_errors_present {false};
  //! Whether an analytic_jacobian-option is present
  bool analytic_jacobian_present {false};
  //! The maximum of the “order” option in method_of_moments statements
  int mom_order {0};
  //! Whether there is a calibrated measurement error
  bool calibrated_measurement_errors {false};
  //! Whether dsge_prior_weight is in the estimated_params block
  bool dsge_prior_weight_in_estimated_params {false};
  //! Whether there is a dsge_var, with calibrated prior weight
  string dsge_var_calibrated;
  //! Whether there is a dsge_var, with prior weight that must be estimated
  bool dsge_var_estimated {false};
  //! Whether there is a bayesian_irf option passed to the estimation statement
  bool bayesian_irf_present {false};
  //! Whether there is a data statement present
  bool estimation_data_statement_present {false};
  //! Last chain number for Markov Switching statement2
  int last_markov_switching_chain {0};
  //! Whether a calib_smoother statement is present
  bool calib_smoother_present {false};
  //! Whether there is an estimated_params_init with use_calibration
  bool estim_params_use_calib {false};
  //! Set of parameters used within shocks blocks, inside the expressions
  //! defining the values of covariances (stored as symbol ids)
  set<int> parameters_within_shocks_values;
  //! Set of estimated parameters (stored as symbol ids)
  set<int> estimated_parameters;
  //! Whether there is a prior statement present
  bool prior_statement_present {false};
  //! Whether there is a std prior statement present
  bool std_prior_statement_present {false};
  //! Whether there is a corr prior statement present
  bool corr_prior_statement_present {false};
  //! Whether there is a options statement present
  bool options_statement_present {false};
  //! Whether there is a std options statement present
  bool std_options_statement_present {false};
  //! Whether there is a corr options statement present
  bool corr_options_statement_present {false};
  //! Whether a Markov Switching DSGE is present
  bool ms_dsge_present {false};
  /* The number of equations in the model present just before adding the
     Lagrange multipliers and computing the Ramsey FOC; it is by construction
     equal to the number of Lagrange multipliers that will be added by the
     process of computing the FOCs. May be greater than the number of equations
     written by the user, because some auxiliary variables may have already
     been added. */
  int ramsey_orig_eq_nbr {0};
  /* The number of endogenous variables in the model present just before adding
     the Lagrange multipliers and computing the Ramsey FOC; it is by
     construction equal to the number of equations that will be added by the
     process of computing the FOCs. May be greater than the number of
     endogenous declared by the user, because some auxiliary variables may have
     already been added. */
  int ramsey_orig_endo_nbr {0};
  //! Whether there was a steady_state_model block
  bool steady_state_model_present {false};
  //! Whether there is a write_latex_steady_state_model statement present
  bool write_latex_steady_state_model_present {false};
  //! Pac growth and discount
  set<int> pac_params;
  //! Instruments if ramsey_model, ramsey_policy or discretionary_policy is present
  SymbolList instruments;
  /* Whether any of shock_decomposition, realtime_shock_decomposition and
     initial_condition_decomposition has the “with_epilogue” option */
  bool with_epilogue_option {false};
  /* Lists symbol IDs of parameters that appear in a “planner_discount” option.
     See dynare#1173 for more details. */
  set<int> parameters_in_planner_discount;
  // Whether an endval block (without the learnt_it=… option) appears
  bool endval_present {false};
  // Whether a plain shocks block appears (no surprise, no learnt_in option)
  bool shocks_present {false};
  // Whether an mshocks block appears
  bool mshocks_present {false};
  // Whether a shocks(surprise) block appears
  bool shocks_surprise_present {false};
  // Whether a shocks(learnt_in=…) block appears
  bool shocks_learnt_in_present {false};
  // Whether an endval(learnt_in=…) block appears
  bool endval_learnt_in_present {false};
  // Whether a perfect_foresight_controlled_paths block appears
  bool perfect_foresight_controlled_paths_present {false};
  // Whether a perfect_foresight_controlled_paths(learnt_in=p) block (with p≠1) appears
  bool perfect_foresight_controlled_paths_learnt_in_present {false};
  /* Number of shock_paths blocks. Also used during checkPass() to compute a unique index for each
     shock_paths block*/
  int shock_paths_number {0};
  // Whether there is a shock_paths(learnt_in=p) block (with p≠1)
  bool shock_paths_learnt_in_present {false};
  // Whether an occbin_constraints block appears
  bool occbin_constraints_present {false};

  // Returns true if we are in a stochastic context
  [[nodiscard]] bool
  isStochasticContext() const
  {
    return stoch_simul_present || estimation_present || osr_present || discretionary_policy_present
           || calib_smoother_present || identification_present || mom_estimation_present
           || sensitivity_present || (extended_path_present && order_option > 0);
  }
};

class Statement
{
public:
  Statement() = default;
  virtual ~Statement() = default;

  Statement(const Statement&) = delete;
  Statement& operator=(const Statement&) = delete;

  //! Do some internal check, and fill the ModFileStructure class
  /*! Don't forget to update ComputingTasks.hh, Shocks.hh and
    NumericalInitialization.hh if you modify the signature of this
    method. Otherwise the default implementation (i.e. a no-op) will apply and
    some checks won't be run. */
  virtual void checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings);
  virtual void computingPass(const ModFileStructure& mod_file_struct);
  //! Write Matlab output code
  /*!
    \param output is the output stream of the main matlab file
    \param basename is the name of the modfile (without extension) which can be used to build
    auxiliary files
  */
  virtual void writeOutput(ostream& output, const string& basename, bool minimal_workspace) const
      = 0;
  virtual void writeJsonOutput(ostream& output) const = 0;
};

class NativeStatement : public Statement
{
private:
  const string native_statement;

public:
  explicit NativeStatement(string native_statement_arg);
  void writeOutput(ostream& output, const string& basename, bool minimal_workspace) const override;
  void writeJsonOutput(ostream& output) const override;
};

class VerbatimStatement : public Statement
{
private:
  const string verbatim_statement;

public:
  explicit VerbatimStatement(string verbatim_statement_arg);
  void writeOutput(ostream& output, const string& basename, bool minimal_workspace) const override;
  void writeJsonOutput(ostream& output) const override;
};

/* Stores a list of named options with their values.
   The values are stored using an std::variant; see the “options” data member
   for the list of available types. */
class OptionsList
{
public:
  // Some types to lift ambiguities
  struct NumVal : string
  {
  };
  struct StringVal : string
  {
  };
  struct DateVal : string
  {
  };
  struct SymbolListVal : SymbolList
  {
    /* This one is needed because vector<string> implicitly converts to
       SymbolList. Otherwise adding a vector<string> to the variant would add a
       SymbolList, which is probably not the intended meaning. */
  };
  struct VecStrVal : vector<string>
  {
  };
  struct VecCellStrVal : vector<string>
  {
  };
  struct VecValueVal : vector<string>
  {
  };
  struct StrOrIntListVal : vector<string>
  {
    /* Mixed list of strings and integers, for calibration_target_equations.
       Outputs as MATLAB cell array with proper typing: {'str', 1, 'str2'} */
  };

  // pair<string, string> corresponds to a pair of numerical values
  // vector<vector<string>> corresponds to a vector of vectors of numerical values
  using OptionValue
      = variant<NumVal, pair<string, string>, StringVal, DateVal, SymbolListVal, vector<int>,
                VecStrVal, VecCellStrVal, VecValueVal, StrOrIntListVal, vector<vector<string>>>;

  [[nodiscard]] bool empty() const;
  void clear();
  // Whether there is an option with that name that has been given a value
  [[nodiscard]] bool contains(const string& name) const;
  // Erase the option with that name
  void erase(const string& name);

  /* Declares an option with a name and value. Overwrite any previous value for
     that name. */
  template<class T>
  void
  set(string name, T&& val)
  {
    options.insert_or_assign(move(name), forward<T>(val));
  }

  struct UnknownOptionException
  {
    const string name;
    UnknownOptionException(string name_arg) : name {move(name_arg)}
    {
    }
  };

  /* Retrieves the value of the option with that name.
     Throws UnknownOptionException if there is no option with that name.
     Throws bad_variant_access if the option has a value of a different type. */
  template<typename T>
    requires requires(T p) { std::get<T>(OptionValue {}); }
  T
  get(const string& name) const
  {
    auto it = options.find(name);
    if (it != options.end())
      return std::get<T>(it->second);
    else
      throw UnknownOptionException {name};
  }

  /* Retrieves the value of the option with that name.
     Returns nullopt if there is no option with that name.
     Throws bad_variant_access if the option has a value of a different type. */
  template<typename T>
    requires requires(T p) { std::get<T>(OptionValue {}); }
  optional<T>
  get_if(const string& name) const
  {
    auto it = options.find(name);
    if (it != options.end())
      return std::get<T>(it->second);
    else
      return nullopt;
  }

  /* Applies a variant visitor to the value of the option with that name.
     Throws UnknownOptionException if there is no option with that name. */
  template<class Visitor>
    requires invocable<Visitor, OptionValue>
  decltype(auto)
  visit(const string& name, Visitor&& vis) const
  {
    auto it = options.find(name);
    if (it != options.end())
      return std::visit(forward<Visitor>(vis), it->second);
    else
      throw UnknownOptionException {name};
  }

  void writeOutput(ostream& output) const;
  void writeOutput(ostream& output, const string& option_group) const;
  void writeJsonOutput(ostream& output) const;

private:
  map<string, OptionValue> options;
  void writeOutputCommon(ostream& output, const string& option_group) const;
};

#endif
