/// @file EntropyBase.tcc
/// @brief Entropy operations as needed for Cornwell-Evans algorithm
/// @ingroup Deconvolver
///
///
/// @copyright (c) 2007 CSIRO
/// Australia Telescope National Facility (ATNF)
/// Commonwealth Scientific and Industrial Research Organisation (CSIRO)
/// PO Box 76, Epping NSW 1710, Australia
/// atnf-enquiries@csiro.au
///
/// This file is part of the ASKAP software distribution.
///
/// The ASKAP software distribution 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 2 of the License,
/// or (at your option) any later version.
///
/// This program 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 this program; if not, write to the Free Software
/// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307 USA
///
/// @author Tim Cornwell <tim.cornwell@csiro.au>
///

#include <string>

#include <askap/AskapLogging.h>
#include <boost/shared_ptr.hpp>
#include <casacore/casa/aips.h>
#include <casacore/casa/Arrays/Array.h>
#include <casacore/casa/Arrays/ArrayMath.h>
#include <casacore/casa/BasicMath/Math.h>
ASKAP_LOGGER(decentropybaselogger, ".deconvolution.entropy");

using namespace casa;

namespace askap {

    namespace synthesis {

        template<class T>
        EntropyBase<T>::EntropyBase() : itsAlpha(T(0.0)), itsBeta(T(0.0)), itsQ(T(40.0)), itsScale(1.0),
                itsTolerance(0.3), itsUseFluxConstraint(false)
        {
        };

        template<class T>
        EntropyBase<T>::~EntropyBase()
        {
        };

        template<class T>
        T EntropyBase<T>::entropy(const Array<T>& model)
        {
            throw(AskapError("Called base class entropy"));
            ASKAPCHECK(model.shape().nelements(), "Model has no elements");
            return 0.0;
        };

        template<class T>
        T EntropyBase<T>::entropy(const Array<T>& model, const Array<T>& mask)
        {
            throw(AskapError("Called base class entropy"));
            ASKAPCHECK(model.shape().conform(mask.shape()), "Model and mask images have different shapes");
            return 0.0;
        };

        template<class T>
        void EntropyBase<T>::gradEntropy(Array<T>& gradH, Array<T>& rHess, const Array<T>& model)
        {
            throw(AskapError("Called base class gradEntropy"));
            // Silence the compiler by doing meaningless comparisons
            ASKAPCHECK(gradH.shape().conform(rHess.shape()), "Gradient and Hessian images have different shapes");
            ASKAPCHECK(gradH.shape().conform(model.shape()), "Gradient and model images have different shapes");
        }

        template<class T>
        void EntropyBase<T>::gradEntropy(Array<T>& gradH, Array<T>& rHess, const Array<T>& model,
                                         const Array<T>& mask)
        {
            throw(AskapError("Called base class gradEntropy"));
            // Silence the compiler by doing meaningless comparisons
            ASKAPCHECK(gradH.shape().conform(rHess.shape()), "Gradient and Hessian images have different shapes");
            ASKAPCHECK(gradH.shape().conform(model.shape()), "Gradient and model images have different shapes");
            ASKAPCHECK(gradH.shape().conform(mask.shape()), "Gradient and mask images have different shapes");
        }

        template<class T>
        void EntropyBase<T>::setScale(const T scale)
        {
            this->itsScale = scale;
        };

        template<class T>
        T EntropyBase<T>::formLength(const Matrix<T>& GDG)
        {
            return GDG(H, H) +  square(this->itsAlpha) * GDG(C, C) + square(this->itsBeta)*GDG(F, F);
        }

        template<class T>
        Matrix<T> EntropyBase<T>::formGDG(const Array<T>& model, const Array<T>& residual)
        {
            ASKAPCHECK(model.shape().conform(residual.shape()), "Model and residual images have different shapes");

            Matrix<T> GDG(4, 4);
            GDG.set(0.0);

            Array<T> rHess(model.shape());
            Array<T> gradH(model.shape());
            gradEntropy(gradH, rHess, model);
            Array<T> gradC = -T(2.0) * residual;
            GDG(H, H) = sum(gradH * rHess * gradH);
            GDG(H, C) = sum(gradH * rHess * gradC);
            GDG(H, F) = sum(gradH * rHess);
            GDG(C, C) = sum(gradC * rHess * gradC);
            GDG(C, F) = sum(gradC * rHess);
            GDG(F, F) = sum(rHess);
            GDG(H, J) = GDG(H, H) -  this->itsAlpha * GDG(H, C) - this->itsBeta * GDG(H, F);
            GDG(C, J) = GDG(H, C) -  this->itsAlpha * GDG(C, C) - this->itsBeta * GDG(C, F);
            GDG(F, J) = GDG(H, F) -  this->itsAlpha * GDG(C, F) - this->itsBeta * GDG(F, F);
            GDG(J, J) = GDG(H, H) +  square(this->itsAlpha) * GDG(C, C)
                        + square(this->itsBeta) * GDG(F, F)  + 2 * this->itsAlpha * this->itsBeta * GDG(C, F)
                        - 2 * this->itsAlpha * GDG(H, C) - 2 * this->itsBeta * GDG(H, F);
            return GDG;
        }

        template<class T>
        Matrix<T> EntropyBase<T>::formGDGStep(const Array<T>& model, const Array<T>& residual, Array<T>& step)
        {
            ASKAPCHECK(model.shape().conform(residual.shape()), "Model and residual images have different shapes");

            Matrix<T> GDG(4, 4);
            GDG.set(0.0);

            // Probably need to write as iterators soon
            Array<T> rHess(model.shape());
            Array<T> gradH(model.shape());
            gradEntropy(gradH, rHess, model);
            Array<T> gradC = - T(2.0) * residual;
            Array<T> gradJ = gradH - this->itsAlpha * gradC - this->itsBeta;
            step = rHess * gradJ;
            GDG(H, H) = sum(gradH * rHess * gradH);
            GDG(H, C) = sum(gradH * rHess * gradC);
            GDG(H, F) = sum(gradH * rHess);
            GDG(C, C) = sum(gradC * rHess * gradC);
            GDG(C, F) = sum(gradC * rHess);
            GDG(F, F) = sum(rHess);
            GDG(H, J) = GDG(H, H) -  this->itsAlpha * GDG(H, C) - this->itsBeta * GDG(H, F);
            GDG(C, J) = GDG(H, C) -  this->itsAlpha * GDG(C, C) - this->itsBeta * GDG(C, F);
            GDG(F, J) = GDG(H, F) -  this->itsAlpha * GDG(C, F) - this->itsBeta * GDG(F, F);
            GDG(J, J) = GDG(H, H) +  square(this->itsAlpha) * GDG(C, C)
                        + square(this->itsBeta) * GDG(F, F)  + 2 * this->itsAlpha * this->itsBeta * GDG(C, F)
                        - 2 * this->itsAlpha * GDG(H, C) - 2 * this->itsBeta * GDG(H, F);

            ASKAPCHECK(model.shape().conform(step.shape()), "Model and step images have different shapes");

            return GDG;
        }

        template<class T>
        T EntropyBase<T>::formGDS(const Array<T>& model, const Array<T>& residual, const Array<T>& step)
        {
            ASKAPCHECK(model.shape().conform(step.shape()), "Model and step images have different shapes");
            ASKAPCHECK(model.shape().conform(residual.shape()), "Model and residual images have different shapes");

            // Probably need to write as iterators soon
            Array<T> gradH(model.shape());
            Array<T> rHess(model.shape());
            gradEntropy(gradH, rHess, model);
            return - sum(step *(gradH + T(2.0) * this->itsAlpha * residual - this->itsBeta));
        };

        template<class T>
        void EntropyBase<T>::changeAlphaBeta(const Matrix<T>& GDG, const T targetChisq, const T chisq,
                                             const T targetFlux, const T flux)
        {
            T length = GDG(H, H) + square(this->itsAlpha) * GDG(C, C) + square(this->itsBeta) * GDG(F, F);
            if (this->itsAlpha == 0.0 && this->itsBeta == 0.0) {
                length = GDG(F, F);
            }
            T normGrad = GDG(J, J) / length;
            if (this->itsAlpha == 0.0) {
                normGrad = 0.0;
            }
            if (normGrad < this->itsTolerance) {
                this->updateAlphaBeta(GDG, targetChisq, chisq, targetFlux, flux);
            } else {
                this->initialiseAlphaBeta(GDG);
            }
        };

        template<class T>
        Bool EntropyBase<T>::initialiseAlphaBeta(const Matrix<T>& GDG)
        {
            if (this->itsAlpha > T(0.0)) {
                return false;
            }
            if (!this->itsUseFluxConstraint) {
                this->itsAlpha = max(0.0,  abs(GDG(H, C) / GDG(C, C)));
                this->itsBeta = 0.0;
            } else {
                Double det = GDG(C, C) * GDG(F, F) - GDG(C, F) * GDG(C, F);
                this->itsAlpha = (GDG(F, F) * GDG(H, C) - GDG(C, F) * GDG(H, F)) / det;
                this->itsBeta = (GDG(C, C) * GDG(H, F) - GDG(C, F) * GDG(H, C)) / det;
            }
            return true;
        };

