///////////////////////////////////////////////////////////////////////////////
//  Copyright 2018 John Maddock. Distributed under the Boost
//  Software License, Version 1.0. (See accompanying file
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_MP_EIGEN_HPP
#define BOOST_MP_EIGEN_HPP

#include <nil/crypto3/multiprecision/number.hpp>

#include <Eigen/Core>

//
// Generic Eigen support code:
//
namespace Eigen {
    template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates>
    struct NumTraits<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>> {
        using self_type = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;
        using Real = typename nil::crypto3::multiprecision::scalar_result_from_possible_complex<self_type>::type;
        using NonInteger = self_type;    // Not correct but we can't do much better??
        using Literal = double;
        using Nested = self_type;
        enum {
            IsComplex = nil::crypto3::multiprecision::number_category<self_type>::value ==
                        nil::crypto3::multiprecision::number_kind_complex,
            IsInteger = nil::crypto3::multiprecision::number_category<self_type>::value ==
                        nil::crypto3::multiprecision::number_kind_integer,
            ReadCost = 1,
            AddCost = 4,
            MulCost = 8,
            IsSigned =
                std::numeric_limits<self_type>::is_specialized ? std::numeric_limits<self_type>::is_signed : true,
            RequireInitialization = 1,
        };
        static Real epsilon() {
            return std::numeric_limits<Real>::epsilon();
        }
        static Real dummy_precision() {
            return 1000 * epsilon();
        }
        static Real highest() {
            return (std::numeric_limits<Real>::max)();
        }
        static Real lowest() {
            return (std::numeric_limits<Real>::min)();
        }
        static int digits10_imp(const std::integral_constant<bool, true>&) {
            return std::numeric_limits<Real>::digits10;
        }
        template<bool B>
        static int digits10_imp(const std::integral_constant<bool, B>&) {
            return Real::default_precision();
        }
        static int digits10() {
            return digits10_imp(
                std::integral_constant < bool,
                std::numeric_limits<Real>::digits10 && (std::numeric_limits<Real>::digits10 != INT_MAX) ? true :
                                                                                false > ());
        }
    };
    template<class tag, class Arg1, class Arg2, class Arg3, class Arg4>
    struct NumTraits<nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>>
        : public NumTraits<
              typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type> { };

#define BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(A)                                                                        \
    template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates,           \
             typename BinaryOp>                                                                                \
    struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, A, BinaryOp> {  \
        /*static_assert(nil::crypto3::multiprecision::is_compatible_arithmetic_type<A,                              \
         * nil::crypto3::multiprecision::number<Backend, ExpressionTemplates> >::value, "Interoperability with this \
         * arithmetic type is not supported.");*/                                                                   \
        using ReturnType = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;                      \
    };                                                                                \
    template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates,           \
             typename BinaryOp>                                                                                \
    struct ScalarBinaryOpTraits<A, nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, BinaryOp> {  \
        /*static_assert(nil::crypto3::multiprecision::is_compatible_arithmetic_type<A,                              \
         * nil::crypto3::multiprecision::number<Backend, ExpressionTemplates> >::value, "Interoperability with this \
         * arithmetic type is not supported.");*/                                                                   \
        using ReturnType = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;                      \
    };

    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(float)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(double)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(long double)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(char)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(unsigned char)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(signed char)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(short)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(unsigned short)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(int)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(unsigned int)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(long)
    BOOST_MP_EIGEN_SCALAR_TRAITS_DECL(unsigned long)

#if 0
      template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates, class Backend2, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates2, typename BinaryOp>
   struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, nil::crypto3::multiprecision::number<Backend2, ExpressionTemplates2>, BinaryOp>
   {
      static_assert(
         nil::crypto3::multiprecision::is_compatible_arithmetic_type<nil::crypto3::multiprecision::number<Backend2, ExpressionTemplates2>, nil::crypto3::multiprecision::number<Backend, ExpressionTemplates> >::value
         || nil::crypto3::multiprecision::is_compatible_arithmetic_type<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, nil::crypto3::multiprecision::number<Backend2, ExpressionTemplates2> >::value, "Interoperability with this arithmetic type is not supported.");
      using ReturnType = typename std::conditional<std::is_convertible<nil::crypto3::multiprecision::number<Backend2, ExpressionTemplates2>, nil::crypto3::multiprecision::number<Backend, ExpressionTemplates> >::value,
         nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, nil::crypto3::multiprecision::number<Backend2, ExpressionTemplates2> >::type;
   };

   template<unsigned D, typename BinaryOp>
   struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::backends::mpc_complex_backend<D>, nil::crypto3::multiprecision::et_on>, nil::crypto3::multiprecision::mpfr_float, BinaryOp>
   {
      using ReturnType = nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::backends::mpc_complex_backend<D>, nil::crypto3::multiprecision::et_on>;
   };

   template<typename BinaryOp>
   struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::mpfr_float, nil::crypto3::multiprecision::mpc_complex, BinaryOp>
   {
      using ReturnType = nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::backends::mpc_complex_backend<0>, nil::crypto3::multiprecision::et_on>;
   };

   template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates, typename BinaryOp>
   struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, BinaryOp>
   {
      using ReturnType = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;
   };
#endif

    template<class Backend, nil::crypto3::multiprecision::expression_template_option ExpressionTemplates, class tag,
             class Arg1, class Arg2, class Arg3, class Arg4, typename BinaryOp>
    struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>,
                                nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>,
                                BinaryOp> {
        static_assert(
            std::is_convertible<
                typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type,
                nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>>::value,
            "Interoperability with this arithmetic type is not supported.");
        using ReturnType = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;
    };

    template<class tag, class Arg1, class Arg2, class Arg3, class Arg4, class Backend,
             nil::crypto3::multiprecision::expression_template_option ExpressionTemplates, typename BinaryOp>
    struct ScalarBinaryOpTraits<nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>,
                                nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>, BinaryOp> {
        static_assert(
            std::is_convertible<
                typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type,
                nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>>::value,
            "Interoperability with this arithmetic type is not supported.");
        using ReturnType = nil::crypto3::multiprecision::number<Backend, ExpressionTemplates>;
    };

    namespace internal {
        template<typename Scalar>
        struct conj_retval;

        template<typename Scalar, bool IsComplex>
        struct conj_impl;

        template<class tag, class Arg1, class Arg2, class Arg3, class Arg4>
        struct conj_retval<nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>> {
            using type =
                typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type;
        };

        template<class tag, class Arg1, class Arg2, class Arg3, class Arg4>
        struct conj_impl<nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>, true> {
            EIGEN_DEVICE_FUNC
            static inline
                typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>::result_type
                run(const typename nil::crypto3::multiprecision::detail::expression<tag, Arg1, Arg2, Arg3, Arg4>& x) {
                return conj(x);
            }
        };

    }    // namespace internal

}    // namespace Eigen

#endif


#include <boost/assert.hpp>
#include "cblas.h"
#include "blas-wrapper.h"

namespace argos {
    namespace blas {
        template <>
        void gemm (float const *A, size_t A_rows, size_t A_cols, bool transA,
                   float const *B, size_t B_rows, size_t B_cols, bool transB,
                   float *C, size_t C_rows, size_t C_cols, float alpha, float beta) {
            size_t M, N, K;
            enum CBLAS_TRANSPOSE TA, TB;
            if (transA) {
                M = A_cols;
                K = A_rows;
                TA = CblasTrans;
            } else {
                M = A_rows;
                K = A_cols;
                TA = CblasNoTrans;
            }

            if (transB) {
                N = B_rows;
                BOOST_VERIFY(K == B_cols);
                TB = CblasTrans;
            } else {
                N = B_cols;
                BOOST_VERIFY(K == B_rows);
                TB = CblasNoTrans;
            }
            BOOST_VERIFY(M == C_rows);
            BOOST_VERIFY(N == C_cols);
            cblas_sgemm(CblasRowMajor, TA, TB, M, N, K, alpha, A, A_cols, B, B_cols, beta, C, C_cols);
        }

        template <>
        void gemm (double const *A, size_t A_rows, size_t A_cols, bool transA,
                   double const *B, size_t B_rows, size_t B_cols, bool transB,
                   double *C, size_t C_rows, size_t C_cols, double alpha, double beta) {
            size_t M, N, K;
            enum CBLAS_TRANSPOSE TA, TB;
            if (transA) {
                M = A_cols;
                K = A_rows;
                TA = CblasTrans;
            } else {
                M = A_rows;
                K = A_cols;
                TA = CblasNoTrans;
            }

            if (transB) {
                N = B_rows;
                BOOST_VERIFY(K == B_cols);
                TB = CblasTrans;
            } else {
                N = B_cols;
                BOOST_VERIFY(K == B_rows);
                TB = CblasNoTrans;
            }
            BOOST_VERIFY(M == C_rows);
            BOOST_VERIFY(N == C_cols);
            cblas_dgemm(CblasRowMajor, TA, TB, M, N, K, alpha, A, A_cols, B, B_cols, beta, C, C_cols);
        }
    }
}


// Copyright 2018 The Simons Foundation, Inc. - All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include <Eigen/Dense>
#include <iostream>
#include <vector>
#include "Lookup/lookup.hpp"
#include "Utils/all_utils.hpp"

#ifndef NETKET_RBM_SPIN_HPP
#define NETKET_RBM_SPIN_HPP

namespace netket {

/** Restricted Boltzmann machine class with spin 1/2 hidden units.
 *
 */
template <typename T>
class RbmSpin : public AbstractMachine<T> {
  using VectorType = typename AbstractMachine<T>::VectorType;
  using MatrixType = typename AbstractMachine<T>::MatrixType;

  // number of visible units
  int nv_;

  // number of hidden units
  int nh_;

  // number of parameters
  int npar_;

  // weights
  MatrixType W_;

  // visible units bias
  VectorType a_;

  // hidden units bias
  VectorType b_;

  VectorType thetas_;
  VectorType lnthetas_;
  VectorType thetasnew_;
  VectorType lnthetasnew_;

  bool usea_;
  bool useb_;

  int mynode_;

  const Hilbert &hilbert_;

 public:
  using StateType = typename AbstractMachine<T>::StateType;
  using LookupType = typename AbstractMachine<T>::LookupType;

  // constructor
  explicit RbmSpin(const Hilbert &hilbert, const json &pars)
      : nv_(hilbert.Size()), hilbert_(hilbert) {
    from_json(pars);
  }

  void Init() {
    W_.resize(nv_, nh_);
    a_.resize(nv_);
    b_.resize(nh_);

    thetas_.resize(nh_);
    lnthetas_.resize(nh_);
    thetasnew_.resize(nh_);
    lnthetasnew_.resize(nh_);

    npar_ = nv_ * nh_;

    if (usea_) {
      npar_ += nv_;
    } else {
      a_.setZero();
    }

    if (useb_) {
      npar_ += nh_;
    } else {
      b_.setZero();
    }

    MPI_Comm_rank(MPI_COMM_WORLD, &mynode_);

    if (mynode_ == 0) {
      std::cout << "# RBM Initizialized with nvisible = " << nv_
                << " and nhidden = " << nh_ << std::endl;
      std::cout << "# Using visible bias = " << usea_ << std::endl;
      std::cout << "# Using hidden bias  = " << useb_ << std::endl;
    }
  }

  int Nvisible() const override { return nv_; }

  int Nhidden() const { return nh_; }

  int Npar() const override { return npar_; }

  void InitRandomPars(int seed, double sigma) override {
    VectorType par(npar_);

    netket::RandomGaussian(par, seed, sigma);

    SetParameters(par);
  }

  void InitLookup(const Eigen::VectorXd &v, LookupType &lt) override {
    if (lt.VectorSize() == 0) {
      lt.AddVector(b_.size());
    }
    if (lt.V(0).size() != b_.size()) {
      lt.V(0).resize(b_.size());
    }

    lt.V(0) = (W_.transpose() * v + b_);
  }

  void UpdateLookup(const Eigen::VectorXd &v, const std::vector<int> &tochange,
                    const std::vector<double> &newconf,
                    LookupType &lt) override {
    if (tochange.size() != 0) {
      for (std::size_t s = 0; s < tochange.size(); s++) {
        const int sf = tochange[s];
        lt.V(0) += W_.row(sf) * (newconf[s] - v(sf));
      }
    }
  }

