/*
 * Copyright © 2003-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/>.
 */

#include <algorithm>
#include <cassert>
#include <iostream>
#include <iterator>
#include <ranges>
#include <sstream>

using namespace std;

#include "ComputingTasks.hh"
#include "ParsingDriver.hh"
#include "Statement.hh"

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wold-style-cast"
#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/tokenizer.hpp>
#pragma GCC diagnostic pop

#include <algorithm>
#include <utility>

SteadyStatement::SteadyStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
SteadyStatement::checkPass(ModFileStructure& mod_file_struct,
                           [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.steady_present = true;
}

void
SteadyStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                             [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "steady;" << '\n';
}

void
SteadyStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "steady")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

CheckStatement::CheckStatement(OptionsList options_list_arg) : options_list {move(options_list_arg)}
{
}

void
CheckStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "oo_.dr.eigval = check(M_,options_,oo_);" << '\n';
}

void
CheckStatement::checkPass(ModFileStructure& mod_file_struct,
                          [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.check_present = true;
}

void
CheckStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "check")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

ModelInfoStatement::ModelInfoStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
ModelInfoStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_model_info_");
  output << "model_info(options_model_info_);" << '\n';
}

void
ModelInfoStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "model_info")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

SimulStatement::SimulStatement(OptionsList options_list_arg) : options_list {move(options_list_arg)}
{
}

void
SimulStatement::checkPass(ModFileStructure& mod_file_struct,
                          [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.perfect_foresight_setup_present = true;
  mod_file_struct.perfect_foresight_solver_present = true;
}

void
SimulStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  // Translate the “datafile” option into “initval_file” (see dynare#1663)
  auto options_list_new = options_list; // Need a copy, because of const
  if (auto opt = options_list_new.get_if<OptionsList::StringVal>("datafile"))
    {
      output << "options_.initval_file = true;" << '\n'
             << "options_initvalf = struct();" << '\n'
             << "options_initvalf.datafile = '" << *opt << "';" << '\n'
             << "oo_.initval_series = histvalf_initvalf('INITVALF', M_, options_initvalf);" << '\n';
      options_list_new.erase("datafile");
    }
  options_list_new.writeOutput(output);
  output << "oo_ = perfect_foresight_setup(M_, options_, oo_);" << '\n'
         << "[oo_, Simulated_time_series] = perfect_foresight_solver(M_, options_, oo_);" << '\n';
}

void
SimulStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "simul")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PerfectForesightSetupStatement::PerfectForesightSetupStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
PerfectForesightSetupStatement::checkPass(ModFileStructure& mod_file_struct,
                                          [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.perfect_foresight_setup_present = true;
}

void
PerfectForesightSetupStatement::writeOutput(ostream& output,
                                            [[maybe_unused]] const string& basename,
                                            [[maybe_unused]] bool minimal_workspace) const
{
  auto options_list_new = options_list; // Need a copy, because of const
  if (auto opt = options_list_new.get_if<OptionsList::StringVal>("datafile"))
    {
      output << "options_.initval_file = true;" << '\n'
             << "options_initvalf = struct();" << '\n'
             << "options_initvalf.datafile = '" << *opt << "';" << '\n'
             << "oo_.initval_series = histvalf_initvalf('INITVALF', M_, options_initvalf);" << '\n';
      options_list_new.erase("datafile");
    }
  options_list_new.writeOutput(output);
  output << "oo_ = perfect_foresight_setup(M_, options_, oo_);" << '\n';
}

void
PerfectForesightSetupStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "perfect_foresight_setup")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PerfectForesightSolverStatement::PerfectForesightSolverStatement(OptionsList options_list_arg) :
    options_list(move(options_list_arg))
{
}

void
PerfectForesightSolverStatement::checkPass(ModFileStructure& mod_file_struct,
                                           [[maybe_unused]] WarningConsolidation& warnings)
{
  if (!mod_file_struct.perfect_foresight_setup_present)
    {
      cerr << "ERROR: A 'perfect_foresight_setup' command must come before "
              "'perfect_foresight_solver'"
           << '\n';
      exit(EXIT_FAILURE);
    }
  mod_file_struct.perfect_foresight_solver_present = true;
}

void
PerfectForesightSolverStatement::writeOutput(ostream& output,
                                             [[maybe_unused]] const string& basename,
                                             [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "[oo_, Simulated_time_series] = perfect_foresight_solver(M_, options_, oo_);" << '\n';
}

void
PerfectForesightSolverStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "perfect_foresight_solver")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PerfectForesightWithExpectationErrorsSetupStatement::
    PerfectForesightWithExpectationErrorsSetupStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
PerfectForesightWithExpectationErrorsSetupStatement::checkPass(
    ModFileStructure& mod_file_struct, [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.perfect_foresight_with_expectation_errors_setup_present = true;
}

void
PerfectForesightWithExpectationErrorsSetupStatement::writeOutput(
    ostream& output, [[maybe_unused]] const string& basename,
    [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "oo_ = perfect_foresight_with_expectation_errors_setup(M_, options_, oo_);" << '\n';
}

void
PerfectForesightWithExpectationErrorsSetupStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "perfect_foresight_with_expectation_errors_setup")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PerfectForesightWithExpectationErrorsSolverStatement::
    PerfectForesightWithExpectationErrorsSolverStatement(OptionsList options_list_arg) :
    options_list(move(options_list_arg))
{
}

void
PerfectForesightWithExpectationErrorsSolverStatement::checkPass(
    ModFileStructure& mod_file_struct, [[maybe_unused]] WarningConsolidation& warnings)
{
  if (!mod_file_struct.perfect_foresight_with_expectation_errors_setup_present)
    {
      cerr << "ERROR: A 'perfect_foresight_with_expectation_errors_setup' command must come before "
              "'perfect_foresight_with_expectation_errors_solver'"
           << '\n';
      exit(EXIT_FAILURE);
    }
  mod_file_struct.perfect_foresight_with_expectation_errors_solver_present = true;
}

void
PerfectForesightWithExpectationErrorsSolverStatement::writeOutput(
    ostream& output, [[maybe_unused]] const string& basename,
    [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "[oo_, Simulated_time_series] = perfect_foresight_with_expectation_errors_solver(M_, "
            "options_, oo_);"
         << '\n';
}

void
PerfectForesightWithExpectationErrorsSolverStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "perfect_foresight_with_expectation_errors_solver")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PriorPosteriorFunctionStatement::PriorPosteriorFunctionStatement(const bool prior_func_arg,
                                                                 OptionsList options_list_arg) :
    prior_func {prior_func_arg}, options_list {move(options_list_arg)}
{
}

void
PriorPosteriorFunctionStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                           [[maybe_unused]] WarningConsolidation& warnings)
{
  if (auto opt = options_list.get_if<OptionsList::StringVal>("function"); !opt || opt->empty())
    {
      cerr << "ERROR: both the 'prior_function' and 'posterior_function' commands require the "
              "'function' option"
           << '\n';
      exit(EXIT_FAILURE);
    }
}

void
PriorPosteriorFunctionStatement::writeOutput(ostream& output,
                                             [[maybe_unused]] const string& basename,
                                             [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  string type = prior_func ? "prior" : "posterior";

  output << "oo_ = execute_prior_posterior_function("
         << "'" << options_list.get<OptionsList::StringVal>("function") << "', "
         << "M_, options_, oo_, estim_params_, dataset_, dataset_info, "
         << "'" << type << "');" << '\n';
}

void
PriorPosteriorFunctionStatement::writeJsonOutput(ostream& output) const
{
  string type = prior_func ? "prior" : "posterior";
  output << R"({"statementName": "prior_posterior_function", "type": ")" << type << R"(")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

StochSimulStatement::StochSimulStatement(SymbolList symbol_list_arg, OptionsList options_list_arg,
                                         const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
StochSimulStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  mod_file_struct.stoch_simul_present = true;

  // Fill in option_order of mod_file_struct
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    mod_file_struct.order_option = max(mod_file_struct.order_option, stoi(*opt));

  // Fill in mod_file_struct.partial_information
  if (auto opt = options_list.get_if<OptionsList::NumVal>("partial_information");
      opt && *opt == "true")
    mod_file_struct.partial_information = true;

  // Option k_order_solver (implicit when order >= 3)
  if (auto opt = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt && *opt == "true") || mod_file_struct.order_option >= 3)
    mod_file_struct.k_order_solver = true;

  if (bool hp = options_list.contains("hp_filter"),
      bandpass = options_list.contains("bandpass.indicator"),
      one_sided_hp = options_list.contains("one_sided_hp_filter");
      (hp && bandpass) || (hp && one_sided_hp) || (bandpass && one_sided_hp))
    {
      cerr << "ERROR: stoch_simul: can only use one of HP, one-sided HP, and bandpass filters"
           << '\n';
      exit(EXIT_FAILURE);
    }

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: stoch_simul: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
StochSimulStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  // Ensure that order 3 implies k_order (#844)
  if (auto opt1 = options_list.get_if<OptionsList::NumVal>("order"),
      opt2 = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt2 && *opt2 == "true") || (opt1 && stoi(*opt1) >= 3))
    output << "options_.k_order_solver = true;" << '\n';

  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "[info, oo_, options_, M_] = stoch_simul(M_, options_, oo_, var_list_);" << '\n';
}

void
StochSimulStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "stoch_simul")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

ForecastStatement::ForecastStatement(SymbolList symbol_list_arg, OptionsList options_list_arg,
                                     const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
ForecastStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                             WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: forecast: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
ForecastStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                               [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "oo_.forecast = forecasts.run(var_list_,M_,options_,oo_,'simul');" << '\n';
}

void
ForecastStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "forecast")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

RamseyModelStatement::RamseyModelStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
RamseyModelStatement::checkPass(ModFileStructure& mod_file_struct,
                                [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.ramsey_model_present = true;

  // Fill list of instruments
  if (auto opt = options_list.get_if<OptionsList::SymbolListVal>("instruments"))
    mod_file_struct.instruments = *opt;
}

void
RamseyModelStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
}

void
RamseyModelStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ramsey_model")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

RamseyPolicyStatement::RamseyPolicyStatement(SymbolList symbol_list_arg,
                                             OptionsList options_list_arg,
                                             const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
RamseyPolicyStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  // Copied from RamseyModelStatement::checkPass()
  mod_file_struct.ramsey_model_present = true;

  if (auto opt = options_list.get_if<OptionsList::SymbolListVal>("instruments"))
    mod_file_struct.instruments = *opt;

  // Copied from StochSimulStatement::checkPass()
  mod_file_struct.stoch_simul_present = true;

  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    mod_file_struct.order_option = max(mod_file_struct.order_option, stoi(*opt));

  if (auto opt = options_list.get_if<OptionsList::NumVal>("partial_information");
      opt && *opt == "true")
    mod_file_struct.partial_information = true;

  if (auto opt = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt && *opt == "true") || mod_file_struct.order_option >= 3)
    mod_file_struct.k_order_solver = true;

  if (bool hp = options_list.contains("hp_filter"),
      bandpass = options_list.contains("bandpass.indicator"),
      one_sided_hp = options_list.contains("one_sided_hp_filter");
      (hp && bandpass) || (hp && one_sided_hp) || (bandpass && one_sided_hp))
    {
      cerr << "ERROR: ramsey_policy: can only use one of hp, one-sided hp, and bandpass filters"
           << '\n';
      exit(EXIT_FAILURE);
    }

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: ramsey_policy: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
RamseyPolicyStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  // Ensure that order 3 implies k_order (#844)
  if (auto opt1 = options_list.get_if<OptionsList::NumVal>("order"),
      opt2 = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt2 && *opt2 == "true") || (opt1 && stoi(*opt1) >= 3))
    output << "options_.k_order_solver = true;" << '\n';

  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "[info, oo_, options_, M_] = stoch_simul(M_, options_, oo_, var_list_);" << '\n'
         << "oo_.steady_state = oo_.dr.ys;" << '\n'
         << "oo_.planner_objective_value = evaluate_planner_objective(M_, options_, oo_);" << '\n';
}

void
RamseyPolicyStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ramsey_policy")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

EvaluatePlannerObjectiveStatement::EvaluatePlannerObjectiveStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
EvaluatePlannerObjectiveStatement::writeOutput(ostream& output,
                                               [[maybe_unused]] const string& basename,
                                               [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "oo_.planner_objective_value = evaluate_planner_objective(M_, options_, oo_);" << '\n';
}

void
EvaluatePlannerObjectiveStatement::writeJsonOutput(ostream& output) const
{
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << R"({"statementName": "evaluate_planner_objective"})";
}

DiscretionaryPolicyStatement::DiscretionaryPolicyStatement(SymbolList symbol_list_arg,
                                                           OptionsList options_list_arg,
                                                           const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
DiscretionaryPolicyStatement::checkPass(ModFileStructure& mod_file_struct,
                                        WarningConsolidation& warnings)
{
  mod_file_struct.discretionary_policy_present = true;

  if (!options_list.contains("instruments"))
    {
      cerr << "ERROR: discretionary_policy: the instruments option is required." << '\n';
      exit(EXIT_FAILURE);
    }

  /* Fill in option_order of mod_file_struct
     Since discretionary policy needs one further order of derivation (for example, for 1st order
     approximation, it needs 2nd derivatives), we add 1 to the order declared by user */
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    {
      int order = stoi(*opt);
      if (order > 1)
        {
          cerr << "ERROR: discretionary_policy: order > 1 is not yet implemented" << '\n';
          exit(EXIT_FAILURE);
        }
      mod_file_struct.order_option = max(mod_file_struct.order_option, order + 1);
    }

  // Fill in mod_file_struct.partial_information
  if (auto opt = options_list.get_if<OptionsList::NumVal>("partial_information");
      opt && *opt == "true")
    mod_file_struct.partial_information = true;

  // Option k_order_solver (implicit when order >= 3)
  if (auto opt = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt && *opt == "true") || mod_file_struct.order_option >= 3)
    mod_file_struct.k_order_solver = true;

  // Fill list of instruments
  if (auto opt = options_list.get_if<OptionsList::SymbolListVal>("instruments"))
    mod_file_struct.instruments = *opt;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: discretionary_policy: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
DiscretionaryPolicyStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                          [[maybe_unused]] bool minimal_workspace) const
{
  // Ensure that order 3 implies k_order (#844)
  if (auto opt1 = options_list.get_if<OptionsList::NumVal>("order"),
      opt2 = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt2 && *opt2 == "true") || (opt1 && stoi(*opt1) >= 3))
    output << "options_.k_order_solver = true;" << '\n';

  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "[info, oo_, options_, M_] = discretionary_policy(M_, options_, oo_, var_list_);"
         << '\n';
}

void
DiscretionaryPolicyStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "discretionary_policy")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

OccbinSetupStatement::OccbinSetupStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
OccbinSetupStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_occbin_");
  output << "[M_, options_] = occbin.setup(M_, options_, options_occbin_);" << '\n';
}

void
OccbinSetupStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "occbin_setup")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

OccbinSolverStatement::OccbinSolverStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
OccbinSolverStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_.occbin");
  output << "[oo_.dr, oo_.occbin.simul]= occbin.solver(M_, options_, oo_.dr , oo_.steady_state, "
            "oo_.exo_steady_state, oo_.exo_det_steady_state);"
         << '\n';
}

void
OccbinSolverStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "occbin_solver")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

OccbinWriteRegimesStatement::OccbinWriteRegimesStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
OccbinWriteRegimesStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_.occbin");
  output << "occbin.write_regimes_to_xls(oo_.occbin, M_, options_);" << '\n';
}

void
OccbinWriteRegimesStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "occbin_write_regimes_xls")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

OccbinGraphStatement::OccbinGraphStatement(SymbolList symbol_list_arg,
                                           OptionsList options_list_arg) :
    symbol_list {move(symbol_list_arg)}, options_list {move(options_list_arg)}
{
}

void
OccbinGraphStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("var_list_", output);
  options_list.writeOutput(output, "options_occbin_");
  output << "occbin.graph(M_, options_, options_occbin_, oo_, var_list_);" << '\n';
}

void
OccbinGraphStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "occbin_graph")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

EstimationStatement::EstimationStatement(const SymbolTable& symbol_table_arg,
                                         SymbolList symbol_list_arg, OptionsList options_list_arg) :
    symbol_table {symbol_table_arg},
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)}
{
}

void
EstimationStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  mod_file_struct.estimation_present = true;

  // Fill in option_order of mod_file_struct
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    {
      int order = stoi(*opt);

      if (order > 2)
        mod_file_struct.k_order_solver = true;

      mod_file_struct.order_option = max(mod_file_struct.order_option, order);
    }

  // Fill in mod_file_struct.partial_information
  if (auto opt = options_list.get_if<OptionsList::NumVal>("partial_information");
      opt && *opt == "true")
    mod_file_struct.partial_information = true;

  // Fill in mod_file_struct.estimation_analytic_derivation
  if (auto opt = options_list.get_if<OptionsList::NumVal>("analytic_derivation");
      opt && *opt == "true")
    mod_file_struct.estimation_analytic_derivation = true;

  if (options_list.contains("dsge_var"))
    options_list.visit("dsge_var", [&]<class T>(const T& v) {
      if constexpr (is_same_v<T, OptionsList::StringVal>)
        mod_file_struct.dsge_var_estimated = true;
      else if constexpr (is_same_v<T, OptionsList::NumVal>)
        mod_file_struct.dsge_var_calibrated = v;
    });

  // Fill in mod_file_struct.bayesian_irf_present
  if (auto opt = options_list.get_if<OptionsList::NumVal>("bayesian_irf"); opt && *opt == "true")
    mod_file_struct.bayesian_irf_present = true;

  if (options_list.contains("dsge_varlag"))
    if (mod_file_struct.dsge_var_calibrated.empty() && !mod_file_struct.dsge_var_estimated)
      {
        cerr << "ERROR: The estimation statement requires a dsge_var option to be passed "
             << "if the dsge_varlag option is passed." << '\n';
        exit(EXIT_FAILURE);
      }

  if (!mod_file_struct.dsge_var_calibrated.empty() && mod_file_struct.dsge_var_estimated)
    {
      cerr << "ERROR: An estimation statement cannot take more than one dsge_var option." << '\n';
      exit(EXIT_FAILURE);
    }

  if (!options_list.contains("datafile") && !mod_file_struct.estimation_data_statement_present)
    {
      cerr << "ERROR: The estimation statement requires a data file to be supplied via the "
              "datafile option."
           << '\n';
      exit(EXIT_FAILURE);
    }

  if (options_list.contains("mode_file") && mod_file_struct.estim_params_use_calib)
    {
      cerr << "ERROR: The mode_file option of the estimation statement is incompatible with the "
              "use_calibration option of the estimated_params_init block."
           << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt = options_list.get_if<OptionsList::NumVal>("mh_tune_jscale.status");
      opt && *opt == "true")
    {
      if (options_list.contains("mh_jscale"))
        {
          cerr << "ERROR: The mh_tune_jscale and mh_jscale options of the estimation statement are "
                  "incompatible."
               << '\n';
          exit(EXIT_FAILURE);
        }
    }
  else if (options_list.contains("mh_tune_jscale.guess"))
    {
      cerr << "ERROR: The option mh_tune_guess in estimation statement cannot be used without "
              "option mh_tune_jscale."
           << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt_filter = options_list.get_if<OptionsList::StringVal>("particle.filter_algorithm");
      opt_filter && *opt_filter == "gmf")
    {
      if (auto opt_proposal
          = options_list.get_if<OptionsList::NumVal>("particle.proposal_approximation.montecarlo");
          opt_proposal && *opt_proposal == "true")
        {
          cerr << "ERROR: The filter_algorithm=gmf option is incompatible with "
                  "proposal_approximation=montecarlo in the estimation statement."
               << '\n';
          exit(EXIT_FAILURE);
        }
      if (auto opt_distribution = options_list.get_if<OptionsList::NumVal>(
              "particle.distribution_approximation.montecarlo");
          opt_distribution && *opt_distribution == "true")
        {
          cerr << "ERROR: The filter_algorithm=gmf option is incompatible with "
                  "distribution_approximation=montecarlo in the estimation statement."
               << '\n';
          exit(EXIT_FAILURE);
        }
    }

  /* Check that we are not trying to estimate a parameter appearing in the
     planner discount factor (see dynare#1173) */
  vector<int> estimated_params_in_planner_discount;
  ranges::set_intersection(mod_file_struct.estimated_parameters,
                           mod_file_struct.parameters_in_planner_discount,
                           back_inserter(estimated_params_in_planner_discount));
  if (!estimated_params_in_planner_discount.empty())
    {
      cerr << "ERROR: It is not possible to estimate a parameter ("
           << symbol_table.getName(estimated_params_in_planner_discount[0])
           << ") that appears in the discount factor of the planner (i.e. in the "
              "'planner_discount' option)."
           << '\n';
      exit(EXIT_FAILURE);
    }

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: estimation: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
EstimationStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);

  // Special treatment for order option and particle filter
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"); !opt)
    output << "options_.order = 1;" << '\n';
  else if (int order {stoi(*opt)}; order >= 2)
    {
      output << "options_.particle.status = true;" << '\n';
      if (order > 2)
        output << "options_.k_order_solver = true;" << '\n';
    }

  // Do not check for the steady state in diffuse filter mode (#400)
  if (auto opt = options_list.get_if<OptionsList::NumVal>("diffuse_filter"); opt && *opt == "true")
    output << "options_.steadystate.nocheck = true;" << '\n';

  symbol_list.writeOutput("var_list_", output);
  output << "oo_recursive_=dynare_estimation(var_list_);" << '\n';
}