        template<class T>
        void EntropyBase<T>::updateAlphaBeta(const Matrix<T>& GDG, const T targetChisq, const T chisq,
                                             const T targetFlux, const T flux)
        {

            // code stolen from SDE's mem.f
            T length(this->formLength(GDG));

            Double a = GDG(C, J) / GDG(C, C);
            Double b = square(a) - (GDG(J, J) - this->itsTolerance * length) / GDG(C, C);

            Double dAMax;
            Double dAMin;
            Double dBMax;
            Double dBMin;

            if (b > 0.0) {
                b = sqrt(b);
                dAMax = a + b;
                dAMin = a - b;
            } else {
                dAMax = 0.0;
                dAMin = 0.0;
            }

            Double dChisq;
            Double dAlpha;
            Double dBeta;
            Double dFlux;

            if (! this->itsUseFluxConstraint) {
                dChisq =  chisq -  targetChisq + GDG(C, J);
                dAlpha = dChisq / GDG(C, C);

                dAlpha = max(dAMin, min(dAMax, dAlpha));
                this->itsAlpha = max(0.0, this->itsAlpha + dAlpha);
            } else {
                a = GDG(F, J) / GDG(F, F);
                b = square(a) - (GDG(J, J) - this->itsTolerance * length) / GDG(F, F);
                if (b > 0.0) {
                    b = sqrt(b);
                    dBMax = a + b;
                    dBMin = a - b;
                } else {
                    dBMax = 0.0;
                    dBMin = 0.0;
                }

                dChisq = chisq - targetChisq + GDG(C, J);
                dFlux  = flux  - targetFlux  + GDG(F, J);
                T det = GDG(C, C) * GDG(F, F) - square(GDG(F, C));
                dAlpha = (GDG(F, F) * dChisq - GDG(C, F) * dFlux) / det;
                dBeta = (GDG(C, C) * dFlux  - GDG(C, F) * dChisq) / det;

                dAlpha = max(dAMin, min(dAMax, dAlpha));
                this->itsAlpha = max(0.0, this->itsAlpha + dAlpha);
                dBeta    = max(dBMin, min(dBMax, dBeta));
                this->itsBeta  = itsBeta + dBeta;
            }
        };

    } // namespace synthesis

} // namespace askap


/*
* MIT License
*
* Copyright (c) 2020 International Business Machines
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/

#include "DoubleMatrix.h"
//#include "DoubleMatrixArray.h"
#include <cassert>
#include <stdlib.h>
#include <algorithm>
#include <iomanip>
#include <math.h>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/matrix_proxy.hpp>
#include "h5Parser.h"

using namespace std;

DoubleMatrix::~DoubleMatrix() {

}

DoubleMatrix::DoubleMatrix() :
            m(0, 0) {

}

DoubleMatrix::DoubleMatrix(int r, int c, double fixedVal) :
            m(r, c, fixedVal) {
}

DoubleMatrix::DoubleMatrix(vector<double> v, int matrixRows) {
  int matrixCols = (v.size() + matrixRows - 1) / matrixRows;
  m.resize(matrixRows, matrixCols);
  for (int i = 0; i < (int) v.size(); ++i)
    set(i / cols(), i % cols(), v[i]);
}

DoubleMatrix::DoubleMatrix(ifstream& matrixFile) {

  vector<vector<double>> inputVals(0);
  string line;

  // Read all double arrays from "matrixFile" into "inputVals" matrix.
  // Note that "matrixFile" is expected to have the following format:
  // [vals_{11}, vals_{12}, ... , vals_{1n}]
  // [vals_{21}, vals_{22}, ... , vals_{2n}]
  // ...
  // [vals_{m1}, vals_{m2}, ... , vals_{mn}].
  while (getline(matrixFile, line)) {
    inputVals.push_back(vector<double>(0));

    // Read current array in matrix file into "sstream"
    istringstream sstream(line);
    char nextChar;

    // Ignore '[' charecter in the current array in "matrixFile".
    // Note that '[' charecter is expected to appear at the begining of every array.
    sstream.ignore();

    // Check whether the current array is empty
    nextChar = sstream.peek();

    if (nextChar == ']') {
      // The current array is empty
      continue;
    }

    string nextVal;

    // Read all doubles from current array
    while (getline(sstream, nextVal, ',')) {
      // If getline() returned extra ']' charecter in the last read, remove ']' charecter
      // from "nextVal".
      // Note that an Extra ']' charecter will be returned when reading the last number in
      // the current array.
      if (nextVal.back() == ']') {
        nextVal.pop_back();
      }

      double doubleVal = stod(nextVal.c_str());

      inputVals.back().push_back(doubleVal);

      // Ignore space charecter
      sstream.ignore();
    }
  }

  init(inputVals);
}

DoubleMatrix::DoubleMatrix(const vector<vector<float> >& matrix) {
  init(matrix);
}

DoubleMatrix::DoubleMatrix(const vector<vector<double> >& matrix) {
  init(matrix);
}

DoubleMatrix::DoubleMatrix(const std::vector<std::vector<double> >& matrix, int r, int c, double fixedVal) :
	m(r, c, fixedVal)
{
	  for (int i = 0; i < matrix.size(); ++i) {
	    for (int j = 0; j < matrix[0].size(); ++j) {
	      m(i, j) = static_cast<double>(matrix[i][j]);
	    }
	  }
}

DoubleMatrix::DoubleMatrix(const string& h5File, const string& path) {
  load(h5File, path);
}

void DoubleMatrix::load(const string& h5File, const string& path) {
  H5Parser h5P(h5File);

  // Initialize "vals" member matrix with the corresponding matrix provided in the h5 file
  init(h5P.parseFC(path));

  transpose();
}

vector<vector<double> > DoubleMatrix::getVals() const {
  vector<vector<double>> res(rows());
  for (int i = 0; i < rows(); ++i) {
    res[i].resize(cols());
    for (int j = 0; j < cols(); ++j) {
      res[i][j] = m(i, j);
    }
  }
  return res;
}

void DoubleMatrix::elementMultiply(const DoubleMatrix& other) {
  assert(rows() == other.rows());
  assert(cols() == other.cols());
  m = boost::numeric::ublas::element_prod(m, other.m);
}

void DoubleMatrix::elementMultiplyAt(const DoubleMatrix&other,int startRow,int startCol) {
  for (int i = 0; i < other.rows(); ++i) {
    for (int j = 0; j < other.cols(); ++j) {
      m(startRow+i, startCol+j) *= other.m(i ,j);
    }
  }
}

void DoubleMatrix::meanAlongRows() {
  *this = getMeanAlongRows();
}

DoubleMatrix DoubleMatrix::getMeanAlongRows() const{
  int rowsOrig = rows();
  DoubleMatrix dm = getSumAlongRows();
  dm.multiplyByScalar((double)1/rowsOrig);
  return dm;
}

void DoubleMatrix::meanAlongCols() {
  *this = getMeanAlongCols();
}

DoubleMatrix DoubleMatrix::getMeanAlongCols() const{
  int colsOrig = cols();
  DoubleMatrix dm = getSumAlongCols();
  dm.multiplyByScalar((double)1/colsOrig);
  return dm;
}

void DoubleMatrix::sumAlongRows() {
  *this = getSumAlongRows();
}

DoubleMatrix DoubleMatrix::getSumAlongRows() const {
  if (rows()==0)
    throw runtime_error("empty matrix");
  DoubleMatrix res(1, m.size2());
  for (int j = 0; j < cols(); ++j) {
    double sum = 0;
    for (int i = 0; i < rows(); ++i) {
      sum += get(i, j);
    }
    res.set(0, j, sum);
  }

  return res;
}

void DoubleMatrix::sumAlongCols() {
  *this = getSumAlongCols();
}

DoubleMatrix DoubleMatrix::getSumAlongCols() const {
  if (cols()==0) {
    throw invalid_argument("empty matrix");
  }
  DoubleMatrix res(rows(), 1);
  for (int i = 0; i < rows(); ++i) {
    double sum = 0;
    for (int j = 0; j < cols(); ++j) {
      sum += get(i, j);
    }
    res.set(i, 0, sum);
  }

  return res;
}
double DoubleMatrix::getMaxDiff(const DoubleMatrix&other) const {
  if (size() == 0)
    throw invalid_argument("empty matrix");
  double res = 0;
  for (int i = 0; i < rows(); ++i)
    for (int j = 0; j < cols(); ++j)
      res = max(res, abs(get(i, j) - other.get(i, j)));
  return res;
}

double DoubleMatrix::getMaxRelDiff(const DoubleMatrix&other,
    double tolerance) const {
  if (size() == 0)
    throw invalid_argument("empty matrix");
  double res = 0;
  for (int i = 0; i < rows(); ++i)
    for (int j = 0; j < cols(); ++j) {
      double v1 = get(i, j);
      double v2 = other.get(i, j);
      double diff = abs(v1 - v2);
      double m = max(abs(v1), abs(v2));
      double relDiff = m < (tolerance * 10) ? diff : (diff / m);
      res = max(res, relDiff);
    }
  return res;
}

double DoubleMatrix::getMaxAbs() const {
  if (size() == 0)
    throw invalid_argument("empty matrix");
  double res = 0;
  for (int i = 0; i < rows(); ++i)
    for (int j = 0; j < cols(); ++j)
      res = max(get(i, j),res);
  return res;
}

DoubleMatrix DoubleMatrix::getMultiply(const DoubleMatrix& other) const {
  if (cols() != other.rows()) {
    cerr << "Can't multiply: " << endl;
    debugPrint(cerr, 0);
    other.debugPrint(cerr, 0);
    throw invalid_argument("mismatching dims");
  }
  DoubleMatrix res(rows(), other.cols());
  res.m = boost::numeric::ublas::prod(m, other.m);
  return res;
}

/*DoubleMatrixArray DoubleMatrix::getConvolution(const DoubleMatrixArray& filters,
    int strideRows, int strideCols) const {
  DoubleMatrixArray res;

  for (int f = 0; f < filters.size(); f++) {
    DoubleMatrix currentFilterConvRes = getConvolution(filters.getMat(f),
        strideRows, strideCols);

    res.pushBackMatrix(currentFilterConvRes);
  }

  return res;
}*/