  VectorType DerLog(const Eigen::VectorXd &v) override {
    VectorType der(npar_);

    int k = 0;

    if (usea_) {
      for (; k < nv_; k++) {
        der(k) = v(k);
      }
    }

    RbmSpin::tanh(W_.transpose() * v + b_, lnthetas_);

    if (useb_) {
      for (int p = 0; p < nh_; p++) {
        der(k) = lnthetas_(p);
        k++;
      }
    }

    for (int i = 0; i < nv_; i++) {
      for (int j = 0; j < nh_; j++) {
        der(k) = lnthetas_(j) * v(i);
        k++;
      }
    }
    return der;
  }

  VectorType GetParameters() override {
    VectorType pars(npar_);

    int k = 0;

    if (usea_) {
      for (; k < nv_; k++) {
        pars(k) = a_(k);
      }
    }

    if (useb_) {
      for (int p = 0; p < nh_; p++) {
        pars(k) = b_(p);
        k++;
      }
    }

    for (int i = 0; i < nv_; i++) {
      for (int j = 0; j < nh_; j++) {
        pars(k) = W_(i, j);
        k++;
      }
    }

    return pars;
  }

  void SetParameters(const VectorType &pars) override {
    int k = 0;

    if (usea_) {
      for (; k < nv_; k++) {
        a_(k) = pars(k);
      }
    }

    if (useb_) {
      for (int p = 0; p < nh_; p++) {
        b_(p) = pars(k);
        k++;
      }
    }

    for (int i = 0; i < nv_; i++) {
      for (int j = 0; j < nh_; j++) {
        W_(i, j) = pars(k);
        k++;
      }
    }
  }

  // Value of the logarithm of the wave-function
  T LogVal(const Eigen::VectorXd &v) override {
    RbmSpin::lncosh(W_.transpose() * v + b_, lnthetas_);

    return (v.dot(a_) + lnthetas_.sum());
  }

  // Value of the logarithm of the wave-function
  // using pre-computed look-up tables for efficiency
  T LogVal(const Eigen::VectorXd &v, LookupType &lt) override {
    RbmSpin::lncosh(lt.V(0), lnthetas_);

    return (v.dot(a_) + lnthetas_.sum());
  }

  // Difference between logarithms of values, when one or more visible variables
  // are being flipped
  VectorType LogValDiff(
      const Eigen::VectorXd &v, const std::vector<std::vector<int>> &tochange,
      const std::vector<std::vector<double>> &newconf) override {
    const std::size_t nconn = tochange.size();
    VectorType logvaldiffs = VectorType::Zero(nconn);

    thetas_ = (W_.transpose() * v + b_);
    RbmSpin::lncosh(thetas_, lnthetas_);

    T logtsum = lnthetas_.sum();

    for (std::size_t k = 0; k < nconn; k++) {
      if (tochange[k].size() != 0) {
        thetasnew_ = thetas_;

        for (std::size_t s = 0; s < tochange[k].size(); s++) {
          const int sf = tochange[k][s];

          logvaldiffs(k) += a_(sf) * (newconf[k][s] - v(sf));

          thetasnew_ += W_.row(sf) * (newconf[k][s] - v(sf));
        }

        RbmSpin::lncosh(thetasnew_, lnthetasnew_);
        logvaldiffs(k) += lnthetasnew_.sum() - logtsum;
      }
    }
    return logvaldiffs;
  }

  // Difference between logarithms of values, when one or more visible variables
  // are being flipped Version using pre-computed look-up tables for efficiency
  // on a small number of spin flips
  T LogValDiff(const Eigen::VectorXd &v, const std::vector<int> &tochange,
               const std::vector<double> &newconf,
               const LookupType &lt) override {
    T logvaldiff = 0.;

    if (tochange.size() != 0) {
      RbmSpin::lncosh(lt.V(0), lnthetas_);

      thetasnew_ = lt.V(0);

      for (std::size_t s = 0; s < tochange.size(); s++) {
        const int sf = tochange[s];

        logvaldiff += a_(sf) * (newconf[s] - v(sf));

        thetasnew_ += W_.row(sf) * (newconf[s] - v(sf));
      }

      RbmSpin::lncosh(thetasnew_, lnthetasnew_);
      logvaldiff += (lnthetasnew_.sum() - lnthetas_.sum());
    }
    return logvaldiff;
  }

  inline static double lncosh(double x) {
    const double xp = std::abs(x);
    if (xp <= 12.) {
      return std::log(std::cosh(xp));
    } else {
      const static double log2v = std::log(2.);
      return xp - log2v;
    }
  }

  // ln(cos(x)) for std::complex argument
  // the modulus is computed by means of the previously defined function
  // for real argument
  inline static std::complex<double> lncosh(std::complex<double> x) {
    const double xr = x.real();
    const double xi = x.imag();

    std::complex<double> res = RbmSpin::lncosh(xr);
    res += std::log(
        std::complex<double>(std::cos(xi), std::tanh(xr) * std::sin(xi)));

    return res;
  }

  static void tanh(const VectorType &x, VectorType &y) {
    assert(y.size() >= x.size());
    y = Eigen::tanh(x.array());
  }

  static void lncosh(const VectorType &x, VectorType &y) {
    assert(y.size() >= x.size());
    for (int i = 0; i < x.size(); i++) {
      y(i) = lncosh(x(i));
    }
  }

  const Hilbert &GetHilbert() const { return hilbert_; }

  void to_json(json &j) const override {
    j["Machine"]["Name"] = "RbmSpin";
    j["Machine"]["Nvisible"] = nv_;
    j["Machine"]["Nhidden"] = nh_;
    j["Machine"]["UseVisibleBias"] = usea_;
    j["Machine"]["UseHiddenBias"] = useb_;
    j["Machine"]["a"] = a_;
    j["Machine"]["b"] = b_;
    j["Machine"]["W"] = W_;
  }

  void from_json(const json &pars) override {
    if (pars.at("Machine").at("Name") != "RbmSpin") {
      if (mynode_ == 0) {
        std::cerr << "# Error while constructing RbmSpin from Json input"
                  << std::endl;
      }
      std::abort();
    }

    if (FieldExists(pars["Machine"], "Nvisible")) {
      nv_ = pars["Machine"]["Nvisible"];
    }
    if (nv_ != hilbert_.Size()) {
      if (mynode_ == 0) {
        std::cerr << "# Number of visible units is incompatible with given "
                     "Hilbert space"
                  << std::endl;
      }
      std::abort();
    }

    if (FieldExists(pars["Machine"], "Nhidden")) {
      nh_ = FieldVal(pars["Machine"], "Nhidden");
    } else {
      nh_ = nv_ * double(FieldVal(pars["Machine"], "Alpha"));
    }

    usea_ = FieldOrDefaultVal(pars["Machine"], "UseVisibleBias", true);
    useb_ = FieldOrDefaultVal(pars["Machine"], "UseHiddenBias", true);

    Init();

    // Loading parameters, if defined in the input
    if (FieldExists(pars["Machine"], "a")) {
      a_ = pars["Machine"]["a"];
    } else {
      a_.setZero();
    }

    if (FieldExists(pars["Machine"], "b")) {
      b_ = pars["Machine"]["b"];
    } else {
      b_.setZero();
    }
    if (FieldExists(pars["Machine"], "W")) {
      W_ = pars["Machine"]["W"];
    }
  }
};

}  // namespace netket

#endif


//BIOIMAGESUITE_LICENSE  ---------------------------------------------------------------------------------
//BIOIMAGESUITE_LICENSE  This file is part of the BioImage Suite Software Package.
//BIOIMAGESUITE_LICENSE
//BIOIMAGESUITE_LICENSE  X. Papademetris, M. Jackowski, N. Rajeevan, R.T. Constable, and L.H
//BIOIMAGESUITE_LICENSE  Staib. BioImage Suite: An integrated medical image analysis suite, Section
//BIOIMAGESUITE_LICENSE  of Bioimaging Sciences, Dept. of Diagnostic Radiology, Yale School of
//BIOIMAGESUITE_LICENSE  Medicine, http://www.bioimagesuite.org.
//BIOIMAGESUITE_LICENSE
//BIOIMAGESUITE_LICENSE  All rights reserved. This file may not be edited/copied/redistributed
//BIOIMAGESUITE_LICENSE  without the explicit permission of the authors.
//BIOIMAGESUITE_LICENSE
//BIOIMAGESUITE_LICENSE  -----------------------------------------------------------------------------------

#include <bisvtkMultiThreader.h>
#include "bisImageDistanceMatrix.h"
#include "bisJSONParameterList.h"
#include "bisUtil.h"
#include <algorithm>
#include <sstream>
#include <Eigen/Core>
#include <Eigen/SparseCore>
#ifndef _WIN32
#include <Spectra/SymEigsSolver.h>
#include <Spectra/GenEigsSolver.h>
#include <Spectra/MatOp/SparseGenMatProd.h>
#endif

typedef double BISTYPE;

namespace bisImageDistanceMatrix {

  // ------------------------------------------------------------------------------------------------
  // Payload classes

  class bisMThreadStructure {
  public:
    short* wgt_dat;
    int*   index_dat;
    float* img_dat;
    long   numvoxels;
    int    numframes;
    long   numbest;
    long   numgoodvox;
    long   slicesize;

    // Stuff for radius
    int dim[3];
    float spa[3];
    float DistanceRadius;
    double maxintensity;
    double normalization;
    std::vector<double> output_array[VTK_MAX_THREADS];
    int numcols;

    bisMThreadStructure() {
      this->wgt_dat=NULL;
      this->index_dat=NULL;
      this->img_dat=NULL;
      this->numcols=4;
    }

    ~bisMThreadStructure() {
      for (int i=0;i<VTK_MAX_THREADS;i++) {
        this->output_array[i].clear();
        this->output_array[i].shrink_to_fit();
      }
      this->wgt_dat=NULL;
      this->index_dat=NULL;
      this->img_dat=NULL;
      this->numcols=0;
    }
  };

  class bisMImagePair {
  public:
    float* idata;
    float* odata;
    int dim[3];
    int radius[3];
    int increment[3];
    int numframes;
  };

  // ------------------------------------------------------------------------------------------------

  float selectKthLargest(unsigned long k0offset, unsigned long n, float* arr0offset) {
    std::nth_element(arr0offset,arr0offset+k0offset,arr0offset+n);
    return arr0offset[k0offset];
  }

  bisSimpleImage<int>* createIndexMap(bisSimpleImage<short>* objectmap) {

    bisSimpleImage<int>* temp=new bisSimpleImage<int>("indexmap");

    int dim2[5]; objectmap->getDimensions(dim2);
    float spa[5];objectmap->getSpacing(spa);

    dim2[3]=1; dim2[4]=1;
    temp->allocate(dim2,spa);
    temp->fill(0);
    int index=1;
    int nt=temp->getLength();
    int* idata=temp->getData();
    short* obj=objectmap->getData();
    for (int voxel=0;voxel<nt;voxel++)
      {
        if (obj[voxel]>0)
          {
            idata[voxel]=index;
            ++index;
          }
      }

    //    double r1[2];
    //temp->getRange(r1);
    //std::cout << "++++ ImageDistanceMatrix: Index Map range=(" << r1[0] << ":" << r1[1] << ")" << std::endl;
    return temp;
  }

  int checkInputImages(bisSimpleImage<float>* Input,bisSimpleImage<short>* ObjectMap,bisSimpleImage<int>* IndexMap) {

    int dim[5]; Input->getDimensions(dim);
    float spa[5]; Input->getSpacing(spa);

    int dim1[5]; ObjectMap->getDimensions(dim1);
    int dim2[5]; IndexMap->getDimensions(dim2);

    int sum=0;
    for (int i=0;i<=2;i++)
      {
        sum+=abs(dim[i]-dim2[i]);
        sum+=abs(dim[i]-dim1[i]);
      }
    if (sum>0)
      {
        std::cerr << "Dim=" << dim[0] << "," << dim[1] << "," << dim[2] << std::endl;
        std::cerr << "Dim1=" << dim1[0] << "," << dim1[1] << "," << dim1[2] << std::endl;
        std::cerr << "Dim2=" << dim2[0] << "," << dim2[1] << "," << dim2[2] << std::endl;

        std::cerr <<"Input, ObjectMap IndexMap must have the same dimensions. Cannot run sum=" << sum << std::endl;
        return 0;
      }

    double r1[2]; ObjectMap->getRange(r1);
    double r2[2]; IndexMap->getRange(r2);
    if (r1[1]<1)
      {
        std::cerr <<"Input Object Map has no postive values " << r1[0] << ":" << r1[1] << std::endl;
        return 0;
      }

    std::cout << "++++ input checking done " << dim[0] << "," << dim[1] << "," << dim[2] << " maxobj=" << r1[1] << " maxindex=" << r2[1] << std::endl;

    return 1;
  }