void
EstimationStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimation")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

SensitivityStatement::SensitivityStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
SensitivityStatement::checkPass(ModFileStructure& mod_file_struct,
                                [[maybe_unused]] WarningConsolidation& warnings)
{
  if (auto opt = options_list.get_if<OptionsList::NumVal>("identification"); opt && *opt == "1")
    {
      mod_file_struct.identification_present = true;
      // The following triggers 3rd order derivatives, see preprocessor#40
      mod_file_struct.identification_order = max(mod_file_struct.identification_order, 2);
    }
  mod_file_struct.sensitivity_present = true;
}

void
SensitivityStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_gsa");

  /* Ensure that nograph, nodisplay and graph_format are also set in top-level
     options_.
     \todo factorize this code between identification and dynare_sensitivity,
     and provide a generic mechanism for this situation (maybe using regexps).
     graph_format can then be turned into a VecCellStrVal. */
  if (auto opt = options_list.get_if<OptionsList::NumVal>("nodisplay"))
    output << "options_.nodisplay = " << *opt << ";" << '\n';
  if (auto opt = options_list.get_if<OptionsList::NumVal>("nograph"))
    output << "options_.nograph = " << *opt << ";" << '\n';
  if (auto opt = options_list.get_if<OptionsList::SymbolListVal>("graph_format"))
    opt->writeOutput("options_.graph_format", output);

  output << "gsa.run(M_,oo_,options_, estim_params_,options_gsa);" << '\n';
}

void
SensitivityStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "sensitivity")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

RplotStatement::RplotStatement(SymbolList symbol_list_arg, const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
RplotStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                          WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous, SymbolType::exogenous},
                            symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: rplot: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
RplotStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("var_list_", output);
  output << "rplot(var_list_);" << '\n';
}

void
RplotStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "rplot")";
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

DsampleStatement::DsampleStatement(int val1_arg) : val1 {val1_arg}, val2 {-1}
{
}

DsampleStatement::DsampleStatement(int val1_arg, int val2_arg) : val1 {val1_arg}, val2 {val2_arg}
{
}

void
DsampleStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                              [[maybe_unused]] bool minimal_workspace) const
{
  if (val2 < 0)
    output << "dsample(" << val1 << ");" << '\n';
  else
    output << "dsample(" << val1 << ", " << val2 << ");" << '\n';
}

void
DsampleStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "dsample", )"
         << R"("value1": )" << val1 << ", "
         << R"("value2": )" << val2 << "}";
}

AbstractEstimatedParamsStatement::AbstractEstimatedParamsStatement(
    vector<EstimationParams> estim_params_list_arg, const SymbolTable& symbol_table_arg) :
    estim_params_list {move(estim_params_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
AbstractEstimatedParamsStatement::commonCheckPass() const
{
  // Check that no parameter/endogenous is declared twice in the block.
  /* In the case of the estimated_params block, there is a similar check across
     concatenated blocks that is implemented in the writeOutput() method. */
  set<string> already_declared;
  set<string> already_declared_stderr;
  set<string> already_declared_skew;
  set<pair<string, string>> already_declared_corr;
  for (const auto& it : estim_params_list)
    {
      if (it.type == 3) // Correlation
        {
          // Use lexical ordering for the pair of symbols
          auto x = it.name < it.name2 ? pair {it.name, it.name2} : pair {it.name2, it.name};

          if (already_declared_corr.contains(x))
            {
              cerr << "ERROR: in `" << blockName() << "' block, the correlation between " << it.name
                   << " and " << it.name2 << " is declared twice." << '\n';
              exit(EXIT_FAILURE);
            }
          else
            already_declared_corr.insert(x);
        }
      else if (it.type == 1) // stderr
        {
          if (already_declared_stderr.contains(it.name))
            {
              cerr << "ERROR: in `" << blockName() << "' block, the stderr of " << it.name
                   << " is declared twice." << '\n';
              exit(EXIT_FAILURE);
            }
          else
            already_declared_stderr.insert(it.name);
        }
      else if (it.type == 4) // skew
        {
          if (already_declared_skew.contains(it.name))
            {
              cerr << "ERROR: in `" << blockName() << "' block, the skewness of " << it.name
                   << " is declared twice." << '\n';
              exit(EXIT_FAILURE);
            }
          else
            already_declared_skew.insert(it.name);
        }
      else
        {
          if (already_declared.contains(it.name))
            {
              cerr << "ERROR: in `" << blockName() << "' block, the symbol " << it.name
                   << " is declared twice." << '\n';
              exit(EXIT_FAILURE);
            }
          else
            already_declared.insert(it.name);
        }
    }

  /* Check that a parameter declared in this block is not used in expressions
     associated to other parameters in the same block (see issue #77) */
  // First compute the symbol IDs of parameters declared in this block
  set<int> declared_params;
  for (const string& name : already_declared)
    if (name != "dsge_prior_weight")
      declared_params.insert(symbol_table.getID(name));
  // Then look for (apparently) recursive definitions
  for (const auto& it : estim_params_list)
    {
      set<int> used_params;
      it.init_val->collectVariables(SymbolType::parameter, used_params);
      it.low_bound->collectVariables(SymbolType::parameter, used_params);
      it.up_bound->collectVariables(SymbolType::parameter, used_params);
      it.mean->collectVariables(SymbolType::parameter, used_params);
      it.std->collectVariables(SymbolType::parameter, used_params);
      it.p3->collectVariables(SymbolType::parameter, used_params);
      it.p4->collectVariables(SymbolType::parameter, used_params);
      it.jscale->collectVariables(SymbolType::parameter, used_params);
      vector<int> intersect;
      ranges::set_intersection(declared_params, used_params, back_inserter(intersect));
      if (!intersect.empty())
        {
          cerr << "ERROR: in `" << blockName() << "' block, the value of estimated parameter "
               << symbol_table.getName(intersect[0]) << " is used in the declaration for ";
          if (it.type == 3)
            cerr << "correlation between " << it.name << " and " << it.name2;
          else if (it.type == 4)
            cerr << "skewness of " << it.name;
          else // either a parameter, the stderr of an exo, or the measurement error of an endo
            cerr << "symbol " << it.name;
          cerr << ". This behaviour is undefined." << '\n';
          exit(EXIT_FAILURE);
        }
    }
}

EstimatedParamsStatement::EstimatedParamsStatement(vector<EstimationParams> estim_params_list_arg,
                                                   const SymbolTable& symbol_table_arg,
                                                   bool overwrite_arg) :
    AbstractEstimatedParamsStatement(move(estim_params_list_arg), symbol_table_arg),
    overwrite {overwrite_arg}
{
}

void
EstimatedParamsStatement::checkPass(ModFileStructure& mod_file_struct,
                                    [[maybe_unused]] WarningConsolidation& warnings)
{
  commonCheckPass();

  for (const auto& it : estim_params_list)
    {
      if (it.name == "dsge_prior_weight")
        mod_file_struct.dsge_prior_weight_in_estimated_params = true;

      // Check that skewness parameters are only for exogenous variables
      if (it.type == 4)
        {
          // First check if symbol exists
          try
            {
              if (symbol_table.getType(it.name) != SymbolType::exogenous)
                {
                  cerr << "ERROR: in `estimated_params' block, skewness can only be specified for "
                          "exogenous variables, not for '"
                       << it.name << "'." << '\n';
                  exit(EXIT_FAILURE);
                }
            }
          catch (SymbolTable::UnknownSymbolNameException& e)
            {
              cerr << "ERROR: in `estimated_params' block, unknown symbol: " << it.name << '\n';
              exit(EXIT_FAILURE);
            }
        }

      // Handle case of degenerate beta prior
      if (it.prior == PriorDistributions::beta)
        try
          {
            if (it.mean->eval(eval_context_t()) == 0.5 && it.std->eval(eval_context_t()) == 0.5)
              {
                cerr << "ERROR: The prior density is not defined for the beta distribution when "
                        "the mean = standard deviation = 0.5."
                     << '\n';
                exit(EXIT_FAILURE);
              }
          }
        catch (ExprNode::EvalException& e)
          {
            // We don't have enough information to compute the numerical value, skip the test
          }
    }

  // Fill in mod_file_struct.estimated_parameters (related to #469)
  for (const auto& it : estim_params_list)
    if (it.type == 2 && it.name != "dsge_prior_weight")
      mod_file_struct.estimated_parameters.insert(symbol_table.getID(it.name));
}

void
EstimatedParamsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  string indent;
  if (!overwrite)
    {
      output << "if isempty(estim_params_)" << '\n';
      indent = "    ";
    }
  output << indent << "estim_params_.var_exo = zeros(0, 10);" << '\n'
         << indent << "estim_params_.var_endo = zeros(0, 10);" << '\n'
         << indent << "estim_params_.corrx = zeros(0, 11);" << '\n'
         << indent << "estim_params_.corrn = zeros(0, 11);" << '\n'
         << indent << "estim_params_.param_vals = zeros(0, 10);" << '\n'
         << indent << "estim_params_.skew_exo = zeros(0, 10);" << '\n';
  if (!overwrite)
    output << "end" << '\n';

  /* Note that we verify that parameters are not declared twice across
     concatenated blocks, because this case is not covered by the check
     implemented in AbstractEstimatedParamsStatement::commonCheckPass() */
  for (const auto& it : estim_params_list)
    {
      int tsid = symbol_table.getTypeSpecificID(it.name) + 1;
      int tsid2;
      SymbolType symb_type = symbol_table.getType(it.name);
      string errmsg = " has been specified twice in two concatenated ''estimated_params'' blocks. "
                      "Depending on your intention, you may want to use the ''overwrite'' option "
                      "or an ''estimated_params_remove'' block.";

      switch (it.type)
        {
        case 1:
          if (symb_type == SymbolType::exogenous)
            output << "if ~isempty(find(estim_params_.var_exo(:,1)==" << tsid << "))" << '\n'
                   << "    error('The standard deviation for " << it.name << errmsg << "')" << '\n'
                   << "end" << '\n'
                   << "estim_params_.var_exo = [estim_params_.var_exo; ";
          else if (symb_type == SymbolType::endogenous)
            output << "if ~isempty(find(estim_params_.var_endo(:,1)==" << tsid << "))" << '\n'
                   << "    error('The standard deviation of the measurement error for " << it.name
                   << errmsg << "')" << '\n'
                   << "end" << '\n'
                   << "estim_params_.var_endo = [estim_params_.var_endo; ";
          output << tsid;
          break;
        case 2:
          output << "if ~isempty(find(estim_params_.param_vals(:,1)==" << tsid << "))" << '\n'
                 << "    error('Parameter " << it.name << errmsg << "')" << '\n'
                 << "end" << '\n'
                 << "estim_params_.param_vals = [estim_params_.param_vals; " << tsid;
          break;
        case 3:
          tsid2 = symbol_table.getTypeSpecificID(it.name2) + 1;
          if (symb_type == SymbolType::exogenous)
            output << "if ~isempty(find((estim_params_.corrx(:,1)==" << tsid
                   << " & estim_params_.corrx(:,2)==" << tsid2 << ") | "
                   << "(estim_params_.corrx(:,2)==" << tsid
                   << " & estim_params_.corrx(:,1)==" << tsid2 << ")))" << '\n'
                   << "    error('The correlation between " << it.name << " and " << it.name2
                   << errmsg << "')" << '\n'
                   << "end" << '\n'
                   << "estim_params_.corrx = [estim_params_.corrx; ";
          else if (symb_type == SymbolType::endogenous)
            output << "if ~isempty(find((estim_params_.corrn(:,1)==" << tsid
                   << " & estim_params_.corrn(:,2)==" << tsid2 << ") | "
                   << "(estim_params_.corrn(:,2)==" << tsid
                   << " & estim_params_.corrn(:,1)==" << tsid2 << ")))" << '\n'
                   << "    error('The correlation between measurement errors on " << it.name
                   << " and " << it.name2 << errmsg << "')" << '\n'
                   << "end" << '\n'
                   << "estim_params_.corrn = [estim_params_.corrn; ";
          output << tsid << ", " << symbol_table.getTypeSpecificID(it.name2) + 1;
          break;
        case 4: // Skewness (single shock name)
          output << "if ~isempty(find(estim_params_.skew_exo(:,1)==" << tsid << "))" << '\n'
                 << "    error('The skewness for " << it.name << errmsg << "')" << '\n'
                 << "end" << '\n'
                 << "estim_params_.skew_exo = [estim_params_.skew_exo; ";
          output << tsid;
          break;
        }
      output << ", ";
      it.init_val->writeOutput(output);
      output << ", ";
      it.low_bound->writeOutput(output);
      output << ", ";
      it.up_bound->writeOutput(output);
      output << ", " << static_cast<int>(it.prior) << ", ";
      it.mean->writeOutput(output);
      output << ", ";
      it.std->writeOutput(output);
      output << ", ";
      it.p3->writeOutput(output);
      output << ", ";
      it.p4->writeOutput(output);
      output << ", ";
      it.jscale->writeOutput(output);
      output << " ];" << '\n';
    }
}

void
EstimatedParamsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimated_params", )"
         << R"("params": [)";
  for (bool printed_something {false}; const auto& it : estim_params_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << "{";
      switch (it.type)
        {
        case 1:
          output << R"("var": ")" << it.name << R"(")";
          break;
        case 2:
          output << R"("param": ")" << it.name << R"(")";
          break;
        case 3:
          output << R"("var1": ")" << it.name << R"(",)"
                 << R"("var2": ")" << it.name2 << R"(")";
          break;
        case 4:
          output << R"("var": ")" << it.name << R"(",)"
                 << R"("type": "skewness")";
          break;
        }

      output << R"(, "init_val": ")";
      it.init_val->writeJsonOutput(output, {}, {});
      output << R"(", "lower_bound": ")";
      it.low_bound->writeJsonOutput(output, {}, {});
      output << R"(", "upper_bound": ")";
      it.up_bound->writeJsonOutput(output, {}, {});
      output << R"(", "prior_distribution": )" << static_cast<int>(it.prior) << R"(, "mean": ")";
      it.mean->writeJsonOutput(output, {}, {});
      output << R"(", "std": ")";
      it.std->writeJsonOutput(output, {}, {});
      output << R"(", "p3": ")";
      it.p3->writeJsonOutput(output, {}, {});
      output << R"(", "p4": ")";
      it.p4->writeJsonOutput(output, {}, {});
      output << R"(", "jscale": ")";
      it.jscale->writeJsonOutput(output, {}, {});
      output << R"("})" << '\n';
    }
  output << "]"
         << "}";
}

EstimatedParamsInitStatement::EstimatedParamsInitStatement(
    vector<EstimationParams> estim_params_list_arg, const SymbolTable& symbol_table_arg,
    const bool use_calibration_arg) :
    AbstractEstimatedParamsStatement(move(estim_params_list_arg), symbol_table_arg),
    use_calibration {use_calibration_arg}
{
}

void
EstimatedParamsInitStatement::checkPass(ModFileStructure& mod_file_struct,
                                        [[maybe_unused]] WarningConsolidation& warnings)
{
  commonCheckPass();

  if (use_calibration)
    mod_file_struct.estim_params_use_calib = true;
}