DoubleMatrix DoubleMatrix::getConvolution(const DoubleMatrix& filter,
    int strideRows, int strideCols) const {
  int filterNumRows = filter.rows();
  int filterNumCols = filter.cols();

  DoubleMatrix res(ceil((double)(rows() - filterNumRows) / strideRows) + 1,
                   ceil((double)(cols() - filterNumCols) / strideCols) + 1);

  for (int r = 0, tr = 0; r + filterNumRows - strideRows < rows(); r += strideRows, tr++) {
    for (int c = 0, tc = 0; c + filterNumCols - strideCols < cols(); c += strideCols, tc++) {
      double currentInnerProduct = 0;
      for (int i = 0; i < filterNumRows; i++) {
        for (int j = 0; j < filterNumCols; j++) {
        	if(r+i >= rows() || c+j >= cols())
        		continue;
          currentInnerProduct += (filter.get(i, j) * get(i + r, j + c));
        }
      }
      res.set(tr, tc, currentInnerProduct);
    }
  }

  return res;
}

DoubleMatrix DoubleMatrix::getSubMatrix(int row1,int col1,int row2,int col2) const {
  if ((row1 < 0) || (row1 >= row2) || (row2 > rows()) || (col1 < 0) || (col1 >= col2) || (col2 > cols())) {
    cerr << "Requesting range: (" << row1 << "," << col1 <<")-("
        << row2 <<"," << col2 << ")" << endl;
    cerr << "Out of this: " << *this << endl;
    throw invalid_argument("out of range");
  }

  DoubleMatrix res(row2-row1,col2-col1);
  for (int i=row1;i<row2;++i)
    for (int j=col1;j<col2;++j)
      res.set(i-row1,j-col1,get(i,j));

  return res;
}

void DoubleMatrix::multiply(const DoubleMatrix& other) {
  *this = getMultiply(other);
}

void DoubleMatrix::appendRow(const vector<double>& vec) {
  if ((vec.size() != cols()) && (rows() != 0)) {
    throw invalid_argument("mismatching dims");
  }

  int newCols = rows() == 0 ? vec.size() : cols();
  m.resize(rows() + 1, newCols, true);
  boost::numeric::ublas::matrix_row<double_matrix_t> lastRow(m, rows() - 1);
  for (int i = 0; i < lastRow.size(); ++i) {
    lastRow(i) = vec[i];
  }
}

void DoubleMatrix::appendCols(const DoubleMatrix&other) {
  if (cols()!=0 && other.rows()!=rows())
    throw invalid_argument("mismatching dims");
  int origCols = cols();
  m.resize(other.rows(), cols() + other.cols(), true);
  for (int i = 0; i < other.rows(); ++i) {
    for (int j = 0; j < other.cols(); ++j) {
      m(i, origCols + j) = other.get(i, j);
    }
  }
}

void DoubleMatrix::appendRows(const DoubleMatrix &other) {
    if (rows() != 0 && other.cols() != cols())
        throw invalid_argument("mismatching dims");
    int origRows = rows();
    m.resize(rows() + other.rows(), other.cols(), true);
    for (int i = 0; i < other.rows(); ++i) {
        for (int j = 0; j < other.cols(); ++j) {
            m(origRows + i, j) = other.get(i, j);
        }
    }
}

void DoubleMatrix::init(vector<vector<float> > matrix) {
  m.resize(matrix.size(), matrix[0].size(), false);
  for (int i = 0; i < m.size1(); ++i) {
    for (int j = 0; j < m.size2(); ++j) {
      m(i, j) = static_cast<double>(matrix[i][j]);
    }
  }
}

void DoubleMatrix::init(vector<vector<double> > matrix) {
  m.resize(matrix.size(), matrix[0].size(), false);
  for (int i = 0; i < m.size1(); ++i) {
    for (int j = 0; j < m.size2(); ++j) {
      m(i, j) = matrix[i][j];
    }
  }
}

void DoubleMatrix::initRandom() {
  for (int i = 0; i < m.size1(); ++i) {
    for (int j = 0; j < m.size2(); ++j) {
      int val = rand() % 10000;
      double dval = ((double) val / 10000.0) * 2 - 1;
      m(i, j) = dval;
    }
  }
}

void DoubleMatrix::initConst(double v) {
  std::fill(m.data().begin(), m.data().end(), v);
}

streamoff DoubleMatrix::save(ostream& stream) const {
  streampos streamStartPos = stream.tellp();

  const int r = rows();
  stream.write(reinterpret_cast<const char*>(&r), sizeof(int));

  const int c = cols();
  stream.write(reinterpret_cast<const char*>(&c), sizeof(int));

  for (int i = 0; i < rows(); i++) {
    for (int j = 0; j < cols(); j++) {
      const double d = get(i, j);
      stream.write(reinterpret_cast<const char*>(&d), sizeof(double));
    }
  }

  streampos streamEndPos = stream.tellp();

  return streamEndPos - streamStartPos;
}

streamoff DoubleMatrix::load(istream& stream) {
  streampos streamStartPos = stream.tellg();

  int r = 0;
  stream.read(reinterpret_cast<char*>(&r), sizeof(int));

  int c = 0;
  stream.read(reinterpret_cast<char*>(&c), sizeof(int));

  m.resize(r, c);

  for (int i = 0; i < rows(); i++) {
    for (int j = 0; j < cols(); j++) {
      double d = 0;
      stream.read(reinterpret_cast<char*>(&d), sizeof(double));
      set(i, j, d);
    }
  }

  streampos streamEndPos = stream.tellg();

  return streamEndPos - streamStartPos;
}

void DoubleMatrix::flattenIntoVec(vector<double>& vv) const {
  for (int i = 0; i < rows(); ++i) {
    for (int j = 0; j < cols(); ++j) {
      vv.push_back(get(i, j));
    }
  }
}

vector<double> DoubleMatrix::getFlatten() const {
  vector<double> res;
  flattenIntoVec(res);
  return res;
}

void DoubleMatrix::unFlatten(const vector<double>& v) {
  if (v.size() != size()) {
    throw invalid_argument("mismatching dims");
  }

  int pos = 0;
  for (int i = 0; i < rows(); ++i)
    for (int j = 0; j < cols(); ++j)
      set(i, j, v[pos++]);
}

void DoubleMatrix::debugPrint(ostream&out, int verboseLevel) const {
  out << "Matrix " << rows() << " x " << cols() << endl;
  if (verboseLevel == 0)
    return;
  const int precision = verboseLevel > 1 ? 10 : 3;
  out << fixed;
  for (int i = 0; i < rows(); ++i) {
    for (int j = 0; j < cols(); ++j)
      out << setw(precision + 6) << setprecision(precision) << get(i, j);
    out << endl;
  }
  out << defaultfloat;
}

ostream& operator<<(ostream&out, const DoubleMatrix&dm) {
  dm.debugPrint(out, 1);
  return out;
}

void DoubleMatrix::multiplyByScalar(double scalar) {
  m *= scalar;
  //for (int i = 0; i < rows(); ++i)
  //  for (int j = 0; j < cols(); ++j)
  //    set(i, j, get(i, j) * scalar);
}

DoubleMatrix DoubleMatrix::getTranspose() const {
  DoubleMatrix res(cols(), rows());
  for (int i = 0; i < rows(); ++i)
    for (int j = 0; j < cols(); ++j)
      res.set(j, i, get(i, j));
  res.m = boost::numeric::ublas::trans(m);
  return res;
}

void DoubleMatrix::transpose() {
  *this = getTranspose();
}

void DoubleMatrix::testEqual(const string& title, const DoubleMatrix&other,
    double tolerance) const {
  testSameSize(title, other);
  double maxDiff = getMaxRelDiff(other, tolerance);
  if (maxDiff < tolerance) {
    cout << title << " OK (diff=" << maxDiff << ")" << endl;
  } else {
    cout << title << endl;
    cout << "EXP: " << *this;
    cout << "ACT: " << other;
    cout << "rel-diff: " << maxDiff << endl;
    cout << "abs-diff: " << getMaxDiff(other) << endl;
    throw runtime_error("test failed");
  }

}

void DoubleMatrix::testSameSize(const string& title,
    const DoubleMatrix&other) const {
  if (other.rows() == rows() && other.cols() == cols())
    return;
  cerr << title << ":" << endl;
  debugPrint(cerr, 0);
  other.debugPrint(cerr, 0);
  throw invalid_argument("mismatching sizes");
}

bool DoubleMatrix::checkIfEqual(const DoubleMatrix&other,
    double tolerance) const {
  if (!checkIfSameSize(other)) {
    cout << "different dims" << endl;
    return false;
  }

  for (int i = 0; i < rows(); i++) {
    for (int j = 0; j < cols(); j++) {
      const double diff = fabs(get(i, j) - other.get(i, j));
      if (diff >= tolerance) {
        cout << "difference in indexes " << i << ", " << j << endl;
        cout << "first val = " << get(i, j) << endl;
        cout << "second val = " << other.get(i, j) << endl;
        cout << "diff = " << std::setprecision(9) << diff << endl;
        cout << "tolerance = " << std::setprecision(9) << tolerance << endl;
        return false;
      }
    }
  }

  return true;
}

bool DoubleMatrix::checkIfSameSize(const DoubleMatrix&other) const {
  return ((rows() == other.rows()) && (cols() == other.cols()));
}

void DoubleMatrix::copy(const DoubleMatrix&other) {
  testSameSize("copy",other);
  m = other.m;
}

DoubleMatrix& DoubleMatrix::operator-=(const DoubleMatrix& other) {
  testSameSize("-=", other);
  m -= other.m;
  return *this;
}

DoubleMatrix& DoubleMatrix::operator+=(const DoubleMatrix& other) {
  testSameSize("+=", other);
  m += other.m;
  return *this;
}