  double computeDistance(int index1,int index2,int dim[3],float spa[3]) {

    double dist=0.0;
    int p1[3],p2[3];

    int slicesize=dim[0]*dim[1];
    //    std::cout << "slicesize=" << slicesize << " " << dim[0] << "," << dim[1] << "," << dim[2] << std::endl;
    p1[2]=index1/slicesize;
    p2[2]=index2/slicesize;

    int t1=index1%slicesize;
    int t2=index2%slicesize;

    p1[0]=t1 % dim[0];
    p1[1]=t1 / dim[0];

    p2[0]=t2 % dim[0];
    p2[1]=t2 / dim[0];

    for (int ia=0;ia<=2;ia++)
      dist+=pow(double(p2[ia]-p1[ia])*spa[ia],2.0);

    return dist;
  }

  // ------------------------------------------------------------------------------------------------------
  // Threaded Version Of Code
  // ------------------------------------------------------------------------------------------------------
  void combineVectorsToCreateSparseMatrix(bisSimpleMatrix<double>* combined,std::vector<double> output_array[VTK_MAX_THREADS],int nc,int NumberOfThreads)
  {
    int nt=0;

    fprintf(stdout,"++++ \n++++ Threads completed, combining %d arrays (comp=%d): ",NumberOfThreads,nc);
    for (int i=0;i<NumberOfThreads;i++)
      {
        int n=output_array[i].size()/nc;
        nt+=n;
        std::cout << n << " ";
      }

    std::cout << ", total rows (pairs)=" << nt << " cols=" << nc <<  std::endl;

    combined->zero(nt,nc);

    double* c_dat=combined->getData();

    int index=0;
    for (int i=0;i<NumberOfThreads;i++)
      {
        int num=output_array[i].size();
        std::cout << "+++ Combining thread=" << i+1 << " num=" << num << " elements=" << num/nc << std::endl;
        if (num>0)
          {
            for (int j=0;j<num;j++)
              c_dat[index+j]=output_array[i][j];
            index+=num;
          }
      }
    return;
  }

  bisMThreadStructure* createThreadStructure(bisSimpleImage<float>* Input,
                                             bisSimpleImage<short>* ObjectMap,
                                             bisSimpleImage<int>* IndexMap,
                                             int NumberOfThreads,float Sparsity,long NumBest=-1)
  {
    bisMThreadStructure* ds=  new bisMThreadStructure();
    int dim[5]; IndexMap->getDimensions(dim);
    float spa[3]; IndexMap->getImageSpacing(spa);
    ds->img_dat=Input->getData();
    ds->wgt_dat=ObjectMap->getData();
    ds->index_dat=IndexMap->getData();
    ds->numvoxels=dim[0]*dim[1]*dim[2];
    ds->numframes=dim[3]*dim[4];
    ds->numgoodvox=0;
    ds->numcols=4;

    for (int i=0;i<ds->numvoxels;i++)
      {
        if (ds->index_dat[i]>0)
          ++ds->numgoodvox;
      }

    if (NumBest<0)
      {
        ds->numbest=(ds->numgoodvox*Sparsity*0.01);
      }
    else
      {
        ds->numbest=NumBest;
      }
    if (ds->numbest<2)
      ds->numbest=2;
    else if (ds->numbest>ds->numgoodvox)
      ds->numbest=ds->numgoodvox;

    int piecesize=2*(ds->numgoodvox*ds->numbest)/NumberOfThreads;
    for (int i=0;i<NumberOfThreads;i++)
      {
        ds->output_array[i].clear();
        ds->output_array[i].reserve(piecesize);
      }
    return ds;
  }

  // --------------------------------------------------------------------------------------------------------
  // Helper Function
  // --------------------------------------------------------------------------------------------------------
  void bisImageDistanceMatrix_ComputeFraction(int thread,int numthreads,int numvoxels,int range[2]) {
    int step=numvoxels/numthreads;
    range[0]=step*thread;
    range[1]=range[0]+step;
    if (thread==numthreads-1)
      range[1]=numvoxels;
  }
  // --------------------------------------------------------------------------------------------------------
  static void sparseThreadFunction(bisvtkMultiThreader::vtkMultiThreader::ThreadInfo *data)
  {

    bisMThreadStructure   *ds = (bisMThreadStructure *)(data->UserData);
    int thread=data->ThreadID;
    int numthreads=data->NumberOfThreads;

    int voxelrange[2];
    bisImageDistanceMatrix_ComputeFraction(thread,numthreads,ds->numvoxels,voxelrange);
    std::cout << "++++ Sparse Matrix Thread (" << thread << ") output_array numvoxels= " << ds->numgoodvox << " * " << ds->numbest << ", numframes=" << ds->numframes <<
      ". Computing " << voxelrange[0] << ":" << voxelrange[1] << std::endl;

    float* d_dist=new float[ds->numgoodvox+10];
    float* d_tmp =new float[ds->numgoodvox+10];
    int*   d_index=new  int[ds->numgoodvox+10];

    int voxelfraction=(voxelrange[1]-voxelrange[0])/5;
    int dvoxel=voxelrange[1]-voxelrange[0];
    if (dvoxel<1)
      dvoxel=1;
    if (voxelfraction<1)
      voxelfraction=1;
    if (voxelfraction>2500)
      voxelfraction=2500;
    //    int first=0;

    for (int voxel1=voxelrange[0];voxel1<voxelrange[1];voxel1++)
      {
        /*if ((voxel1-voxelrange[0])%voxelfraction==0 && voxel1>voxelrange[0])
          {
            std::cout << "_____ Thread (" << thread << "). Processed " << 100.0*double(voxel1-voxelrange[0])/double(dvoxel) << "%, " <<
              "(voxel " << voxel1 << " of " << voxelrange[0] << "->" << voxelrange[1] << ")." << std::endl;
              }*/
        int v1=ds->index_dat[voxel1];
        short w1=ds->wgt_dat[voxel1];
        //std::cout << "voxel1=" << voxel1 << " (" << v1 << "," << w1 << ")" << std::endl;
        if (v1>0)
          {
            int num_used=0;
            for (int voxel2=0;voxel2<ds->numvoxels;voxel2++)
              {
                if (voxel2!=voxel1)
                  {
                    short w2=ds->wgt_dat[voxel2];
                    int v2=ds->index_dat[voxel2];
                    if (v2>0 && (w1==w2))
                      {
                        int index1=voxel1*ds->numframes;
                        int index2=voxel2*ds->numframes;
                        double sum=0.0;
                        for (int frame=0;frame<ds->numframes;frame++)
                          {
                            sum+=pow(ds->img_dat[index1]-ds->img_dat[index2],2.0f);
                            ++index1;
                            ++index2;
                          }
                        d_dist[num_used]=sum;
                        d_tmp[num_used]=sum;
                        d_index[num_used]=voxel2;
                        ++num_used;
                      }
                  }
              }

            //std::cout << "voxel1=" << voxel1 << ", v1=" << v1 << " " << w1 << " num_used=" << num_used << std::endl;

            double thr=selectKthLargest(ds->numbest,num_used,d_tmp);
            //            ++first;
            //            if (first<3)
            //  std::cout << "***** Thread ("<< thread << ") thr=" << thr << ", numbest=" << ds->numbest
            //        << "num_used=" << num_used << " numgood=" << ds->numgoodvox << " nvox=" << ds->numvoxels << std::endl;

            for (int ia=0;ia<num_used;ia++)
              {
                if (d_dist[ia]<thr)
                  {
                    int index1=ds->index_dat[voxel1];
                    int index2=ds->index_dat[d_index[ia]];
                    //std::cout << index1 << "," << index2 << std::endl;
                    double dist=computeDistance(index1,index2,ds->dim,ds->spa);
                    //std::cout << index1 << "," << index2 << ":" << dist << std::endl;
                    ds->output_array[thread].push_back(index1);
                    ds->output_array[thread].push_back(index2);
                    ds->output_array[thread].push_back(d_dist[ia]);
                    ds->output_array[thread].push_back(dist);
                  }
              }
            // This adds itself as a zero
            int index=ds->index_dat[voxel1];
            ds->output_array[thread].push_back(index);
            ds->output_array[thread].push_back(index);
            ds->output_array[thread].push_back(0.0);
            ds->output_array[thread].push_back(0.0);
          }
      }

    std::cout << "++++      Thread (" << thread << ") done numpairs=" << ds->output_array[thread].size()/ds->numcols << std::endl;

    delete [] d_dist;
    delete [] d_index;
    delete [] d_tmp;
  }
  // ---------------------------------------------------------------------------
  static void radiusThreadFunction(bisvtkMultiThreader::vtkMultiThreader::ThreadInfo *data)
  {
    bisMThreadStructure   *ds = (bisMThreadStructure *)(data->UserData);
    int thread=data->ThreadID;
    int numthreads=data->NumberOfThreads;

    int slicerange[2];
    bisImageDistanceMatrix_ComputeFraction(thread,numthreads,ds->dim[2],slicerange);
    if (slicerange[1]==0)
      slicerange[1]=1;

    std::cout << "++++ Radius Matrix Thread(" << thread << ") radius=" << ds->DistanceRadius << " computing slices " << slicerange[0] << "->" << slicerange[1] << std::endl;

    float DistanceRadius2=ds->DistanceRadius*ds->DistanceRadius;
    int slicesize=ds->dim[0]*ds->dim[1];
    int dslice=(slicerange[1]-slicerange[0])/5;
    if (dslice<1)
      dslice=1;
    // Normalize maxintensity
    for (int k=slicerange[0];k<slicerange[1];k++)
      {
        int kmin=k-int(ds->DistanceRadius/ds->spa[2]); if (kmin<0) kmin=0;
        int kmax=k+int(ds->DistanceRadius/ds->spa[2]); if (kmax>=ds->dim[2]) kmax=ds->dim[2]-1;
        for (int j=0;j<ds->dim[1];j++)
          {
            int jmin=j-int(ds->DistanceRadius/ds->spa[1]); if (jmin<0) jmin=0;
            int jmax=j+int(ds->DistanceRadius/ds->spa[1]); if (jmax>=ds->dim[1]) jmax=ds->dim[1]-1;
            for (int i=0;i<ds->dim[0];i++)
              {
                int imin=i-int(ds->DistanceRadius/ds->spa[0]); if (imin<0) imin=0;
                int imax=i+int(ds->DistanceRadius/ds->spa[0]); if (imax>=ds->dim[0]) imax=ds->dim[0]-1;
                int vox_index=i+j*ds->dim[0]+k*slicesize;
                double v[3];
                v[0]=ds->index_dat[vox_index];
                short w0=ds->wgt_dat[vox_index];
                int index1=vox_index*ds->numframes;

                if (v[0]>0.0)
                  {
                    v[1]=v[0];
                    v[2]=0.0;
                    //		  v[3]=0.0;
                    ds->output_array[thread].push_back(v[0]);
                    ds->output_array[thread].push_back(v[1]);
                    ds->output_array[thread].push_back(0.0);
                    ds->output_array[thread].push_back(0.0);

                    for (int ka=kmin;ka<=kmax;ka++)
                      for (int ja=jmin;ja<=jmax;ja++)
                        for (int ia=imin;ia<=imax;ia++)
                          {
                            int sec_index=ia+ja*ds->dim[0]+ka*slicesize;
                            v[1]=ds->index_dat[sec_index];
                            short w1=ds->wgt_dat[sec_index];

                            if (v[1]>0.0 && w1==w0)
                              {
                                double dist=
                                  pow(double(ka-k)*ds->spa[2],2.0)+
                                  pow(double(ja-j)*ds->spa[1],2.0)+
                                  pow(double(ia-i)*ds->spa[0],2.0);
                                if (dist<=DistanceRadius2 && dist>0.01)
                                  {
                                    int index2=sec_index*ds->numframes;
                                    v[2]=0.0;
                                    for (int frame=0;frame<ds->numframes;frame++)
                                      v[2]+=pow(ds->img_dat[index1+frame]-ds->img_dat[index2+frame],2.0f);
                                    v[2]=(v[2])*ds->normalization;
                                    //  v[3]=dist;
                                    ds->output_array[thread].push_back(v[0]);
                                    ds->output_array[thread].push_back(v[1]);
                                    ds->output_array[thread].push_back(v[2]);
                                    ds->output_array[thread].push_back(dist);
                                  }
                              }
                          }
                  }
              }
          }
      }
    std::cout << "++++      Thread (" << thread << ") done numpairs=" << ds->output_array[thread].size()/ds->numcols << std::endl;

  }

