/*
 * Copyright © 2005 Ondra Kamenik
 * Copyright © 2019 Dynare Team
 *
 * This file is part of Dynare.
 *
 * Dynare is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * Dynare is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with Dynare.  If not, see <https://www.gnu.org/licenses/>.
 */

#ifndef OGP_FINE_ATOMS_H
#define OGP_FINE_ATOMS_H

#include "dynamic_atoms.hh"

#include <vector>
#include <string>
#include <memory>

namespace ogp
{
  using std::vector;
  using std::string;

  /** This is just ordering used for endogenous variables. It
   * assumes that we have only time t-1, t, and t+1, orders them as
   * pred(t-1), both(t-1), stat(t), pred(t), both(t), forw(t),
   * both(t+1), forw(t+1). */
  class EndoVarOrdering1 : public VarOrdering
  {
  public:
    EndoVarOrdering1(const vector<string> &vnames, const DynamicAtoms &a)
      : VarOrdering(vnames, a)
    {
    }
    EndoVarOrdering1(const EndoVarOrdering1 &vo, const vector<string> &vnames,
                     const DynamicAtoms &a)
      : VarOrdering(vo, vnames, a)
    {
    }
    std::unique_ptr<VarOrdering>
    clone(const vector<string> &vnames, const DynamicAtoms &a) const override
    {
      return std::make_unique<EndoVarOrdering1>(*this, vnames, a);
    }
    void
    do_ordering() override
    {
      do_pbspbfbf();
    }
  };

  /** This is just another ordering used for endogenous
   * variables. It assumes that we have only time t-1, t, and t+1,
   * orders them as both(t+1), forw(t+1), pred(t-1), both(t-1),
   * stat(t), pred(t), both(t), forw(t). */
  class EndoVarOrdering2 : public VarOrdering
  {
  public:
    EndoVarOrdering2(const vector<string> &vnames, const DynamicAtoms &a)
      : VarOrdering(vnames, a)
    {
    }
    EndoVarOrdering2(const EndoVarOrdering2 &vo, const vector<string> &vnames,
                     const DynamicAtoms &a)
      : VarOrdering(vo, vnames, a)
    {
    }
    std::unique_ptr<VarOrdering>
    clone(const vector<string> &vnames, const DynamicAtoms &a) const override
    {
      return std::make_unique<EndoVarOrdering2>(*this, vnames, a);
    }
    void
    do_ordering() override
    {
      do_bfspbfpb();
    }
  };

  /** This is just ordering used for exogenous variables. It makes
   * no assumptions about their timing. It orders them from the
   * least time to the latest time. */
  class ExoVarOrdering : public VarOrdering
  {
  public:
    ExoVarOrdering(const vector<string> &vnames, const DynamicAtoms &a)
      : VarOrdering(vnames, a)
    {
    }
    ExoVarOrdering(const ExoVarOrdering &vo, const vector<string> &vnames,
                   const DynamicAtoms &a)
      : VarOrdering(vo, vnames, a)
    {
    }
    std::unique_ptr<VarOrdering>
    clone(const vector<string> &vnames, const DynamicAtoms &a) const override
    {
      return std::make_unique<ExoVarOrdering>(*this, vnames, a);
    }
    void
    do_ordering() override
    {
      do_increasing_time();
    }
  };

  class FineAtoms;

  /** This class provides an outer ordering of all variables (endo
   * and exo). It maps the ordering to the particular outer
   * orderings of endo and exo. It works tightly with the FineAtoms
   * class. */
  class AllvarOuterOrdering
  {
  protected:
    /** Type for a map mapping a variable name to an integer. */
    using Tvarintmap = map<string, int>;
    /** Reference to atoms. */
    const FineAtoms &atoms;
    /** The vector of all endo and exo variables in outer
     * ordering. The pointers point to storage in atoms. */
    vector<string> allvar;
    /** The mapping from outer endogenous to outer all. For
     * example endo2all[0] is the order of the first outer
     * endogenous variable in the allvar ordering. */
    vector<int> endo2all;
    /** The mapping from outer exogenous to outer all. For example
     * exo2all[0] is the order of the first outer exogenous
     * variables in the allvar ordering. */
    vector<int> exo2all;
  public:
    /** Construct the allvar outer ordering from the provided
     * sequence of endo and exo names. The names can have an
     * arbitrary storage, the storage is transformed to the atoms
     * storage. An exception is thrown if either the list is not
     * exhaustive, or some string is not a variable. */
    AllvarOuterOrdering(const vector<string> &allvar_outer, const FineAtoms &a);
    /** Copy constructor using the storage of provided atoms. */
    AllvarOuterOrdering(const AllvarOuterOrdering &allvar_outer, const FineAtoms &a);
    /** Return endo2all mapping. */
    const vector<int> &
    get_endo2all() const
    {
      return endo2all;
    }
    /** Return exo2all mapping. */
    const vector<int> &
    get_exo2all() const
    {
      return exo2all;
    }
    /** Return the allvar ordering. */
    const vector<string> &
    get_allvar() const
    {
      return allvar;
    }
  };

