// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2013 Alec Jacobson <alecjacobson@gmail.com>
//
// This Source Code Form is subject to the terms of the Mozilla Public License
// v. 2.0. If a copy of the MPL was not distributed with this file, You can
// obtain one at http://mozilla.org/MPL/2.0/.
#include <igl/matlab/matlabinterface.h>

// Implementation

// Init the MATLAB engine
// (no need to call it directly since it is automatically invoked by any other command)
IGL_INLINE void igl::matlab::mlinit(Engine** mlengine)
{
  *mlengine = engOpen("\0");
}

// Closes the MATLAB engine
IGL_INLINE void igl::matlab::mlclose(Engine** mlengine)
{
  engClose(*mlengine);
  *mlengine = 0;
}

// Send a matrix to MATLAB
IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::MatrixXd& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL);
  double *pM = mxGetPr(A);

  int c = 0;
  for(int j=0; j<M.cols();++j)
    for(int i=0; i<M.rows();++i)
      pM[c++] = double(M(i,j));

  engPutVariable(*mlengine, name.c_str(), A);
  mxDestroyArray(A);
}

// Send a matrix to MATLAB
IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::MatrixXf& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL);
  double *pM = mxGetPr(A);

  int c = 0;
  for(int j=0; j<M.cols();++j)
    for(int i=0; i<M.rows();++i)
      pM[c++] = double(M(i,j));

  engPutVariable(*mlengine, name.c_str(), A);
  mxDestroyArray(A);
}

// Send a matrix to MATLAB
IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::MatrixXi& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL);
  double *pM = mxGetPr(A);

  int c = 0;
  for(int j=0; j<M.cols();++j)
    for(int i=0; i<M.rows();++i)
      pM[c++] = double(M(i,j))+1;

  engPutVariable(*mlengine, name.c_str(), A);
  mxDestroyArray(A);
}

// Send a matrix to MATLAB
IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::Matrix<unsigned int, Eigen::Dynamic, Eigen::Dynamic >& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  mxArray *A = mxCreateDoubleMatrix(M.rows(), M.cols(), mxREAL);
  double *pM = mxGetPr(A);

  int c = 0;
  for(int j=0; j<M.cols();++j)
    for(int i=0; i<M.rows();++i)
      pM[c++] = double(M(i,j))+1;

  engPutVariable(*mlengine, name.c_str(), A);
  mxDestroyArray(A);
}

// Receive a matrix from MATLAB
IGL_INLINE void igl::matlab::mlgetmatrix(Engine** mlengine, std::string name, Eigen::MatrixXd& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  unsigned long m = 0;
  unsigned long n = 0;
  std::vector<double> t;

  mxArray *ary = engGetVariable(*mlengine, name.c_str());
  if (ary == NULL)
  {
    m = 0;
    n = 0;
    M = Eigen::MatrixXd(0,0);
  }
  else
  {
    m = mxGetM(ary);
    n = mxGetN(ary);
    M = Eigen::MatrixXd(m,n);

    double *pM = mxGetPr(ary);

    int c = 0;
    for(int j=0; j<M.cols();++j)
      for(int i=0; i<M.rows();++i)
        M(i,j) = pM[c++];
  }

  mxDestroyArray(ary);
}

IGL_INLINE void igl::matlab::mlgetmatrix(Engine** mlengine, std::string name, Eigen::MatrixXf& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  unsigned long m = 0;
  unsigned long n = 0;
  std::vector<double> t;

  mxArray *ary = engGetVariable(*mlengine, name.c_str());
  if (ary == NULL)
  {
    m = 0;
    n = 0;
    M = Eigen::MatrixXf(0,0);
  }
  else
  {
    m = mxGetM(ary);
    n = mxGetN(ary);
    M = Eigen::MatrixXf(m,n);

    double *pM = mxGetPr(ary);

    int c = 0;
    for(int j=0; j<M.cols();++j)
      for(int i=0; i<M.rows();++i)
        M(i,j) = pM[c++];
  }

  mxDestroyArray(ary);
}

// Receive a matrix from MATLAB
IGL_INLINE void igl::matlab::mlgetmatrix(Engine** mlengine, std::string name, Eigen::MatrixXi& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  unsigned long m = 0;
  unsigned long n = 0;
  std::vector<double> t;

  mxArray *ary = engGetVariable(*mlengine, name.c_str());
  if (ary == NULL)
  {
    m = 0;
    n = 0;
    M = Eigen::MatrixXi(0,0);
  }
  else
  {
    m = mxGetM(ary);
    n = mxGetN(ary);
    M = Eigen::MatrixXi(m,n);

    double *pM = mxGetPr(ary);

    int c = 0;
    for(int j=0; j<M.cols();++j)
      for(int i=0; i<M.rows();++i)
        M(i,j) = int(pM[c++])-1;
  }

  mxDestroyArray(ary);
}