void
EstimatedParamsInitStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                          [[maybe_unused]] bool minimal_workspace) const
{
  if (use_calibration)
    output << "options_.use_calibration_initialization = 1;" << '\n';

  bool skipline = false;

  for (const auto& it : estim_params_list)
    {
      int tsid = symbol_table.getTypeSpecificID(it.name) + 1;
      SymbolType symb_type = symbol_table.getType(it.name);

      if (it.type < 3)
        {
          if (symb_type == SymbolType::exogenous)
            {
              output << "tmp1 = find(estim_params_.var_exo(:,1)==" << tsid << ");" << '\n'
                     << "if isempty(tmp1)" << '\n'
                     << "    disp(sprintf('The standard deviation of %s is not estimated (the "
                        "value provided in estimated_params_init is not used).', M_.exo_names{"
                     << tsid << "}))" << '\n';
              skipline = true;
              output << "else" << '\n' << "    estim_params_.var_exo(tmp1,2) = ";
              it.init_val->writeOutput(output);
              output << ";" << '\n' << "end" << '\n';
            }
          else if (symb_type == SymbolType::endogenous)
            {
              output << "tmp1 = find(estim_params_.var_endo(:,1)==" << tsid << ");" << '\n'
                     << "if isempty(tmp1)" << '\n'
                     << "    disp(sprintf('The standard deviation of the measurement error on %s "
                        "is not estimated (the value provided in estimated_params_init is not "
                        "used).', M_.endo_names{"
                     << tsid << "}))" << '\n';
              skipline = true;
              output << "else" << '\n' << "    estim_params_.var_endo(tmp1,2) = ";
              it.init_val->writeOutput(output);
              output << ";" << '\n' << "end" << '\n';
            }
          else if (symb_type == SymbolType::parameter)
            {
              output << "tmp1 = find(estim_params_.param_vals(:,1)==" << tsid << ");" << '\n'
                     << "if isempty(tmp1)" << '\n'
                     << "    disp(sprintf('Parameter %s is not estimated (the value provided in "
                        "estimated_params_init is not used).', M_.param_names{"
                     << tsid << "}))" << '\n';
              skipline = true;
              output << "else" << '\n' << "    estim_params_.param_vals(tmp1,2) = ";
              it.init_val->writeOutput(output);
              output << ";" << '\n' << "end" << '\n';
            }
        }
      else if (it.type == 3)
        {
          int tsid2 = symbol_table.getTypeSpecificID(it.name2) + 1;
          if (symb_type == SymbolType::exogenous)
            {
              output << "tmp1 = find((estim_params_.corrx(:,1)==" << tsid
                     << " & estim_params_.corrx(:,2)==" << tsid2 << ") | "
                     << "(estim_params_.corrx(:,2)==" << tsid
                     << " & estim_params_.corrx(:,1)==" << tsid2 << "));" << '\n'
                     << "if isempty(tmp1)" << '\n'
                     << "    disp(sprintf('The correlation between %s and %s is not estimated (the "
                        "value provided in estimated_params_init is not used).', M_.exo_names{"
                     << tsid << "}, M_.exo_names{" << tsid2 << "}))" << '\n';
              skipline = true;
              output << "else" << '\n' << "    estim_params_.corrx(tmp1,3) = ";
              it.init_val->writeOutput(output);
              output << ";" << '\n' << "end" << '\n';
            }
          else if (symb_type == SymbolType::endogenous)
            {
              output << "tmp1 = find((estim_params_.corrn(:,1)==" << tsid
                     << " & estim_params_.corrn(:,2)==" << tsid2 << ") | "
                     << "(estim_params_.corrn(:,2)==" << tsid
                     << " & estim_params_.corrn(:,1)==" << tsid2 << "));" << '\n'
                     << "if isempty(tmp1)" << '\n'
                     << "    disp(sprintf('The correlation between measurement errors on %s and %s "
                        "is not estimated (the value provided in estimated_params_init is not "
                        "used).', M_.endo_names{"
                     << tsid << "}, M_.endo_names{" << tsid2 << "}))" << '\n';
              skipline = true;
              output << "else" << '\n' << "    estim_params_.corrn(tmp1,3) = ";
              it.init_val->writeOutput(output);
              output << ";" << '\n' << "end" << '\n';
            }
        }
      else if (it.type == 4) // Skewness
        {
          output << "tmp1 = find(estim_params_.skew_exo(:,1)==" << tsid << ");" << '\n'
                 << "if isempty(tmp1)" << '\n'
                 << "    disp(sprintf('The skewness of %s is not estimated (the value provided in "
                    "estimated_params_init is not used).', M_.exo_names{"
                 << tsid << "}))" << '\n';
          skipline = true;
          output << "else" << '\n' << "    estim_params_.skew_exo(tmp1,2) = ";
          it.init_val->writeOutput(output);
          output << ";" << '\n' << "end" << '\n';
        }
    }
  if (skipline == true)
    output << "skipline()" << '\n';
}

void
EstimatedParamsInitStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimated_params_init")";

  if (use_calibration)
    output << R"(, "use_calibration_initialization": 1)";

  output << R"(, "params": [)";
  for (bool printed_something {false}; const auto& it : estim_params_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << "{";
      switch (it.type)
        {
        case 1:
          output << R"("var": ")" << it.name << R"(")";
          break;
        case 2:
          output << R"("param": ")" << it.name << R"(")";
          break;
        case 3:
          output << R"("var1": ")" << it.name << R"(",)"
                 << R"("var2": ")" << it.name2 << R"(")";
          break;
        case 4:
          output << R"("var": ")" << it.name << R"(",)"
                 << R"("type": "skewness")";
          break;
        }
      output << R"(, "init_val": ")";
      it.init_val->writeJsonOutput(output, {}, {});
      output << R"("})";
    }
  output << "]"
         << "}";
}

EstimatedParamsBoundsStatement::EstimatedParamsBoundsStatement(
    vector<EstimationParams> estim_params_list_arg, const SymbolTable& symbol_table_arg) :
    AbstractEstimatedParamsStatement(move(estim_params_list_arg), symbol_table_arg)
{
}

void
EstimatedParamsBoundsStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                          [[maybe_unused]] WarningConsolidation& warnings)
{
  commonCheckPass();
}

void
EstimatedParamsBoundsStatement::writeOutput(ostream& output,
                                            [[maybe_unused]] const string& basename,
                                            [[maybe_unused]] bool minimal_workspace) const
{
  for (const auto& it : estim_params_list)
    {
      int tsid = symbol_table.getTypeSpecificID(it.name) + 1;
      SymbolType symb_type = symbol_table.getType(it.name);

      if (it.type < 3)
        {
          if (symb_type == SymbolType::exogenous)
            {
              output << "tmp1 = find(estim_params_.var_exo(:,1)==" << tsid << ");" << '\n'
                     << "estim_params_.var_exo(tmp1,3) = ";
              it.low_bound->writeOutput(output);
              output << ";" << '\n' << "estim_params_.var_exo(tmp1,4) = ";
              it.up_bound->writeOutput(output);
              output << ";" << '\n';
            }
          else if (symb_type == SymbolType::endogenous)
            {
              output << "tmp1 = find(estim_params_.var_endo(:,1)==" << tsid << ");" << '\n'
                     << "estim_params_.var_endo(tmp1,3) = ";
              it.low_bound->writeOutput(output);
              output << ";" << '\n' << "estim_params_.var_endo(tmp1,4) = ";
              it.up_bound->writeOutput(output);
              output << ";" << '\n';
            }
          else if (symb_type == SymbolType::parameter)
            {
              output << "tmp1 = find(estim_params_.param_vals(:,1)==" << tsid << ");" << '\n'
                     << "estim_params_.param_vals(tmp1,3) = ";
              it.low_bound->writeOutput(output);
              output << ";" << '\n' << "estim_params_.param_vals(tmp1,4) = ";
              it.up_bound->writeOutput(output);
              output << ";" << '\n';
            }
        }
      else if (it.type == 3) // Correlation
        {
          int tsid2 = symbol_table.getTypeSpecificID(it.name2) + 1;
          if (symb_type == SymbolType::exogenous)
            {
              output << "tmp1 = find((estim_params_.corrx(:,1)==" << tsid
                     << " & estim_params_.corrx(:,2)==" << tsid2 << ") | "
                     << "(estim_params_.corrx(:,2)==" << tsid
                     << " & estim_params_.corrx(:,1)==" << tsid2 << "));" << '\n'
                     << "estim_params_.corrx(tmp1,4) = ";
              it.low_bound->writeOutput(output);
              output << ";" << '\n' << "estim_params_.corrx(tmp1,5) = ";
              it.up_bound->writeOutput(output);
              output << ";" << '\n';
            }
          else if (symb_type == SymbolType::endogenous)
            {
              output << "tmp1 = find((estim_params_.corrn(:,1)==" << tsid
                     << " & estim_params_.corrn(:,2)==" << tsid2 << ") | "
                     << "(estim_params_.corrn(:,2)==" << tsid
                     << " & estim_params_.corrn(:,1)==" << tsid2 << "));" << '\n'
                     << "estim_params_.corrn(tmp1,4) = ";
              it.low_bound->writeOutput(output);
              output << ";" << '\n' << "estim_params_.corrn(tmp1,5) = ";
              it.up_bound->writeOutput(output);
              output << ";" << '\n';
            }
        }
      else if (it.type == 4) // Skewness
        {
          output << "tmp1 = find(estim_params_.skew_exo(:,1)==" << tsid << ");" << '\n'
                 << "estim_params_.skew_exo(tmp1,3) = ";
          it.low_bound->writeOutput(output);
          output << ";" << '\n' << "estim_params_.skew_exo(tmp1,4) = ";
          it.up_bound->writeOutput(output);
          output << ";" << '\n';
        }
    }
}

void
EstimatedParamsBoundsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimated_params_bounds", )"
         << R"("params": [)";

  for (bool printed_something {false}; const auto& it : estim_params_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << "{";
      switch (it.type)
        {
        case 1:
          output << R"("var": ")" << it.name << R"(")";
          break;
        case 2:
          output << R"("param": ")" << it.name << R"(")";
          break;
        case 3:
          output << R"("var1": ")" << it.name << R"(",)"
                 << R"("var2": ")" << it.name2 << R"(")";
          break;
        case 4:
          output << R"("var": ")" << it.name << R"(",)"
                 << R"("type": "skewness")";
          break;
        }
      output << R"(, "lower_bound": )";
      it.low_bound->writeJsonOutput(output, {}, {});
      output << R"(, "upper_bound": )";
      it.up_bound->writeJsonOutput(output, {}, {});
      output << "}";
    }
  output << "]"
         << "}";
}

EstimatedParamsRemoveStatement::EstimatedParamsRemoveStatement(
    vector<EstimationParams> estim_params_list_arg, const SymbolTable& symbol_table_arg) :
    estim_params_list {move(estim_params_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
EstimatedParamsRemoveStatement::writeOutput(ostream& output,
                                            [[maybe_unused]] const string& basename,
                                            [[maybe_unused]] bool minimal_workspace) const
{
  for (const auto& it : estim_params_list)
    {
      int tsid = symbol_table.getTypeSpecificID(it.name) + 1;
      SymbolType symb_type = symbol_table.getType(it.name);

      if (it.type < 3)
        {
          if (symb_type == SymbolType::exogenous)
            output << "tmp1 = find(estim_params_.var_exo(:,1)==" << tsid << ");" << '\n'
                   << "if isempty(tmp1)" << '\n'
                   << "    error('estimated_params_remove: the standard deviation of %s is "
                      "not estimated.', M_.exo_names{"
                   << tsid << "})" << '\n'
                   << "else" << '\n'
                   << "    estim_params_.var_exo(tmp1,:) = [];"
                   << "end" << '\n';
          else if (symb_type == SymbolType::endogenous)
            output << "tmp1 = find(estim_params_.var_endo(:,1)==" << tsid << ");" << '\n'
                   << "if isempty(tmp1)" << '\n'
                   << "    error('estimated_params_remove: the standard deviation of the "
                      "measurement error on %s is not estimated.', M_.endo_names{"
                   << tsid << "})" << '\n'
                   << "else" << '\n'
                   << "    estim_params_.var_endo(tmp1,:) = [];"
                   << "end" << '\n';
          else if (symb_type == SymbolType::parameter)
            output << "tmp1 = find(estim_params_.param_vals(:,1)==" << tsid << ");" << '\n'
                   << "if isempty(tmp1)" << '\n'
                   << "    error('estimated_params_remove: parameter %s is not "
                      "estimated.', M_.param_names{"
                   << tsid << "})" << '\n'
                   << "else" << '\n'
                   << "    estim_params_.param_vals(tmp1,:) = [];"
                   << "end" << '\n';
        }
      else if (it.type == 3) // Correlation
        {
          int tsid2 = symbol_table.getTypeSpecificID(it.name2) + 1;
          if (symb_type == SymbolType::exogenous)
            output << "tmp1 = find((estim_params_.corrx(:,1)==" << tsid
                   << " & estim_params_.corrx(:,2)==" << tsid2 << ") | "
                   << "(estim_params_.corrx(:,2)==" << tsid
                   << " & estim_params_.corrx(:,1)==" << tsid2 << "));" << '\n'
                   << "if isempty(tmp1)" << '\n'
                   << "    error('estimated_params_remove: the correlation between %s and "
                      "%s is not estimated.', M_.exo_names{"
                   << tsid << "}, M_.exo_names{" << tsid2 << "})" << '\n'
                   << "else" << '\n'
                   << "    estim_params_.corrx(tmp1,:) = [];"
                   << "end" << '\n';
          else if (symb_type == SymbolType::endogenous)
            output << "tmp1 = find((estim_params_.corrn(:,1)==" << tsid
                   << " & estim_params_.corrn(:,2)==" << tsid2 << ") | "
                   << "(estim_params_.corrn(:,2)==" << tsid
                   << " & estim_params_.corrn(:,1)==" << tsid2 << "));" << '\n'
                   << "if isempty(tmp1)" << '\n'
                   << "    error('estimated_params_remove: the correlation between "
                      "measurement errors on %s and %s is not estimated.', M_.endo_names{"
                   << tsid << "}, M_.endo_names{" << tsid2 << "})" << '\n'
                   << "else" << '\n'
                   << "    estim_params_.corrn(tmp1,:) = [];"
                   << "end" << '\n';
        }
      else if (it.type == 4) // Skewness
        {
          output << "tmp1 = find(estim_params_.skew_exo(:,1)==" << tsid << ");" << '\n'
                 << "if isempty(tmp1)" << '\n'
                 << "    error('estimated_params_remove: the skewness of %s is not "
                    "estimated.', M_.exo_names{"
                 << tsid << "})" << '\n'
                 << "else" << '\n'
                 << "    estim_params_.skew_exo(tmp1,:) = [];"
                 << "end" << '\n';
        }
    }
}

void
EstimatedParamsRemoveStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimated_params_remove", )"
         << R"("params": [)";

  for (bool printed_something {false}; const auto& it : estim_params_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << "{";
      switch (it.type)
        {
        case 1:
          output << R"("var": ")" << it.name << R"(")";
          break;
        case 2:
          output << R"("param": ")" << it.name << R"(")";
          break;
        case 3:
          output << R"("var1": ")" << it.name << R"(",)"
                 << R"("var2": ")" << it.name2 << R"(")";
          break;
        case 4:
          output << R"("var": ")" << it.name << R"(",)"
                 << R"("type": "skewness")";
          break;
        }
      output << "}";
    }
  output << "]"
         << "}";
}

DeterministicTrendsStatement::DeterministicTrendsStatement(
    ObservationTrendsStatement::trend_elements_t trend_elements_arg,
    const SymbolTable& symbol_table_arg) :
    trend_elements {move(trend_elements_arg)}, symbol_table {symbol_table_arg}
{
}

void
DeterministicTrendsStatement::writeOutput([[maybe_unused]] ostream& output,
                                          [[maybe_unused]] const string& basename,
                                          [[maybe_unused]] bool minimal_workspace) const
{
  cerr << "ERROR: deterministic_trends is not implemented for MATLAB/Octave" << '\n';
  exit(EXIT_FAILURE);
}

void
DeterministicTrendsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "deterministic_trends", )"
         << R"("trends" : {)";
  for (bool printed_something {false}; const auto& [symb_id, val] : trend_elements)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"(")" << symbol_table.getName(symb_id) << R"(": ")";
      val->writeJsonOutput(output, {}, {});
      output << R"(")" << '\n';
    }
  output << "}"
         << "}";
}

ObservationTrendsStatement::ObservationTrendsStatement(trend_elements_t trend_elements_arg,
                                                       const SymbolTable& symbol_table_arg) :
    trend_elements {move(trend_elements_arg)}, symbol_table {symbol_table_arg}
{
}

void
ObservationTrendsStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                      [[maybe_unused]] WarningConsolidation& warnings)
{
  for (int symb_id : views::keys(trend_elements))
    if (!symbol_table.isObservedVariable(symb_id))
      {
        cerr << "ERROR: variable " << symbol_table.getName(symb_id)
             << " in observation_trends block is not an observed variable" << '\n';
        exit(EXIT_FAILURE);
      }
}

void
ObservationTrendsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                        [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_.trend_coeff = {};" << '\n';
  for (const auto& [symb_id, val] : trend_elements)
    {
      output << "options_.trend_coeffs{" << symbol_table.getObservedVariableIndex(symb_id) + 1
             << "} = '";
      val->writeOutput(output);
      output << "';" << '\n';
    }
}

void
ObservationTrendsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "observation_trends", )"
         << R"("trends" : {)";
  for (bool printed_something {false}; const auto& [symb_id, val] : trend_elements)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"(")" << symbol_table.getName(symb_id) << R"(": ")";
      val->writeJsonOutput(output, {}, {});
      output << R"(")" << '\n';
    }
  output << "}"
         << "}";
}

FilterInitialStateStatement::FilterInitialStateStatement(
    filter_initial_state_elements_t filter_initial_state_elements_arg,
    const SymbolTable& symbol_table_arg) :
    filter_initial_state_elements {move(filter_initial_state_elements_arg)},
    symbol_table {symbol_table_arg}
{
}

void
FilterInitialStateStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  output << "M_.filter_initial_state = cell(M_.endo_nbr, 2);" << '\n';
  for (const auto& [key, val] : filter_initial_state_elements)
    {
      auto [symb_id, lag] = key;
      SymbolType type = symbol_table.getType(symb_id);

      if ((type == SymbolType::endogenous && lag < 0) || type == SymbolType::exogenous)
        {
          try
            {
              // This function call must remain the 1st statement in this block
              symb_id = symbol_table.searchAuxiliaryVars(symb_id, lag);
            }
          catch (SymbolTable::SearchFailedException& e)
            {
              if (type == SymbolType::endogenous)
                {
                  cerr << "filter_initial_state: internal error, please contact the developers";
                  exit(EXIT_FAILURE);
                }
              // We don't fail for exogenous, because they are not replaced by
              // auxiliary variables in deterministic mode.
            }
        }

      output << "M_.filter_initial_state(" << symbol_table.getTypeSpecificID(symb_id) + 1
             << ",:) = {'" << symbol_table.getName(symb_id) << "', '";
      val->writeOutput(output);
      output << ";'};" << '\n';
    }
}

void
FilterInitialStateStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "filter_initial_state", )"
         << R"("states": [)";

  for (bool printed_something {false}; const auto& [key, val] : filter_initial_state_elements)
    {
      if (exchange(printed_something, true))
        output << ", ";
      auto& [symb_id, lag] = key;
      output << R"({ "var": ")" << symbol_table.getName(symb_id) << R"(", "lag": )" << lag
             << R"(, "value": ")";
      val->writeJsonOutput(output, {}, {});
      output << R"(" })";
    }
  output << "] }";
}

OsrParamsStatement::OsrParamsStatement(SymbolList symbol_list_arg,
                                       const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
OsrParamsStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  if (mod_file_struct.osr_params_present)
    warnings << "WARNING: You have more than one osr_params statement in the .mod file." << '\n';
  mod_file_struct.osr_params_present = true;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::parameter}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: osr: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
OsrParamsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("M_.osr.param_names", output);
  output << "M_.osr.param_names = cellstr(M_.osr.param_names);" << '\n'
         << "M_.osr.param_indices = zeros(length(M_.osr.param_names), 1);" << '\n';
  for (int i {0}; auto& symbol : symbol_list.getSymbols())
    output << "M_.osr.param_indices(" << ++i << ") = " << symbol_table.getTypeSpecificID(symbol) + 1
           << ";" << '\n';
}