  // --------------------------------------------------------------------------------------------------------
  static void temporalSparseThreadFunction(bisvtkMultiThreader::vtkMultiThreader::ThreadInfo *data)
  {
    bisMThreadStructure *ds = (bisMThreadStructure *)(data->UserData);
    int thread=data->ThreadID;
    int numthreads=data->NumberOfThreads;
    int framerange[2];

    bisImageDistanceMatrix_ComputeFraction(thread,numthreads,ds->numframes,framerange);
    std::cout << "++++ Temporal Sparse Matrix Thread (" << thread << "). Computing " << framerange[0] << ":" << framerange[1] << std::endl;
    float* d_dist=new float[ds->numframes];
    float* d_tmp =new float[ds->numframes];

    for (int frame1=framerange[0];frame1<framerange[1];frame1++)
      {
        d_tmp[frame1]=0.0;
        d_dist[frame1]=0.0;

        for (int frame2=0;frame2<ds->numframes;frame2++)
          {
            if (frame2!=frame1)
              {
                int index1=frame1*ds->numvoxels;
                int index2=frame2*ds->numvoxels;
                double sum=0.0;
                for (int voxel=0;voxel<ds->numvoxels;voxel++)
                  sum+=pow(ds->img_dat[index1+voxel]-ds->img_dat[index2+voxel],2.0f);
                d_dist[frame2]=sum;
                d_tmp[frame2]=sum;
              }
          }

        double thr=selectKthLargest(ds->numbest,ds->numframes,d_tmp);

        for (int frame2=0;frame2<ds->numframes;frame2++)
          {
            if (d_dist[frame2]<thr)
              {
                ds->output_array[thread].push_back(frame1);
                ds->output_array[thread].push_back(frame2);
                ds->output_array[thread].push_back(d_dist[frame2]);
              }
          }
      }

    std::cout << "++++      Thread (" << thread << ") done numpairs=" << ds->output_array[thread].size()/ds->numcols << std::endl;

    delete [] d_dist;
    delete [] d_tmp;
  }

  // ---------------------------------------------------------------------------
  int createSparseMatrixParallel(bisSimpleImage<float>* Input,
                                 bisSimpleImage<short>* ObjectMap,
                                 bisSimpleImage<int>* IndexMap,
                                 bisSimpleMatrix<double>* Output,
                                 float sparsity,int numthreads)
  {
    float Sparsity=bisUtil::frange(sparsity,0.001,50.0);
    int NumberOfThreads=bisUtil::irange(numthreads,1,VTK_MAX_THREADS);

    if (!checkInputImages(Input,ObjectMap,IndexMap))
      return 0;

    int d[3]; Input->getImageDimensions(d);
    int nv=d[0]*d[1]*d[2];
    if (nv<NumberOfThreads)
      NumberOfThreads=nv;

    std::cout << "++++ CreateSparseMatrixParallel sparsity=" << Sparsity << " Number Of Threads= "
              << NumberOfThreads << " (max=" << VTK_MAX_THREADS  << ")" << std::endl;
    bisMThreadStructure* ds=  createThreadStructure(Input,ObjectMap,IndexMap,NumberOfThreads,Sparsity);
    Input->getImageDimensions(ds->dim);
    Input->getImageSpacing(ds->spa);

    std::stringstream strss;  strss <<  "Numgoodvox=" << ds->numgoodvox << ", expected total size=" << ds->numgoodvox*ds->numbest;
    bisvtkMultiThreader::runMultiThreader((bisvtkMultiThreader::vtkThreadFunctionType)&sparseThreadFunction,ds,strss.str(),NumberOfThreads,1);
    combineVectorsToCreateSparseMatrix(Output,ds->output_array,ds->numcols,NumberOfThreads);
    double density=100.0*Output->getNumRows()/(double(ds->numgoodvox*ds->numgoodvox));
    std::cout << "++++ Sparse matrix done. Final density: num_rows=" << ds->numgoodvox << " density=" << density << "% (components=" << Output->getNumCols() << ")" << std::endl;

    delete ds;
    return 1;
  }

  int createRadiusMatrixParallel(bisSimpleImage<float>* Input,
                                 bisSimpleImage<short>* ObjectMap,
                                 bisSimpleImage<int>* IndexMap,
                                 bisSimpleMatrix<double>* Output,
                                 float radius,int numthreads)
  {

    int NumberOfThreads=bisUtil::irange(numthreads,1,VTK_MAX_THREADS);
    float DistanceRadius=bisUtil::frange(radius,1.0,4000.0);

    if (!checkInputImages(Input,ObjectMap,IndexMap))
      return 0;

    int d[3]; Input->getImageDimensions(d);
    if (d[2]<NumberOfThreads)
      NumberOfThreads=d[2];

    std::cout << "++++ Beginning CreateRadiusMatrixParallel. Radius=" << DistanceRadius << ", numthreads=" << NumberOfThreads << std::endl;

    float spa[3]; Input->getImageSpacing(spa);

    int nbest=1;
    double meanspa=0.0;
    for (int ia=0;ia<=2;ia++)
      {
        nbest=nbest*(2*int(radius/spa[ia]+0.5)+1);
        meanspa+=spa[ia];
      }
    meanspa=meanspa/3.0;

    double r[2]; Input->getRange(r);
    double minintensity=r[0];
    double maxintensity=r[1];

    maxintensity=maxintensity-minintensity;
    if (maxintensity<0.0001)
      maxintensity=0.0001;

    bisMThreadStructure* ds=createThreadStructure(Input,ObjectMap,IndexMap,NumberOfThreads,0.0,nbest);

    ds->DistanceRadius=DistanceRadius;
    Input->getImageDimensions(ds->dim);
    Input->getImageSpacing(ds->spa);
    ds->maxintensity=maxintensity;
    ds->normalization=1.0;

    std::cout << "++++ Parameters: maxintensity" << ds->maxintensity << ", numframes=" << ds->numframes << " distradius=" <<
      ds->DistanceRadius << std::endl;
    std::cout << "++++ Normalization=" << ds->normalization << " Mean spacing=" << meanspa << std::endl;

    std::stringstream strss;  strss <<  "Numgoodvox=" << ds->numgoodvox << ", expected total size=" << ds->numgoodvox*ds->numbest;
    bisvtkMultiThreader::runMultiThreader((bisvtkMultiThreader::vtkThreadFunctionType)&radiusThreadFunction,ds,strss.str(),NumberOfThreads,1);

    combineVectorsToCreateSparseMatrix(Output,ds->output_array,ds->numcols,NumberOfThreads);
    double density=100.0*Output->getNumRows()/(double(ds->numgoodvox*ds->numgoodvox));
    std::cout << "++++ Radius matrix done. Final density: num_rows=" << ds->numgoodvox << " density=" << density << "% (components=" << Output->getNumCols() << ")" << std::endl;

    delete ds;
    return 1;
  }

  // ---------------------------------------------------------------------------
  int createSparseMatrixParallelTemporal(bisSimpleImage<float>* Input,
                                         bisSimpleMatrix<double>* Output,
                                         float sparsity,int numthreads)
  {
    float Sparsity=bisUtil::frange(sparsity,0.001,50.0);
    int NumberOfThreads=bisUtil::irange(numthreads,1,VTK_MAX_THREADS);

    int d[3]; Input->getImageDimensions(d);
    int nv=d[0]*d[1]*d[2];
    if (nv<NumberOfThreads)
      NumberOfThreads=nv;

    std::cout << "++++ CreateSparseMatrixParallel sparsity=" << Sparsity << " Number Of Threads= "
              << NumberOfThreads << " (max=" << VTK_MAX_THREADS  << ")" << std::endl;

    bisMThreadStructure* ds=  new bisMThreadStructure();
    int dim[5]; Input->getDimensions(dim);
    ds->img_dat=Input->getData();
    ds->numvoxels=dim[0]*dim[1]*dim[2];
    ds->numframes=dim[3]*dim[4];
    ds->numbest=int(sparsity*ds->numframes)+1;
    ds->numcols=3;

    int piecesize=2*(ds->numbest*ds->numframes)/NumberOfThreads;
    for (int i=0;i<NumberOfThreads;i++)
      {
        ds->output_array[i].clear();
        ds->output_array[i].reserve(piecesize);
      }

    std::stringstream strss;  strss <<  "Numbest=" << ds->numbest << ", expected total size=" << ds->numframes*ds->numbest;
    bisvtkMultiThreader::runMultiThreader((bisvtkMultiThreader::vtkThreadFunctionType)&temporalSparseThreadFunction,ds,strss.str(),NumberOfThreads,1);
    combineVectorsToCreateSparseMatrix(Output,ds->output_array,ds->numcols,NumberOfThreads);
    std::cout << "Total Rows=" << Output->getNumRows() << " frames=" << ds->numframes << std::endl;
    double density=100.0*Output->getNumRows()/(double(ds->numframes*ds->numframes));
    std::cout << "++++ Sparse matrix done. Final density: num_rows=" << ds->numframes << " density=" << density << "% (components=" << Output->getNumCols() << ")" << std::endl;
    delete ds;
    return 1;
  }

  // ----------------------------------------------------------------------------------
  //
  // -------------------------- reformat Image Code -- make patches into frames

  static void reformatThreadFunction(bisvtkMultiThreader::vtkMultiThreader::ThreadInfo *data) {

    bisMImagePair   *ds = (bisMImagePair *)(data->UserData);
    int thread=data->ThreadID;
    int numthreads=data->NumberOfThreads;

    int slicerange[2];
    bisImageDistanceMatrix_ComputeFraction(thread,numthreads,ds->dim[2],slicerange);
    if (slicerange[1]==0)
      slicerange[1]=1;

    std::cout << "++++ reformatImage Thread(" << thread << ") radius=" << ds->radius[0] << "," << ds->radius[1] << "," << ds->radius[2];
    std::cout << ", computing slices " << slicerange[0] << "->" << slicerange[1] << " numframes=" << ds->numframes << std::endl;

    int volumesize=ds->dim[0]*ds->dim[1]*ds->dim[2];
    int slicesize=ds->dim[0]*ds->dim[1];

    int voxel=slicerange[0]*slicesize;

    for (int k=slicerange[0];k<slicerange[1];k++) {
      for (int j=0;j<ds->dim[1];j++) {
        for (int i=0;i<ds->dim[0];i++) {

          int frame=0;

          for (int ka=-ds->radius[2];ka<=ds->radius[2];ka++) {
            int newk=k+ka*ds->increment[2];
            if (newk<0)
              newk=0;
            else if (newk>=ds->dim[2])
              newk=ds->dim[2]-1;

            //std::cout << "ka = " << ka << "-->" << newk << std::endl;

            for (int ja=-ds->radius[1];ja<=ds->radius[1];ja++) {
              int newj=j+ja*ds->increment[1];
              if (newj<0)
                newj=0;
              else if (newj>=ds->dim[1])
                newj=ds->dim[1]-1;

              //std::cout << "ja = " << ja << "-->" << newj << std::endl;

              for (int ia=-ds->radius[0];ia<=ds->radius[0];ia++) {
                int newi=i+ia*ds->increment[0];
                if (newi<0)
                  newi=0;
                else if (newi>=ds->dim[0])
                  newi=ds->dim[0]-1;

                ds->odata[volumesize*frame+voxel]=ds->idata[newk*slicesize+newj*ds->dim[0]+newi];
                ++frame;
              } //ia
            } //ja
          } //ka
          ++voxel;
        } //i
      } //j
    } //k

  }

  int reformatImage(bisSimpleImage<float>* input, bisSimpleImage<float>* output,int radius[3],int increment[3],int NumberOfThreads=4) {

    // First copy data around
    int numframes=1;
    for (int i=0;i<=2;i++) {
      if (increment[i]<1)
        increment[i]=1;
      if (increment[i]>4)
        increment[i]=4;
      if (radius[i]<1)
        radius[i]=1;
      else if (radius[i]>4)
        radius[i]=4;
      numframes=numframes*(1+2*radius[i]);
    }

    int dim[5]; input->getDimensions(dim);
    dim[3]=numframes; dim[4]=1;
    float spa[5]; input->getSpacing(spa);
    output->allocateIfDifferent(dim,spa);

    std::cout << "++++ Allocating output image " << dim[0] << "*" << dim[1] << "*" << dim[2] << ", numframes=" << numframes << std::endl;
    std::cout << "++++ \t radius = " << radius[0] << "," << radius[1] << "," << radius[2] << std::endl;

    bisMImagePair* ds=  new bisMImagePair();
    ds->idata=input->getImageData();
    ds->odata=output->getImageData();
    for (int i=0;i<=2;i++) {
      ds->dim[i]=dim[i];
      ds->radius[i]=radius[i];
      ds->increment[i]=increment[i];
    }
    ds->numframes=numframes;

    bisvtkMultiThreader::runMultiThreader((bisvtkMultiThreader::vtkThreadFunctionType)&reformatThreadFunction,ds,"Reformat Image",NumberOfThreads);
    delete ds;
    return numframes;
  }

