/*
 * Copyright © 2008-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 <http://www.gnu.org/licenses/>.
 */

// GP, based on work by O.Kamenik

#include "k_ord_dynare.hh"
#include "dynamic_abstract_class.hh"
#include "dynare_exception.hh"

#include <utility>

KordpDynare::KordpDynare(const std::vector<std::string> &endo,
                         const std::vector<std::string> &exo, int nexog, int npar,
                         Vector &ysteady, TwoDMatrix &vcov, Vector &inParams, int nstat,
                         int npred, int nforw, int nboth, const ConstVector &nnzd,
                         int nsteps, int norder,
                         Journal &jr, std::unique_ptr<DynamicModelAC> dynamicModelFile_arg,
                         const std::vector<int> &dr_order, const ConstTwoDMatrix &llincidence) :
  nStat{nstat}, nBoth{nboth}, nPred{npred}, nForw{nforw}, nExog{nexog}, nPar{npar},
  nYs{npred + nboth}, nYss{nboth + nforw}, nY{nstat + npred + nboth + nforw},
  nJcols{nExog+nY+nYs+nYss}, NNZD{nnzd}, nSteps{nsteps},
  nOrder{norder}, journal{jr}, ySteady{ysteady}, params{inParams}, vCov{vcov},
  md{1}, dnl{endo}, denl{exo}, dsnl{*this, dnl, denl},
  ll_Incidence{llincidence},
  dynamicModelFile{std::move(dynamicModelFile_arg)}
{
  computeJacobianPermutation(dr_order);
}

void
KordpDynare::solveDeterministicSteady()
{
  JournalRecordPair pa(journal);
  pa << "Non-linear solver for deterministic steady state skipped" << endrec;
}

void
KordpDynare::evaluateSystem(Vector &out, const ConstVector &yy, const Vector &xx)
{
  // This method is only called when checking the residuals at steady state (Approximation::check), so return zero residuals
  out.zeros();
}

void
KordpDynare::evaluateSystem(Vector &out, const ConstVector &yym, const ConstVector &yy,
                            const ConstVector &yyp, const Vector &xx)
{
  // This method is only called when checking the residuals at steady state (Approximation::check), so return zero residuals
  out.zeros();
}

void
KordpDynare::calcDerivativesAtSteady()
{
  if (dyn_md.empty())
    {
      dyn_md.emplace_back(nY, nJcols); // Allocate Jacobian
      dyn_md.back().zeros();

      for (int i = 2; i <= nOrder; i++)
        {
          // Higher order derivatives, as sparse (3-column) matrices
          dyn_md.emplace_back(static_cast<int>(NNZD[i-1]), 3);
          dyn_md.back().zeros();
        }

      Vector xx(nexog());
      xx.zeros();

      Vector out(nY);
      out.zeros();
      Vector llxSteady(nJcols-nExog);
      LLxSteady(ySteady, llxSteady);

      dynamicModelFile->eval(llxSteady, xx, params, ySteady, out, dyn_md);
    }

  for (int i = 1; i <= nOrder; i++)
    populateDerivativesContainer(i);
}

void
KordpDynare::populateDerivativesContainer(int ord)
{
  const TwoDMatrix &g = dyn_md[ord-1];

  // model derivatives FSSparseTensor instance
  auto mdTi = std::make_unique<FSSparseTensor>(ord, nJcols, nY);

  IntSequence s(ord, 0);

  if (ord == 1)
    for (int i = 0; i < g.ncols(); i++)
      {
        for (int j = 0; j < g.nrows(); j++)
          {
            double x = g.get(j, dynppToDyn[s[0]]);
            if (x != 0.0)
              mdTi->insert(s, j, x);
          }
        s[0]++;
      }
  else // ord ≥ 2
    for (int i = 0; i < g.nrows(); i++)
      {
        int j = static_cast<int>(g.get(i, 0))-1;
        int i1 = static_cast<int>(g.get(i, 1))-1;
        if (j < 0 || i1 < 0)
          continue; // Discard empty entries (see comment in DynamicModelAC::unpackSparseMatrix())

        for (int k = 0; k < ord; k++)
          {
            s[k] = dynToDynpp[i1 % nJcols];
            i1 /= nJcols;
          }

        if (ord == 2 && !s.isSorted())
          continue; // Skip symmetric elements (only needed at order 2)
        else if (ord > 2)
          s.sort(); // For higher order, canonicalize the multi-index

        double x = g.get(i, 2);
        mdTi->insert(s, j, x);
      }

  md.insert(std::move(mdTi));
}