void
OsrParamsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "osr_params")";
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

OsrParamsBoundsStatement::OsrParamsBoundsStatement(vector<OsrParams> osr_params_list_arg) :
    osr_params_list {move(osr_params_list_arg)}
{
}

void
OsrParamsBoundsStatement::checkPass(ModFileStructure& mod_file_struct,
                                    [[maybe_unused]] WarningConsolidation& warnings)
{
  if (!mod_file_struct.osr_params_present)
    {
      cerr << "ERROR: you must have an osr_params statement before the osr_params_bounds block."
           << '\n';
      exit(EXIT_FAILURE);
    }
}

void
OsrParamsBoundsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{

  output << "M_.osr.param_bounds = [-inf(length(M_.osr.param_names), 1), "
            "inf(length(M_.osr.param_names), 1)];"
         << '\n';

  for (const auto& it : osr_params_list)
    {
      output << "M_.osr.param_bounds(strcmp(M_.osr.param_names, '" << it.name << "'), :) = [";
      it.low_bound->writeOutput(output);
      output << ", ";
      it.up_bound->writeOutput(output);
      output << "];" << '\n';
    }
}

void
OsrParamsBoundsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "osr_params_bounds")"
         << R"(, "bounds": [)";
  for (bool printed_something {false}; const auto& it : osr_params_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"({"parameter": ")" << it.name << R"(",)"
             << R"("bounds": [")";
      it.low_bound->writeJsonOutput(output, {}, {});
      output << R"(", ")";
      it.up_bound->writeJsonOutput(output, {}, {});
      output << R"("])"
             << "}";
    }
  output << "]"
         << "}";
}

OsrStatement::OsrStatement(SymbolList symbol_list_arg, OptionsList options_list_arg,
                           const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
OsrStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  mod_file_struct.osr_present = true;

  planner_discount_declared = mod_file_struct.planner_objective_present;
  // Fill in option_order of mod_file_struct
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    mod_file_struct.order_option = max(mod_file_struct.order_option, stoi(*opt));

  // Fill in mod_file_struct.partial_information
  if (auto opt = options_list.get_if<OptionsList::NumVal>("partial_information");
      opt && *opt == "true")
    mod_file_struct.partial_information = true;

  // Fill in mod_file_struct.estimation_analytic_derivation
  if (auto opt = options_list.get_if<OptionsList::NumVal>("analytic_derivation");
      opt && *opt == "true")
    mod_file_struct.osr_analytic_derivation = true;

  // Option k_order_solver (implicit when order >= 3)
  if (auto opt = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt && *opt == "true") || mod_file_struct.order_option >= 3)
    mod_file_struct.k_order_solver = true;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: osr: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
OsrStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                          [[maybe_unused]] bool minimal_workspace) const
{
  // Ensure that order 3 implies k_order (#844)
  if (auto opt1 = options_list.get_if<OptionsList::NumVal>("order"),
      opt2 = options_list.get_if<OptionsList::NumVal>("k_order_solver");
      (opt2 && *opt2 == "true") || (opt1 && stoi(*opt1) >= 3))
    output << "options_.k_order_solver = true;" << '\n';

  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  if (planner_discount_declared)
    output
        << "[info, oo_, options_, M_] = osr.run(M_, options_, oo_, var_list_, M_.osr.param_names);"
        << '\n';
  else
    output << "[info, oo_, options_, M_] = osr.run(M_, options_, oo_, "
              "var_list_, M_.osr.param_names, M_.osr.variable_indices, M_.osr.variable_weights);"
           << '\n';
}

void
OsrStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "osr")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

OptimWeightsStatement::OptimWeightsStatement(var_weights_t var_weights_arg,
                                             covar_weights_t covar_weights_arg,
                                             const SymbolTable& symbol_table_arg) :
    var_weights {move(var_weights_arg)},
    covar_weights {move(covar_weights_arg)},
    symbol_table {symbol_table_arg}
{
}

void
OptimWeightsStatement::checkPass(ModFileStructure& mod_file_struct,
                                 [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.optim_weights_present = true;
}

void
OptimWeightsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  output << "%" << '\n'
         << "% OPTIM_WEIGHTS" << '\n'
         << "%" << '\n'
         << "M_.osr.variable_weights = sparse(M_.endo_nbr,M_.endo_nbr);" << '\n'
         << "M_.osr.variable_indices = [];" << '\n'
         << '\n';

  for (const auto& [name, value] : var_weights)
    {
      int id = symbol_table.getTypeSpecificID(name) + 1;
      output << "M_.osr.variable_weights(" << id << "," << id << ") = ";
      value->writeOutput(output);
      output << ";" << '\n';
      output << "M_.osr.variable_indices = [M_.osr.variable_indices; " << id << "];" << '\n';
    }

  for (const auto& [names, value] : covar_weights)
    {
      int id1 = symbol_table.getTypeSpecificID(names.first) + 1;
      int id2 = symbol_table.getTypeSpecificID(names.second) + 1;
      output << "M_.osr.variable_weights(" << id1 << "," << id2 << ") = ";
      value->writeOutput(output);
      output << ";" << '\n';
      output << "M_.osr.variable_indices = [M_.osr.variable_indices; " << id1 << "; " << id2 << "];"
             << '\n';
    }
}

void
OptimWeightsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "optim_weights", )"
         << R"("weights": [)";
  bool printed_something {false};
  for (const auto& [name, value] : var_weights)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"({"name": ")" << name << R"(")"
             << R"(, "value": ")";
      value->writeJsonOutput(output, {}, {});
      output << R"("})";
    }

  for (const auto& [names, value] : covar_weights)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"({"name1": ")" << names.first << R"(")"
             << R"(, "name2": ")" << names.second << R"(")"
             << R"(, "value": ")";
      value->writeJsonOutput(output, {}, {});
      output << R"("})";
    }
  output << "]}";
}

DynaSaveStatement::DynaSaveStatement(SymbolList symbol_list_arg, string filename_arg,
                                     const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    filename {move(filename_arg)},
    symbol_table {symbol_table_arg}
{
}

void
DynaSaveStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                             WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous, SymbolType::exogenous},
                            symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: dynasave: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
DynaSaveStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                               [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("var_list_", output);
  output << "dynasave('" << filename << "',var_list_);" << '\n';
}

void
DynaSaveStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "dynasave", )"
         << R"("filename": ")" << filename << R"(")";
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

DynaTypeStatement::DynaTypeStatement(SymbolList symbol_list_arg, string filename_arg,
                                     const SymbolTable& symbol_table_arg) :
    symbol_list(move(symbol_list_arg)),
    filename(move(filename_arg)),
    symbol_table {symbol_table_arg}
{
}

void
DynaTypeStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                             WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous, SymbolType::exogenous},
                            symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: dynatype: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
DynaTypeStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                               [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("var_list_", output);
  output << "dynatype('" << filename << "',var_list_);" << '\n';
}

void
DynaTypeStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "dynatype", )"
         << R"("filename": ")" << filename << R"(")";
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

ModelComparisonStatement::ModelComparisonStatement(filename_list_t filename_list_arg,
                                                   OptionsList options_list_arg) :
    filename_list {move(filename_list_arg)}, options_list {move(options_list_arg)}
{
}

void
ModelComparisonStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);

  output << "ModelNames_ = {};" << '\n' << "ModelPriors_ = [];" << '\n';

  for (const auto& [name, prior] : filename_list)
    output << "ModelNames_ = { ModelNames_{:} '" << name << "'};" << '\n'
           << "ModelPriors_ = [ ModelPriors_ ; " << prior << "];" << '\n';

  output << "oo_ = model_comparison(ModelNames_,ModelPriors_,oo_,options_,M_.fname);" << '\n';
}

void
ModelComparisonStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "model_comparison")";
  if (!filename_list.empty())
    output << R"(, "filename_list": {)";

  for (bool printed_something {false}; const auto& [name, prior] : filename_list)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"("name": ")" << name << R"(")"
             << R"("prior": ")" << prior << R"(")";
    }

  if (!filename_list.empty())
    output << "}";

  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }

  output << "}";
}

PlannerObjectiveStatement::PlannerObjectiveStatement(unique_ptr<PlannerObjective> model_tree_arg) :
    model_tree {move(model_tree_arg)}
{
}

void
PlannerObjectiveStatement::checkPass(ModFileStructure& mod_file_struct,
                                     [[maybe_unused]] WarningConsolidation& warnings)
{
  assert(model_tree->equation_number() == 1);
  if (model_tree->exoPresentInEqs())
    {
      cerr << "ERROR: You cannot include exogenous variables (or variables of undeclared type) in "
              "the planner objective. Please "
           << "define an auxiliary endogenous variable like eps_aux=epsilon and use it instead "
           << "of the varexo." << '\n';
      exit(EXIT_FAILURE);
    }
  mod_file_struct.planner_objective_present = true;
}

const PlannerObjective&
PlannerObjectiveStatement::getPlannerObjective() const
{
  return *model_tree;
}

void
PlannerObjectiveStatement::computingPass(const ModFileStructure& mod_file_struct)
{
  // Only request higher-order derivatives in stochastic contexts. Perfect-foresight setups
  // stick to first-order derivatives, while discretionary policy always needs a second order.
  int deriv_order {
      mod_file_struct.isStochasticContext()
          ? (mod_file_struct.discretionary_policy_present ? 2 : mod_file_struct.order_option)
          : 1};

  model_tree->computingPass(max(deriv_order, 1), 0, {}, false, false, false);
  computing_pass_called = true;
}

void
PlannerObjectiveStatement::writeOutput(ostream& output, const string& basename,
                                       [[maybe_unused]] bool minimal_workspace) const
{
  model_tree->writeDriverOutput(output);
  model_tree->writeStaticFile(basename + ".objective", false, "", {}, false);
}

void
PlannerObjectiveStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "planner_objective")"
         << ", ";
  if (computing_pass_called)
    model_tree->writeJsonComputingPassOutput(output, false);
  else
    model_tree->writeJsonOutput(output);

  output << "}";
}

BVARDensityStatement::BVARDensityStatement(int maxnlags_arg, OptionsList options_list_arg) :
    maxnlags {maxnlags_arg}, options_list {move(options_list_arg)}
{
}

void
BVARDensityStatement::checkPass(ModFileStructure& mod_file_struct,
                                [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
BVARDensityStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "bvar.density(" << maxnlags << ");" << '\n';
}

void
BVARDensityStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "bvar_density")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

BVARForecastStatement::BVARForecastStatement(int nlags_arg, OptionsList options_list_arg) :
    nlags {nlags_arg}, options_list {move(options_list_arg)}
{
}

void
BVARForecastStatement::checkPass(ModFileStructure& mod_file_struct,
                                 [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
BVARForecastStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "bvar.forecast(" << nlags << ");" << '\n';
}

void
BVARForecastStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "bvar_forecast")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

BVARIRFStatement::BVARIRFStatement(int nirf_arg, string identificationname_arg) :
    nirf {nirf_arg}, identificationname {move(identificationname_arg)}
{
}

void
BVARIRFStatement::checkPass(ModFileStructure& mod_file_struct,
                            [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
BVARIRFStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                              [[maybe_unused]] bool minimal_workspace) const
{
  output << "bvar.irf(" << nirf << ",'" << identificationname << "');" << '\n';
}

void
BVARIRFStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "bvar_irf")";
  output << "}";
}

SBVARStatement::SBVARStatement(OptionsList options_list_arg) : options_list {move(options_list_arg)}
{
}

void
SBVARStatement::checkPass(ModFileStructure& mod_file_struct,
                          [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
SBVARStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "sbvar(M_,options_);" << '\n';
}

void
SBVARStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "sbvar")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVAREstimationStatement::MSSBVAREstimationStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MSSBVAREstimationStatement::checkPass(ModFileStructure& mod_file_struct,
                                      [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;

  if (!options_list.contains("ms.create_init")
      && (!options_list.contains("datafile") || !options_list.contains("ms.initial_year")))
    {
      cerr << "ERROR: If you do not pass no_create_init to ms_estimation, "
           << "you must pass the datafile and initial_year options." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MSSBVAREstimationStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                        [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n'
         << "options_.datafile = '';" << '\n';
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_estimation(M_, options_, oo_);" << '\n';
}

void
MSSBVAREstimationStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_estimation")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARSimulationStatement::MSSBVARSimulationStatement(OptionsList options_list_arg) :
    options_list(move(options_list_arg))
{
}

void
MSSBVARSimulationStatement::checkPass(ModFileStructure& mod_file_struct,
                                      [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
MSSBVARSimulationStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                        [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  options_list.writeOutput(output);

  // Redeclare drop option if necessary
  if ((options_list.contains("ms.mh_replic") || options_list.contains("ms.thinning_factor"))
      && !options_list.contains("ms.drop"))
    output << "options_.ms.drop = 0.1*options_.ms.mh_replic*options_.ms.thinning_factor;" << '\n';

  output << "[options_, oo_] = ms_simulation(M_, options_, oo_);" << '\n';
}

void
MSSBVARSimulationStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_simulation")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARComputeMDDStatement::MSSBVARComputeMDDStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MSSBVARComputeMDDStatement::checkPass(ModFileStructure& mod_file_struct,
                                      [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;
}

void
MSSBVARComputeMDDStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                        [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_compute_mdd(M_, options_, oo_);" << '\n';
}

void
MSSBVARComputeMDDStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_compute_mdd")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARComputeProbabilitiesStatement::MSSBVARComputeProbabilitiesStatement(
    OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MSSBVARComputeProbabilitiesStatement::checkPass(ModFileStructure& mod_file_struct,
                                                [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;

  if (options_list.contains("ms.real_time_smoothed_probabilities")
      && options_list.contains("ms.filtered_probabilities"))
    {
      cerr << "ERROR: You may only pass one of real_time_smoothed "
           << "and filtered_probabilities to ms_compute_probabilities." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MSSBVARComputeProbabilitiesStatement::writeOutput(ostream& output,
                                                  [[maybe_unused]] const string& basename,
                                                  [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_compute_probabilities(M_, options_, oo_);" << '\n';
}

void
MSSBVARComputeProbabilitiesStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_compute_probabilities")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARIrfStatement::MSSBVARIrfStatement(SymbolList symbol_list_arg, OptionsList options_list_arg,
                                         const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
MSSBVARIrfStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;

  if (bool regime_present = options_list.contains("ms.regime"),
      regimes_present = options_list.contains("ms.regimes"),
      filtered_probabilities_present = options_list.contains("ms.filtered_probabilities");
      (filtered_probabilities_present && regime_present)
      || (filtered_probabilities_present && regimes_present) || (regimes_present && regime_present))
    {
      cerr << "ERROR: You may only pass one of regime, regimes and "
           << "filtered_probabilities to ms_irf" << '\n';
      exit(EXIT_FAILURE);
    }

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: ms_irf: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MSSBVARIrfStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  symbol_list.writeOutput("var_list_", output);
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_irf(var_list_,M_, options_, oo_);" << '\n';
}

void
MSSBVARIrfStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_irf")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARForecastStatement::MSSBVARForecastStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MSSBVARForecastStatement::checkPass(ModFileStructure& mod_file_struct,
                                    [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;

  if (options_list.contains("ms.regimes") && options_list.contains("ms.regime"))
    {
      cerr << "ERROR: You may only pass one of regime and regimes to ms_forecast" << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MSSBVARForecastStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_forecast(M_, options_, oo_);" << '\n';
}

void
MSSBVARForecastStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_forecast")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MSSBVARVarianceDecompositionStatement::MSSBVARVarianceDecompositionStatement(
    OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MSSBVARVarianceDecompositionStatement::checkPass(ModFileStructure& mod_file_struct,
                                                 [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.bvar_present = true;

  if (bool regime_present = options_list.contains("ms.regime"),
      regimes_present = options_list.contains("ms.regimes"),
      filtered_probabilities_present = options_list.contains("ms.filtered_probabilities");
      (filtered_probabilities_present && regime_present)
      || (filtered_probabilities_present && regimes_present) || (regimes_present && regime_present))
    {
      cerr << "ERROR: You may only pass one of regime, regimes and "
           << "filtered_probabilities to ms_variance_decomposition" << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MSSBVARVarianceDecompositionStatement::writeOutput(ostream& output,
                                                   [[maybe_unused]] const string& basename,
                                                   [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = initialize_ms_sbvar_options(M_, options_);" << '\n';
  options_list.writeOutput(output);
  output << "[options_, oo_] = ms_variance_decomposition(M_, options_, oo_);" << '\n';
}

void
MSSBVARVarianceDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "ms_sbvar_variance_decomposition")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

IdentificationStatement::IdentificationStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
  if (auto opt = options_list.get_if<OptionsList::NumVal>("max_dim_cova_group");
      opt && stoi(*opt) == 0)
    {
      cerr << "ERROR: The max_dim_cova_group option to identification only accepts integers > 0."
           << '\n';
      exit(EXIT_FAILURE);
    }
}

void
IdentificationStatement::checkPass(ModFileStructure& mod_file_struct,
                                   [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.identification_present = true;

  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    {
      int order = stoi(*opt);
      if (order < 1 || order > 3)
        {
          cerr << "ERROR: the order option of identification command must be between 1 and 3"
               << '\n';

          exit(EXIT_FAILURE);
        }
      mod_file_struct.identification_order = max(mod_file_struct.identification_order, order);
    }
  else
    // The default value for order is 1 (which triggers 2nd order dynamic derivatives, see
    // preprocessor#40)
    mod_file_struct.identification_order = max(mod_file_struct.identification_order, 1);
}

void
IdentificationStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                     [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_ident");

  /* Ensure that nograph, nodisplay and graph_format are also set in top-level
     options_.
     \todo factorize this code between identification and dynare_sensitivity,
     and provide a generic mechanism for this situation (maybe using regexps).
     graph_format can then be turned into a VecCellStrVal. */
  if (auto opt = options_list.get_if<OptionsList::NumVal>("nodisplay"))
    output << "options_.nodisplay = " << *opt << ";" << '\n';
  if (auto opt = options_list.get_if<OptionsList::NumVal>("nograph"))
    output << "options_.nograph = " << *opt << ";" << '\n';
  if (auto opt = options_list.get_if<OptionsList::SymbolListVal>("graph_format"))
    opt->writeOutput("options_.graph_format", output);

  output << "identification.run(M_, oo_, options_, estim_params_, "
         << "options_ident);" << '\n';
}

void
IdentificationStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "identification")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

WriteLatexDynamicModelStatement::WriteLatexDynamicModelStatement(
    const DynamicModel& dynamic_model_arg, bool write_equation_tags_arg) :
    dynamic_model {dynamic_model_arg}, write_equation_tags {write_equation_tags_arg}
{
}

void
WriteLatexDynamicModelStatement::writeOutput([[maybe_unused]] ostream& output,
                                             const string& basename,
                                             [[maybe_unused]] bool minimal_workspace) const
{
  dynamic_model.writeLatexFile(basename, write_equation_tags);
}

void
WriteLatexDynamicModelStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "write_latex_dynamic_model"})";
}

WriteLatexStaticModelStatement::WriteLatexStaticModelStatement(const StaticModel& static_model_arg,
                                                               bool write_equation_tags_arg) :
    static_model(static_model_arg), write_equation_tags(write_equation_tags_arg)
{
}

void
WriteLatexStaticModelStatement::writeOutput([[maybe_unused]] ostream& output,
                                            const string& basename,
                                            [[maybe_unused]] bool minimal_workspace) const
{
  static_model.writeLatexFile(basename, write_equation_tags);
}

void
WriteLatexStaticModelStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "write_latex_static_model"})";
}

WriteLatexOriginalModelStatement::WriteLatexOriginalModelStatement(
    const DynamicModel& original_model_arg, bool write_equation_tags_arg) :
    original_model {original_model_arg}, write_equation_tags {write_equation_tags_arg}
{
}

void
WriteLatexOriginalModelStatement::writeOutput([[maybe_unused]] ostream& output,
                                              const string& basename,
                                              [[maybe_unused]] bool minimal_workspace) const
{
  original_model.writeLatexOriginalFile(basename, write_equation_tags);
}

void
WriteLatexOriginalModelStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "write_latex_original_model"})";
}

WriteLatexSteadyStateModelStatement::WriteLatexSteadyStateModelStatement(
    const SteadyStateModel& steady_state_model_arg) :
    steady_state_model {steady_state_model_arg}
{
}

void
WriteLatexSteadyStateModelStatement::checkPass(ModFileStructure& mod_file_struct,
                                               [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.write_latex_steady_state_model_present = true;
}

void
WriteLatexSteadyStateModelStatement::writeOutput([[maybe_unused]] ostream& output,
                                                 const string& basename,
                                                 [[maybe_unused]] bool minimal_workspace) const
{
  steady_state_model.writeLatexSteadyStateFile(basename);
}

void
WriteLatexSteadyStateModelStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "write_latex_steady_state_model"})";
}