  // End name space
}

// -----------------------------------------------------------------------------------------------------------------------

namespace bisSparseEigenSystem {

  // sparseMatrix is output of createSparseMatrixParallel/createSparseMatrixRadius  4 columns i,j, dist and euc dist

  int computeEigenVectors(bisSimpleMatrix<double>* sparseMatrix,
                          bisSimpleImage<int>*     indexMap,
                          bisSimpleImage<float>*   eigenVectors,
                          int maxeigen=10,
                          double sigma=1.0, double lambda=0.0,double tolerance=0.001,int maxiter=50,float scale=10000) {

#ifndef _WIN32

    int nt=sparseMatrix->getNumRows();
    int nc=sparseMatrix->getNumCols();

    if (nc!=4 || nt< 4) {
      std::cerr << "Bad Distance Matrix " << nt << "*" << nc << std::endl;
      return 0;
    }

    double* inp_dat=sparseMatrix->getData();
    double r[2]; indexMap->getRange(r);
    int numrows=int(r[1]);

    std::cout << "+++++ Beginning sparse eigensystem: numelements=" << nt << " numrows=" << numrows << std::endl;

    // Assume I have exponentiated and normalized
    // 1. Compute Median
    // 2. Exponentiate

    // Inp_dat is an array of size nt*3

    // Compute The Median
    // Take every nth value to compute the median (for now n=1)
    int samplerate=1;
    int numvalues=int(nt/samplerate);
    float* values=new float[numvalues];
    for(int i=0;i<numvalues;i++) {
      int offset=i*(nc*samplerate);
      values[i]=inp_dat[offset+2]+lambda*inp_dat[offset+3];
    }
    float median=bisImageDistanceMatrix::selectKthLargest(numvalues/2,numvalues,values);
    delete [] values;
    if (median<0.00001)
      median=0.00001;
    if (sigma<0.0001)
      sigma=0.0001;
    double factor=1.0/(median*sigma);

    std::cout << "+++++ Computing Degree ... median= " << median << "factor= " << factor << " lamda=" << lambda << " sigma=" << sigma << std::endl;

    // remember row,col in input sparse matrix triple are 1-offset so subtract 1 for row,col
    BISTYPE* D=new BISTYPE[numrows];
    for (int i=0;i<numrows;i++)
      D[i]=0.0;
    int index=0;

    int minrow=(int)inp_dat[0],maxrow=(int)inp_dat[0];

    for (int i=0;i<nt;i++)
      {
        int row=(long)inp_dat[index]-1;
        double v2=inp_dat[index+2]+lambda*inp_dat[index+3];
        double v=exp(-v2*factor);

        //        inp_dat[index+2]=v;
        //      if (row%step==0 && abs(col-row)<10 )
        //fprintf(stdout,"Reporting %d,%d = \t %f->%f\n",row,col,v2,v);

        D[row]+=v;
        if (row<minrow)
          minrow=row;
        else if (row>maxrow)
          maxrow=row;
        index+=nc;
      }

    std::cout << "++++ Minrow=" << minrow << "\t maxrow=" << maxrow << std::endl;

    // Compute Dinv plus regularizer
    int step=numrows/7;
    for (int row=0;row<numrows;row++)
      {
        D[row]=1.0/sqrt(D[row]+1.0);
        if (row%step == 0 || row==numrows-1)
          std::cout << "+++++ 1.0/sqrt(Degree) row=" << row+1 << " D=" << D[row] << std::endl;
      }

    std::cout << "+++++ Storing in Sparse matrix " << numrows << "*" << numrows << std::endl;
    // Store in Sparse Matrix
    // remember row,col in input sparse matrix triple are 1-offset so subtract 1 for row,col -- Ignore

    typedef Eigen::Triplet<BISTYPE> T;
    std::vector<T> tripletList;
    tripletList.reserve(nt*2);
    index=0;
    std::cout << "+++++ Allocated in Sparse matrix " << std::endl;
    for(int i = 0; i < nt; i++) {
      long row=(long)inp_dat[index]-1;
      long col=(long)inp_dat[index+1]-1;
      double v0=inp_dat[index+2]+lambda*inp_dat[index+3];
      double v1=exp(-v0*factor);

      if (row==col)
	{
	  // Add 0.5 regularizer to diagonal ...
	  BISTYPE v=D[row]*D[row]*(v1+0.5);
	  tripletList.push_back(T(row,col,v));
	}
      else
	{
	  BISTYPE v=0.5*D[row]*D[col]*v1;
	  tripletList.push_back(T(row,col,v));
	  tripletList.push_back(T(col,row,v));
	}
      index+=nc;
    }

    delete [] D;

    std::cout << "+++++ Beginning eigendecomposition num triplets=" << tripletList.size() << std::endl;
    // Now On To Solver from Spectra
    Eigen::SparseMatrix<BISTYPE> M(numrows,numrows);
    M.setFromTriplets(tripletList.begin(),tripletList.end());
    std::cout << "+++++ Compressed Matrix created" << std::endl;

    Spectra::SparseGenMatProd<BISTYPE> op(M);
    Spectra::SymEigsSolver< BISTYPE, Spectra::LARGEST_ALGE, Spectra::SparseGenMatProd<BISTYPE> > eigs(&op,  maxeigen, maxeigen*2);

    eigs.init();
    std::cout << "+++++ Init Done on to Compute " << maxeigen << " Eigenvalues (tolerance=" << tolerance << " maxiter=" << maxiter << ")" << std::endl;

    int nconv = eigs.compute(maxiter,tolerance);

    // Retrieve results
    if(eigs.info() != Spectra::SUCCESSFUL) {
      std::cerr << "---- Eigen decomposition failed " << std::endl;
      return 0;
    }

    int numeigen=eigs.eigenvalues().size();
    std::cout << "+++++ Done with Eigendecomposition (numeigen=" << numeigen << "), nconv=" << nconv << std::endl;

    int tenth=numeigen/10;
    if (tenth<1)
      tenth=1;
    for (int ia=0;ia<numeigen;ia+=tenth) {
      float l=eigs.eigenvalues().coeff(ia);
      std::cout << "+++++\t Eigenvalue " << ia+1 << "/" << numeigen << " = " << l << std::endl;
    }

    int numeigenrows=eigs.eigenvectors().rows();
    int numeigencols=eigs.eigenvectors().cols();

    std::cout << "+++++ numeigenrows*numeigencols=" << numeigenrows << "*" << numeigencols << std::endl;
    std::cout.flush();

    int dim[5];   indexMap->getDimensions(dim);
    dim[3]=numeigen; dim[4]=1;
    float spa[5]; indexMap->getSpacing(spa);

    eigenVectors->allocateIfDifferent(dim,spa);
    eigenVectors->fill(0.0);
    float* eig_dat=eigenVectors->getImageData();

    int* ind_dat=indexMap->getImageData();
    BISTYPE *eigcolmajor=eigs.eigenvectors().data();

    int volumesize=dim[0]*dim[1]*dim[2];
    int eleventh=volumesize/11;
    int numgood=0;
    for (int voxel=0;voxel<volumesize;voxel++)
      {
        int index=ind_dat[voxel]-1;
        if (voxel%eleventh==0 || (index>=0 && numgood < 10 ))
          std::cout << "voxel=" << voxel << "\t" << index << std::endl;

        if (index>=0) {
          numgood++;
          for (int frame=0;frame<numeigen;frame++) {
            int ia=voxel+frame*volumesize;
            int ib=frame*numeigenrows+index;
            eig_dat[ia]=eigcolmajor[ib]*scale;
          }
        }
      }

    std::cout << "++++ Done Assigning numgood=" << numgood << " vs " << volumesize << std::endl;
    double range[2];
    eigenVectors->getRange(range);
    std::cout << "+++++ Range of eigenvector image =" << range[0] << ":" << range[1] << " Numeigen=" << numeigen << std::endl;
    return numeigen;
#else
    return 0;
#endif
  }

  // ----------------------------------------------------------------------------------

  int eigenvectorDenoiseImage(bisSimpleImage<float>* Input,
                              bisSimpleImage<float>* Eigenvectors,
                              bisSimpleImage<float>* Output,
                              float scale)
  {

    int dim[5]; Input->getDimensions(dim);
    int dim2[5]; Eigenvectors->getDimensions(dim2);
    float* idata=Input->getData();
    float* edata=Eigenvectors->getImageData();

    Output->copyStructure(Input);
    float* odata=Output->getImageData();

    int numinputframes=dim[3]*dim[4];
    int numeigenvectors=dim2[3]*dim2[4];

    std::cout << "++++ denoiseImageParallel scale=" << scale << " numeigenvectors=" << numeigenvectors << std::endl;

    int volumesize=dim[0]*dim[1]*dim[2];
    double* coeff=new double[numeigenvectors];

    for (int frame=0;frame<numinputframes;frame++) {

      int i_offset=frame*volumesize;
      std::cout << "Frame=" << frame << " off=" << i_offset << std::endl;

      for (int c=0;c<numeigenvectors;c++) {
        coeff[c]=0.0;
        int e_offset=c*volumesize;
        for (int voxel=0;voxel<volumesize;voxel++) {
          coeff[c]+=idata[i_offset+voxel]*edata[e_offset+voxel];
        }
        coeff[c]/=(scale*scale);
        std::cout << "Coeff=" << c << " = " << coeff[c] << " e_offset=" << e_offset << std::endl;
      }

      for (int voxel=0;voxel<volumesize;voxel++) {
        odata[voxel]=0.0;
        for (int c=0;c<numeigenvectors;c++)
          odata[voxel]+=edata[c*volumesize+voxel]*coeff[c];
      }
    }
    delete [] coeff;
    return 1;
  }

  // -----------------------------------------------------------------------------------------------------------------------
  // End of namespace
}

// -----------------------------------------------------------------------------------------------------------------------

// --------------- External stufff --------------------------------------

/** Computes a sparse distance matrix among voxels in the image
 * @param input serialized 4D input file as unsigned char array
 * @param objectmap serialized input objectmap as unsigned char array
 * @param jsonstring the parameter string for the algorithm
 * { "useradius" : false, "radius" : 2.0, sparsity : 0.01, numthreads: 4}
 * @param debug if > 0 print debug messages
 * @returns a pointer to the sparse distance matrix serialized
 */
// BIS: { 'computeImageDistanceMatrixWASM', 'bisImage', [ 'bisImage', 'bisImage', 'ParamObj', 'debug' ], {"checkorientation" : "all"} }
unsigned char* computeImageDistanceMatrixWASM(unsigned char* input, unsigned char* objectmap,const char* jsonstring,int debug) {

  std::unique_ptr<bisJSONParameterList> params(new bisJSONParameterList());
  int ok=params->parseJSONString(jsonstring);
  if (!ok)
    return 0;

  if (debug)
    params->print();

  std::unique_ptr<bisSimpleImage<float> > inp_image(new bisSimpleImage<float>("inp_image"));
  if (!inp_image->linkIntoPointer(input))
    return 0;

  std::unique_ptr<bisSimpleImage<short> > obj_image(new bisSimpleImage<short>("obj_image"));
  if (objectmap) {
    if (!obj_image->linkIntoPointer(objectmap))
      return 0;
  } else {
    std::cout << "++++ creating mask as none was provided" << std::endl;
    int dim[5];   inp_image->getDimensions(dim);
    float spa[5]; inp_image->getSpacing(spa);
    dim[3]=1; dim[4]=1;
    obj_image->allocate(dim,spa);
    obj_image->fill(1);
  }

  int useradius=params->getBooleanValue("useradius",true);
  float radius=params->getFloatValue("radius",2.0);
  float sparsity=params->getFloatValue("sparsity",0.01);
  int numthreads=params->getIntValue("numthreads",4);
#ifdef _WIN32
  if (numthreads>1) {
	std::cout << ".... Windows: forcing numthreads=" << 1 << std::endl;
	numthreads=1;
  }
#endif

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning image distance matrix computation " << std::endl;
    int dim[5]; inp_image->getDimensions(dim);
    std::cout << "....      Input  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    obj_image->getDimensions(dim);
    std::cout << "....      Objectmap  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    std::cout << "........................" << std::endl << std::endl;
  }

  std::unique_ptr<bisSimpleImage<int> > indexmap(bisImageDistanceMatrix::createIndexMap(obj_image.get()));
  std::unique_ptr<bisSimpleMatrix<double> > Output(new bisSimpleMatrix<double>("combined"));

  if (useradius) {
    bisImageDistanceMatrix::createRadiusMatrixParallel(inp_image.get(),obj_image.get(),indexmap.get(),Output.get(),radius,numthreads);
  } else {
    bisImageDistanceMatrix::createSparseMatrixParallel(inp_image.get(),obj_image.get(),indexmap.get(),Output.get(),sparsity,numthreads);
  }

  return Output->releaseAndReturnRawArray();
}