void DoubleMatrix::addAt(const DoubleMatrix&other,int startRow,int startCol) {
  if ((startRow < 0) || (startCol < 0) || (startRow + other.rows() > rows()) || (startCol + other.cols() > cols())) {
    throw invalid_argument("mismatching dims");
  }

  for (int i = 0; i < other.rows(); ++i) {
    for (int j = 0; j < other.cols(); ++j) {
      m(startRow+i, startCol+j) += other.get(i, j);
    }
  }
}

DoubleMatrix DoubleMatrix::getDuplicate(int dupRows, int dupCols) const {
  DoubleMatrix res(rows() * dupRows, cols() * dupCols);
  for (int i = 0; i < res.rows(); ++i)
    for (int j = 0; j < res.cols(); ++j)
      res.set(i, j, get(i / dupRows, j / dupCols));
  return res;
}

string DoubleMatrix::niceFormat(double v) {
  if (abs(v) < 1e-8)
    return "[0]";
  ostringstream out;
  out.precision(12);
  out << std::fixed << v;
  return out.str();
  //return to_string(v);
}

DoubleMatrix DoubleMatrix::smartUnFlatten(const vector<double>& vals,
    int innerDup, int outerDup, int valsPerRow, int cols) {
  assert(cols >= valsPerRow * innerDup);
  int rows = (int) (vals.size() * outerDup + valsPerRow - 1) / valsPerRow;
  DoubleMatrix res(rows, cols);
  int row = 0;
  int col = 0;
  int valsInRow = 0;
  for (int odup = 0; odup < outerDup; ++odup) {
    for (int i = 0; i < (int) vals.size(); ++i) {
      for (int dup = 0; dup < innerDup; ++dup) {
        res.set(row, col, vals[i]);
        ++col;
      }
      ++valsInRow;
      if (valsInRow >= valsPerRow) {
        valsInRow = 0;
        ++row;
        col = 0;
      }
    }
  }
  return res;
}

DoubleMatrix DoubleMatrix::smartReFlatten(int innerDup, int outerDup,
    int valsPerRow, int cols) {
  return smartUnFlatten(getFlatten(), innerDup, outerDup, valsPerRow, cols);
}

void DoubleMatrix::clear() {
  m.resize(0, 0, false);
}


/**
 * @file kmedoids_ucb.cpp
 * @date 2020-06-10
 *
 * This file contains the primary C++ implementation of the BanditPAM code.
 *
 */
#include "kmedoids_algorithm.hpp"
#include "log_helper.hpp"
#include "fastpam1.hpp"
#include "pam.hpp"
#include "banditpam.hpp"

#include <carma>
#include <armadillo>
#include <unordered_map>
#include <regex>

/**
 *  \brief Class implementation for running KMedoids methods.
 *
 *  KMedoids class. Creates a KMedoids object that can be used to find the medoids
 *  for a particular set of input data.
 *
 *  @param n_medoids Number of medoids/clusters to create
 *  @param algorithm Algorithm used to find medoids; options are "BanditPAM" for
 *  the "Bandit-PAM" algorithm, or "naive" to use the naive method
 *  @param verbosity Verbosity of the algorithm, 0 will have no log file
 *  emitted, 1 will emit a log file
 *  @param max_iter The maximum number of iterations the algorithm runs for
 *  @param buildConfidence Constant that affects the sensitivity of build confidence bounds
 *  @param swapConfidence Constant that affects the sensitiviy of swap confidence bounds
 *  @param logFilename The name of the output log file
 */
km::KMedoids::KMedoids(size_t n_medoids, const std::string& algorithm, size_t verbosity,
                   size_t max_iter, size_t buildConfidence, size_t swapConfidence,
                   std::string logFilename
    ): n_medoids(n_medoids),
       algorithm(algorithm),
       max_iter(max_iter),
       buildConfidence(buildConfidence),
       swapConfidence(swapConfidence),
       verbosity(verbosity),
       logFilename(logFilename) {
  km::KMedoids::checkAlgorithm(algorithm);
}

/**
 *  \brief Destroys KMedoids object.
 *
 *  Destructor for the KMedoids class.
 */
km::KMedoids::~KMedoids() {;}

/**
 *  \brief Checks whether algorithm input is valid
 *
 *  Checks whether the user's selected algorithm is a valid option.
 *
 *  @param algorithm Name of the algorithm input by the user.
 */
void km::KMedoids::checkAlgorithm(const std::string& algorithm) {
  if ((algorithm != "BanditPAM") && (algorithm != "naive") && (algorithm != "FastPAM1")) {
    throw "unrecognized algorithm";
  }
}

/**
 *  \brief Returns the final medoids
 *
 *  Returns the final medoids at the end of the SWAP step after km::KMedoids::fit
 *  has been called.
 */
arma::rowvec km::KMedoids::getMedoidsFinal() {
  return medoid_indices_final;
}

/**
 *  \brief Returns the build medoids
 *
 *  Returns the build medoids at the end of the BUILD step after km::KMedoids::fit
 *  has been called.
 */
arma::rowvec km::KMedoids::getMedoidsBuild() {
  return medoid_indices_build;
}

/**
 *  \brief Returns the medoid assignments for each datapoint
 *
 *  Returns the medoid each input datapoint is assigned to after km::KMedoids::fit
 *  has been called and the final medoids have been identified
 */
arma::rowvec km::KMedoids::getLabels() {
  return labels;
}

/**
 *  \brief Returns the number of swap steps
 *
 *  Returns the number of SWAP steps completed during the last call to
 *  km::KMedoids::fit
 */
size_t km::KMedoids::getSteps() {
  return steps;
}

/**
 *  \brief Sets the loss function
 *
 *  Sets the loss function used during km::KMedoids::fit
 *
 *  @param loss Loss function to be used e.g. L2
 */
void km::KMedoids::setLossFn(std::string loss) {
  if (std::regex_match(loss, std::regex("L\\d*"))) {
      loss = loss.substr(1);
  }
  try {
    if (loss == "manhattan") {
        lossFn = &km::KMedoids::manhattan;
    } else if (loss == "cos") {
        lossFn = &km::KMedoids::cos;
    } else if (loss == "inf") {
        lossFn = &km::KMedoids::LINF;
    } else if (std::isdigit(loss.at(0))) {
        lossFn = &km::KMedoids::LP;
        lp     = atoi(loss.c_str());
    } else {
        throw std::invalid_argument("error: unrecognized loss function");
    }
  } catch (std::invalid_argument& e) {
      std::cout << e.what() << std::endl;
    }
}

/**
 *  \brief Returns the number of medoids
 *
 *  Returns the number of medoids to be identified during km::KMedoids::fit
 */
size_t km::KMedoids::getNMedoids() {
  return n_medoids;
}

/**
 *  \brief Sets the number of medoids
 *
 *  Sets the number of medoids to be identified during km::KMedoids::fit
 */
void km::KMedoids::setNMedoids(size_t new_num) {
  n_medoids = new_num;
}

/**
 *  \brief Returns the algorithm for KMedoids
 *
 *  Returns the algorithm used for identifying the medoids during km::KMedoids::fit
 */
std::string km::KMedoids::getAlgorithm() {
  return algorithm;
}

/**
 *  \brief Sets the algorithm for KMedoids
 *
 *  Sets the algorithm used for identifying the medoids during km::KMedoids::fit
 *
 *  @param new_alg New algorithm to use
 */
void km::KMedoids::setAlgorithm(const std::string& new_alg) {
  algorithm = new_alg;
  km::KMedoids::checkAlgorithm(algorithm);
}

/**
 *  \brief Returns the verbosity for KMedoids
 *
 *  Returns the verbosity used during km::KMedoids::fit, with 0 not creating a
 *  logfile, and >0 creating a detailed logfile.
 */
size_t km::KMedoids::getVerbosity() {
  return verbosity;
}

/**
 *  \brief Sets the verbosity for KMedoids
 *
 *  Sets the verbosity used during km::KMedoids::fit, with 0 not creating a
 *  logfile, and >0 creating a detailed logfile.
 *
 *  @param new_ver New verbosity to use
 */
void km::KMedoids::setVerbosity(size_t new_ver) {
  verbosity = new_ver;
}

/**
 *  \brief Returns the maximum number of iterations for KMedoids
 *
 *  Returns the maximum number of iterations that can be run during
 *  km::KMedoids::fit
 */
size_t km::KMedoids::getMaxIter() {
  return max_iter;
}

/**
 *  \brief Sets the maximum number of iterations for KMedoids
 *
 *  Sets the maximum number of iterations that can be run during km::KMedoids::fit
 *
 *  @param new_max New maximum number of iterations to use
 */
void km::KMedoids::setMaxIter(size_t new_max) {
  max_iter = new_max;
}

/**
 *  \brief Returns the constant buildConfidence
 *
 *  Returns the constant that affects the sensitivity of build confidence bounds
 *  that can be run during km::KMedoids::fit
 */
size_t km::KMedoids::getbuildConfidence() {
  return buildConfidence;
}

/**
 *  \brief Sets the constant buildConfidence
 *
 *  Sets the constant that affects the sensitivity of build confidence bounds
 *  that can be run during km::KMedoids::fit
 *
 *  @param new_buildConfidence New buildConfidence
 */
void km::KMedoids::setbuildConfidence(size_t new_buildConfidence) {
  buildConfidence = new_buildConfidence;
}

/**
 *  \brief Returns the constant swapConfidence
 *
 *  Returns the constant that affects the sensitivity of swap confidence bounds
 *  that can be run during km::KMedoids::fit
 */
size_t km::KMedoids::getswapConfidence() {
  return swapConfidence;
}