ShockDecompositionStatement::ShockDecompositionStatement(SymbolList symbol_list_arg,
                                                         OptionsList options_list_arg,
                                                         const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
ShockDecompositionStatement::checkPass(ModFileStructure& mod_file_struct,
                                       WarningConsolidation& warnings)
{
  if (auto opt = options_list.get_if<OptionsList::NumVal>("shock_decomp.with_epilogue");
      opt && *opt == "true")
    mod_file_struct.with_epilogue_option = true;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: shock_decomposition: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
ShockDecompositionStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "oo_ = shock_decomposition(M_, oo_, options_, var_list_, estim_params_);" << '\n';
}

void
ShockDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "shock_decomposition")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

RealtimeShockDecompositionStatement::RealtimeShockDecompositionStatement(
    SymbolList symbol_list_arg, OptionsList options_list_arg, const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
RealtimeShockDecompositionStatement::checkPass(ModFileStructure& mod_file_struct,
                                               WarningConsolidation& warnings)
{
  if (auto opt = options_list.get_if<OptionsList::NumVal>("shock_decomp.with_epilogue");
      opt && *opt == "true")
    mod_file_struct.with_epilogue_option = true;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: realtime_shock_decomposition: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
RealtimeShockDecompositionStatement::writeOutput(ostream& output,
                                                 [[maybe_unused]] const string& basename,
                                                 [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "oo_ = realtime_shock_decomposition(M_, oo_, options_, var_list_, estim_params_);"
         << '\n';
}

void
RealtimeShockDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "realtime_shock_decomposition")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

PlotShockDecompositionStatement::PlotShockDecompositionStatement(
    SymbolList symbol_list_arg, OptionsList options_list_arg, const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
PlotShockDecompositionStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                           WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous, SymbolType::epilogue}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: plot_shock_decomposition: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
PlotShockDecompositionStatement::writeOutput(ostream& output,
                                             [[maybe_unused]] const string& basename,
                                             [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = set_default_plot_shock_decomposition_options(options_);" << '\n';
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "oo_ = plot_shock_decomposition(M_, oo_, options_, var_list_);" << '\n';
}

void
PlotShockDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "plot_shock_decomposition")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

InitialConditionDecompositionStatement::InitialConditionDecompositionStatement(
    SymbolList symbol_list_arg, OptionsList options_list_arg, const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
InitialConditionDecompositionStatement::checkPass(ModFileStructure& mod_file_struct,
                                                  WarningConsolidation& warnings)
{
  if (auto opt = options_list.get_if<OptionsList::NumVal>("initial_condition_decomp.with_epilogue");
      opt && *opt == "true")
    mod_file_struct.with_epilogue_option = true;

  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: initial_condition_decomposition: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
InitialConditionDecompositionStatement::writeOutput(ostream& output,
                                                    [[maybe_unused]] const string& basename,
                                                    [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_ = set_default_initial_condition_decomposition_options(options_);" << '\n';
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "oo_ = initial_condition_decomposition(M_, oo_, options_, var_list_, "
            "estim_params_);"
         << '\n';
}

void
InitialConditionDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "initial_condition_decomposition")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

SqueezeShockDecompositionStatement::SqueezeShockDecompositionStatement(
    SymbolList symbol_list_arg, const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
SqueezeShockDecompositionStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                              WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: squeeze_shock_decomposition: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
SqueezeShockDecompositionStatement::writeOutput(ostream& output,
                                                [[maybe_unused]] const string& basename,
                                                [[maybe_unused]] bool minimal_workspace) const
{
  if (symbol_list.empty())
    output << "oo_ = squeeze_shock_decomposition(M_, oo_, options_);" << '\n';
  else
    {
      symbol_list.writeOutput("var_list_", output);
      output << "oo_ = squeeze_shock_decomposition(M_, oo_, options_, var_list_);" << '\n';
    }
}

void
SqueezeShockDecompositionStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "squeeze_shock_decomposition")";
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

ConditionalForecastStatement::ConditionalForecastStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
ConditionalForecastStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                        [[maybe_unused]] WarningConsolidation& warnings)
{
  if (!options_list.contains("parameter_set"))
    {
      cerr << "ERROR: You must pass the `parameter_set` option to conditional_forecast" << '\n';
      exit(EXIT_FAILURE);
    }
}

void
ConditionalForecastStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                          [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_cond_fcst_");
  output << "oo_.conditional_forecast=conditional_forecasts.run(M_,options_,"
         << "oo_, estim_params_, constrained_paths_, constrained_vars_, "
         << "options_cond_fcst_);" << '\n';
}

void
ConditionalForecastStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "conditional_forecast")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

PlotConditionalForecastStatement::PlotConditionalForecastStatement(
    const optional<int>& periods_arg, SymbolList symbol_list_arg,
    const SymbolTable& symbol_table_arg) :
    periods {periods_arg}, symbol_list {move(symbol_list_arg)}, symbol_table {symbol_table_arg}
{
}

void
PlotConditionalForecastStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                            WarningConsolidation& warnings)
{
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: plot_conditional_forecast: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
PlotConditionalForecastStatement::writeOutput(ostream& output,
                                              [[maybe_unused]] const string& basename,
                                              [[maybe_unused]] bool minimal_workspace) const
{
  symbol_list.writeOutput("var_list_", output);
  if (periods)
    output << "conditional_forecasts.plot(var_list_, " << *periods << ",options_,oo_);" << '\n';
  else
    output << "conditional_forecasts.plot(var_list_,[],options_,oo_);" << '\n';
}

void
PlotConditionalForecastStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "plot_conditional_forecast")";
  if (periods)
    output << R"(, "periods": )" << *periods;
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

SvarIdentificationStatement::SvarIdentificationStatement(
    svar_identification_restrictions_t restrictions_arg, bool upper_cholesky_present_arg,
    bool lower_cholesky_present_arg, bool constants_exclusion_present_arg,
    const SymbolTable& symbol_table_arg) :
    restrictions {move(restrictions_arg)},
    upper_cholesky_present {upper_cholesky_present_arg},
    lower_cholesky_present {lower_cholesky_present_arg},
    constants_exclusion_present {constants_exclusion_present_arg},
    symbol_table {symbol_table_arg}
{
}

int
SvarIdentificationStatement::getMaxLag() const
{
  int max_lag = 0;
  for (const auto& restriction : restrictions)
    max_lag = max(restriction.lag, max_lag);

  return max_lag;
}

void
SvarIdentificationStatement::checkPass(ModFileStructure& mod_file_struct,
                                       [[maybe_unused]] WarningConsolidation& warnings)
{
  // no equations OK with Svar Identification
  mod_file_struct.bvar_present = true;
  if (!mod_file_struct.svar_identification_present)
    mod_file_struct.svar_identification_present = true;
  else
    {
      cerr << "ERROR: You may only have one svar_identification block in your .mod file." << '\n';
      exit(EXIT_FAILURE);
    }

  if (upper_cholesky_present && lower_cholesky_present)
    {
      cerr << "ERROR: Within the svar_identification statement, you may only have one of "
           << "upper_cholesky and lower_cholesky." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
SvarIdentificationStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  assert(!(upper_cholesky_present && lower_cholesky_present));
  output << "%" << '\n' << "% SVAR IDENTIFICATION" << '\n' << "%" << '\n';

  if (upper_cholesky_present)
    output << "options_.ms.upper_cholesky=1;" << '\n';

  if (lower_cholesky_present)
    output << "options_.ms.lower_cholesky=1;" << '\n';

  if (constants_exclusion_present)
    output << "options_.ms.constants_exclusion=1;" << '\n';

  if (!upper_cholesky_present && !lower_cholesky_present)
    {
      int n = symbol_table.endo_nbr();
      int m = 1; // this is the constant, not the shocks
      int r = getMaxLag();
      int k = r * n + m;

      if (k < 1)
        {
          cerr << "ERROR: lag = " << r << ", number of endogenous variables = " << n
               << ", number of exogenous variables = " << m
               << ". If this is not a logical error in the specification"
               << " of the .mod file, please report it to the Dynare Team." << '\n';
          exit(EXIT_FAILURE);
        }
      if (n < 1)
        {
          cerr
              << "ERROR: Number of endogenous variables = " << n << "< 1. If this is not a logical "
              << "error in the specification of the .mod file, please report it to the Dynare Team."
              << '\n';
          exit(EXIT_FAILURE);
        }
      output << "options_.ms.Qi = cell(" << n << ",1);" << '\n'
             << "options_.ms.Ri = cell(" << n << ",1);" << '\n';

      for (auto& it : restrictions)
        {
          assert(it.lag >= 0);
          if (it.lag == 0)
            output << "options_.ms.Qi{" << it.equation << "}(" << it.restriction_nbr << ", "
                   << it.variable + 1 << ") = ";
          else
            {
              int col = (it.lag - 1) * n + it.variable + 1;
              if (col > k)
                {
                  cerr << "ERROR: lag =" << it.lag << ", num endog vars = " << n
                       << "current endog var index = " << it.variable << ". Index "
                       << "out of bounds. If the above does not represent a logical error, please "
                          "report this to the Dynare Team."
                       << '\n';
                  exit(EXIT_FAILURE);
                }
              output << "options_.ms.Ri{" << it.equation << "}(" << it.restriction_nbr << ", "
                     << col << ") = ";
            }
          it.value->writeOutput(output);
          output << ";" << '\n';
        }
      output << "options_.ms.nlags = " << r << ";" << '\n';
    }
}

void
SvarIdentificationStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "svar_identification")";

  if (upper_cholesky_present)
    output << R"(, "upper_cholesky": 1)";

  if (lower_cholesky_present)
    output << R"(, "lower_cholesky": 1)";

  if (constants_exclusion_present)
    output << R"(, "constants_exclusion": 1)";

  if (!upper_cholesky_present && !lower_cholesky_present)
    {
      output << R"(, "nlags": )" << getMaxLag() << R"(, "restrictions": [)";

      for (bool printed_something {false}; const auto& it : restrictions)
        {
          if (exchange(printed_something, true))
            output << ", ";
          output << "{"
                 << R"("equation_number": )" << it.equation << ", "
                 << R"("restriction_number": )" << it.restriction_nbr << ", "
                 << R"("variable": ")" << symbol_table.getName(it.variable) << R"(", )"
                 << R"("expression": ")";
          it.value->writeOutput(output);
          output << R"("})";
        }
      output << "]";
    }
  output << "}";
}

MarkovSwitchingStatement::MarkovSwitchingStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
  if (auto opt = options_list.get_if<vector<vector<string>>>("ms.restrictions"))
    {
      int num_regimes {stoi(options_list.get<OptionsList::NumVal>("ms.number_of_regimes"))};

      for (const vector<string>& restriction : *opt)
        {
          if (restriction.size() != 3)
            {
              cerr
                  << "ERROR: restrictions in the subsample statement must be specified in the form "
                  << "[current_period_regime, next_period_regime, transition_probability]" << '\n';
              exit(EXIT_FAILURE);
            }

          try
            {
              auto from_regime = stoi(restriction[0]);
              auto to_regime = stoi(restriction[1]);
              if (from_regime > num_regimes || to_regime > num_regimes)
                {
                  cerr << "ERROR: the regimes specified in the restrictions option must be "
                       << "<= the number of regimes specified in the number_of_regimes option"
                       << '\n';
                  exit(EXIT_FAILURE);
                }

              if (restriction_map.contains({from_regime, to_regime}))
                {
                  cerr << "ERROR: two restrictions were given for: " << from_regime << ", "
                       << to_regime << '\n';
                  exit(EXIT_FAILURE);
                }

              auto transition_probability = stod(restriction[2]);
              if (transition_probability > 1.0)
                {
                  cerr << "ERROR: the transition probability, " << transition_probability
                       << " must be less than 1" << '\n';
                  exit(EXIT_FAILURE);
                }
              restriction_map[{from_regime, to_regime}] = transition_probability;
            }
          catch (const invalid_argument&)
            {
              cerr << "ERROR: The first two arguments for a restriction must be integers "
                   << "specifying the regime and the last must be a floating point specifying the "
                   << "transition probability.";
              exit(EXIT_FAILURE);
            }
        }
    }
}

void
MarkovSwitchingStatement::checkPass(ModFileStructure& mod_file_struct,
                                    [[maybe_unused]] WarningConsolidation& warnings)
{
  int chainNumber {stoi(options_list.get<OptionsList::NumVal>("ms.chain"))};
  if (++mod_file_struct.last_markov_switching_chain != chainNumber)
    {
      cerr << "ERROR: The markov_switching chain option takes consecutive integers "
           << "beginning at 1." << '\n';
      exit(EXIT_FAILURE);
    }

  if (options_list.contains("ms.restrictions"))
    {
      int num_regimes {stoi(options_list.get<OptionsList::NumVal>("ms.number_of_regimes"))};
      vector col_trans_prob_sum(num_regimes, 0.0);
      vector row_trans_prob_sum(num_regimes, 0.0);
      vector all_restrictions_in_row(num_regimes, true);
      vector all_restrictions_in_col(num_regimes, true);
      for (int row = 0; row < num_regimes; row++)
        for (int col = 0; col < num_regimes; col++)
          if (restriction_map.contains({row + 1, col + 1}))
            {
              row_trans_prob_sum[row] += restriction_map[{row + 1, col + 1}];
              col_trans_prob_sum[col] += restriction_map[{row + 1, col + 1}];
            }
          else
            {
              all_restrictions_in_row[row] = false;
              all_restrictions_in_col[col] = false;
            }

      for (int i = 0; i < num_regimes; i++)
        {
          if (all_restrictions_in_row[i])
            {
              if (row_trans_prob_sum[i] != 1.0)
                {
                  cerr << "ERROR: When all transitions probabilities are specified for a certain "
                       << "regime, they must sum to 1" << '\n';
                  exit(EXIT_FAILURE);
                }
            }
          else if (row_trans_prob_sum[i] >= 1.0)
            {
              cerr << "ERROR: When transition probabilites are not specified for every regime, "
                   << "their sum must be < 1" << '\n';
              exit(EXIT_FAILURE);
            }

          if (all_restrictions_in_col[i])
            {
              if (col_trans_prob_sum[i] != 1.0)
                {
                  cerr << "ERROR: When all transitions probabilities are specified for a certain "
                       << "regime, they must sum to 1" << '\n';
                  exit(EXIT_FAILURE);
                }
            }
          else if (col_trans_prob_sum[i] >= 1.0)
            {
              cerr << "ERROR: When transition probabilites are not specified for every regime, "
                   << "their sum must be < 1" << '\n';
              exit(EXIT_FAILURE);
            }
        }
    }

  if (options_list.contains("ms.parameters"))
    mod_file_struct.ms_dsge_present = true;
}

void
MarkovSwitchingStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  string chainNumber {options_list.get<OptionsList::NumVal>("ms.chain")};

  assert(options_list.contains("ms.duration"));

  output << "options_.ms.duration = ";
  bool isDurationAVec {options_list.visit("ms.duration", [&]<class T>(const T& v) -> bool {
    if constexpr (is_same_v<T, OptionsList::VecValueVal>)
      {
        output << "[";
        for (bool printed_something {false}; const auto& it : v)
          {
            if (exchange(printed_something, true))
              output << ", ";
            output << it;
          }
        output << "]";
        return true;
      }
    else if constexpr (is_same_v<T, OptionsList::NumVal>)
      {
        output << v;
        return false;
      }
    else
      {
        cerr << "MarkovSwitchingStatement::writeOutput: incorrect value type for 'ms.duration' "
                "option"
             << '\n';
        exit(EXIT_FAILURE);
      }
  })};
  output << ";" << '\n';

  int NOR {stoi(options_list.get<OptionsList::NumVal>("ms.number_of_regimes"))};
  for (int i {0}; i < NOR; i++)
    {
      output << "options_.ms.ms_chain(" << chainNumber << ").regime(" << i + 1
             << ").duration = options_.ms.duration";
      if (isDurationAVec)
        output << "(" << i + 1 << ")";
      output << ";" << '\n';
    }

  for (int restrictions_index {0}; const auto& [regimes, prob] : restriction_map)
    output << "options_.ms.ms_chain(" << chainNumber << ").restrictions(" << ++restrictions_index
           << ") = {[" << regimes.first << ", " << regimes.second << ", " << prob << "]};" << '\n';
}

void
MarkovSwitchingStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "markov_switching")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }

  if (!restriction_map.empty())
    output << ", {";
  for (bool printed_something {false}; const auto& [regimes, prob] : restriction_map)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"({"current_period_regime": )" << regimes.first << R"(, "next_period_regime": )"
             << regimes.second << R"(, "transition_probability": )" << prob << "}";
    }
  if (!restriction_map.empty())
    output << "}";
  output << "}";
}

SvarStatement::SvarStatement(OptionsList options_list_arg) : options_list {move(options_list_arg)}
{
}