/** Computes a sparse temporal distance matrix among frames in the image (patches perhaps)
 * @param input serialized 4D input file as unsigned char array
 * @param jsonstring the parameter string for the algorithm
 * { sparsity : 0.01, numthreads: 4 }
 * @param debug if > 0 print debug messages
 * @returns a pointer to the sparse distance matrix serialized
 */
// BIS: { 'computeTemporalImageDistanceMatrixWASM', 'Matrix', [ 'bisImage', 'ParamObj', 'debug' ], {"checkorientation" : "all"} }
unsigned char* computeTemporalImageDistanceMatrixWASM(unsigned char* input,const char* jsonstring,int debug) {

  std::unique_ptr<bisJSONParameterList> params(new bisJSONParameterList());
  int ok=params->parseJSONString(jsonstring);
  if (!ok)
    return 0;

  if (debug)
    params->print();

  std::unique_ptr<bisSimpleImage<float> > inp_image(new bisSimpleImage<float>("inp_image"));
  if (!inp_image->linkIntoPointer(input))
    return 0;

  float sparsity=params->getFloatValue("sparsity",0.01);
  int numthreads=params->getIntValue("numthreads",4);

#ifdef _WIN32
  if (numthreads>1) {
	std::cout << ".... Windows: forcing numthreads=" << 1 << std::endl;
	numthreads=1;
  }
#endif

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning temporal image distance matrix computation " << std::endl;
    int dim[5]; inp_image->getDimensions(dim);
    std::cout << "....      Input  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
  }

  std::unique_ptr<bisSimpleMatrix<double> > Output(new bisSimpleMatrix<double>("combined"));
  bisImageDistanceMatrix::createSparseMatrixParallelTemporal(inp_image.get(),Output.get(),sparsity,numthreads);
  return Output->releaseAndReturnRawArray();
}

/** Creates an indexmap image
 * @param input objectmap
 * @param debug if > 0 print debug messages
 * @returns a pointer to the serialized index map image (int)
 */
// BIS: { 'computeImageIndexMapWASM', 'bisIamage', [ 'bisImage', 'debug' ]
unsigned char* computeImageIndexMapWASM(unsigned char* input,int debug) {

  std::unique_ptr<bisSimpleImage<short> > inp_image(new bisSimpleImage<short>("inp_image"));
  if (!inp_image->linkIntoPointer(input))
    return 0;

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning image indexmap computation " << std::endl;
    int dim[5]; inp_image->getDimensions(dim);
    std::cout << "....      Input  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
  }

  std::unique_ptr<bisSimpleImage<int> > result(bisImageDistanceMatrix::createIndexMap(inp_image.get()));
  return result->releaseAndReturnRawArray();
}

/** Creates a reformatted image where a patch is mapped into frames. This is so as to recycle the ImageDistanceMatrix code for
 * patch distances as opposed to frame comparisons
 * @param input serialized 3D input file as unsigned char array
 * @param jsonstring the parameter string for the algorithm
 * { "radius" : 2,  numthreads: 4 }
 * @param debug if > 0 print debug messages
 * @returns a pointer to the reformated image
 */
// BIS: { 'createPatchReformatedImage', 'bisImage', [ 'bisImage', 'ParamObj',  'debug' ] }
unsigned char* createPatchReformatedImage(unsigned char* input,const char* jsonstring,int debug) {

  std::unique_ptr<bisJSONParameterList> params(new bisJSONParameterList());
  int ok=params->parseJSONString(jsonstring);
  if (!ok)
    return 0;

  if (debug)
    params->print();

  std::unique_ptr<bisSimpleImage<float> > inp_image(new bisSimpleImage<float>("inp_image"));
  if (!inp_image->linkIntoPointer(input))
    return 0;

  int radius=params->getIntValue("radius",2);
  int numthreads=params->getIntValue("numthreads",4);
  int increment=params->getIntValue("increment",1);

#ifdef _WIN32
  if (numthreads>1) {
	std::cout << ".... Windows: forcing numthreads=" << 1 << std::endl;
	numthreads=1;
  }
#endif

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning reformatted image " << std::endl;
    int dim[5]; inp_image->getDimensions(dim);
    std::cout << "....      Input  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    std::cout << "........................" << std::endl << std::endl;
  }

  int rad[3] = { radius,radius,radius };
  int incr[3] = { increment,increment,increment };

  std::unique_ptr<bisSimpleImage<float> > out_image(new bisSimpleImage<float>("output"));
  bisImageDistanceMatrix::reformatImage(inp_image.get(),out_image.get(),rad,incr,numthreads);
  return out_image->releaseAndReturnRawArray();
}

/** Compute sparse Eigen Vectors based on distance Matrix and IndexMap
 * @param sparseMatrix the sparse Matrix (output of computeImageDistanceMatrix)
 * @param indexMap the indexMap image (output of computeImageIndexMap)
 * @param eigenVectors the output eigenVector image
 * @param jsonstring the parameter string for the algorithm
 * { "maxeigen" : 10, "sigma" : 1.0, "lambda" : 0.0, "tolerance" : 0.00001 , "maxiter" : 500, "scale" : 10000 }
 * @param debug if > 0 print debug messages
 * @returns a pointer to the reformated image
 */
// BIS: { 'computeSparseImageEigenvectorsWASM', 'bisImage', [ 'Matrix', 'bisImage', 'ParamObj',  'debug' ] }
unsigned char* computeSparseImageEigenvectorsWASM(unsigned char* input, unsigned char* indexmap,const char* jsonstring,int debug) {

  std::unique_ptr<bisJSONParameterList> params(new bisJSONParameterList());
  int ok=params->parseJSONString(jsonstring);
  if (!ok)
    return 0;

  if (debug)
    params->print();

  std::unique_ptr<bisSimpleMatrix<double> > dist_matrix(new bisSimpleMatrix<double>("inp_matrix"));
  if (!dist_matrix->linkIntoPointer(input))
    return 0;

  std::unique_ptr<bisSimpleImage<int> > obj_image(new bisSimpleImage<int>("indexmap_image"));
  if (!obj_image->linkIntoPointer(indexmap))
    return 0;

  int   maxeigen=params->getIntValue("maxeigen",10);
  float  sigma=params->getFloatValue("sigma",1.0);
  float  lambda=params->getFloatValue("lambda",0.0);
  float  tolerance=params->getFloatValue("tolerance",1.0e-5);
  int iter=params->getIntValue("maxiter",500);
  float scale=params->getFloatValue("scale",10000);

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning image distance matrix computation " << std::endl;
    int rows=dist_matrix->getNumRows();
    int cols=dist_matrix->getNumCols();

    std::cout << "....      Input  Matrix=" << rows << "*" << cols << std::endl;
    int dim[5]; obj_image->getDimensions(dim);
    std::cout << "....      Indexmap  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    std::cout << "........................" << std::endl << std::endl;
  }

  std::unique_ptr<bisSimpleImage<float> > Output(new bisSimpleImage<float>("eigenvect"));
  bisSparseEigenSystem::computeEigenVectors(dist_matrix.get(),obj_image.get(),Output.get(),
                                            maxeigen,sigma,lambda,tolerance,iter,scale);
  return Output->releaseAndReturnRawArray();
}

/** Eigenvector denoise image -- project image into eigenspace
 * @param input serialized 3D input file as unsigned char array
 * @param 4D eigenvector image
 * @param jsonstring the parameter string for the algorithm
 * { "scale" : 10000 , numthreads: 4 }
 * @param debug if > 0 print debug messages
 * @returns a pointer to the denoise image
 */
// BIS: { 'computeEigenvectorDenoiseImageWASM', 'bisImage', [ 'bisImage', 'bisImage', 'ParamObj',  'debug' ], {"checkorientation" : "all"} }
unsigned char* computeEigenvectorDenoiseImageWASM(unsigned char* input, unsigned char* eigenvectors,const char* jsonstring,int debug) {

  std::unique_ptr<bisJSONParameterList> params(new bisJSONParameterList());
  int ok=params->parseJSONString(jsonstring);
  if (!ok)
    return 0;

  if (debug)
    params->print();

  std::unique_ptr<bisSimpleImage<float> > inp_image(new bisSimpleImage<float>("inp_image"));
  if (!inp_image->linkIntoPointer(input))
    return 0;

  std::unique_ptr<bisSimpleImage<float> > eig_image(new bisSimpleImage<float>("obj_image"));
  if (!eig_image->linkIntoPointer(eigenvectors))
    return 0;

  float scale=params->getFloatValue("scale",10000.0);

  if (debug)  {
    std::cout << "........................" << std::endl;
    std::cout << ".... Beginning image eigenvector denoising " << std::endl;
    int dim[5]; inp_image->getDimensions(dim);
    std::cout << "....      Input  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    eig_image->getDimensions(dim);
    std::cout << "....      Eigenvector  dimensions=" << dim[0] << "," << dim[1] << "," << dim[2] << "," << dim[3] << "," << dim[4] << std::endl;
    std::cout << "........................" << std::endl << std::endl;
  }

  std::unique_ptr<bisSimpleImage<float> > Output(new bisSimpleImage<float>("output"));
  bisSparseEigenSystem::eigenvectorDenoiseImage(inp_image.get(),eig_image.get(),Output.get(),scale);
  return Output->releaseAndReturnRawArray();

}


// This file is part of libigl, a simple c++ geometry processing library.
//
// Copyright (C) 2014 Daniele Panozzo <daniele.panozzo@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 "Viewer.h"

//#include <chrono>
#include <thread>

#include <Eigen/LU>

#include <cmath>
#include <cstdio>
#include <sstream>
#include <iomanip>
#include <iostream>
#include <fstream>
#include <algorithm>
#include <limits>
#include <cassert>

#include <igl/project.h>
//#include <igl/get_seconds.h>
#include <igl/readOBJ.h>
#include <igl/readOFF.h>
#include <igl/adjacency_list.h>
#include <igl/writeOBJ.h>
#include <igl/writeOFF.h>
#include <igl/massmatrix.h>
#include <igl/file_dialog_open.h>
#include <igl/file_dialog_save.h>
#include <igl/quat_mult.h>
#include <igl/axis_angle_to_quat.h>
#include <igl/trackball.h>
#include <igl/two_axis_valuator_fixed_up.h>
#include <igl/snap_to_canonical_view_quat.h>
#include <igl/unproject.h>
#include <igl\circulation.h>
#include <igl/serialize.h>
#include<igl\edge_collapse_is_valid.h>
#include <igl\edge_flaps.h>
#include <igl\vertex_triangle_adjacency.h>
#include <igl/collapse_edge.h>

// Internal global variables used for glfw event handling
//static igl::opengl::glfw::Viewer * __viewer;
static double highdpi = 1;
static double scroll_x = 0;
static double scroll_y = 0;

namespace igl
{
namespace opengl
{
namespace glfw
{

  void Viewer::Init(const std::string config)
  {

    //  load_mesh_from_configuration(config);

  }

  IGL_INLINE Viewer::Viewer():
    data_list(1),
    selected_data_index(0),
    next_data_id(1),
	isPicked(false),
	isActive(false)
  {
    data_list.front().id = 0;

    // Temporary variables initialization
   // down = false;
  //  hack_never_moved = true;
    scroll_position = 0.0f;

    // Per face
    data().set_face_based(false);

#ifndef IGL_VIEWER_VIEWER_QUIET
    const std::string usage(R"(igl::opengl::glfw::Viewer usage:
  [drag]  Rotate scene
  A,a     Toggle animation (tight draw loop)
  F,f     Toggle face based
  I,i     Toggle invert normals
  L,l     Toggle wireframe
  O,o     Toggle orthographic/perspective projection
  T,t     Toggle filled faces
  [,]     Toggle between cameras
  1,2     Toggle between models
  ;       Toggle vertex labels
  :       Toggle face labels
  P    Enable simplefication
  x    Disable simplefication )"
);
    std::cout<<usage<<std::endl;
#endif
  }