/* Returns ySteady extended with leads and lags suitable for passing to
   <model>_dynamic */
void
KordpDynare::LLxSteady(const Vector &yS, Vector &llxSteady)
{
  if (yS.length() == nJcols-nExog)
    throw DynareException(__FILE__, __LINE__, "ySteady already of right size");

  /* Create temporary square 2D matrix size nEndo×nEndo (sparse)
     for the lag, current and lead blocks of the jacobian */
  if (llxSteady.length() != nJcols-nExog)
    throw DynareException(__FILE__, __LINE__, "llxSteady has wrong size");

  for (int ll_row = 0; ll_row < ll_Incidence.nrows(); ll_row++)
    // populate (non-sparse) vector with ysteady values
    for (int i = 0; i < nY; i++)
      if (ll_Incidence.get(ll_row, i))
        llxSteady[static_cast<int>(ll_Incidence.get(ll_row, i))-1] = yS[i];
}

/*
   Computes mapping between Dynare and Dynare++ orderings of the (dynamic)
   variable indices in derivatives.

   If one defines:
   – y (resp. x) as the vector of all endogenous (size nY), in DR-order (resp.
     declaration order)
   – y⁻ (resp. x⁻) as the vector of endogenous that appear at previous period (size nYs),
     in DR-order (resp. declaration order)
   – y⁺ (resp. x⁺) as the vector of endogenous that appear at future period (size nYss) in
     DR-order (resp. declaration order)
   – u as the vector of exogenous (size nExog)

   In Dynare, the ordering is (x⁻, x, x⁺, u).
   In Dynare++, the ordering is (y⁺, y, y⁻, u).

   dr_order is typically equal to oo_.dr.order_var.
*/
void
KordpDynare::computeJacobianPermutation(const std::vector<int> &dr_order)
{
  // Compute restricted inverse DR-orderings: x⁻→y⁻ and x⁺→y⁺
  std::vector<int> dr_inv_order_forw(nBoth+nForw), dr_inv_order_pred(nBoth+nPred);
  std::iota(dr_inv_order_forw.begin(), dr_inv_order_forw.end(), 0);
  std::sort(dr_inv_order_forw.begin(), dr_inv_order_forw.end(),
            [&](int i, int j) { return dr_order[nStat+nPred+i] < dr_order[nStat+nPred+j]; });
  std::iota(dr_inv_order_pred.begin(), dr_inv_order_pred.end(), 0);
  std::sort(dr_inv_order_pred.begin(), dr_inv_order_pred.end(),
            [&](int i, int j) { return dr_order[nStat+i] < dr_order[nStat+j]; });

  // Compute restricted DR-orderings: y⁻→x⁻ and y⁺→x⁺
  std::vector<int> dr_order_forw(nBoth+nForw), dr_order_pred(nBoth+nPred);
  for (int i = 0; i < nBoth+nForw; i++)
    dr_order_forw[dr_inv_order_forw[i]] = i;
  for (int i = 0; i < nBoth+nPred; i++)
    dr_order_pred[dr_inv_order_pred[i]] = i;

  // Compute Dynare++ → Dynare ordering
  dynppToDyn.resize(nJcols);
  int j = 0;
  for (; j < nYss; j++)
    dynppToDyn[j] = dr_order_forw[j]+nYs+nY; // Forward variables
  for (; j < nYss+nY; j++)
    dynppToDyn[j] = dr_order[j-nYss]+nYs; // Variables in current period
  for (; j < nYss+nY+nYs; j++)
    dynppToDyn[j] = dr_order_pred[j-nY-nYss]; // Predetermined variables
  for (; j < nJcols; j++)
    dynppToDyn[j] = j; // Exogenous

  // Compute Dynare → Dynare++ ordering
  dynToDynpp.resize(nJcols);
  for (int i = 0; i < nJcols; i++)
    dynToDynpp[dynppToDyn[i]] = i;
}

void
KordpDynare::push_back_md(const mxArray *m)
{
  dyn_md.emplace_back(ConstTwoDMatrix{m});
}

DynareNameList::DynareNameList(std::vector<std::string> names_arg)
  : names(std::move(names_arg))
{
}

DynareStateNameList::DynareStateNameList(const KordpDynare &dynare, const DynareNameList &dnl,
                                         const DynareNameList &denl)
{
  for (int i = 0; i < dynare.nYs; i++)
    names.emplace_back(dnl.getName(i+dynare.nstat()));
  for (int i = 0; i < dynare.nexog(); i++)
    names.emplace_back(denl.getName(i));
}