  /** This class refines the DynamicAtoms by distinguishing among
   * parameters (no lag and leads) and endogenous and exogenous
   * variables (with lags and leads). For parameters, endogenous and
   * exogenous, it defines outer orderings and internal
   * orderings. The internal orderings are created by
   * parsing_finished() method when it is sure that no new variables
   * would be registered. The outer orderings are given by the order
   * of calls of registering methods.
   *
   * In addition, the class also defines outer ordering of
   * endogenous and exogenous variables. This is input as a
   * parameter to parsing_finished(). By default, this whole outer
   * ordering is just a concatenation of outer ordering of
   * endogenous and exogenous variables.
   *
   * The internal ordering of all endo and exo variables is just a
   * concatenation of endo and exo variables in their internal
   * orderings. This is the ordering with respect to which all
   * derivatives are taken. */
  class FineAtoms : public DynamicAtoms
  {
    friend class AllvarOuterOrdering;
  protected:
    using Tvarintmap = map<string, int>;
  private:
    /** The vector of parameters names. The order gives the order
     * the data is communicated with outside world. */
    vector<string> params;
    /** A map mapping a name of a parameter to an index in the outer
     * ordering. */
    Tvarintmap param_outer_map;
    /** The vector of endogenous variables. This defines the order
     * like parameters. */
    vector<string> endovars;
    /** A map mapping a name of an endogenous variable to an index
     * in the outer ordering. */
    Tvarintmap endo_outer_map;
    /** The vector of exogenous variables. Also defines the order
     * like parameters and endovars. */
    vector<string> exovars;
    /** A map mapping a name of an exogenous variable to an index
     * in the outer ordering. */
    Tvarintmap exo_outer_map;