  IGL_INLINE Viewer::~Viewer()
  {
  }

  /*
  in order to perform a certian contraction we will need to calculate the cost of a contraction
  to define this cost we attemp to characterize the error at each vertex in the mesh .

  to do this : we associate a symmatric 4x4 matrix named Q with each vertex .

  and we define the error at a vertix v to be

  v = = [vx vy vz 1]T  to be the quadratic form tirangle(v) = vTQv.

  meaning that the error is equal to vTQv.

  let symbol and name a given constraction (V1,v2) as v~

  * now we mush derive a new matrix that we call Q~ which approximates the error at v~
  *
  *  the rule is that Q~ = Q1 + Q2 - WHERE Q1 is the associted matrix for v1 and Q2 is the associated matrix of v2
  *
  *
  * in order to do the constraction we must find a position for v~  - the simple scheme would be to either choose
  * 1. v1
  * 2. v2
  * 3. v1+v2 \2
  *
  *
  * now it depends on which of these have the lowest value at triangelV
  *
  *
  *  meaning which of the following has the lowest value
  *
  * 1. v1T X  Q1 X v1
  *
  * 2. v2T X  Q2 X v2
  *
  * 3. ((v1+v2)/2)T  x Q~ x (v1+v2)
  *
  *
  * now it is mentioned how to calculate the v~
  *
  *
  *
  *
  *
  *

  */

  void calculate_cost(
      std::vector<Eigen::Matrix<double, 4, 4>>& Qs,
      const int e,
      const Eigen::MatrixXd& V,
      const Eigen::MatrixXi& /*F*/,
      const Eigen::MatrixXi& E,
      const Eigen::VectorXi& /*EMAP*/,
      const Eigen::MatrixXi& /*EF*/,
      const Eigen::MatrixXi& /*EI*/,
      double& cost,
      Eigen::RowVectorXd& p)
  {
      auto Q1 = Qs[E.row(e)[0]];
      auto Q2 = Qs[E.row(e)[1]];
      Eigen::Matrix4d Q = Q1 + Q2;
      Q.row(3) = Eigen::Vector4d(0, 0, 0, 1);

      bool invertable = false;
      Eigen::Matrix<double, 4, 4> Qi;
      Q.computeInverseWithCheck(Qi, invertable, 0);

      Eigen::Matrix<double, 4, 1> a;
      if (invertable) {
          // this is the v~
          a = Qi * Eigen::Matrix<double, 4, 1>(0, 0, 0, 1);
      }
      else {
          // we need to choose the midpoint
          a = (V.row(E.row(e)[0]) + V.row(E.row(e)[1])) / 2;
          a(3) = 1;
      }

      cost = a.transpose() * (Q1 + Q2) * a;
      // new point s
      p = Eigen::RowVector3d(a(0), a(1), a(2));
  }

  //Assingment 1 task 6
  IGL_INLINE void Viewer::init_objs_simpelified() {

      data().Q = new std::set<std::pair<double, int> >(); // priority Q contains the cost of edges <cost , number of edge >
      data().EMAP = new Eigen::VectorXi();               // connects faces to edges

      /*
      E - matrix of edges
      |1 , 3 |
      |2 , 5 |
      |3, 1  |

       source , destenation

      */

      data().E = new Eigen::MatrixXi();                   // this is the Edges -> edge is represented by <index of source vertex, index of destinations vertex> - matrix []

      data().EI = new Eigen::MatrixXi();                  //connects edge to vertex index in triangle (0 1 2)

      data().EF = new Eigen::MatrixXi();                    //connects edges to faces this is a matrix that have [index of first face , index of second face ]

      data().C = new Eigen::MatrixXd();                 //position of the new vertex after collapsing the corresponding edge

      data().V1 = new Eigen::MatrixXd(data().V);            // the new back up for v

      data().F1 = new Eigen::MatrixXi(data().F);            // the new backup for F

      data().Qs = std::vector<Eigen::Matrix<double, 4, 4>>();
      data().Qit = new std::vector<std::set<std::pair<double, int> >::iterator >(); // saved iterator for every dege so that

      //this gets the EMAP EF E EI ready
      edge_flaps(*data().F1, *data().E, *data().EMAP, *data().EF, *data().EI);

      data().Qit->resize(data().E->rows());

      data().C->resize(data().E->rows(), data().V.cols());

      Eigen::VectorXd costs(data().E->rows());

      data().Q->clear();

      auto VF = std::vector<std::vector<int> >();
      auto VFi = std::vector<std::vector<int> >();

      vertex_triangle_adjacency(data().V, data().F, VF, VFi);

      // this is for getting the Q ready
      for (int v = 0; v < data().V.rows(); v++) {

          std::vector<int> faces = VF[v];
          Eigen::Matrix4d Q = Eigen::Matrix4d::Zero();

          for (int face : faces) {
              auto n = data().F_normals.row(face).normalized();
              float d = 0;
              for (int j = 0; j < 3; j++)
              d += (-n[j] * data().V.row(v)[j]);
              Eigen::RowVector4d p(n[0], n[1], n[2], d);
              p = p.transpose();
              Q += p.transpose() * p;
          }

          data().Qs.push_back(Q);
      }
      for (int e = 0; e < data().E->rows(); e++)
      {
     // here i want to calculate all the edges cost
          double cost = 0;
          Eigen::RowVectorXd p(1, 3);

          calculate_cost(data().Qs, e, *data().V1, *data().F1, *data().E, *data().EMAP, *data().EF, *data().EI, cost, p);
          data().C->row(e) = p;
          (*data().Qit)[e] = (data().Q->insert(std::pair<double, int>(cost, e))).first;
      }

  }

  // ASSIGNMENT 1 TASK 7 & TASK 9

  IGL_INLINE void Viewer::simplify_mesh(int num_of_faces_to_delete) {

      printf("# of faces to delete %d \n", num_of_faces_to_delete);
      bool something_collapsed = false;
      int num_of_collapsed_edges = 0;

      for (int i = 0; i < num_of_faces_to_delete; i++) {

          if (!collapse_edge(calculate_cost, data().Qs,
              *data().V1,*data().F1,
              *data().E, *data().EMAP,
              *data().EF, *data().EI ,
              *data().Q, *data().Qit,
              * data().C))
          {
              break;

          }
          something_collapsed = true;
          data().num_collapsed++;

      }
      if (something_collapsed) {
          printf("succesfully collapsed # %d of edges \n",data().num_collapsed);
          data().clear();
          data().set_mesh(*data().V1, *data().F1);
          data().set_face_based(true);
          data().dirty = 157;

      }

  }
  IGL_INLINE bool Viewer::collapse_edge(
      const std::function<void(
          std::vector<Eigen::Matrix<double, 4, 4>>& Qs,
          const int,
          const Eigen::MatrixXd&,
          const Eigen::MatrixXi&,
          const Eigen::MatrixXi&,
          const Eigen::VectorXi&,
          const Eigen::MatrixXi&,
          const Eigen::MatrixXi&,
          double&,
          Eigen::RowVectorXd&)>& cost_and_placement,
      std::vector<Eigen::Matrix<double, 4, 4>>& Qs,
      Eigen::MatrixXd& V,
      Eigen::MatrixXi& F,
      Eigen::MatrixXi& E,
      Eigen::VectorXi& EMAP,
      Eigen::MatrixXi& EF,
      Eigen::MatrixXi& EI,
      std::set<std::pair<double, int> >& Q,
      std::vector<std::set<std::pair<double, int> >::iterator >& Qit,
      Eigen::MatrixXd& C)
   {

      printf("in collapse edge func \n");

      using namespace Eigen;

      int e;
      int e1;
      int e2;
      int f1;
      int f2;

      if (Q.empty()) {
          printf("Q is empty");
          return false;
      }
      printf("QUEUE SIZE IS %d", Q.size());

      // now we know that the qeueu is not empty and we have some data in it
      // we want to get the first pair , meaning the first edge - with the lowest cost?

      std::pair<double, int> p = *(Q.begin());

      if (p.first == std::numeric_limits<double>::infinity())
      {
          printf("its infinity - returning \n");
          // min cost edge is infinite cost
          return false;
      }

      // the first edge is not ininity and we want to remove it
      // remember that the data of first edge is saved in p
      Q.erase(Q.begin());
      e = p.second;
      Qit[e] = Q.end();
      std::vector<int> N = igl::circulation(e, true, EMAP, EF, EI);
      std::vector<int> Nd = igl::circulation(e, false, EMAP, EF, EI);
      N.insert(N.begin(), Nd.begin(), Nd.end());

      bool collapsed = this->collapse(e, C.row(e), V, F, E, EMAP, EF, EI, Qs, e1, e2, f1, f2);

      if (collapsed) {
          double cost_of_Edge = p.first;

          printf("we collapsed the following edges : e -> %d , cost : %f     e1 -> %d  , cost: %f   e2 ->  , cost:    ", e, cost_of_Edge, e1, Qit[e1]->first);
          // Erase the two, other collapsed edges
          Q.erase(Qit[e1]);
          Qit[e1] = Q.end();
          Q.erase(Qit[e2]);
          Qit[e2] = Q.end();

          // update local neighbors
          // loop over original face neighbors
          for (auto n : N)
          {
              if (F(n, 0) != IGL_COLLAPSE_EDGE_NULL ||
                  F(n, 1) != IGL_COLLAPSE_EDGE_NULL ||
                  F(n, 2) != IGL_COLLAPSE_EDGE_NULL)
              {
                  for (int v = 0; v < 3; v++)
                  {

                      const int ei = EMAP(v * F.rows() + n);

                      Q.erase(Qit[ei]);

                      double cost;
                      RowVectorXd place;
                      cost_and_placement(Qs, ei, V, F, E, EMAP, EF, EI, cost, place);

                      Qit[ei] = Q.insert(std::pair<double, int>(cost, ei)).first;
                      C.row(ei) = place;
                  }
              }
          }
      }
      else
      {

          p.first = std::numeric_limits<double>::infinity();
          Qit[e] = Q.insert(p).first;
      }
      return collapsed;
  }