// Receive a matrix from MATLAB
IGL_INLINE void igl::matlab::mlgetmatrix(Engine** mlengine, std::string name, Eigen::Matrix<unsigned int, Eigen::Dynamic, Eigen::Dynamic >& M)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  unsigned long m = 0;
  unsigned long n = 0;
  std::vector<double> t;

  mxArray *ary = engGetVariable(*mlengine, name.c_str());
  if (ary == NULL)
  {
    m = 0;
    n = 0;
    M = Eigen::Matrix<unsigned int, Eigen::Dynamic, Eigen::Dynamic >(0,0);
  }
  else
  {
    m = mxGetM(ary);
    n = mxGetN(ary);
    M = Eigen::Matrix<unsigned int, Eigen::Dynamic, Eigen::Dynamic >(m,n);

    double *pM = mxGetPr(ary);

    int c = 0;
    for(int j=0; j<M.cols();++j)
      for(int i=0; i<M.rows();++i)
        M(i,j) = (unsigned int)(pM[c++])-1;
  }

  mxDestroyArray(ary);
}


// Send a single scalar to MATLAB
IGL_INLINE void igl::matlab::mlsetscalar(Engine** mlengine, std::string name, double s)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  Eigen::MatrixXd M(1,1);
  M(0,0) = s;
  mlsetmatrix(mlengine, name, M);
}

// Receive a single scalar from MATLAB
IGL_INLINE double igl::matlab::mlgetscalar(Engine** mlengine, std::string name)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  Eigen::MatrixXd M;
  mlgetmatrix(mlengine, name,M);
  return M(0,0);
}

// Execute arbitrary MATLAB code and return the MATLAB output
IGL_INLINE std::string igl::matlab::mleval(Engine** mlengine, std::string code)
{
  if (*mlengine == 0)
    mlinit(mlengine);

  const char *matlab_code = code.c_str();
  const int BUF_SIZE = 4096*4096;
  // allocate on the heap to avoid running out of stack
  std::string bufauto(BUF_SIZE+1, '\0');
  char *buf = &bufauto[0];

  assert(matlab_code != NULL);

  // Use RAII ensure that on leaving this scope, the output buffer is
  // always nullified (to prevent Matlab from accessing memory that might
  // have already been deallocated).
  struct cleanup {
    Engine *m_ep;
    cleanup(Engine *ep) : m_ep(ep) { }
    ~cleanup() { engOutputBuffer(m_ep, NULL, 0); }
  } cleanup_obj(*mlengine);

  if (buf != NULL)
    engOutputBuffer(*mlengine, buf, BUF_SIZE);

  int res = engEvalString(*mlengine, matlab_code);

  if (res != 0) {
    std::ostringstream oss;
    oss << "ERROR: Matlab command failed with error code " << res << ".\n";
    return oss.str();
  }

  if (buf[0] == '>' && buf[1] == '>' && buf[2] == ' ')
    buf += 3;
  if (buf[0] == '\n') ++buf;

  return std::string(buf);
}

// Send a sparse matrix
IGL_INLINE void igl::matlab::mlsetmatrix(Engine** mlengine, std::string name, const Eigen::SparseMatrix<double>& M)
{
  int count = 0;
//  // Count non-zero
//  for (unsigned k=0; k<M.outerSize(); ++k)
//    for (Eigen::SparseMatrix<double>::InnerIterator it(M,k); it; ++it)
//      if (it.value() != 0)
//        ++count;
  
  Eigen::MatrixXd T(M.nonZeros(),3);
  for (unsigned k=0; k<(unsigned)M.outerSize(); ++k)
  {
    for (Eigen::SparseMatrix<double>::InnerIterator it(M,k); it; ++it)
    {
      T(count,0) = it.row();
      T(count,1) = it.col();
      T(count,2) = it.value();
      ++count;
    }
  }

  T.col(0) = T.col(0).array()+1;
  T.col(1) = T.col(1).array()+1;

  mlsetmatrix(mlengine,"temp93765",T);
  
  std::string temp = name + " = sparse(temp93765(:,1),temp93765(:,2),temp93765(:,3),"
                          + std::to_string(M.rows()) + ","
                          + std::to_string(M.cols()) + ");";
  
  mleval(mlengine,temp);
  mleval(mlengine,"clear temp93765");
}