/**
 *  \brief Sets the constant swapConfidence
 *
 *  Sets the constant that affects the sensitivity of swap confidence bounds
 *  that can be run during km::KMedoids::fit
 *
 *  @param new_swapConfidence New swapConfidence
 */
void km::KMedoids::setswapConfidence(size_t new_swapConfidence) {
  swapConfidence = new_swapConfidence;
}

/**
 *  \brief Returns the log filename for KMedoids
 *
 *  Returns the name of the logfile that will be output at the end of
 *  km::KMedoids::fit if verbosity is >0
 */
std::string km::KMedoids::getLogfileName() {
  return logFilename;
}

/**
 *  \brief Sets the log filename for KMedoids
 *
 *  Sets the name of the logfile that will be output at the end of
 *  km::KMedoids::fit if verbosity is >0
 *
 *  @param new_lname New logfile name
 */
void km::KMedoids::setLogFilename(const std::string& new_lname) {
  logFilename = new_lname;
}

/**
 * \brief Finds medoids for the input data under identified loss function
 *
 * Primary function of the KMedoids class. Identifies medoids for input dataset
 * after both the SWAP and BUILD steps, and outputs logs if verbosity is >0
 *
 * @param input_data Input data to find the medoids of
 * @param loss The loss function used during medoid computation
 */
void km::KMedoids::fit(const arma::mat& input_data, const std::string& loss) {
  km::KMedoids::setLossFn(loss);
  if (algorithm == "naive") {
    static_cast<PAM*>(this)->fit_naive(input_data);
  } else if (algorithm == "BanditPAM") {
    static_cast<BanditPAM*>(this)->fit_bpam(input_data);
  } else if (algorithm == "FastPAM1") {
    static_cast<FastPAM1*>(this)->fit_fastpam1(input_data);
  }

  if (this->verbosity > 0) {
      this->logHelper.init(this->logFilename);
      this->logHelper.writeProfile(this->medoid_indices_build, this->medoid_indices_final, this->steps,
                                                        this->logHelper.loss_swap.back());
      this->logHelper.close();
  }
}

/**
 * \brief Calculates confidence intervals in build step
 *
 * Calculates the confidence intervals about the reward for each arm
 *
 * @param data Transposed input data to find the medoids of
 * @param batch_size Number of datapoints sampled for updating confidence
 * intervals
 * @param best_distances Array of best distances from each point to previous set
 * of medoids
 * @param use_aboslute Determines whether the absolute cost is added to the total
 */
arma::rowvec km::KMedoids::build_sigma(
  const arma::mat& data,
  arma::rowvec& best_distances,
  arma::uword batch_size,
  bool use_absolute) {
    size_t N = data.n_cols;
    // without replacement, requires updated version of armadillo
    arma::uvec tmp_refs = arma::randperm(N, batch_size);
    arma::vec sample(batch_size);
    arma::rowvec updated_sigma(N);
// for each possible swap
#pragma omp parallel for
    for (size_t i = 0; i < N; i++) {
        // gather a sample of points
        for (size_t j = 0; j < batch_size; j++) {
            double cost = (this->*lossFn)(data, i, tmp_refs(j));
            if (use_absolute) {
                sample(j) = cost;
            } else {
                sample(j) = cost < best_distances(tmp_refs(j))
                              ? cost
                              : best_distances(tmp_refs(j));
                sample(j) -= best_distances(tmp_refs(j));
            }
        }
        updated_sigma(i) = arma::stddev(sample);
    }
    arma::rowvec P = {0.25, 0.5, 0.75};
    arma::rowvec Q = arma::quantile(updated_sigma, P);
    std::ostringstream sigma_out;
    sigma_out << "min: " << arma::min(updated_sigma)
              << ", 25th: " << Q(0)
              << ", median: " << Q(1)
              << ", 75th: " << Q(2)
              << ", max: " << arma::max(updated_sigma)
              << ", mean: " << arma::mean(updated_sigma);
    logHelper.sigma_build.push_back(sigma_out.str());
    return updated_sigma;
}

/**
 * \brief Calculates distances in swap step
 *
 * Calculates the best and second best distances for each datapoint to one of
 * the medoids in the current medoid set.
 *
 * @param data Transposed input data to find the medoids of
 * @param medoid_indices Array of medoid indices corresponding to dataset entries
 * @param best_distances Array of best distances from each point to previous set
 * of medoids
 * @param second_best_distances Array of second smallest distances from each
 * point to previous set of medoids
 * @param assignments Assignments of datapoints to their closest medoid
 */
void km::KMedoids::calc_best_distances_swap(
  const arma::mat& data,
  arma::rowvec& medoid_indices,
  arma::rowvec& best_distances,
  arma::rowvec& second_distances,
  arma::rowvec& assignments) {
#pragma omp parallel for
    for (size_t i = 0; i < data.n_cols; i++) {
        double best = std::numeric_limits<double>::infinity();
        double second = std::numeric_limits<double>::infinity();
        for (size_t k = 0; k < medoid_indices.n_cols; k++) {
            double cost = (this->*lossFn)(data, medoid_indices(k), i);
            if (cost < best) {
                assignments(i) = k;
                second = best;
                best = cost;
            } else if (cost < second) {
                second = cost;
            }
        }
        best_distances(i) = best;
        second_distances(i) = second;
    }
}

/**
 * \brief Calculates confidence intervals in swap step
 *
 * Calculates the confidence intervals about the reward for each arm
 *
 * @param data Transposed input data to find the medoids of
 * @param batch_size Number of datapoints sampled for updating confidence
 * intervals
 * @param best_distances Array of best distances from each point to previous set
 * of medoids
 * @param second_best_distances Array of second smallest distances from each
 * point to previous set of medoids
 * @param assignments Assignments of datapoints to their closest medoid
 */
arma::mat km::KMedoids::swap_sigma(
  const arma::mat& data,
  size_t batch_size,
  arma::rowvec& best_distances,
  arma::rowvec& second_best_distances,
  arma::rowvec& assignments)
{
    size_t N = data.n_cols;
    size_t K = n_medoids;
    arma::mat updated_sigma(K, N, arma::fill::zeros);
    arma::uvec tmp_refs = arma::randperm(N,
                                   batch_size); // without replacement, requires
                                                // updated version of armadillo

    arma::vec sample(batch_size);
// for each considered swap
#pragma omp parallel for
    for (size_t i = 0; i < K * N; i++) {
        // extract data point of swap
        size_t n = i / K;
        size_t k = i % K;

        // calculate change in loss for some subset of the data
        for (size_t j = 0; j < batch_size; j++) {
            double cost = (this->*lossFn)(data, n, tmp_refs(j));

            if (k == assignments(tmp_refs(j))) {
                if (cost < second_best_distances(tmp_refs(j))) {
                    sample(j) = cost;
                } else {
                    sample(j) = second_best_distances(tmp_refs(j));
                }
            } else {
                if (cost < best_distances(tmp_refs(j))) {
                    sample(j) = cost;
                } else {
                    sample(j) = best_distances(tmp_refs(j));
                }
            }
            sample(j) -= best_distances(tmp_refs(j));
        }
        updated_sigma(k, n) = arma::stddev(sample);
    }
  return updated_sigma;
}

/**
* \brief Write the sigma distribution into logfile
*
* Calculates the statistical measures of the sigma distribution
* and writes the results to the log file.
*
* @param sigma Dispersion paramater for each datapoint
*/
void km::KMedoids::sigma_log(arma::mat& sigma) {
  arma::rowvec flat_sigma = sigma.as_row();
  arma::rowvec P = {0.25, 0.5, 0.75};
  arma::rowvec Q = arma::quantile(flat_sigma, P);
  std::ostringstream sigma_out;
  sigma_out << "min: " << arma::min(flat_sigma)
            << ", 25th: " << Q(0)
            << ", median: " << Q(1)
            << ", 75th: " << Q(2)
            << ", max: " << arma::max(flat_sigma)
            << ", mean: " << arma::mean(flat_sigma);
  logHelper.sigma_swap.push_back(sigma_out.str());
}

/**
 * \brief Calculate loss for medoids
 *
 * Calculates the loss under the previously identified loss function of the
 * medoid indices.
 *
 * @param data Transposed input data to find the medoids of
 * @param medoid_indices Indices of the medoids in the dataset.
 */
double km::KMedoids::calc_loss(
  const arma::mat& data,
  arma::rowvec& medoid_indices) {
    double total = 0;

    for (size_t i = 0; i < data.n_cols; i++) {
        double cost = std::numeric_limits<double>::infinity();
        for (size_t k = 0; k < n_medoids; k++) {
            double currCost = (this->*lossFn)(data, medoid_indices(k), i);
            if (currCost < cost) {
                cost = currCost;
            }
        }
        total += cost;
    }
    return total;
}

// Loss and miscellaneous functions

/**
 * \brief LP loss
 *
 * Calculates the LP loss between the datapoints at index i and j of the dataset
 *
 * @param data Transposed input data to find the medoids of
 * @param i Index of first datapoint
 * @param j Index of second datapoint
 */
double km::KMedoids::LP(const arma::mat& data, size_t i, size_t j) const {
    return arma::norm(data.col(i) - data.col(j), lp);
}

/**
 * \brief cos loss
 *
 * Calculates the cosine loss between the datapoints at index i and j of the
 * dataset
 *
 * @param data Transposed input data to find the medoids of
 * @param i Index of first datapoint
 * @param j Index of second datapoint
 */
double km::KMedoids::cos(const arma::mat& data, size_t i, size_t j) const {
    return arma::dot(data.col(i), data.col(j)) / (arma::norm(data.col(i))
                                                    * arma::norm(data.col(j)));
}

/**
 * \brief Manhattan loss
 *
 * Calculates the Manhattan loss between the datapoints at index i and j of the
 * dataset
 *
 * @param data Transposed input data to find the medoids of
 * @param i Index of first datapoint
 * @param j Index of second datapoint
 */