void
SvarStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                         [[maybe_unused]] WarningConsolidation& warnings)
{
  bool has_coefficients = options_list.contains("ms.coefficients"),
       has_variances = options_list.contains("ms.variances"),
       has_constants = options_list.contains("ms.constants");
  assert((has_coefficients && !has_variances && !has_constants)
         || (!has_coefficients && has_variances && !has_constants)
         || (!has_coefficients && !has_variances && has_constants));
}

void
SvarStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                           [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_.ms.ms_chain(" << options_list.get<OptionsList::NumVal>("ms.chain") << ")";

  if (auto opt1 = options_list.get_if<OptionsList::StringVal>("ms.coefficients"))
    output << "." << *opt1;
  else if (auto opt2 = options_list.get_if<OptionsList::StringVal>("ms.variances"))
    output << "." << *opt2;
  else
    output << "." << options_list.get<OptionsList::StringVal>("ms.constants");

  output << ".equations = ";
  if (auto opt = options_list.get_if<vector<int>>("ms.equations"))
    {
      assert(opt->size() >= 1);
      if (opt->size() > 1)
        {
          output << "[";
          for (int viit : *opt)
            output << viit << ";";
          output << "];" << '\n';
        }
      else
        output << opt->front() << ";" << '\n';
    }
  else
    output << "'ALL';" << '\n';
}

void
SvarStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "svar")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

void
SvarGlobalIdentificationCheckStatement::writeOutput(ostream& output,
                                                    [[maybe_unused]] const string& basename,
                                                    [[maybe_unused]] bool minimal_workspace) const
{
  output << "svar_global_identification_check(options_);" << '\n';
}

void
SvarGlobalIdentificationCheckStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "svar_global_identification"})";
}

SetTimeStatement::SetTimeStatement(string period_arg) : period {move(period_arg)}
{
}

void
SetTimeStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                              [[maybe_unused]] bool minimal_workspace) const
{
  output << "options_.initial_period = " << period << '\n';
}

void
SetTimeStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "set_time", "period": ")" << period << R"("})";
}

EstimationDataStatement::EstimationDataStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
EstimationDataStatement::checkPass(ModFileStructure& mod_file_struct,
                                   [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.estimation_data_statement_present = true;

  if (auto opt = options_list.get_if<OptionsList::NumVal>("nobs"))
    if (stoi(*opt) <= 0)
      {
        cerr << "ERROR: The nobs option of the data statement only accepts positive integers."
             << '\n';
        exit(EXIT_FAILURE);
      }

  bool has_file = options_list.contains("file"), has_series = options_list.contains("series");
  if (!has_file && !has_series)
    {
      cerr << "ERROR: The file or series option must be passed to the data statement." << '\n';
      exit(EXIT_FAILURE);
    }
  if (has_file && has_series)
    {
      cerr << "ERROR: The file and series options cannot be used simultaneously in the data "
              "statement."
           << '\n';
      exit(EXIT_FAILURE);
    }
}

void
EstimationDataStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                     [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_.dataset");
}

void
EstimationDataStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "estimation_data")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

SubsamplesStatement::SubsamplesStatement(string name1_arg, string name2_arg,
                                         subsample_declaration_map_t subsample_declaration_map_arg,
                                         const SymbolTable& symbol_table_arg) :
    name1 {move(name1_arg)},
    name2 {move(name2_arg)},
    subsample_declaration_map {move(subsample_declaration_map_arg)},
    symbol_table {symbol_table_arg}
{
}

void
SubsamplesStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  output << "subsamples_indx = get_new_or_existing_ei_index('subsamples_index', '" << name1 << "','"
         << name2 << "');" << '\n'
         << "estimation_info.subsamples_index(subsamples_indx) = {'" << name1;
  if (!name2.empty())
    output << ":" << name2;
  output << "'};" << '\n'
         << "estimation_info.subsamples(subsamples_indx).range = {};" << '\n'
         << "estimation_info.subsamples(subsamples_indx).range_index = {};" << '\n';

  for (int map_indx {1}; const auto& [range, dates] : subsample_declaration_map)
    {
      output << "estimation_info.subsamples(subsamples_indx).range_index(" << map_indx << ") = {'"
             << range << "'};" << '\n'
             << "estimation_info.subsamples(subsamples_indx).range(" << map_indx
             << ").date1 = " << dates.first << ";" << '\n'
             << "estimation_info.subsamples(subsamples_indx).range(" << map_indx
             << ").date2 = " << dates.second << ";" << '\n';
      map_indx++;
    }

  // Initialize associated subsample substructures in estimation_info
  const SymbolType symb_type = symbol_table.getType(name1);
  string lhs_field;
  switch (symb_type)
    {
    case SymbolType::parameter:
      lhs_field = "parameter";
      break;
    case SymbolType::exogenous:
      lhs_field = "structural_innovation";
      break;
    case SymbolType::endogenous:
      lhs_field = "measurement_error";
      break;
    default:
      cerr << "subsamples: invalid symbol type for " << name1 << '\n';
      exit(EXIT_FAILURE);
    }

  output << "eifind = get_new_or_existing_ei_index('" << lhs_field;

  if (!name2.empty())
    output << "_corr";
  output << "_prior_index', '" << name1 << "', '";
  if (!name2.empty())
    output << name2;
  output << "');" << '\n';

  lhs_field = "estimation_info." + lhs_field;
  if (!name2.empty())
    lhs_field += "_corr";
  output << lhs_field << "_prior_index(eifind) = {'" << name1;
  if (!name2.empty())
    output << ":" << name2;
  output << "'};" << '\n';

  output << lhs_field << "(eifind).subsample_prior = estimation_info.empty_prior;" << '\n'
         << lhs_field << "(eifind).subsample_prior(1:" << subsample_declaration_map.size()
         << ") = estimation_info.empty_prior;" << '\n'
         << lhs_field
         << "(eifind).range_index = estimation_info.subsamples(subsamples_indx).range_index;"
         << '\n';
}

void
SubsamplesStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "subsamples")"
         << R"(, "name1": ")" << name1 << R"(")";
  if (!name2.empty())
    output << R"(, "name2": ")" << name2 << R"(")";

  output << R"(, "declarations": {)";
  for (bool printed_something {false}; const auto& [range, dates] : subsample_declaration_map)
    {
      if (exchange(printed_something, true))
        output << ",";
      output << "{"
             << R"("range_index": ")" << range << R"(")"
             << R"(, "date1": ")" << dates.first << R"(")"
             << R"(, "date2": ")" << dates.second << R"(")"
             << "}";
    }
  output << "}"
         << "}";
}

SubsamplesEqualStatement::SubsamplesEqualStatement(string to_name1_arg, string to_name2_arg,
                                                   string from_name1_arg, string from_name2_arg,
                                                   const SymbolTable& symbol_table_arg) :
    to_name1 {move(to_name1_arg)},
    to_name2 {move(to_name2_arg)},
    from_name1 {move(from_name1_arg)},
    from_name2 {move(from_name2_arg)},
    symbol_table {symbol_table_arg}
{
}

void
SubsamplesEqualStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  output << "subsamples_to_indx = get_new_or_existing_ei_index('subsamples_index', '" << to_name1
         << "','" << to_name2 << "');" << '\n'
         << "estimation_info.subsamples_index(subsamples_to_indx) = {'" << to_name1;
  if (!to_name2.empty())
    output << ":" << to_name2;
  output << "'};" << '\n'
         << "subsamples_from_indx = get_existing_subsamples_indx('" << from_name1 << "','"
         << from_name2 << "');" << '\n'
         << "estimation_info.subsamples(subsamples_to_indx) = "
            "estimation_info.subsamples(subsamples_from_indx);"
         << '\n';

  // Initialize associated subsample substructures in estimation_info
  const SymbolType symb_type = symbol_table.getType(to_name1);
  string lhs_field;
  switch (symb_type)
    {
    case SymbolType::parameter:
      lhs_field = "parameter";
      break;
    case SymbolType::exogenous:
      lhs_field = "structural_innovation";
      break;
    case SymbolType::endogenous:
      lhs_field = "measurement_error";
      break;
    default:
      cerr << "subsamples: invalid symbol type for " << to_name1 << '\n';
      exit(EXIT_FAILURE);
    }

  output << "eifind = get_new_or_existing_ei_index('" << lhs_field;

  if (!to_name2.empty())
    output << "_corr";
  output << "_prior_index', '" << to_name1 << "', '";
  if (!to_name2.empty())
    output << to_name2;
  output << "');" << '\n';

  lhs_field = "estimation_info." + lhs_field;
  if (!to_name2.empty())
    lhs_field += "_corr";
  output << lhs_field << "_prior_index(eifind) = {'" << to_name1;
  if (!to_name2.empty())
    output << ":" << to_name2;
  output << "'};" << '\n';

  output << lhs_field << "(eifind).subsample_prior = estimation_info.empty_prior;" << '\n'
         << lhs_field
         << "(eifind).subsample_prior(1:size(estimation_info.subsamples(subsamples_to_indx).range_"
            "index,2)) = estimation_info.empty_prior;"
         << '\n'
         << lhs_field
         << "(eifind).range_index = estimation_info.subsamples(subsamples_to_indx).range_index;"
         << '\n';
}

void
SubsamplesEqualStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "subsamples_equal")"
         << R"(, "to_name1": ")" << to_name1 << R"(")";
  if (!to_name2.empty())
    output << R"(, "to_name2": ")" << to_name2 << R"(")";
  output << R"(, "from_name1": ")" << from_name1 << R"(")";
  if (!from_name2.empty())
    output << R"(, "from_name2": ")" << from_name2 << R"(")";
  output << "}";
}

JointPriorStatement::JointPriorStatement(vector<string> joint_parameters_arg,
                                         PriorDistributions prior_shape_arg,
                                         OptionsList options_list_arg) :
    joint_parameters {move(joint_parameters_arg)},
    prior_shape {prior_shape_arg},
    options_list {move(options_list_arg)}
{
}

void
JointPriorStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                               [[maybe_unused]] WarningConsolidation& warnings)
{
  if (joint_parameters.size() < 2)
    {
      cerr << "ERROR: you must pass at least two parameters to the joint prior statement" << '\n';
      exit(EXIT_FAILURE);
    }

  if (prior_shape == PriorDistributions::noShape)
    {
      cerr << "ERROR: You must pass the shape option to the prior statement." << '\n';
      exit(EXIT_FAILURE);
    }

  if (!options_list.contains("mean") && !options_list.contains("mode"))
    {
      cerr << "ERROR: You must pass at least one of mean and mode to the prior statement." << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt = options_list.get_if<OptionsList::VecValueVal>("domain"); opt && opt->size() != 4)
    {
      cerr << "ERROR: You must pass exactly four values to the domain option." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
JointPriorStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  for (const auto& joint_parameter : joint_parameters)
    output << "eifind = get_new_or_existing_ei_index('joint_parameter_prior_index', '"
           << joint_parameter << "', '');" << '\n'
           << "estimation_info.joint_parameter_prior_index(eifind) = {'" << joint_parameter << "'};"
           << '\n';

  output << "key = {[";
  for (const auto& joint_parameter : joint_parameters)
    output << "get_new_or_existing_ei_index('joint_parameter_prior_index', '" << joint_parameter
           << "', '') ..." << '\n'
           << "    ";
  output << "]};" << '\n';

  string lhs_field("estimation_info.joint_parameter_tmp");

  writeOutputHelper(output, "domain", lhs_field);
  writeOutputHelper(output, "interval", lhs_field);
  writeOutputHelper(output, "mean", lhs_field);
  writeOutputHelper(output, "median", lhs_field);
  writeOutputHelper(output, "mode", lhs_field);

  assert(prior_shape != PriorDistributions::noShape);
  output << lhs_field << ".shape = " << static_cast<int>(prior_shape) << ";" << '\n';

  writeOutputHelper(output, "shift", lhs_field);
  writeOutputHelper(output, "stdev", lhs_field);
  writeOutputHelper(output, "truncate", lhs_field);
  writeOutputHelper(output, "variance", lhs_field);

  output << "estimation_info.joint_parameter_tmp = [key, ..." << '\n'
         << "    " << lhs_field << ".domain , ..." << '\n'
         << "    " << lhs_field << ".interval , ..." << '\n'
         << "    " << lhs_field << ".mean , ..." << '\n'
         << "    " << lhs_field << ".median , ..." << '\n'
         << "    " << lhs_field << ".mode , ..." << '\n'
         << "    " << lhs_field << ".shape , ..." << '\n'
         << "    " << lhs_field << ".shift , ..." << '\n'
         << "    " << lhs_field << ".stdev , ..." << '\n'
         << "    " << lhs_field << ".truncate , ..." << '\n'
         << "    " << lhs_field << ".variance];" << '\n'
         << "estimation_info.joint_parameter = [estimation_info.joint_parameter; "
            "estimation_info.joint_parameter_tmp];"
         << '\n'
         << "estimation_info=rmfield(estimation_info, 'joint_parameter_tmp');" << '\n';
}

void
JointPriorStatement::writeOutputHelper(ostream& output, const string& field,
                                       const string& lhs_field) const
{
  output << lhs_field << "." << field << " = {";
  if (options_list.contains(field))
    options_list.visit(field, [&]<class T>(const T& v) {
      if constexpr (is_same_v<T, OptionsList::NumVal>)
        output << v;
      else if constexpr (is_same_v<T, OptionsList::VecValueVal>)
        {
          output << "[";
          for (bool printed_something {false}; const auto& it2 : v)
            {
              if (exchange(printed_something, true))
                output << ", ";
              output << it2;
            }
          output << "]";
        }
      else if constexpr (is_same_v<T, vector<vector<string>>>)
        {
          output << "{";
          for (bool printed_something {false}; const auto& it2 : v)
            {
              if (exchange(printed_something, true))
                output << ", ";
              output << "[";
              for (bool printed_something2 {false}; const auto& it3 : it2)
                {
                  if (exchange(printed_something2, true))
                    output << ", ";
                  output << it3;
                }
              output << "]";
            }
          output << "}";
        }
      else
        {
          cerr << "JointPriorStatement::writeOutputHelper: unhandled alternative" << '\n';
          exit(EXIT_FAILURE);
        }
    });
  else
    output << "{}";
  output << "};" << '\n';
}

void
JointPriorStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "joint_prior")"
         << R"(, "key": [)";
  for (bool printed_something {false}; const auto& it : joint_parameters)
    {
      if (exchange(printed_something, true))
        output << ", ";
      output << R"(")" << it << R"(")";
    }
  output << "]";

  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }

  output << R"(, "shape": )";
  switch (prior_shape)
    {
    case PriorDistributions::beta:
      output << R"("beta")";
      break;
    case PriorDistributions::gamma:
      output << R"("gamma")";
      break;
    case PriorDistributions::normal:
      output << R"("normal")";
      break;
    case PriorDistributions::invGamma:
      output << R"("inv_gamma")";
      break;
    case PriorDistributions::uniform:
      output << R"("uniform")";
      break;
    case PriorDistributions::invGamma2:
      output << R"("inv_gamma2")";
      break;
    case PriorDistributions::dirichlet:
      output << R"("dirichlet")";
      break;
    case PriorDistributions::weibull:
      output << R"("weibull")";
      break;
    case PriorDistributions::noShape:
      cerr << "Impossible case." << '\n';
      exit(EXIT_FAILURE);
    }
  output << "}";
}

BasicPriorStatement::BasicPriorStatement(string name_arg, string subsample_name_arg,
                                         PriorDistributions prior_shape_arg, expr_t variance_arg,
                                         OptionsList options_list_arg) :
    name {move(name_arg)},
    subsample_name {move(subsample_name_arg)},
    prior_shape {prior_shape_arg},
    variance {variance_arg},
    options_list {move(options_list_arg)}
{
}

void
BasicPriorStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                               [[maybe_unused]] WarningConsolidation& warnings)
{
  if (prior_shape == PriorDistributions::noShape)
    {
      cerr << "ERROR: You must pass the shape option to the prior statement." << '\n';
      exit(EXIT_FAILURE);
    }

  if (!options_list.contains("mean") && !options_list.contains("mode"))
    {
      cerr << "ERROR: You must pass at least one of mean and mode to the prior statement." << '\n';
      exit(EXIT_FAILURE);
    }

  if (bool has_stdev = options_list.contains("stdev");
      (!has_stdev && !variance) || (has_stdev && variance))
    {
      cerr << "ERROR: You must pass exactly one of stdev and variance to the prior statement."
           << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt = options_list.get_if<OptionsList::VecValueVal>("domain"); opt && opt->size() != 2)
    {
      cerr << "ERROR: You must pass exactly two values to the domain option." << '\n';
      exit(EXIT_FAILURE);
    }
}

bool
BasicPriorStatement::is_structural_innovation(const SymbolType symb_type) const
{
  return symb_type == SymbolType::exogenous;
}

void
BasicPriorStatement::get_base_name(const SymbolType symb_type, string& lhs_field) const
{
  lhs_field = (symb_type == SymbolType::exogenous ? "structural_innovation" : "measurement_error");
}

void
BasicPriorStatement::writeCommonOutput(ostream& output, const string& lhs_field) const
{
  output << lhs_field << " = estimation_info.empty_prior;" << '\n';

  writeCommonOutputHelper(output, "domain", lhs_field);
  writeCommonOutputHelper(output, "interval", lhs_field);
  writeCommonOutputHelper(output, "mean", lhs_field);
  writeCommonOutputHelper(output, "median", lhs_field);
  writeCommonOutputHelper(output, "mode", lhs_field);

  assert(prior_shape != PriorDistributions::noShape);
  output << lhs_field << ".shape = " << static_cast<int>(prior_shape) << ";" << '\n';

  writeCommonOutputHelper(output, "shift", lhs_field);
  writeCommonOutputHelper(output, "stdev", lhs_field);
  writeCommonOutputHelper(output, "truncate", lhs_field);

  if (variance)
    {
      output << lhs_field << ".variance = ";
      variance->writeOutput(output);
      output << ";" << '\n';
    }
}

void
BasicPriorStatement::writeCommonOutputHelper(ostream& output, const string& field,
                                             const string& lhs_field) const
{
  if (options_list.contains(field))
    options_list.visit(field, [&]<class T>(const T& v) {
      if constexpr (is_same_v<T, OptionsList::NumVal>)
        output << lhs_field << "." << field << " = " << v << ";" << endl;
      // TODO: handle other variant types
    });
}

void
BasicPriorStatement::writePriorOutput(ostream& output, string& lhs_field, const string& name2) const
{
  if (subsample_name.empty())
    lhs_field += ".prior(1)";
  else
    {
      output << "subsamples_indx = get_existing_subsamples_indx('" << name << "','" << name2
             << "');" << '\n'
             << "eisind = get_subsamples_range_indx(subsamples_indx, '" << subsample_name << "');"
             << '\n';
      lhs_field += ".subsample_prior(eisind)";
    }
  writeCommonOutput(output, lhs_field);
}