  IGL_INLINE bool Viewer::collapse(
      const int e,
      const Eigen::RowVectorXd& p,
      Eigen::MatrixXd& V,
      Eigen::MatrixXi& F,
      Eigen::MatrixXi& E,
      Eigen::VectorXi& EMAP,
      Eigen::MatrixXi& EF,
      Eigen::MatrixXi& EI,
      std::vector<Eigen::Matrix<double, 4, 4>>& Qs,
      int& a_e1,
      int& a_e2,
      int& a_f1,
      int& a_f2)
  {
      // Assign this to 0 rather than, say, -1 so that deleted elements will get
                 // draw as degenerate elements at vertex 0 (which should always exist and
                 // never get collapsed to anything else since it is the smallest index)
      using namespace Eigen;
      using namespace std;
      const int eflip = E(e, 0) > E(e, 1);
      // source and destination
      const int s = eflip ? E(e, 1) : E(e, 0);
      const int d = eflip ? E(e, 0) : E(e, 1);

      if (!igl::edge_collapse_is_valid(e, F, E, EMAP, EF, EI))
      {
          return false;
      }

      // Important to grab neighbors of d before monkeying with edges
      const std::vector<int> nV2Fd = igl::circulation(e, !eflip, EMAP, EF, EI);

      // The following implementation strongly relies on s<d
      assert(s < d && "s should be less than d");
      Qs[s] = Qs[s] + Qs[d];
      // move source and destination to midpoint
      V.row(s) = p;
      V.row(d) = p;

      // Helper function to replace edge and associate information with NULL
      const auto& kill_edge = [&E, &EI, &EF](const int e)
      {
          E(e, 0) = IGL_COLLAPSE_EDGE_NULL;
          E(e, 1) = IGL_COLLAPSE_EDGE_NULL;
          EF(e, 0) = IGL_COLLAPSE_EDGE_NULL;
          EF(e, 1) = IGL_COLLAPSE_EDGE_NULL;
          EI(e, 0) = IGL_COLLAPSE_EDGE_NULL;
          EI(e, 1) = IGL_COLLAPSE_EDGE_NULL;
      };

      // update edge info
      // for each flap
      const int m = F.rows();
      for (int side = 0; side < 2; side++)
      {
          const int f = EF(e, side);
          const int v = EI(e, side);
          const int sign = (eflip == 0 ? 1 : -1) * (1 - 2 * side);
          // next edge emanating from d
          const int e1 = EMAP(f + m * ((v + sign * 1 + 3) % 3));
          // prev edge pointing to s
          const int e2 = EMAP(f + m * ((v + sign * 2 + 3) % 3));
          assert(E(e1, 0) == d || E(e1, 1) == d);
          assert(E(e2, 0) == s || E(e2, 1) == s);
          // face adjacent to f on e1, also incident on d
          const bool flip1 = EF(e1, 1) == f;
          const int f1 = flip1 ? EF(e1, 0) : EF(e1, 1);
          assert(f1 != f);
          assert(F(f1, 0) == d || F(f1, 1) == d || F(f1, 2) == d);
          // across from which vertex of f1 does e1 appear?
          const int v1 = flip1 ? EI(e1, 0) : EI(e1, 1);
          // Kill e1
          kill_edge(e1);
          // Kill f
          F(f, 0) = IGL_COLLAPSE_EDGE_NULL;
          F(f, 1) = IGL_COLLAPSE_EDGE_NULL;
          F(f, 2) = IGL_COLLAPSE_EDGE_NULL;
          // map f1's edge on e1 to e2
          assert(EMAP(f1 + m * v1) == e1);
          EMAP(f1 + m * v1) = e2;
          // side opposite f2, the face adjacent to f on e2, also incident on s
          const int opp2 = (EF(e2, 0) == f ? 0 : 1);
          assert(EF(e2, opp2) == f);
          EF(e2, opp2) = f1;
          EI(e2, opp2) = v1;
          // remap e2 from d to s
          E(e2, 0) = E(e2, 0) == d ? s : E(e2, 0);
          E(e2, 1) = E(e2, 1) == d ? s : E(e2, 1);
          if (side == 0)
          {
              a_e1 = e1;
              a_f1 = f;
          }
          else
          {
              a_e2 = e1;
              a_f2 = f;
          }
      }

      // finally, reindex faces and edges incident on d. Do this last so asserts
      // make sense.
      //
      // Could actually skip first and last, since those are always the two
      // collpased faces.
      for (auto f : nV2Fd)
      {
          for (int v = 0; v < 3; v++)
          {
              if (F(f, v) == d)
              {
                  const int flip1 = (EF(EMAP(f + m * ((v + 1) % 3)), 0) == f) ? 1 : 0;
                  const int flip2 = (EF(EMAP(f + m * ((v + 2) % 3)), 0) == f) ? 0 : 1;
                  assert(
                      E(EMAP(f + m * ((v + 1) % 3)), flip1) == d ||
                      E(EMAP(f + m * ((v + 1) % 3)), flip1) == s);
                  E(EMAP(f + m * ((v + 1) % 3)), flip1) = s;
                  assert(
                      E(EMAP(f + m * ((v + 2) % 3)), flip2) == d ||
                      E(EMAP(f + m * ((v + 2) % 3)), flip2) == s);
                  E(EMAP(f + m * ((v + 2) % 3)), flip2) = s;
                  F(f, v) = s;
                  break;
              }
          }
      }
      // Finally, "remove" this edge and its information
      kill_edge(e);

      return true;
  }
  IGL_INLINE bool Viewer::check_if_infinity(double first) {
      return first == std::numeric_limits<double>::infinity();

  }

  IGL_INLINE bool Viewer::load_mesh_from_file(
      const std::string & mesh_file_name_string)
  {

    // Create new data slot and set to selected
    if(!(data().F.rows() == 0  && data().V.rows() == 0))
    {
      append_mesh();
    }
    data().clear();

    size_t last_dot = mesh_file_name_string.rfind('.');
    if (last_dot == std::string::npos)
    {
      std::cerr<<"Error: No file extension found in "<<
        mesh_file_name_string<<std::endl;
      return false;
    }

    std::string extension = mesh_file_name_string.substr(last_dot+1);

    if (extension == "off" || extension =="OFF")
    {
      Eigen::MatrixXd V;
      Eigen::MatrixXi F;
      if (!igl::readOFF(mesh_file_name_string, V, F))
        return false;
      data().set_mesh(V,F);
    }
    else if (extension == "obj" || extension =="OBJ")
    {
      Eigen::MatrixXd corner_normals;
      Eigen::MatrixXi fNormIndices;

      Eigen::MatrixXd UV_V;
      Eigen::MatrixXi UV_F;
      Eigen::MatrixXd V;
      Eigen::MatrixXi F;

      if (!(
            igl::readOBJ(
              mesh_file_name_string,
              V, UV_V, corner_normals, F, UV_F, fNormIndices)))
      {
        return false;
      }

      data().set_mesh(V,F);
      if (UV_V.rows() > 0)
      {
          data().set_uv(UV_V, UV_F);
      }

    }
    else
    {
      // unrecognized file type
      printf("Error: %s is not a recognized file type.\n",extension.c_str());
      return false;
    }

    data().compute_normals();
    data().uniform_colors(Eigen::Vector3d(51.0/255.0,43.0/255.0,33.3/255.0),
                   Eigen::Vector3d(255.0/255.0,228.0/255.0,58.0/255.0),
                   Eigen::Vector3d(255.0/255.0,235.0/255.0,80.0/255.0));

    // Alec: why?
    if (data().V_uv.rows() == 0)
    {
      data().grid_texture();
    }

    //for (unsigned int i = 0; i<plugins.size(); ++i)
    //  if (plugins[i]->post_load())
    //    return true;

    return true;
  }

  IGL_INLINE bool Viewer::save_mesh_to_file(
      const std::string & mesh_file_name_string)
  {
    // first try to load it with a plugin
    //for (unsigned int i = 0; i<plugins.size(); ++i)
    //  if (plugins[i]->save(mesh_file_name_string))
    //    return true;

    size_t last_dot = mesh_file_name_string.rfind('.');
    if (last_dot == std::string::npos)
    {
      // No file type determined
      std::cerr<<"Error: No file extension found in "<<
        mesh_file_name_string<<std::endl;
      return false;
    }
    std::string extension = mesh_file_name_string.substr(last_dot+1);
    if (extension == "off" || extension =="OFF")
    {
      return igl::writeOFF(
        mesh_file_name_string,data().V,data().F);
    }
    else if (extension == "obj" || extension =="OBJ")
    {
      Eigen::MatrixXd corner_normals;
      Eigen::MatrixXi fNormIndices;

      Eigen::MatrixXd UV_V;
      Eigen::MatrixXi UV_F;

      return igl::writeOBJ(mesh_file_name_string,
          data().V,
          data().F,
          corner_normals, fNormIndices, UV_V, UV_F);
    }
    else
    {
      // unrecognized file type
      printf("Error: %s is not a recognized file type.\n",extension.c_str());
      return false;
    }
    return true;
  }

  IGL_INLINE bool Viewer::load_scene()
  {
    std::string fname = igl::file_dialog_open();
    if(fname.length() == 0)
      return false;
    return load_scene(fname);
  }

  IGL_INLINE bool Viewer::load_scene(std::string fname)
  {
   // igl::deserialize(core(),"Core",fname.c_str());
    igl::deserialize(data(),"Data",fname.c_str());
    return true;
  }

  IGL_INLINE bool Viewer::save_scene()
  {
    std::string fname = igl::file_dialog_save();
    if (fname.length() == 0)
      return false;
    return save_scene(fname);
  }

  IGL_INLINE bool Viewer::save_scene(std::string fname)
  {
    //igl::serialize(core(),"Core",fname.c_str(),true);
    igl::serialize(data(),"Data",fname.c_str());

    return true;
  }

  IGL_INLINE void Viewer::open_dialog_load_mesh()
  {
    std::string fname = igl::file_dialog_open();

    if (fname.length() == 0)
      return;

    this->load_mesh_from_file(fname.c_str());
  }

  IGL_INLINE void Viewer::open_dialog_save_mesh()
  {
    std::string fname = igl::file_dialog_save();

    if(fname.length() == 0)
      return;

    this->save_mesh_to_file(fname.c_str());
  }

  IGL_INLINE ViewerData& Viewer::data(int mesh_id /*= -1*/)
  {
    assert(!data_list.empty() && "data_list should never be empty");
    int index;
    if (mesh_id == -1)
      index = selected_data_index;
    else
      index = mesh_index(mesh_id);

    assert((index >= 0 && index < data_list.size()) &&
      "selected_data_index or mesh_id should be in bounds");
    return data_list[index];
  }

  IGL_INLINE const ViewerData& Viewer::data(int mesh_id /*= -1*/) const
  {
    assert(!data_list.empty() && "data_list should never be empty");
    int index;
    if (mesh_id == -1)
      index = selected_data_index;
    else
      index = mesh_index(mesh_id);

    assert((index >= 0 && index < data_list.size()) &&
      "selected_data_index or mesh_id should be in bounds");
    return data_list[index];
  }

  IGL_INLINE int Viewer::append_mesh(bool visible /*= true*/)
  {
    assert(data_list.size() >= 1);

    data_list.emplace_back();
    selected_data_index = data_list.size()-1;
    data_list.back().id = next_data_id++;
    //if (visible)
    //    for (int i = 0; i < core_list.size(); i++)
    //        data_list.back().set_visible(true, core_list[i].id);
    //else
    //    data_list.back().is_visible = 0;
    return data_list.back().id;
  }

  IGL_INLINE bool Viewer::erase_mesh(const size_t index)
  {
    assert((index >= 0 && index < data_list.size()) && "index should be in bounds");
    assert(data_list.size() >= 1);
    if(data_list.size() == 1)
    {
      // Cannot remove last mesh
      return false;
    }
    data_list[index].meshgl.free();
    data_list.erase(data_list.begin() + index);
    if(selected_data_index >= index && selected_data_index > 0)
    {
      selected_data_index--;
    }

    return true;
  }

  IGL_INLINE size_t Viewer::mesh_index(const int id) const {
    for (size_t i = 0; i < data_list.size(); ++i)
    {
      if (data_list[i].id == id)
        return i;
    }
    return 0;
  }

  // ASSIGNMENT 1 - TASK 4

  IGL_INLINE bool Viewer::load_mesh_from_configuration(const std::string config , bool assignment2) {

      if (assignment2)
          return true;
          //init_obj_collision();

      else {
          std::string mesh_path;
          std::fstream infile;
          infile.open("configuration.txt");
          if (!infile) {
              std::cout << "Can't open file configuration.txt\n";
              return false;
          }
          else {
              while (getline(infile, mesh_path)) {
                  std::cout << "opening " << mesh_path << std::endl;
                  this->load_mesh_from_file(mesh_path);
                 // if (enable_simplefication) this->init_simplefication_objs();
              }
              infile.close();
              return true;
          }
      }
  }

  /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  //////////////////////////////////////////// ASSIGNMENT 2 //////////////////////////////////////////////////////////////////////

  IGL_INLINE void Viewer::collission_init(const std::string config) {

          std::string mesh_path;
          std::fstream infile;
        //  this->animating_collision = false;
          infile.open(config);
          if (!infile) {
              std::cout << "Can't open file configuration.txt\n";
              return;
          }
          int mesh = 0;

          printf("both opened \n");

          /// <summary>
          ///  in config we have 2 cubes
          /// cube 1 and cube 2
          ///
          /// the while will go over both and init trees for both object .
          ///
          ///
          ///
          /// </summary>
     /*   while (getline(infile, mesh_path)) {

              this->load_mesh_from_file(mesh_path);
              this->data().MyTranslate(Eigen::Vector3f((mesh - 0.5) * 0.5, 0, 0), false);

              igl::AABB<Eigen::MatrixXd, 3>* tree = new igl::AABB<Eigen::MatrixXd, 3>();
              tree->init(this->data().V, this->data().F);
              this->trees.push_back(tree);
              this->tree_roots.push_back(tree);
              this->last_box.push_back(&tree->m_box);

              data().original_V = new Eigen::MatrixXd(this->data().V);
              data().original_F = new Eigen::MatrixXi(this->data().F);

              this->data().show_overlay_depth = false;
              this->data().show_lines = false;
              this->data().point_size = 1;
              this->data().line_width = 1;
              Eigen::RowVector3d red_color(1, 0, 0);
              this->draw_m_box(this->selected_data_index, tree->m_box, red_color);
              mesh++;
          }*/

  }

  Eigen::Matrix4d Viewer::CalcParentsTrans(int indx)
  {
	  Eigen::Matrix4d prevTrans = Eigen::Matrix4d::Identity();

	  for (int i = indx; parents[i] >= 0; i = parents[i])
	  {
		  //std::cout << "parent matrix:\n" << scn->data_list[scn->parents[i]].MakeTrans() << std::endl;
		  prevTrans = data_list[parents[i]].MakeTransd() * prevTrans;
	  }

	  return prevTrans;
  }

} // end namespace
} // end namespace

}