double km::KMedoids::manhattan(const arma::mat& data, size_t i, size_t j) const {
    return arma::accu(arma::abs(data.col(i) - data.col(j)));
}

/**
 * \brief L_INFINITY loss
 *
 * Calculates the Manhattan loss between the datapoints at index i and j of the
 * dataset
 *
 * @param data Transposed input data to find the medoids of
 * @param i Index of first datapoint
 * @param j Index of second datapoint
 */
double km::KMedoids::LINF(const arma::mat& data, size_t i, size_t j) const {
    return arma::max(arma::abs(data.col(i) - data.col(j)));
}


#ifndef CANNON_LOGIC_CNF_H
#define CANNON_LOGIC_CNF_H

/*!
 * \file cannon/logic/cnf.hpp
 * \brief File containing classes and utility functions for working with
 * Conjunctive Normal Form (CNF) formulas.
 */

#include <set>
#include <vector>
#include <iostream>
#include <valarray>
#include <algorithm>
#include <iostream>

#include <Eigen/Dense>

using namespace Eigen;

namespace cannon {
  namespace logic {

    /*!
     * \brief Enumeration representing the three possible states that can be
     * assigned to a proposition.
     */
    enum class PropAssignment {
      False,
      Unassigned,
      True
    };

    using Assignment=std::valarray<PropAssignment>;
    using Simplification=std::valarray<bool>;

    /*!
     * \brief Class representing a single literal in a CNF formula.
     */
    class Literal {
      public:
        Literal() = delete;

        /*!
         * \brief Constructor taking a proposition number for this literal and
         * whether it is negated.
         */
        Literal(unsigned int prop_num, bool negated) : prop_(prop_num), negated_(negated) {}

        /*!
         * \brief Copy constructor.
         */
        Literal(const Literal& l) : prop_(l.prop_), negated_(l.negated_) {}

        /*!
         * \brief Move constructor.
         */
        Literal(Literal&& l) : prop_(l.prop_), negated_(l.negated_) {}

        /*!
         * \brief Copy assignment operator.
         */
        Literal& operator=(const Literal &o) {
          prop_ = o.prop_;
          negated_ = o.negated_;
          return *this;
        }

        /*!
         * \brief Evaluate this literal with the input assignment. The
         * proposition number stored by this literal is taken as an index into
         * the assignment.
         *
         * \param assignment An assignment of values to propositions to
         * evaluate this literal on.
         *
         * \returns The value of this literal in the assignment.
         */
        PropAssignment eval(const Assignment& assignment) const;

        /*!
         * \brief Comparison operator for sorting literals.
         */
        bool operator<(const Literal& l) const {
          if (prop_ < l.prop_) {
            return true;
          } else if (prop_ > l.prop_) {
            return false;
          } else {
            return negated_ == l.negated_ ? false : negated_;
          }
        }

        friend std::ostream& operator<<(std::ostream& os, const Literal& l);

        friend class Clause;
        friend class CNFFormula;

        unsigned int prop_; //!< Proposition number for this literal
        bool negated_; //!< Whether this literal is negated
    };

    /*!
     * \brief Class representing a clause in a Conjunctive Normal Form (CNF)
     * formula, which is defined as a disjunction of literals.
     */
    class Clause {
      public:

        /*!
         * \brief Default constructor.
         */
        Clause() {}

        /*!
         * \brief Copy constructor.
         */
        Clause(const Clause& o) : literals_(o.literals_), num_props_(o.num_props_) {}

        /*!
         * \brief Move constructor.
         */
        Clause(Clause&& o) : literals_(std::move(o.literals_)), num_props_(o.num_props_) {}

        /*!
         * \brief Destructor.
         */
        ~Clause() {}

        /*!
         * \brief Copy assignment operator.
         */
        Clause& operator=(const Clause& o) {
          literals_ = o.literals_;
          num_props_ = o.num_props_;
          return *this;
        }

        /*!
         * \brief Add a literal to this clause.
         *
         * \param l The literal to add to this clause.
         */
        void add_literal(Literal l);

        /*!
         * \brief Construct and add a literal to this clause.
         *
         * \param prop_num Proposition number for the clause to add.
         * \param negated Whether the literal should be negated.
         */
        void add_literal(unsigned int prop_num, bool negated);

        /*!
         * \brief Get the number of literals in this clause.
         *
         * \returns Clause size.
         */
        unsigned int size() const;

        /*!
         * \brief Get the number of unassigned literals in this clause with
         * respect to the input assignment.
         *
         * \param a Assignment to use to compute size.
         *
         * \returns The number of unassigned literals in this clause.
         */
        unsigned int size(const Assignment& a) const;

        /*!
         * \brief Determine if this clause is a unit clause.
         *
         * \returns Whether this clause has only a single literal.
         */
        bool is_unit() const;

        /*!
         * \brief Determine if this clause is a unit clause with respect to the
         * input assignment.
         *
         * \param a Assignment to use to compute if this clause is a unit.
         *
         * \returns Whether this clause has only a single unassigned literal.
         */
        bool is_unit(const Assignment& a) const;

        /*!
         * \brief Get the propositions in this clause which are unassigned in
         * the input assignment.
         *
         * \param a Assignment to compute props with respect to.
         *
         * \returns Set of propositions in this clause.
         */
        std::set<unsigned int> get_props(const Assignment& a);

        /*!
         * \brief Determine whether this clause includes the input prop when
         * simplified according to the input assignment.
         *
         * \param a The assignment to check membership with respect to.
         * \param prop The prop to check for membership.
         *
         * \returns Whether this clause contains the query prop.
         */
        bool contains_prop(const Assignment& a, unsigned int prop) const;

        /*!
         * \brief Determine whether this clause includes an unnegated literal
         * of the input prop when simplified according to the input assignment.
         *
         * \param a The assignment to check membership with respect to.
         * \param prop The prop to check for membership.
         *
         * \returns Whether this clause contains the query prop.
         */
        bool has_pos_literal(const Assignment& a, unsigned int prop) const;

        /*!
         * \brief Evalute this clause on the input assignment. Clauses evaluate
         * to True if a single Literal in them evaluates to True, and otherwise
         * evalute to Unassigned if any Literal evalues to Unassigned. A Clause
         * evaluates to False iff all contained Literals evaluate to False.
         * Trivially, an empty Clause evaluates to False.
         *
         * \param a Assignment to evaluate this clause on.
         *
         * \returns Evaluation of this clause.
         */
        PropAssignment eval(const Assignment& assignment) const;

        /*!
         * \brief If this clause is a unit with respect to the input
         * assignment, get the corresponding proposition.
         *
         * \param a Assignment that this clause is a unit with respect to.
         *
         * \returns Proposition for unit clause.
         */
        unsigned int get_unit_prop(const Assignment& a);

        /*!
         * \brief If this clause is a unit with respect to the input
         * assignment, get whether it is negated.
         *
         * \param a Assignment that this clause is a unit with respect to.
         *
         * \returns Whether the unit clause is negated.
         */
        bool get_unit_negated(const Assignment& a);

        /*!
         * \brief Get the assignment for the input proposition which causes
         * this clause to evaluate to True.
         *
         * \param prop Proposition to get assignment for.
         *
         * \returns Assignment for input prop that causes this clause to
         * evaluate to True, or Unassigned if the proposition does not appear
         * in this clause.
         */
        PropAssignment get_assignment_for_literal(unsigned int prop);

        friend std::ostream& operator<<(std::ostream& os, const Clause& c);

        friend class CNFFormula;

        bool operator==(const Clause& c) const {
          if (c.literals_.size() == literals_.size()) {
            for (auto& l : literals_) {
              if (c.literals_.find(l) == c.literals_.end()) {
                return false;
              }
            }

            return true;
          } else {
            return false;
          }
        }

        std::set<Literal> literals_; //!< Literals in this clause

      private:
        unsigned int num_props_ = 0; //!< Number of propositions implied by proposition numbers stored in this Clause
    };

    /*!
     * \brief Class representing a Conjunctive Normal Form formula, which is a
     * conjunction of Clauses.
     */
    class CNFFormula {
      public:

        /*!
         * \brief Add a Clause to this formula.
         *
         * \param c Clause to add. This argument is an rval to avoid copying Literal set.
         */
        void add_clause(Clause&& c);

        /*!
         * \brief Construct and add a unit clause to this formula.
         *
         * \param prop Proposition for the unit clause.
         * \param negated Whether the proposition should be negated.
         */
        void add_unit_clause(unsigned int prop, bool negated);

        /*!
         * \brief Evaluate this formula on the input assignment and
         * simplification. Clauses are simplified when their truth can be
         * deduced from a partial assignment, and so further proposition
         * assignments cannot change their truth value.
         *
         * \param assignment Assignment to evaluate this formula on.
         * \param s Simplification to accelerate evaluation.
         */
        PropAssignment eval(const Assignment& assignment,
            const Simplification& s) const;

        /*!
         * \brief Get propositions occuring in unit clauses in this formula.
         *
         * \param a Assignment to find unit clauses with respect to.
         * \param s Simplification to accelerate unit clause finding.
         *
         * \returns Vector of tuples containing propositions that occur in unit
         * clauses, whether they are negated, and indices of the clauses in which
         * they occur.
         */
        std::vector<std::tuple<unsigned int, bool, int>> get_unit_clause_props(const
            Assignment& a, const Simplification& s) const;