void
BasicPriorStatement::writeJsonPriorOutput(ostream& output) const
{
  output << R"(, "name": ")" << name << R"(")"
         << R"(, "subsample": ")" << subsample_name << R"(")"
         << ", ";
  writeJsonShape(output);
  if (variance)
    {
      output << R"(, "variance": ")";
      variance->writeJsonOutput(output, {}, {});
      output << R"(")";
    }
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
}

void
BasicPriorStatement::writeJsonShape(ostream& output) const
{
  output << R"("shape": )";
  switch (prior_shape)
    {
    case PriorDistributions::beta:
      output << R"("beta")";
      break;
    case PriorDistributions::gamma:
      output << R"("gamma")";
      break;
    case PriorDistributions::normal:
      output << R"("normal")";
      break;
    case PriorDistributions::invGamma:
      output << R"("inv_gamma")";
      break;
    case PriorDistributions::uniform:
      output << R"("uniform")";
      break;
    case PriorDistributions::invGamma2:
      output << R"("inv_gamma2")";
      break;
    case PriorDistributions::dirichlet:
      output << R"("dirichlet")";
      break;
    case PriorDistributions::weibull:
      output << R"("weibull")";
      break;
    case PriorDistributions::noShape:
      assert(prior_shape != PriorDistributions::noShape);
    }
}

PriorStatement::PriorStatement(string name_arg, string subsample_name_arg,
                               PriorDistributions prior_shape_arg, expr_t variance_arg,
                               OptionsList options_list_arg) :
    BasicPriorStatement {move(name_arg), move(subsample_name_arg), prior_shape_arg, variance_arg,
                         move(options_list_arg)}
{
}

void
PriorStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field = "estimation_info.parameter(eifind)";
  output << "eifind = get_new_or_existing_ei_index('parameter_prior_index', '" << name << "', '');"
         << '\n'
         << "estimation_info.parameter_prior_index(eifind) = {'" << name << "'};" << '\n';
  writePriorOutput(output, lhs_field, "");
}

void
PriorStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "prior")";
  writeJsonPriorOutput(output);
  output << "}";
}

StdPriorStatement::StdPriorStatement(string name_arg, string subsample_name_arg,
                                     PriorDistributions prior_shape_arg, expr_t variance_arg,
                                     OptionsList options_list_arg,
                                     const SymbolTable& symbol_table_arg) :
    BasicPriorStatement {move(name_arg), move(subsample_name_arg), prior_shape_arg, variance_arg,
                         move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
StdPriorStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                               [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field;
  get_base_name(symbol_table.getType(name), lhs_field);
  output << "eifind = get_new_or_existing_ei_index('" << lhs_field << "_prior_index', '" << name
         << "', '');" << '\n'
         << "estimation_info." << lhs_field << "_prior_index(eifind) = {'" << name << "'};" << '\n';

  lhs_field = "estimation_info." + lhs_field + "(eifind)";
  writePriorOutput(output, lhs_field, "");
}

void
StdPriorStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "std_prior")";
  writeJsonPriorOutput(output);
  output << "}";
}

CorrPriorStatement::CorrPriorStatement(string name_arg1, string name_arg2,
                                       string subsample_name_arg,
                                       PriorDistributions prior_shape_arg, expr_t variance_arg,
                                       OptionsList options_list_arg,
                                       const SymbolTable& symbol_table_arg) :
    BasicPriorStatement {move(name_arg1), move(subsample_name_arg), prior_shape_arg, variance_arg,
                         move(options_list_arg)},
    name1 {move(name_arg2)},
    symbol_table {symbol_table_arg}
{
}

void
CorrPriorStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  BasicPriorStatement::checkPass(mod_file_struct, warnings);
  if (symbol_table.getType(name) != symbol_table.getType(name1))
    {
      cerr << "ERROR: In the corr(A,B).prior statement, A and B must be of the same type. "
           << "In your case, " << name << " and " << name1 << " are of different "
           << "types." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
CorrPriorStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field;
  get_base_name(symbol_table.getType(name), lhs_field);

  output << "eifind = get_new_or_existing_ei_index('" << lhs_field << "_corr_prior_index', '"
         << name << "', '" << name1 << "');" << '\n'
         << "estimation_info." << lhs_field << "_corr_prior_index(eifind) = {'" << name << ":"
         << name1 << "'};" << '\n';

  lhs_field = "estimation_info." + lhs_field + "_corr(eifind)";
  writePriorOutput(output, lhs_field, name1);
}

void
CorrPriorStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "corr_prior")"
         << R"(, "name2": ")" << name1 << R"(")";
  writeJsonPriorOutput(output);
  output << "}";
}

PriorEqualStatement::PriorEqualStatement(string to_declaration_type_arg, string to_name1_arg,
                                         string to_name2_arg, string to_subsample_name_arg,
                                         string from_declaration_type_arg, string from_name1_arg,
                                         string from_name2_arg, string from_subsample_name_arg,
                                         const SymbolTable& symbol_table_arg) :
    to_declaration_type {move(to_declaration_type_arg)},
    to_name1 {move(to_name1_arg)},
    to_name2 {move(to_name2_arg)},
    to_subsample_name {move(to_subsample_name_arg)},
    from_declaration_type {move(from_declaration_type_arg)},
    from_name1 {move(from_name1_arg)},
    from_name2 {move(from_name2_arg)},
    from_subsample_name {move(from_subsample_name_arg)},
    symbol_table {symbol_table_arg}
{
}

void
PriorEqualStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                               [[maybe_unused]] WarningConsolidation& warnings)
{
  if ((to_declaration_type != "par" && to_declaration_type != "std"
       && to_declaration_type != "corr")
      || (from_declaration_type != "par" && from_declaration_type != "std"
          && from_declaration_type != "corr"))
    {
      cerr << "Internal Dynare Error" << '\n';
      exit(EXIT_FAILURE);
    }
}

void
PriorEqualStatement::get_base_name(const SymbolType symb_type, string& lhs_field) const
{
  if (symb_type == SymbolType::exogenous)
    lhs_field = "structural_innovation";
  else
    lhs_field = "measurement_error";
}

void
PriorEqualStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field, rhs_field;

  if (to_declaration_type == "par")
    lhs_field = "parameter";
  else
    get_base_name(symbol_table.getType(to_name1), lhs_field);

  if (from_declaration_type == "par")
    rhs_field = "parameter";
  else
    get_base_name(symbol_table.getType(from_name1), rhs_field);

  if (to_declaration_type == "corr")
    lhs_field += "_corr";

  if (from_declaration_type == "corr")
    rhs_field += "_corr";

  output << "ei_to_ind = get_new_or_existing_ei_index('" << lhs_field << "_prior_index', '"
         << to_name1 << "', '" << to_name2 << "');" << '\n'
         << "ei_from_ind = get_new_or_existing_ei_index('" << rhs_field << "_prior_index', '"
         << from_name1 << "', '" << from_name2 << "');" << '\n'
         << "estimation_info." << lhs_field << "_prior_index(ei_to_ind) = {'" << to_name1;

  if (to_declaration_type == "corr")
    output << ":" << to_name2;
  output << "'};" << '\n';

  if (to_declaration_type == "par")
    lhs_field = "parameter";

  if (from_declaration_type == "par")
    rhs_field = "parameter";

  lhs_field = "estimation_info." + lhs_field + "(ei_to_ind)";
  rhs_field = "estimation_info." + rhs_field + "(ei_from_ind)";

  if (to_subsample_name.empty())
    lhs_field += ".prior";
  else
    {
      output << "subsamples_to_indx = get_existing_subsamples_indx('" << to_name1 << "','"
             << to_name2 << "');" << '\n'
             << "ei_to_ss_ind = get_subsamples_range_indx(subsamples_to_indx, '"
             << to_subsample_name << "');" << '\n';
      lhs_field += ".subsample_prior(ei_to_ss_ind)";
    }

  if (from_subsample_name.empty())
    rhs_field += ".prior";
  else
    {
      output << "subsamples_from_indx = get_existing_subsamples_indx('" << from_name1 << "','"
             << from_name2 << "');" << '\n'
             << "ei_from_ss_ind = get_subsamples_range_indx(subsamples_from_indx, '"
             << from_subsample_name << "');" << '\n';
      rhs_field += ".subsample_prior(ei_from_ss_ind)";
    }

  output << lhs_field << " = " << rhs_field << ";" << '\n';
}

void
PriorEqualStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "prior_equal")"
         << R"(, "to_name1": ")" << to_name1 << R"(")";
  if (to_declaration_type == "corr")
    output << R"(, "to_name2": ")" << to_name2 << R"(")";
  output << R"(, "to_subsample": ")" << to_subsample_name << R"(")"
         << R"(, "from_name1": ")" << from_name1 << R"(")";
  if (to_declaration_type == "corr")
    output << R"(, "from_name2": ")" << from_name2 << R"(")";
  output << R"(, "from_subsample": ")" << from_subsample_name << R"(")"
         << "}";
}

BasicOptionsStatement::BasicOptionsStatement(string name_arg, string subsample_name_arg,
                                             OptionsList options_list_arg) :
    name {move(name_arg)},
    subsample_name {move(subsample_name_arg)},
    options_list {move(options_list_arg)}
{
}

bool
BasicOptionsStatement::is_structural_innovation(const SymbolType symb_type) const
{
  return symb_type == SymbolType::exogenous;
}

void
BasicOptionsStatement::get_base_name(const SymbolType symb_type, string& lhs_field) const
{
  lhs_field = (symb_type == SymbolType::exogenous ? "structural_innovation" : "measurement_error");
}

void
BasicOptionsStatement::writeCommonOutput(ostream& output, const string& lhs_field) const
{
  output << lhs_field << " = estimation_info.empty_options;" << '\n';

  writeCommonOutputHelper(output, "bounds", lhs_field);
  writeCommonOutputHelper(output, "init", lhs_field);
  writeCommonOutputHelper(output, "jscale", lhs_field);
}

void
BasicOptionsStatement::writeCommonOutputHelper(ostream& output, const string& field,
                                               const string& lhs_field) const
{
  if (options_list.contains(field))
    options_list.visit(field, [&]<class T>(const T& v) {
      if constexpr (is_same_v<T, OptionsList::NumVal>)
        output << lhs_field << "." << field << " = " << v << ";" << endl;
      // TODO: handle other variant types
    });
}

void
BasicOptionsStatement::writeOptionsOutput(ostream& output, string& lhs_field,
                                          const string& name2) const
{
  if (subsample_name.empty())
    lhs_field += ".options(1)";
  else
    {
      output << "subsamples_indx = get_existing_subsamples_indx('" << name << "','" << name2
             << "');" << '\n'
             << "eisind = get_subsamples_range_indx(subsamples_indx, '" << subsample_name << "');"
             << '\n';
      lhs_field += ".subsample_options(eisind)";
    }
  writeCommonOutput(output, lhs_field);
}

void
BasicOptionsStatement::writeJsonOptionsOutput(ostream& output) const
{
  output << R"(, "name": ")" << name << R"(")";
  if (!subsample_name.empty())
    output << R"(, "subsample_name": ")" << subsample_name << R"(")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
}

OptionsStatement::OptionsStatement(string name_arg, string subsample_name_arg,
                                   OptionsList options_list_arg) :
    BasicOptionsStatement {move(name_arg), move(subsample_name_arg), move(options_list_arg)}
{
}

void
OptionsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                              [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field = "estimation_info.parameter(eifind)";
  output << "eifind = get_new_or_existing_ei_index('parameter_options_index', '" << name
         << "', '');" << '\n'
         << "estimation_info.parameter_options_index(eifind) = {'" << name << "'};" << '\n';
  writeOptionsOutput(output, lhs_field, "");
}

void
OptionsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "options")";
  writeJsonOptionsOutput(output);
  output << "}";
}

StdOptionsStatement::StdOptionsStatement(string name_arg, string subsample_name_arg,
                                         OptionsList options_list_arg,
                                         const SymbolTable& symbol_table_arg) :
    BasicOptionsStatement {move(name_arg), move(subsample_name_arg), move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
StdOptionsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                 [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field;
  get_base_name(symbol_table.getType(name), lhs_field);
  output << "eifind = get_new_or_existing_ei_index('" << lhs_field << "_options_index', '" << name
         << "', '');" << '\n'
         << "estimation_info." << lhs_field << "_options_index(eifind) = {'" << name << "'};"
         << '\n';

  lhs_field = "estimation_info." + lhs_field + "(eifind)";
  writeOptionsOutput(output, lhs_field, "");
}

void
StdOptionsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "std_options")";
  writeJsonOptionsOutput(output);
  output << "}";
}

CorrOptionsStatement::CorrOptionsStatement(string name_arg1, string name_arg2,
                                           string subsample_name_arg, OptionsList options_list_arg,
                                           const SymbolTable& symbol_table_arg) :
    BasicOptionsStatement {move(name_arg1), move(subsample_name_arg), move(options_list_arg)},
    name1 {move(name_arg2)},
    symbol_table {symbol_table_arg}
{
}

void
CorrOptionsStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                [[maybe_unused]] WarningConsolidation& warnings)
{
  if (symbol_table.getType(name) != symbol_table.getType(name1))
    {
      cerr << "ERROR: In the corr(A,B).options statement, A and B must be of the same type. "
           << "In your case, " << name << " and " << name1 << " are of different "
           << "types." << '\n';
      exit(EXIT_FAILURE);
    }
}

void
CorrOptionsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field;
  get_base_name(symbol_table.getType(name), lhs_field);

  output << "eifind = get_new_or_existing_ei_index('" << lhs_field << "_corr_options_index', '"
         << name << "', '" << name1 << "');" << '\n'
         << "estimation_info." << lhs_field << "_corr_options_index(eifind) = {'" << name << ":"
         << name1 << "'};" << '\n';

  lhs_field = "estimation_info." + lhs_field + "_corr(eifind)";
  writeOptionsOutput(output, lhs_field, name1);
}

void
CorrOptionsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "corr_options")"
         << R"(, "name2": ")" << name1 << R"(")";
  writeJsonOptionsOutput(output);
  output << "}";
}

OptionsEqualStatement::OptionsEqualStatement(string to_declaration_type_arg, string to_name1_arg,
                                             string to_name2_arg, string to_subsample_name_arg,
                                             string from_declaration_type_arg,
                                             string from_name1_arg, string from_name2_arg,
                                             string from_subsample_name_arg,
                                             const SymbolTable& symbol_table_arg) :
    to_declaration_type {move(to_declaration_type_arg)},
    to_name1 {move(to_name1_arg)},
    to_name2 {move(to_name2_arg)},
    to_subsample_name {move(to_subsample_name_arg)},
    from_declaration_type {move(from_declaration_type_arg)},
    from_name1 {move(from_name1_arg)},
    from_name2 {move(from_name2_arg)},
    from_subsample_name {move(from_subsample_name_arg)},
    symbol_table {symbol_table_arg}
{
}

void
OptionsEqualStatement::checkPass([[maybe_unused]] ModFileStructure& mod_file_struct,
                                 [[maybe_unused]] WarningConsolidation& warnings)
{
  if ((to_declaration_type != "par" && to_declaration_type != "std"
       && to_declaration_type != "corr")
      || (from_declaration_type != "par" && from_declaration_type != "std"
          && from_declaration_type != "corr"))
    {
      cerr << "Internal Dynare Error" << '\n';
      exit(EXIT_FAILURE);
    }
}

void
OptionsEqualStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "options_equal")"
         << R"(, "to_name1": ")" << to_name1 << R"(")";
  if (to_declaration_type == "corr")
    output << R"(, "to_name2": ")" << to_name2 << R"(")";
  output << R"(, "to_subsample": ")" << to_subsample_name << R"(")"
         << R"(, "from_name1": ")" << from_name1 << R"(")";
  if (to_declaration_type == "corr")
    output << R"(, "from_name2": ")" << from_name2 << R"(")";
  output << R"(, "from_subsample": ")" << from_subsample_name << R"(")"
         << "}";
}

void
OptionsEqualStatement::get_base_name(const SymbolType symb_type, string& lhs_field) const
{
  lhs_field = (symb_type == SymbolType::exogenous ? "structural_innovation" : "measurement_error");
}

void
OptionsEqualStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  string lhs_field, rhs_field;

  if (to_declaration_type == "par")
    lhs_field = "parameter";
  else
    get_base_name(symbol_table.getType(to_name1), lhs_field);

  if (from_declaration_type == "par")
    rhs_field = "parameter";
  else
    get_base_name(symbol_table.getType(from_name1), rhs_field);

  if (to_declaration_type == "corr")
    lhs_field += "_corr";

  if (from_declaration_type == "corr")
    rhs_field += "_corr";

  output << "ei_to_ind = get_new_or_existing_ei_index('" << lhs_field << "_options_index', '"
         << to_name1 << "', '" << to_name2 << "');" << '\n'
         << "ei_from_ind = get_new_or_existing_ei_index('" << rhs_field << "_options_index', '"
         << from_name1 << "', '" << from_name2 << "');" << '\n'
         << "estimation_info." << lhs_field << "_options_index(ei_to_ind) = {'" << to_name1;

  if (to_declaration_type == "corr")
    output << ":" << to_name2;
  output << "'};" << '\n';

  if (to_declaration_type == "par")
    lhs_field = "parameter";

  if (from_declaration_type == "par")
    rhs_field = "parameter";

  lhs_field = "estimation_info." + lhs_field + "(ei_to_ind)";
  rhs_field = "estimation_info." + rhs_field + "(ei_from_ind)";

  if (to_subsample_name.empty())
    lhs_field += ".options";
  else
    {
      output << "subsamples_to_indx = get_existing_subsamples_indx('" << to_name1 << "','"
             << to_name2 << "');" << '\n'
             << "ei_to_ss_ind = get_subsamples_range_indx(subsamples_to_indx, '"
             << to_subsample_name << "');" << '\n';
      lhs_field += ".subsample_options(ei_to_ss_ind)";
    }

  if (from_subsample_name.empty())
    rhs_field += ".options";
  else
    {
      output << "subsamples_from_indx = get_existing_subsamples_indx('" << from_name1 << "','"
             << from_name2 << "');" << '\n'
             << "ei_from_ss_ind = get_subsamples_range_indx(subsamples_from_indx, '"
             << from_subsample_name << "');" << '\n';
      rhs_field += ".subsample_options(ei_from_ss_ind)";
    }

  output << lhs_field << " = " << rhs_field << ";" << '\n';
}

CalibSmootherStatement::CalibSmootherStatement(SymbolList symbol_list_arg,
                                               OptionsList options_list_arg,
                                               const SymbolTable& symbol_table_arg) :
    symbol_list {move(symbol_list_arg)},
    options_list {move(options_list_arg)},
    symbol_table {symbol_table_arg}
{
}

void
CalibSmootherStatement::checkPass(ModFileStructure& mod_file_struct, WarningConsolidation& warnings)
{
  mod_file_struct.calib_smoother_present = true;
  try
    {
      symbol_list.checkPass(warnings, {SymbolType::endogenous}, symbol_table);
    }
  catch (SymbolList::SymbolListException& e)
    {
      cerr << "ERROR: calib_smoother: " << e.message << '\n';
      exit(EXIT_FAILURE);
    }
}