  protected:
    /** This is the internal ordering of all atoms corresponding
     * to endogenous variables. It is constructed by
     * parsing_finished() method, which should be called after all
     * parsing jobs have been finished. */
    std::unique_ptr<VarOrdering> endo_order;
    /** This is the internal ordering of all atoms corresponding
     * to exogenous variables. It has the same handling as
     * endo_order. */
    std::unique_ptr<VarOrdering> exo_order;
    /** This is the all variables outer ordering. It is
     * constructed by parsing finished. */
    std::unique_ptr<AllvarOuterOrdering> allvar_order;
    /** This vector defines a set of atoms as tree indices used
     * for differentiation. The order of the atoms in this vector
     * defines ordering of the derivative tensors. The ordering is
     * a concatenation of atoms from endo_order and then
     * exo_order. This vector is setup by parsing_finished() and
     * is returned by variables(). */
    vector<int> der_atoms;
    /** This is a mapping from endogenous atoms to all atoms in
     * der_atoms member. The mapping maps index in endogenous atom
     * ordering to index (not value) in der_atoms. It is useful if
     * one wants to evaluate derivatives wrt only endogenous
     * variables. It is set by parsing_finished(). By definition,
     * it is monotone. */
    vector<int> endo_atoms_map;
    /** This is a mapping from exogenous atoms to all atoms in
     * der_atoms member. It is the same as endo_atoms_map for
     * atoms of exogenous variables. */
    vector<int> exo_atoms_map;
  public:
    FineAtoms() = default;
    FineAtoms(const FineAtoms &fa);
    /** Deletes endo_order and exo_order. */
    ~FineAtoms() override = default;
    /** Overrides DynamicAtoms::check_variable so that the error
     * would be raised if the variable name is not declared. A
     * variable is declared by inserting it to
     * DynamicAtoms::varnames. This is a responsibility of a
     * subclass. */
    int check_variable(const string &name) const override;
    /** This calculates min lag and max lead of endogenous variables. */
    void
    endovarspan(int &mlead, int &mlag) const
    {
      varspan(endovars, mlead, mlag);
    }
    /** This calculates mim lag and max lead of exogenous variables. */
    void
    exovarspan(int &mlead, int &mlag) const
    {
      varspan(exovars, mlead, mlag);
    }
    /** This calculates the number of periods in which at least
     * one exogenous variable occurs. */
    int num_exo_periods() const;
    /** Return an (external) ordering of parameters. */
    const vector<string> &
    get_params() const
    {
      return params;
    }
    /** Return an external ordering of endogenous variables. */
    const vector<string> &
    get_endovars() const
    {
      return endovars;
    }
    /** Return an external ordering of exogenous variables. */
    const vector<string> &
    get_exovars() const
    {
      return exovars;
    }
    /** This constructs internal orderings and makes the indices
     * returned by variables method available. Further it
     * constructs outer ordering of all variables by a simple
     * concatenation of outer endogenous and outer exogenous. In
     * addition, it makes nstat, npred, nboth, nforw available. */
    void parsing_finished(VarOrdering::ord_type ot);
    /** This does the same thing as
     * parsing_finished(VarOrdering::ord_type) plus it allows for
     * inputing a different outer ordering of all variables. The
     * ordering is input as a list of strings, their storage can
     * be arbitrary. */
    void parsing_finished(VarOrdering::ord_type ot, const vector<string> &avo);
    /** Return the external ordering of all variables (endo and
     * exo). This is either the second argument to
     * parsing_finished or the default external ordering. This
     * must be called only after parsing_finished. */
    const vector<string> &get_allvar() const;
    /** Return the map from outer ordering of endo variables to
     * the allvar ordering. This must be called only after
     * parsing_finished. */
    const vector<int> &outer_endo2all() const;
    /** Return the map from outer ordering of exo variables to
     * the allvar ordering. This must be called only after
     * parsing_finished. */
    const vector<int> &outer_exo2all() const;
    /** Return the atoms with respect to which we are going to
     * differentiate. This must be called after
     * parsing_finished. */
    vector<int> variables() const override;
    /** Return the number of static. */
    int nstat() const;
    /** Return the number of predetermined. */
    int npred() const;
    /** Return the number of both. */
    int nboth() const;
    /** Return the number of forward looking. */
    int nforw() const;
    /** Return the index of an endogenous atom given by tree index in
     * the endo ordering. This must be also called only after
     * parsing_finished(). */
    int get_pos_of_endo(int t) const;
    /** Return the index of an exogenous atom given by tree index in
     * the exo ordering. This must be also called only after
     * parsing_finished(). */
    int get_pos_of_exo(int t) const;
    /** Return the index of either endogenous or exogenous atom
     * given by tree index in the concatenated ordering of
     * endogenous and exogenous atoms. This must be also called
     * only after parsing_finished(). */
    int get_pos_of_all(int t) const;
    /** Return the mapping from endogenous at time t to outer
     * ordering of endogenous. */
    const vector<int> &y2outer_endo() const;
    /** Return the mapping from the outer ordering of endogenous to endogenous
     * at time t. */
    const vector<int> &outer2y_endo() const;
    /** Return the mapping from exogenous at time t to outer
     * ordering of exogenous. */
    const vector<int> &y2outer_exo() const;
    /** Return the mapping from the outer ordering of exogenous to exogenous
     * at time t. */
    const vector<int> &outer2y_exo() const;
    /** Return the endo_atoms_map. */
    const vector<int> &get_endo_atoms_map() const;
    /** Return the exo_atoms_map. */
    const vector<int> &get_exo_atoms_map() const;
    /** Return an index in the outer ordering of a given
     * parameter. An exception is thrown if the name is not a
     * parameter. */
    int name2outer_param(const string &name) const;
    /** Return an index in the outer ordering of a given
     * endogenous variable. An exception is thrown if the name is not a
     * and endogenous variable. */
    int name2outer_endo(const string &name) const;
    /** Return an index in the outer ordering of a given
     * exogenous variable. An exception is thrown if the name is not a
     * and exogenous variable. */
    int name2outer_exo(const string &name) const;
    /** Return an index in the outer ordering of all variables
     * (endo and exo) for a given name. An exception is thrown if
     * the name is not a variable. This must be called only after
     * parsing_finished(). */
    int name2outer_allvar(const string &name) const;
    /** Return the number of endogenous variables at time t-1, these are state
     * variables. */
    int
    nys() const
    {
      return npred()+nboth();
    }
    /** Return the number of endogenous variables at time t+1. */
    int
    nyss() const
    {
      return nboth()+nforw();
    }
    /** Return the number of endogenous variables. */
    int
    ny() const
    {
      return endovars.size();
    }
    /** Return the number of exogenous variables. */
    int
    nexo() const
    {
      return static_cast<int>(exovars.size());
    }
    /** Return the number of parameters. */
    int
    np() const
    {
      return static_cast<int>(params.size());
    }
    /** Register unique endogenous variable name. The order of
     * calls defines the endo outer ordering. The method is
     * virtual, since a superclass may want to do some additional
     * action. */
    virtual void register_uniq_endo(string name);
    /** Register unique exogenous variable name. The order of
     * calls defines the exo outer ordering. The method is
     * virtual, since a superclass may want to do somem additional
     * action. */
    virtual void register_uniq_exo(string name);
    /** Register unique parameter name. The order of calls defines
     * the param outer ordering. The method is
     * virtual, since a superclass may want to do somem additional
     * action. */
    virtual void register_uniq_param(string name);
    /** Debug print. */
    void print() const override;
  private:
    /** This performs the common part of parsing_finished(), which
     * is a construction of internal orderings. */
    void make_internal_orderings(VarOrdering::ord_type ot);
  protected:
    /** This remembers the ordering type of the last call make_internal_ordering. */
    VarOrdering::ord_type order_type;
  };
};

#endif