        /*!
         * \brief Get propositions occuring in unit clauses in this formula.
         *
         * \param a Assignment to find unit clauses with respect to.
         * \param s Simplification to accelerate unit clause finding.
         * \param watched Watched list for all propositions used to speed up search.
         *
         * \returns Vector of tuples containing propositions that occur in unit
         * clauses, whether they are negated, and indices of the clauses in which
         * they occur.
         */
        std::vector<std::tuple<unsigned int, bool, int>> get_unit_clause_props(const
            Assignment& a, const Simplification& s,
            std::vector<std::vector<unsigned int>> watched) const;

        /*!
         * \brief Get propositions occuring in unit clauses in this formula.
         *
         * \param a Assignment to find unit clauses with respect to.
         * \param s Simplification to accelerate unit clause finding.
         * \param unit_clauses Candidate clauses to check for being units.
         *
         * \returns Vector of tuples containing propositions that occur in unit
         * clauses, whether they are negated, and indices of the clauses in which
         * they occur.
         */
        std::vector<std::tuple<unsigned int, bool, int>> get_unit_clause_props(const
            Assignment& a, const Simplification& s, std::vector<unsigned int>
            unit_clauses) const;

        /*!
         * \brief Get the size of the smallest clause in this Formula.
         *
         * \param a Assignment to compute clause sizes with respect to.
         * \param s Simplification to speed up checking.
         *
         * \returns Smallest clause size.
         */
        unsigned int get_smallest_clause_size(const Assignment& a, const Simplification& s) const;

        /*!
         * \brief Get the number of propositions in this formula.
         *
         * \returns Number of propositions.
         */
        unsigned int get_num_props() const;

        /*!
         * \brief Get the number of clauses in this formula.
         *
         * \returns The number of clauses.
         */
        unsigned int get_num_clauses() const;

        /*!
         * \brief Get the number of clauses containing the input proposition
         * and one other literal in this formula.
         *
         * \param a Assignment to compute clauses sizes with respect to.
         * \param s Simplification to speed counting.
         * \param prop Proposition to compute two-clauses for.
         *
         * \returns Number of two-clauses in this formula for the input proposition.
         */
        unsigned int get_num_two_clauses(const Assignment& a, const
            Simplification& s, unsigned int prop) const;

        /*!
         * \brief Get the number of two-clauses for each proposition in the input.
         *
         * \param a Assignment to compute clauses sizes with respect to.
         * \param s Simplification to speed counting.
         * \param props Propositions to compute two-clauses for.
         *
         * \returns Number of two-clauses in this formula for each input proposition.
         */
        std::vector<unsigned int> get_num_two_clauses(const Assignment& a, const
            Simplification& s, const std::vector<unsigned int>& props) const;

        /*!
         * \brief Get all unassigned propositions in this formula.
         *
         * \param a Assignment to compute unassigned propositions with respect to.
         * \param s Simplification to speed up search.
         *
         * \returns Unassigned propositions.
         */
        std::vector<unsigned int> get_props(const Assignment& a,
            const Simplification& s);

        /*!
         * \brief Make adjacency matrix for this formula. Two propositions are
         * adjacent if they occur in the same clause.
         *
         * \param a Assignment to compute adjacency matrix with respect to.
         * \param s Simplification to speed up construction.
         *
         * \returns Adjacency matrix
         */
        MatrixXd make_adjacency_mat(const Assignment& a, const Simplification& s) const;

        /*!
         * \brief Get multiset of all unassigned propositions in this formula.
         *
         * \param a Assignment to compute unassigned propositions with respect to.
         * \param s Simplification to speed up search.
         *
         * \returns Unassigned propositions.
         */
        std::multiset<unsigned int> get_props_multiset(const Assignment& a,
            const Simplification& s);

        /*!
         * \brief Check whether the input proposition is a pure literal in this
         * formula. A pure literal is one that appears only as a positive
         * proposition or negated proposition in the formula.
         *
         * \param a Assignment to compute whether the input prop is a pure
         * literal with respect to.
         * \param s Simplification to speed up search.
         * \param prop Proposition to check.
         *
         * \returns Unassigned if the input prop is not a pure literal in this
         * formula, or else the assignment (True or False) which makes literals
         * containing the input prop evaluate true.
         */
        PropAssignment is_pure_literal(const Assignment& a, const Simplification& s,
            unsigned int prop);

        /*!
         * \brief Generate a simplification for this formula.
         *
         * \param a Assignment to generate a simplification with respect to.
         * \param s Previous simplification to speed up generation.
         *
         * \returns A new simplification skipping clauses which evaluate to true.
         */
        Simplification simplify(const Assignment& a,
            const Simplification& s) const;

        /*!
         * \brief Merge the input formula into this formula.
         *
         * \param f Formula to merge.
         */
        void merge(CNFFormula&& f);

        bool operator==(const CNFFormula f) const {
          if (f.clauses_.size() == clauses_.size()) {
            for (auto& c : clauses_) {
              if (std::find(f.clauses_.begin(), f.clauses_.end(), c) == f.clauses_.end()) {
                return false;
              }
            }

            return true;
          } else {
            return false;
          }
        }

        friend std::ostream& operator<<(std::ostream& os, const CNFFormula& f);

        std::vector<Clause> clauses_; //!< Clauses in this formula

      private:
        unsigned int num_props_ = 0; //!< Number of propositions in this formula
    };

    /*!
     * \brief Generate a random 3-clause using propositions 0 to num_props-1.
     *
     * \param num_props Maximum number of propositions to choose from.
     *
     * \returns Generated clause.
     */
    Clause generate_random_clause(unsigned int num_props);

    /*!
     * \brief Generate a random 3-CNF formula.
     *
     * \param num_props Maximum number of propositions to choose from.
     * \param num_clauses Number of clauses in random formula
     *
     * \returns Generated formula.
     */
    CNFFormula generate_random_formula(unsigned int num_props, unsigned int num_clauses);

    /*!
     * \brief Attempt to resolve two CNF clauses on the input proposition.
     * Throws an exception if the two clauses cannot be resolved on the input
     * prop (i.e., they do not both contain the proposition or the literals
     * containing the proposition don't have opposite negation).
     *
     * \param c1 First clause to resolve, which will be modified to contain the resolved clause
     * \param c2 Second clause to use in resolution.
     * \param prop Proposition to resolve on.
     */
    void resolve(Clause& c1, const Clause& c2, unsigned int prop);

    std::ostream& operator<<(std::ostream& os, PropAssignment a);
    std::ostream& operator<<(std::ostream& os, const Literal& l);
    std::ostream& operator<<(std::ostream& os, const Clause& c);
    std::ostream& operator<<(std::ostream& os, const CNFFormula& f);

    std::ostream& operator<<(std::ostream& os, const std::valarray<PropAssignment>& v);
    bool operator==(const std::valarray<PropAssignment>& v1, const std::valarray<PropAssignment>& v2);
    bool operator!=(const std::valarray<PropAssignment>& v1, const std::valarray<PropAssignment>& v2);

  } // namespace logic
} // namespace cannon

#endif /* ifndef CANNON_LOGIC_CNF_H */


/******************************************************************************
* Turbo C++11 metaprogramming Library                                         *
*                                                                             *
* Copyright (C) 2013 - 2014, Manuel Sánchez Pérez                             *
*                                                                             *
* This file is part of The Turbo Library.                                     *
*                                                                             *
* The Turbo Library is free software: you can redistribute it and/or modify   *
* it under the terms of the GNU Lesser General Public License as published by *
* the Free Software Foundation, version 2 of the License.                     *
*                                                                             *
* The Turbo Library 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 Lesser General Public License for more details.                         *
*                                                                             *
* You should have received a copy of the GNU Lesser General Public License    *
* along with The Turbo Library. If not, see <http://www.gnu.org/licenses/>.   *
******************************************************************************/

#ifndef EVAL_HPP
#define	EVAL_HPP

#include <type_traits>

#include "list.hpp"
#include "enable_if.hpp"
#include "function.hpp"
#include "chameleon.hpp"

#include <boost/preprocessor.hpp>

#define TURBO_EVAL_ARGS_APPLY_NO_ARGS(...) >
#define TURBO_EVAL_ARGS_APPLY_ARGS(...) ,__VA_ARGS__>

#define TURBO_EVAL_ARGS_APPLY(...) BOOST_PP_IIF( \
    BOOST_PP_GREATER(BOOST_PP_VARIADIC_SIZE(__VA_ARGS__), 1), \
        TURBO_EVAL_ARGS_APPLY_ARGS, TURBO_EVAL_ARGS_APPLY_NO_ARGS)

#define TURBO_EVAL_ARGS_APPLY_CALLER(...) TURBO_EVAL_ARGS_APPLY(__,__VA_ARGS__)(__VA_ARGS__)

#define $(...) tml::eval<__VA_ARGS__>

namespace tml
{

    /*
     * This metafunction says if one type of expression has overrided the default
     * behaviour of tml::eval. Every user-defined type which wants to override
     * tml::eval should be registered specializing this metafunction inheriting
     * from tml::true_type.
     */
    template<typename E>
    struct overrides_eval : public tml::false_type
    {};

    namespace impl
    {
        /*
         * Here we implement the user-side tml::eval<> metafunction.
         *
         * The purpose of that metafunction is to evaluate any kind of expression.
         * That serves to evaluate functions and other functional expressions.
         */

         template<typename... Args>
         struct args_list {};

         using no_args = args_list<>;

        /*
         * The implementation has three parameters:
         *
         *  - E: The expression to be evaluated.
         *
         *  - ARGS: evaluate could be used as a high-order metafunction to evaluate a given
         *             function entity with the specified parameters. This variadic pack is that
         *             set of parameters. The result of the evaluation is the result of evaluating
         *             the functional expresion E with the specified ARGS... arguments.
         *             Note that in this case the argumments are evaluated too (Just for the case they are
         *             functional expressions).
         *
         *  - SFINAE_FALGS: Parameters used to enable/disable certain specializations.
         *
         *  Of course this metafunction is a function too, so it stores the result of the evaluation in a 'result' member type.
         */
        template<typename E , typename ARGS , typename SFINAE_FLAGS = tml::sfinae_return>
        struct eval
        {
            using result = E;
        };