void
CalibSmootherStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                    [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  if (!options_list.contains("parameter_set"))
    output << "options_.parameter_set = 'calibration';" << '\n';
  symbol_list.writeOutput("var_list_", output);
  output << "options_.smoother = true;" << '\n'
         << "options_.order = 1;" << '\n'
         << "[oo_, M_, options_] = evaluate_smoother(options_.parameter_set, "
            "var_list_, M_, oo_, options_, estim_params_);"
         << '\n';
}

void
CalibSmootherStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "calib_smoother")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}

ExtendedPathStatement::ExtendedPathStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
ExtendedPathStatement::checkPass(ModFileStructure& mod_file_struct,
                                 [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.extended_path_present = true;

  if (!options_list.contains("periods"))
    {
      cerr << "ERROR: the 'periods' option of 'extended_path' is mandatory" << '\n';
      exit(EXIT_FAILURE);
    }

  // Extract hybrid order if present
  if (auto opt = options_list.get_if<OptionsList::NumVal>("ep.stochastic.hybrid_order"))
    mod_file_struct.extended_path_hybrid_order = stoi(*opt);

  // Extract stochastic order if present
  if (auto opt = options_list.get_if<OptionsList::NumVal>("ep.stochastic.order"))
    mod_file_struct.extended_path_order = stoi(*opt);
}

void
ExtendedPathStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  // Beware: options do not have the same name in the interface and in the M code...
  string periods {options_list.get<OptionsList::NumVal>("periods")};

  OptionsList options_list_new {options_list};
  options_list_new.erase("periods");
  options_list_new.writeOutput(output);

  // Set k_order_solver if hybrid > 2
  if (auto opt = options_list.get_if<OptionsList::NumVal>("ep.stochastic.hybrid_order");
      opt && stoi(*opt) > 2)
    output << "options_.k_order_solver = true;" << '\n';

  output << "[Simulated_time_series, oo_] = extended_path([], " << periods
         << ", [], options_, M_, oo_);" << '\n';
}

void
ExtendedPathStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "extended_path")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

void
ModelDiagnosticsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                       [[maybe_unused]] bool minimal_workspace) const
{
  output << "model_diagnostics(M_,options_,oo_);" << '\n';
}

void
ModelDiagnosticsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "model_diagnostics"})";
}

Smoother2histvalStatement::Smoother2histvalStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
Smoother2histvalStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                       [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_smoother2histval");
  output << "smoother2histval(options_smoother2histval);" << '\n';
}

void
Smoother2histvalStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "smoother_2_histval")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MethodOfMomentsStatement::MethodOfMomentsStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
MethodOfMomentsStatement::checkPass(ModFileStructure& mod_file_struct,
                                    [[maybe_unused]] WarningConsolidation& warnings)
{
  mod_file_struct.mom_estimation_present = true;
  // Fill in option_order of mod_file_struct
  if (auto opt = options_list.get_if<OptionsList::NumVal>("order"))
    {
      int order = stoi(*opt);

      if (order > 2)
        mod_file_struct.k_order_solver = true;

      mod_file_struct.mom_order = order;
      mod_file_struct.order_option = max(mod_file_struct.order_option, order);
    }

  if (!options_list.contains("mom.mom_method"))
    {
      cerr << "ERROR: The 'method_of_moments' statement requires a method to be supplied via the "
              "'mom_method' option. Possible values are 'GMM', 'SMM', or 'IRF_MATCHING'."
           << '\n';
      exit(EXIT_FAILURE);
    }

  auto mom_method_value = options_list.get_if<OptionsList::StringVal>("mom.mom_method");
  if ((mom_method_value == "GMM" || mom_method_value == "SMM")
      && !options_list.contains("datafile"))
    {
      cerr << "ERROR: The 'method_of_moments' statement requires a data file to be supplied via "
              "the 'datafile' option."
           << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt = options_list.get_if<OptionsList::StringVal>("mom.mom_method");
      opt && *opt == "GMM")
    mod_file_struct.GMM_present = true;

  if (auto opt = options_list.get_if<OptionsList::NumVal>("mom.analytic_standard_errors");
      opt && *opt == "true")
    mod_file_struct.analytic_standard_errors_present = true;

  if (!mod_file_struct.GMM_present && mod_file_struct.analytic_standard_errors_present)
    {
      cerr << "ERROR: The analytic_standard_errors statement requires the GMM option." << '\n';
      exit(EXIT_FAILURE);
    }

  if (auto opt = options_list.get_if<OptionsList::NumVal>("mom.analytic_jacobian");
      opt && *opt == "true")
    mod_file_struct.analytic_jacobian_present = true;

  if (!mod_file_struct.GMM_present && mod_file_struct.analytic_jacobian_present)
    {
      cerr << "ERROR: The analytic_jacobian statement requires the GMM option." << '\n';
      exit(EXIT_FAILURE);
    }

  if (bool hp = options_list.contains("hp_filter"),
      bandpass = options_list.contains("bandpass.indicator"),
      one_sided_hp = options_list.contains("one_sided_hp_filter");
      (hp && bandpass) || (hp && one_sided_hp) || (bandpass && one_sided_hp))
    {
      cerr << "ERROR: method_of_moments: can only use one of HP, one-sided HP, and bandpass filters"
           << '\n';
      exit(EXIT_FAILURE);
    }
}

void
MethodOfMomentsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                      [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_mom_");

  output << "[oo_, options_mom_, M_] = mom.run(options_, oo_, estim_params_, M_, "
            "options_mom_);"
         << '\n';
}

void
MethodOfMomentsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "method_of_moments")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

GenerateIRFsStatement::GenerateIRFsStatement(
    OptionsList options_list_arg, vector<string> generate_irf_names_arg,
    vector<map<string, double>> generate_irf_elements_arg) :
    options_list {move(options_list_arg)},
    generate_irf_names {move(generate_irf_names_arg)},
    generate_irf_elements {move(generate_irf_elements_arg)}
{
}

void
GenerateIRFsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                   [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);

  if (generate_irf_names.empty())
    return;

  output << "options_.irf_opt.irf_shock_graphtitles = { ";
  for (const auto& generate_irf_name : generate_irf_names)
    output << "'" << generate_irf_name << "'; ";
  output << "};" << '\n';

  output << "options_.irf_opt.irf_shocks = zeros(M_.exo_nbr, " << generate_irf_names.size() << ");"
         << '\n';

  for (size_t i = 0; i < generate_irf_names.size(); i++)
    for (auto& [exo_name, exo_value] : generate_irf_elements[i])
      output << "options_.irf_opt.irf_shocks(M_.exo_names == '" << exo_name << "', " << i + 1
             << ") = " << exo_value << ";" << '\n';
}

void
GenerateIRFsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "generate_irfs")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }

  if (!generate_irf_names.empty())
    {
      output << R"(, "irf_elements": [)";
      for (size_t i = 0; i < generate_irf_names.size(); i++)
        {
          output << R"({"name": ")" << generate_irf_names[i] << R"(", "shocks": [)";
          for (bool printed_something {false};
               auto& [exo_name, exo_value] : generate_irf_elements[i])
            {
              if (exchange(printed_something, true))
                output << ", ";
              output << R"({"exogenous_variable": ")" << exo_name << R"(", )"
                     << R"("exogenous_variable_value": ")" << exo_value << R"("})";
            }
          output << "]}";
          if (i + 1 < generate_irf_names.size())
            output << ", ";
        }
      output << "]";
    }
  output << "}";
}

MatchedMomentsStatement::MatchedMomentsStatement(
    const SymbolTable& symbol_table_arg,
    vector<tuple<vector<int>, vector<int>, vector<int>>> moments_arg) :
    symbol_table {symbol_table_arg}, moments {move(moments_arg)}
{
}

void
MatchedMomentsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                     [[maybe_unused]] bool minimal_workspace) const
{
  output << "M_.matched_moments = {" << '\n';
  for (const auto& [symb_ids, lags, powers] : moments)
    {
      output << "  [";
      for (int s : symb_ids)
        output << symbol_table.getTypeSpecificID(s) + 1 << ',';
      output << "], [";
      for (int l : lags)
        output << l << ',';
      output << "], [";
      for (int p : powers)
        output << p << ',';
      output << "]," << '\n';
    }
  output << "};" << '\n';
}

void
MatchedMomentsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "matched_moments", "moments": [)" << '\n';
  for (bool printed_something {false}; const auto& [symb_ids, lags, powers] : moments)
    {
      if (exchange(printed_something, true))
        output << ',';
      output << R"(  { "endos": [)";
      for (bool printed_something2 {false}; int s : symb_ids)
        {
          if (exchange(printed_something2, true))
            output << ',';
          output << symbol_table.getTypeSpecificID(s) + 1;
        }
      output << R"(], "lags": [)";
      for (bool printed_something2 {false}; int l : lags)
        {
          if (exchange(printed_something2, true))
            output << ',';
          output << l;
        }
      output << R"(], "powers": [)";
      for (bool printed_something2 {false}; int p : powers)
        {
          if (exchange(printed_something2, true))
            output << ',';
          output << p;
        }
      output << "]}" << '\n';
    }
  output << "]}" << '\n';
}

OccbinConstraintsStatement::OccbinConstraintsStatement(
    const DataTree& data_tree_arg,
    vector<tuple<string, BinaryOpNode*, BinaryOpNode*, expr_t, expr_t>> constraints_arg) :
    data_tree {data_tree_arg}, constraints {move(constraints_arg)}
{
}

void
OccbinConstraintsStatement::checkPass(ModFileStructure& mod_file_struct,
                                      [[maybe_unused]] WarningConsolidation& warnings)
{
  if (mod_file_struct.occbin_constraints_present)
    {
      cerr << "ERROR: Multiple 'occbin_constraints' blocks are not allowed" << '\n';
      exit(EXIT_FAILURE);
    }
  if (constraints.size() > 2)
    {
      cerr << "ERROR: only up to two constraints are supported in 'occbin_constraints' block"
           << '\n';
      exit(EXIT_FAILURE);
    }
  mod_file_struct.occbin_constraints_present = true;
}

void
OccbinConstraintsStatement::writeOutput(ostream& output, const string& basename,
                                        [[maybe_unused]] bool minimal_workspace) const
{
  output << "M_.occbin.constraint_nbr = " << constraints.size() << ';' << '\n'
         << "M_.occbin.pswitch = [" << '\n';
  for (const auto& [name, bind, relax, error_bind, error_relax] : constraints)
    output << data_tree.symbol_table.getTypeSpecificID(
                  ParsingDriver::buildOccbinBindParamName(name))
                  + 1
           << ' ';
  output << "];" << '\n'
         << "options_.occbin = struct();" << '\n'
         << "options_.occbin = occbin.set_default_options(options_.occbin, M_);" << '\n'
         << "oo_.dr=set_state_space(oo_.dr,M_);" << '\n';

  filesystem::path filename {"+" + basename + "/occbin_difference.m"};
  ofstream diff_output {filename, ios::out | ios::binary};
  if (!diff_output.is_open())
    {
      cerr << "Error: Can't open file " << filename.string() << " for writing" << '\n';
      exit(EXIT_FAILURE);
    }
  diff_output
      << "function [binding, relax, err] = occbin_difference(zdatalinear, params, steady_state)"
      << '\n';
  for (int idx {1}; const auto& [name, bind, relax, error_bind, error_relax] : constraints)
    {
      diff_output << "binding.constraint_" << idx << " = ";
      dynamic_cast<ExprNode*>(bind)->writeOutput(diff_output,
                                                 ExprNodeOutputType::occbinDifferenceFile);
      diff_output << ';' << '\n' << "relax.constraint_" << idx << " = ";
      if (relax)
        dynamic_cast<ExprNode*>(relax)->writeOutput(diff_output,
                                                    ExprNodeOutputType::occbinDifferenceFile);
      else
        diff_output << "~binding.constraint_" << idx;
      diff_output << ';' << '\n' << "err.binding_constraint_" << idx << " = ";
      if (error_bind)
        error_bind->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
      else
        {
          diff_output << "abs((";
          bind->arg1->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << ")-(";
          bind->arg2->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << "))";
        }
      diff_output << ';' << '\n' << "err.relax_constraint_" << idx << " = ";
      if (error_relax)
        error_relax->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
      else if (relax)
        {
          diff_output << "abs((";
          relax->arg1->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << ")-(";
          relax->arg2->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << "))";
        }
      else if (!error_bind)
        /* If relax, error_relax and error_bind have not been specified, then
           error_bind and error_relax have the same default value. */
        diff_output << "err.binding_constraint_" << idx;
      else
        {
          /* If relax and error_relax have not been specified, but error_bind
             has been specified, then we need to compute the default value for
             error_relax since it is different from error_bind. */
          diff_output << "abs((";
          bind->arg1->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << ")-(";
          bind->arg2->writeOutput(diff_output, ExprNodeOutputType::occbinDifferenceFile);
          diff_output << "))";
        }
      diff_output << ';' << '\n';
      idx++;
    }
  diff_output << "end" << '\n';
  diff_output.close();
}

void
OccbinConstraintsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "occbin_constraints", "constraints": [)" << '\n';
  for (bool printed_something {false};
       const auto& [name, bind, relax, error_bind, error_relax] : constraints)
    {
      if (exchange(printed_something, true))
        output << ',';
      output << R"({ "name": ")" << name << R"(", "bind": ")";
      dynamic_cast<ExprNode*>(bind)->writeJsonOutput(output, {}, {});
      output << R"(", "relax": ")";
      if (relax)
        dynamic_cast<ExprNode*>(relax)->writeJsonOutput(output, {}, {});
      output << R"(", "error_bind": ")";
      if (error_bind)
        error_bind->writeJsonOutput(output, {}, {});
      output << R"(", "error_relax": ")";
      if (error_relax)
        error_relax->writeJsonOutput(output, {}, {});
      output << R"(" })" << '\n';
    }
  output << "]}" << '\n';
}

ResidStatement::ResidStatement(OptionsList options_list_arg) : options_list {move(options_list_arg)}
{
}

void
ResidStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                            [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  output << "display_static_residuals(M_, options_, oo_);" << '\n';
}

void
ResidStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "resid")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

MatchedIrfsStatement::MatchedIrfsStatement(matched_irfs_t values_weights_arg, bool overwrite_arg) :
    values_weights {move(values_weights_arg)}, overwrite {overwrite_arg}
{
}

void
MatchedIrfsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                  [[maybe_unused]] bool minimal_workspace) const
{
  if (overwrite)
    output << "M_.matched_irfs = {};" << '\n';

  for (const auto& [key, vec] : values_weights)
    {
      const auto& [endo, exo] = key;
      output << "M_.matched_irfs = [M_.matched_irfs; {'" << endo << "', '" << exo << "', {";
      for (const auto& [p1, p2, value, weight] : vec)
        {
          output << p1 << ":" << p2 << ", ";
          value->writeOutput(output);
          output << ", ";
          weight->writeOutput(output);
          output << "; ";
        }
      output << "}}];" << '\n';
    }
}

void
MatchedIrfsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "matched_irfs")"
         << R"(, "overwrite": )" << boolalpha << overwrite << R"(, "contents": [)";
  for (bool printed_something {false}; const auto& [key, vec] : values_weights)
    {
      if (exchange(printed_something, true))
        output << ", ";
      const auto& [endo, exo] = key;
      output << R"({"var": ")" << endo << R"(", "varexo": ")" << exo
             << R"(", "periods_values_weights": [)";
      for (bool printed_something2 {false}; const auto& [p1, p2, value, weight] : vec)
        {
          if (exchange(printed_something2, true))
            output << ", ";
          output << R"({"period1": )" << p1 << ", "
                 << R"("period2": })" << p2 << ", "
                 << R"("value": ")";
          value->writeJsonOutput(output, {}, {});
          output << R"(", "weight": ")";
          weight->writeJsonOutput(output, {}, {});
          output << R"("})";
        }
      output << "]}";
    }
  output << "]}";
}

MatchedIrfsWeightsStatement::MatchedIrfsWeightsStatement(matched_irfs_weights_t weights_arg,
                                                         bool overwrite_arg) :
    weights {move(weights_arg)}, overwrite {overwrite_arg}
{
}

void
MatchedIrfsWeightsStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  if (overwrite)
    output << "M_.matched_irfs_weights = {};" << '\n';

  for (const auto& [key, val] : weights)
    {
      const auto& [endo1, periods1, exo1, endo2, periods2, exo2] = key;
      output << "M_.matched_irfs_weights = [M_.matched_irfs_weights; {'" << endo1 << "', "
             << periods1 << ", '" << exo1 << "', '" << endo2 << "', " << periods2 << ", '" << exo2
             << "', ";
      val->writeOutput(output);
      output << "}];" << '\n';
    }
}

void
MatchedIrfsWeightsStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "matched_irfs_weights")"
         << R"(, "overwrite": )" << boolalpha << overwrite << R"(, "contents": [)";
  for (bool printed_something {false}; const auto& [key, val] : weights)
    {
      const auto& [endo1, periods1, exo1, endo2, periods2, exo2] = key;
      if (exchange(printed_something, true))
        output << ", ";
      output << R"({"endo1": ")" << endo1 << R"(", "periods1": ")" << periods1 << R"(", "exo1": )"
             << exo1 << R"(", "endo2": ")" << endo2 << R"(", "periods2": ")" << periods2
             << R"(", "exo2": )" << exo2 << R"(", "weight": ")";
      val->writeJsonOutput(output, {}, {});
      output << R"("})";
    }
  output << "]}";
}

HeterogeneitySolveStatement::HeterogeneitySolveStatement(OptionsList options_list_arg) :
    options_list {move(options_list_arg)}
{
}

void
HeterogeneitySolveStatement::writeOutput(ostream& output, [[maybe_unused]] const string& basename,
                                         [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output, "options_.heterogeneity.solve");
  output << "oo_.heterogeneity.dr = heterogeneity.solve(M_, "
            "options_.heterogeneity.solve, oo_.heterogeneity);"
         << '\n';
}

void
HeterogeneitySolveStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "heterogeneity_solve")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  output << "}";
}

HeterogeneitySimulateStatement::HeterogeneitySimulateStatement(SymbolList symbol_list_arg,
                                                               OptionsList options_list_arg) :
    symbol_list {move(symbol_list_arg)}, options_list {move(options_list_arg)}
{
}

void
HeterogeneitySimulateStatement::writeOutput(ostream& output,
                                            [[maybe_unused]] const string& basename,
                                            [[maybe_unused]] bool minimal_workspace) const
{
  options_list.writeOutput(output);
  symbol_list.writeOutput("var_list_", output);
  output << "[oo_, options_] = heterogeneity.simulate(M_, options_, oo_, var_list_);" << '\n';
}

void
HeterogeneitySimulateStatement::writeJsonOutput(ostream& output) const
{
  output << R"({"statementName": "heterogeneity_simulate")";
  if (!options_list.empty())
    {
      output << ", ";
      options_list.writeJsonOutput(output);
    }
  if (!symbol_list.empty())
    {
      output << ", ";
      symbol_list.writeJsonOutput(output);
    }
  output << "}";
}