        /*
         * Non-decayed function pointer types (i.e. R(ARGS...) ) are used as a shorthand for a metafunction call F(ARGS...).
         * So they should be discarded to not match the simple expression tml::eval specialization.
         */
        template<typename T>
        struct is_function_ptr_type : public tml::false_type
        {};

        template<typename F , typename... ARGS>
        struct is_function_ptr_type<F(ARGS...)> : public tml::true_type
        {};

        /* This is the most simple case: There are no evaluation parameters (So the expression could be any
         * kind of expression, not just a function) BUT the flag says the expression is not a function.
         * The result of evaluating such expression is the expression itself.
         */
        template<typename E>
        struct eval<E,no_args,
                    TURBO_SFINAE_ALL(
                                     DISABLE_IF(tml::overrides_eval<E>),
                                     DISABLE_IF(tml::is_function<E>),
                                     DISABLE_IF(is_function_ptr_type<E>),
                                     DISABLE_IF(tml::is_aggregate<E>),
                                     DISABLE_IF(tml::is_metafunction_class<E>)
                                    )
                   >
        {
            using result = E;
        };

        template<typename E, typename Args, typename = void>
        struct expand
        {
            using result = E;
        };

        template<typename E, typename Args>
        struct expand<E,Args,TURBO_ENABLE_IF(tml::is_expandible<E>)>
        {
            using result = typename eval<E,Args>::result;
        };

        template<typename E>
        struct eval<E,no_args,
                TURBO_SFINAE_ALL(
                        DISABLE_IF(tml::overrides_eval<E>),
                        ENABLE_IF(tml::is_function<E>),
                        DISABLE_IF(is_function_ptr_type<E>),
                        DISABLE_IF(tml::is_aggregate<E>),
                        DISABLE_IF(tml::is_metafunction_class<E>)
                )
        >
        {
            template<typename T, bool is_stl_function = tml::is_stl_function<T>::value>
            struct call
            {
                using result = typename T::type;
            };

            template<typename T>
            struct call<T, false>
            {
                using result = typename T::result;
            };

            using result = typename call<E>::result;
        };

        template<template<typename...> class F , typename... ARGS>
        struct eval<F<ARGS...>,no_args,
                    TURBO_SFINAE_ALL(DISABLE_IF(tml::overrides_eval<F<ARGS...>>),
                                     ENABLE_IF(tml::is_aggregate<F<ARGS...>>),
                                     ENABLE_IF(tml::is_turbo_function<F<ARGS...>>),
                                     DISABLE_IF(tml::is_metafunction_class<F<ARGS...>>)
                                    )
                   >
        {
            using f = F<typename expand<ARGS,no_args>::result...>;

            template<typename T, bool is_stl_function = tml::is_stl_function<f>::value>
            struct call
            {
                using result = typename T::type;
            };

            template<typename T>
            struct call<T, false>
            {
                using result = typename T::result;
            };

            using result = typename call<f>::result;
        };

        /*
         * This specialization matches the case when the expression passed is a parametrized
         * expression (But not a function).
         *
         * The parameters are evaluated too (Could be functional/parametrized expressions too) to evaluate the entire
         * expression recursively.
         */
        template<template<typename...> class E , typename... ARGS>
        struct eval<E<ARGS...>,no_args,
                    TURBO_SFINAE_ALL(
                                     DISABLE_IF(tml::overrides_eval<E<ARGS...>>),
                                     DISABLE_IF(tml::is_function<E<ARGS...>>),
                                     ENABLE_IF(tml::is_aggregate<E<ARGS...>>),
                                     DISABLE_IF(tml::is_metafunction_class<E<ARGS...>>)
                                    )
                   > :
                   public tml::function<E<typename expand<ARGS,no_args>::result...>>
        {};

        /*
         * This is the case when the expression passed is a function, and a set of parameters (At least one) is
         * passed to evaluate the function with.
         *
         * Note that the parameters of the function call are evaluated too.
         *
         * The result is the result of evaluating the function with that parameters.
         */
        template<template<typename...> class F , typename... PLACEHOLDERS , typename ARG , typename... ARGS>
        struct eval<F<PLACEHOLDERS...> , args_list<ARG,ARGS...>,
                    TURBO_SFINAE_ALL(
                                     DISABLE_IF(tml::overrides_eval<F<PLACEHOLDERS...>>),
                                     ENABLE_IF(tml::is_function<F<PLACEHOLDERS...>>),
                                     DISABLE_IF(tml::is_metafunction_class<F<PLACEHOLDERS...>>)
                                    )
                   > :
                   public F<typename expand<ARG,no_args>::result,
                            typename expand<ARGS,no_args>::result...
                           >
        {

        };

        /*
         * This is the case when the expression passed is a parametrized expression (And not a function),
         * and a set of parameters (At least one) is passed to evaluate the expression with.
         *
         * Note that the parameters of the function call are evaluated too.
         */
        template<template<typename...> class E , typename... PLACEHOLDERS , typename ARG , typename... ARGS>
        struct eval<E<PLACEHOLDERS...> , args_list<ARG,ARGS...>,
                    TURBO_SFINAE_ALL(
                                     DISABLE_IF(tml::overrides_eval<E<PLACEHOLDERS...>>),
                                     DISABLE_IF(tml::is_function<E<PLACEHOLDERS...>>),
                                     DISABLE_IF(tml::is_metafunction_class<E<PLACEHOLDERS...>>)
                                    )
                   > :
                   public tml::function<E<typename expand<ARG,no_args>::result,
                                          typename expand<ARGS,no_args>::result...
                                         >
                                       >
        {
            //static_assert( sizeof...(PLACEHOLDERS) == (1 + sizeof...(ARGS)) , "Wrong number of function call parameters." );
        };

        template<typename F, typename... ARGS>
        struct eval<F, args_list<ARGS...>,
                    TURBO_SFINAE_ALL(ENABLE_IF(tml::is_metafunction_class<F>))
                   >
        {
            //static_assert(sizeof(F) != sizeof(F), "compiler bug!");

            using apply = tml::impl::get_apply<F, typename expand<ARGS,no_args>::result...>;

            template<typename T, bool is_stl_function = tml::is_stl_function<T>::value>
            struct call
            {
                using result = typename T::type;
            };

            template<typename T>
            struct call<T, false>
            {
                using result = typename T::result;
            };

            using result = typename call<apply>::result;
        };

        /*
         * Alternative call-like syntax
         */
        template<typename F , typename... ARGS>
        struct eval<F(ARGS...),no_args,
                    TURBO_SFINAE_ALL(
                                     ENABLE_IF( tml::true_type )
                                    )
                   >:
                   public tml::impl::eval<F,args_list<ARGS...>>
        {};

        template<typename F, typename G, typename... Args>
        struct eval<F(*)(G), args_list<Args...>>
        {
            using result = typename eval<F,args_list<typename eval<G,args_list<Args...>>::result>>::result;
        };
    }

    /*
     * Metafunction to evaluate expressions.
     *
     * The purpose of this metafunction is to evaluate homogeneously any kind of expression.
     * Also, this could be used as a high-order metafunction to reevaluate an specified functional
     * expression with a custom set of parameters.
     *
     * Note that the parameters of the expression will be ignored during reevaluation.
     * If your intention is to pass the expression to a high order metafunction,
     * you have to fill that parameters even if they will not be used during evaluation.
     * The set of placeholders defined in "placeholders.hpp" could be used for that purpose.
     * Also the template wrapper 'tml::lazy' defined in "lazy.hpp" could be used to pass a template
     * without parameters and pass that parameters later when the evaluation is done.
     *
     * The metafunction has the following parameters:
     *  - E: The expression to be evaluated.
     *
     *  - ARGS...: eval could be used as a high-order metafunction to reevaluate a given
     *             function entity with the specified parameters. This variadic pack is that
     *             set of parameters. The result of the evaluation is the result of evaluating
     *             the functional expresion E with the specified ARGS... arguments.
     *             Note that in this case the argumments are evaluated too (Just for the case they are
     *             functional/parametrized expressions).
     */
    template<typename EXPRESSION , typename... ARGS>
    using eval = typename impl::eval<EXPRESSION , impl::args_list<ARGS...>>::result;

    /*
     * Provides delayed evaluation of an expression until its placeholders are substituted by its values.
     * Is a special case for lambda bodies expressions.
     *
     * Lambda bodies with evaluating expressions such as 'tml::eval<F,_1>' doesn't work because the placeholder is substituted after the
     * expression evaluation.
     *
     * The library provides the template tml::delayed_eval<F,ARGS...> for that purpose: It holds a functional expression reevaluation
     * with parameters that may be placeholders. When doing let on such template, tml::let substitutes the letted placeholders with its
     * values, and the tml::delayed_eval template with tml::eval.
     *
     * NOTE: See the documentation of the specialization of tml::let in "let_expressions.hpp" for more info.
     */
    template<typename... ARGS>
    struct delayed_eval : public tml::value_chameleon
    {
    };

    template<typename... ARGS>
    struct overrides_eval<tml::delayed_eval<ARGS...>> : public tml::true_type
    {};

    namespace impl
    {
        template<typename E, typename... ARGS>
        struct eval<tml::delayed_eval<E,ARGS...>, no_args> :
            public tml::function<tml::eval<E,ARGS...>>
        {};
    }

    /*
     * Simple shorthand alias:
     */
    template<typename... ARGS>
    using deval = tml::delayed_eval<ARGS...>;
}

#endif	/* EVAL_HPP */