#ifndef CONTEXT_HELIB_F2_HPP
#define CONTEXT_HELIB_F2_HPP

#include <NTL/BasicThreadPool.h>
#include <chrono>
#include <type_traits>
#include <unordered_map>
#include <complex>
#include <cmath>
#include "bits.hpp"
NTL_CLIENT

#include "circuit.hpp"
#include "context.hpp"

#include "Ctxt.h"
#include "EncryptedArray.h"
#include "FHE.h"

#include "binaryArith.h"
#include "binaryCompare.h"
#include "intraSlot.h"

namespace SHEEP {

template <typename PlaintextT, typename CiphertextT>
class ContextHElib : public Context<PlaintextT, CiphertextT> {
 public:
  typedef PlaintextT Plaintext;
  typedef CiphertextT Ciphertext;
  // typedef std::vector<PlaintextT> Ciphertext;
  // typedef PlaintextT CiphertextEl;

  /// constructors
  ContextHElib(long p,               // plaintext modulus
               long param_set = 0,   // parameter set, from 0 (tiny) to 4 (huge)
               long bootstrapl = 1,  // bootstrap or not?
               long hamming_weight = 128)
      :  // Hamming weight of secret key

        m_p(p),
        m_param_set(param_set),
        m_bootstrapl(bootstrapl),
        m_w(hamming_weight) {
    /// BITWIDTH(bool) is 8, so need to deal with this by hand...
    //// (better to specialize class?)
    if (std::is_same<Plaintext, bool>::value)
      m_bitwidth = 1;
    else
      m_bitwidth = BITWIDTH(Plaintext);

    ////  populate the map that will allow us to set parameters via an input
    ///file (or string)

    this->m_param_name_map.insert({"BaseParamSet", m_param_set});
    this->m_param_name_map.insert({"HammingWeight", m_w});
    this->m_param_name_map.insert({"Bootstrap", m_bootstrapl});
    this->m_param_name_map.insert({"m", m_m});
    this->m_param_name_map.insert({"phim", m_phim});
    this->m_param_name_map.insert({"d", m_d});
    this->m_param_name_map.insert({"m1", m_m1});
    this->m_param_name_map.insert({"m2", m_m2});
    this->m_param_name_map.insert({"m3", m_m3});
    this->m_param_name_map.insert({"g1", m_g1});
    this->m_param_name_map.insert({"g2", m_g2});
    this->m_param_name_map.insert({"g3", m_g3});
    this->m_param_name_map.insert({"ord1", m_ord1});
    this->m_param_name_map.insert({"ord2", m_ord2});
    this->m_param_name_map.insert({"ord3", m_ord3});
    this->m_param_name_map.insert({"c", m_c});
    this->m_param_name_map.insert({"BitsPerLevel", m_B});
    this->m_param_name_map.insert({"Levels", m_L});
    /// sizes of objects in bytes.  Assign values when they are constructed.
    this->m_ciphertext_size = 0;
    this->m_public_key_size = 0;
    this->m_private_key_size = 0;

    /// configure
    configure();
  }

  void configure() {
    m_bootstrap = (bool)m_bootstrapl;
    /// Set all the other parameters.

    long mValues[][15] = {
        // { p, phi(m),   m,   d, m1, m2, m3,    g1,   g2,   g3, ord1,ord2,ord3,
        // B,c}
        {2, 48, 105, 12, 3, 35, 0, 71, 76, 0, 2, 2, 0, 25, 2},
        {2, 600, 1023, 10, 11, 93, 0, 838, 584, 0, 10, 6, 0, 25, 2},
        {2, 2304, 4641, 24, 7, 3, 221, 3979, 3095, 3760, 6, 2, -8, 25, 3},
        {2, 15004, 15709, 22, 23, 683, 0, 4099, 13663, 0, 22, 31, 0, 25, 3},
        {2, 27000, 32767, 15, 31, 7, 151, 11628, 28087, 25824, 30, 6, -10, 28,
         4}};

    long* vals = mValues[m_param_set];
    //// if parameters were not set explicitly in a parameters file, take the
    ///value from /  the mValues array.  (But if they were set explicitly, use
    ///that value!)

    if (!this->override_param("m")) m_m = vals[2];
    if (!this->override_param("phi(m)")) m_phim = vals[1];
    if (!this->override_param("d")) m_d = vals[3];
    if (!this->override_param("m1")) m_m1 = vals[4];
    if (!this->override_param("m2")) m_m2 = vals[5];
    if (!this->override_param("m3")) m_m3 = vals[6];
    if (!this->override_param("g1")) m_g1 = vals[7];
    if (!this->override_param("g2")) m_g2 = vals[8];
    if (!this->override_param("g3")) m_g3 = vals[9];
    if (!this->override_param("ord1")) m_ord1 = vals[10];
    if (!this->override_param("ord2")) m_ord2 = vals[11];
    if (!this->override_param("ord3")) m_ord3 = vals[12];
    if (!this->override_param("B")) m_B = vals[13];
    if (!this->override_param("c")) m_c = vals[14];

    NTL::Vec<long> mvec;
    append(mvec, m_m1);
    if (m_m2 > 1) append(mvec, m_m2);
    if (m_m3 > 1) append(mvec, m_m3);

    std::vector<long> gens;
    gens.push_back(m_g1);
    if (m_g2 > 1) gens.push_back(m_g2);
    if (m_g3 > 1) gens.push_back(m_g3);

    std::vector<long> ords;
    ords.push_back(m_ord1);
    if (abs(m_ord2) > 1) ords.push_back(m_ord2);
    if (abs(m_ord3) > 1) ords.push_back(m_ord3);

    /// number of levels  (copied from HElib's Test_binaryCompare)
    if (!this->override_param("Levels")) {
      if (m_bootstrap)
        m_L = 30;
      else
        m_L = 3 + NTL::NumBits(m_bitwidth + 2);
    }
    /// initialize HElib context
    m_helib_context = new FHEcontext(m_m, m_p, 1, gens, ords);
    m_helib_context->bitsPerLevel = m_B;
    /// modify context, add primes to modulus chain
    buildModChain(*m_helib_context, m_L, m_c, 8);

    if (m_bootstrap) {
      m_helib_context->makeBootstrappable(mvec, /*t=*/0,
                                          /*flag=*/false, /*cacheType=DCRT*/ 2);
    }

    // unpack slot encoding
    buildUnpackSlotEncoding(m_unpackSlotEncoding, *(m_helib_context->ea));

    /// create secret key structure
    m_secretKey = new FHESecKey(*m_helib_context);

    m_publicKey = m_secretKey;  //// points to the same place

    /// generate a secret key
    m_secretKey->GenSecKey(m_w);  /// Hamming weight of 128

    addSome1DMatrices(*m_secretKey);
    addFrbMatrices(*m_secretKey);

    /// how big are keys?
    this->m_private_key_size = sizeof(*m_secretKey);
    this->m_public_key_size = sizeof(*m_publicKey);

    if (m_bootstrap) m_secretKey->genRecryptData();

    m_ea = new EncryptedArray(*m_helib_context);

    this->m_nslots = m_ea->size();
  };

  // destructor
  virtual ~ContextHElib() {
    /// delete everything we new-ed in the constructor
    if (m_ea != NULL) delete m_ea;
    if (m_secretKey != NULL) delete m_secretKey;
    if (m_helib_context != NULL) delete m_helib_context;
  };

  virtual Ciphertext encrypt(std::vector<Plaintext> pt) = 0;
  virtual std::vector<Plaintext> decrypt(Ciphertext pt) = 0;

 protected:
  long m_param_set;  // which set of parameters to use (0 to 4).
  long m_p;          //  modulus of plaintext
  long m_B;          // number of bits per level
  long m_L;          // maximum number of homomorphic levels
  long m_w;          // Hamming weight of secret key
  long m_c;          // number of columns in key-switching matrix

  long m_m;
  long m_phim;
  long m_d;
  long m_m1;
  long m_m2;
  long m_m3;
  long m_g1;
  long m_g2;
  long m_g3;
  long m_ord1;
  long m_ord2;
  long m_ord3;
  EncryptedArray* m_ea;
  FHESecKey* m_secretKey;
  const FHEPubKey* m_publicKey;
  FHEcontext* m_helib_context;
  int m_bitwidth;
  std::vector<zzX> m_unpackSlotEncoding;
  bool m_bootstrap;
  long m_bootstrapl;  /// long version of the bootstrap flag to allow it to be
                      /// settable from param_name_map

};  //// end of ContextHElib class definition.

////////////////////////////////////////////////////////////////////////////////////
///  ContextHElib_F2 -  use p=2, do everything with arrays of Ciphertext,
///  and binary operations for add, multiply, compare etc.
template <typename PlaintextT>
class ContextHElib_F2 : public ContextHElib<PlaintextT, NTL::Vec<Ctxt> > {
 public:
  typedef PlaintextT Plaintext;
  typedef NTL::Vec<Ctxt> Ciphertext;

  ContextHElib_F2(
      long param_set = 0,         // parameter set, from 0 (tiny) to 4 (huge)
      bool bootstrap = true,      // bootstrap or not?
      long hamming_weight = 128)  // Hamming weight of secret key
      : ContextHElib<Plaintext, Ciphertext>(2, param_set, bootstrap,
                                            hamming_weight) {
    /// this is not nice, but for Compare, it helps to know if we are dealing
    /// with signed or unsigned inputs
    m_signed_plaintext = (std::is_same<Plaintext, int8_t>::value ||
                          std::is_same<Plaintext, int16_t>::value ||
                          std::is_same<Plaintext, int32_t>::value);

    m_bool_plaintext = std::is_same<Plaintext, bool>::value;
  }

  Ciphertext encrypt(std::vector<Plaintext> pt) {
    if (pt.size() > this->m_nslots) {
      throw std::runtime_error(
          "The number of slots is greater than the number of SIMD operations "
          "that can be done at a time");
    }

    Ctxt mu(*(this->m_publicKey), (long)this->m_nslots);
    Ciphertext ct;

    resize(ct, this->m_bitwidth, mu);

    for (int j = 0; j < this->m_bitwidth; j++) {
      vector<ZZX> sliced_pt = vector<ZZX>(this->m_nslots);

      // sliced_pt[i] is the jth bit of input i
      for (int i = 0; i < pt.size(); i++) {
        sliced_pt[i] = ZZX((pt[i] >> j) & 1);
      }

      this->m_ea->encrypt(ct[j], *(this->m_publicKey), sliced_pt);
    }
    return ct;
  }

  std::vector<Plaintext> decrypt(Ciphertext ct) {
    std::vector<long> ct_decrypt = std::vector<long>(this->m_nslots);
    std::vector<Plaintext> pt_transformed =
        std::vector<Plaintext>(this->m_nslots);

    decryptBinaryNums(ct_decrypt, CtPtrs_VecCt(ct), *(this->m_secretKey),
                      *(this->m_ea));

    for (int i = 0; i < this->m_nslots; i++) {
      pt_transformed[i] = ct_decrypt[i] % int(pow(2, this->m_bitwidth));
    }
    return pt_transformed;
  }

  std::string encrypt_and_serialize(std::vector<PlaintextT> pt) {
    Ciphertext ct = encrypt(pt);
    std::stringstream ss;
    for (int i=0; i< this->m_bitwidth; ++i) {
      ss << ct[i];
    }
    std::string ctstring = ss.str();
    return ctstring;
  };

  Ciphertext Negate(Ciphertext a) {
    /// bootstrapping method copied from HElib's Test_binaryCompare
    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
      }
    }

    /// Two's complement negation - negate all bits then add one
    Ciphertext output;

    for (int i = 0; i < this->m_bitwidth; ++i) {
      Ctxt abit = a[i];
      abit.addConstant(to_ZZX(1L));
      output.append(abit);
    }

    if (this->m_bitwidth == 1)
      return output;  // for a bool, we are already done..

    /// for integers, need to add 1.
    std::vector<Plaintext> one;
    for (int i = 0; i < this->m_nslots; i++) {
      one.push_back(1);
    }

    Ciphertext one_enc = encrypt(one);
    Ciphertext output_final = Add(output, one_enc);

    return output_final;
  }

  Ciphertext Maximum(Ciphertext a, Ciphertext b) {
    /// "Maximum" i.e. "OR" only valid for bool inputs.  If not, call the
    /// base-class function (which will throw a GateNotImplemented error).
    if (!this->m_bool_plaintext) Context<Plaintext, Ciphertext>::Maximum(a, b);
    /// OR(a,b) = XOR( XOR(a,b), AND(a,b))
    Ciphertext output;

    Ctxt a1 = a[0];
    Ctxt a2 = a[0];
    a1 += b[0];  // XOR(a,b)
    a2 *= b[0];  // AND(a,b)
    a1 += a2;    // XOR the previous two lines
    output.append(a1);
    return output;
  }

  Ciphertext Compare_unsigned(Ciphertext a, Ciphertext b) {
    Ctxt mu(*(this->m_publicKey));
    Ctxt ni(*(this->m_publicKey));
    Ciphertext cmax, cmin;
    CtPtrs_VecCt wMin(cmin), wMax(cmax);  /// wrappers around output vectors
    compareTwoNumbers(wMax, wMin, mu, ni, CtPtrs_VecCt(a), CtPtrs_VecCt(b),
                      &(this->m_unpackSlotEncoding));
    /// mu is now a Ctxt which is the encryption of 1 if a>b and 0 otherwise.
    /// but we need to put it into NTL::Vec<Ctxt> as that is our new
    /// "Ciphertext" type.
    Ciphertext output;
    output.append(mu);
    return output;
  }

  Ciphertext Compare_signed(Ciphertext a, Ciphertext b) {
    //// subtract a-b and look at sign-bit
    Ciphertext b_minus_a = Subtract(b, a);
    Ciphertext output;

    Ctxt sign_bit =
        b_minus_a[this->m_bitwidth - 1];  /// is sign-bit set?  if yes, b
    output.append(sign_bit);
    return output;
  }

  Ciphertext Compare(Ciphertext a, Ciphertext b) {
    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
        b[i].modDownToLevel(5);
      }
    }
    if (this->m_signed_plaintext)
      return Compare_signed(a, b);
    else
      return Compare_unsigned(a, b);
  }

  Ciphertext Subtract(Ciphertext a, Ciphertext b) {
    if (this->m_bitwidth == 1)
      return Add(a, b);  //// for bools, add and subtract are the same

    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
      }
    }

    Ciphertext output;
    Ciphertext b_neg = Negate(b);
    CtPtrs_VecCt wout(output);
    addTwoNumbers(wout, CtPtrs_VecCt(a), CtPtrs_VecCt(b_neg), this->m_bitwidth,
                  &(this->m_unpackSlotEncoding));
    return output;
  }

  Ciphertext Add(Ciphertext a, Ciphertext b) {
    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
      }
    }

    Ciphertext sum;
    CtPtrs_VecCt wsum(sum);

    addTwoNumbers(wsum, CtPtrs_VecCt(a), CtPtrs_VecCt(b), this->m_bitwidth,
                  &(this->m_unpackSlotEncoding));

    return sum;
  }

  Ciphertext Multiply(Ciphertext a, Ciphertext b) {
    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
      }
    }

    Ciphertext product;
    CtPtrs_VecCt wprod(product);

    multTwoNumbers(wprod, CtPtrs_VecCt(a), CtPtrs_VecCt(b), false,
                   this->m_bitwidth, &(this->m_unpackSlotEncoding));

    return product;
  }

  Ciphertext Select(Ciphertext s, Ciphertext a, Ciphertext b) {
    if (this->m_bootstrap) {
      for (int i = 0; i < this->m_bitwidth; ++i) {
        a[i].modDownToLevel(5);
        b[i].modDownToLevel(5);
      }
    }
    /// s is 0 or 1
    /// for each bit of a,b,output, do output = s*a + (1-s)*b
    Ciphertext output;

    for (int i = 0; i < this->m_bitwidth; ++i) {
      Ctxt sbit = s[0];
      Ctxt abit = a[i];
      Ctxt bbit = b[i];
      abit *= sbit;
      sbit.addConstant(to_ZZX(-1L));
      sbit.multByConstant(to_ZZX(-1L));
      sbit *= bbit;
      abit += sbit;
      output.append(abit);
    }
    return output;
  }

  Ciphertext Rotate(Ciphertext a, long n) {
    /// Cyclically rotate the linear array by n positions
    if (n > this->m_nslots) {
      throw std::runtime_error("Error in Rotate: cannot rotate by more than nslots positions");
    }
    Ciphertext result;
    if (n > 0) n = n - this->m_ninputs;
    /// loop over all bits
    for (int j = 0; j < this->m_bitwidth; j++) {
      Ctxt result_bit(a[j]);
      this->m_ea->rotate(result_bit, n);
      result.append(result_bit);
    }
    return result;

  }

 private:
  bool m_signed_plaintext;
  bool m_bool_plaintext;

};  /// end of class definition

// (dummy) specializations for double and complex<double>
template <>
class ContextHElib_F2<double> : public Context<double, NTL::Vec<Ctxt> > {
 public:
  typedef double Plaintext;
  typedef NTL::Vec<Ctxt>  Ciphertext;

  ContextHElib_F2() {
    throw InputTypeNotSupported();
  }
  Ciphertext encrypt(std::vector<Plaintext> pt) {
    throw InputTypeNotSupported();
  }
  std::vector<Plaintext> decrypt(Ciphertext ct) {
    throw InputTypeNotSupported();
  }
};

template <>
class ContextHElib_F2<std::complex<double> >: public Context<std::complex<double>, NTL::Vec<Ctxt> > {
 public:
  typedef std::complex<double> Plaintext;
  typedef NTL::Vec<Ctxt>  Ciphertext;

  ContextHElib_F2() {
    throw InputTypeNotSupported();
  }
  Ciphertext encrypt(std::vector<Plaintext> pt) {
    throw InputTypeNotSupported();
  }
  std::vector<Plaintext> decrypt(Ciphertext ct) {
    throw InputTypeNotSupported();
  }
};

////////////////////////////////////////////////////////////////////////////
//// ContextHElib_Fp  - use integer plaintext space, e.g. p=65537

template <typename PlaintextT>
class ContextHElib_Fp : public ContextHElib<PlaintextT, Ctxt> {
 public:
  typedef PlaintextT Plaintext;
  typedef Ctxt Ciphertext;

  ContextHElib_Fp(
      long p = 65537,             // plaintext modulus
      long param_set = 0,         // parameter set, from 0 (tiny) to 4 (huge)
      bool bootstrap = false,     // bootstrap or not?
      long hamming_weight = 128)  // Hamming weight of secret key
      : ContextHElib<Plaintext, Ciphertext>(p, param_set, bootstrap,
                                            hamming_weight) {
    this->m_ninputs = 0;
    this->m_param_name_map.insert({"p", this->m_p});
  }

  Ciphertext encrypt(std::vector<Plaintext> pt) {
    std::vector<long> ptvec;

    int pt_len = pt.size();

    // Check whether the input is too long to be encrypted in one go
    if (pt_len > this->m_nslots) {
      throw std::runtime_error(
          "The number of slots is greater than the number of SIMD operations "
          "that can be done at a time");
    }
    // convert plaintext input into a vector of longs
    for (int i = 0; i < this->m_nslots; i++) {
      ptvec.push_back(pt[i % pt_len]);
    }

    // encrypt vector of longs
    Ciphertext ct(*(this->m_publicKey));

    this->m_ea->encrypt(ct, *(this->m_publicKey), ptvec);
    this->m_ciphertext_size = sizeof(ct);

    return ct;
  }

  std::vector<Plaintext> decrypt(Ciphertext ct) {
    std::vector<Plaintext> result;
    std::vector<long> pt;

    long pt_transformed;

    this->m_ea->decrypt(ct, *(this->m_secretKey), pt);

    for (int i = 0; i < pt.size(); i++) {
      // convention - treat this as a negative number
      if ((pt[i]) > this->m_p / 2)
        pt_transformed = pt[i] - this->m_p;
      else
        pt_transformed = pt[i];

      result.push_back(pt_transformed % int(pow(2, this->m_bitwidth)));
    }

    return result;
  }

  std::string encrypt_and_serialize(std::vector<PlaintextT> pt) {
    Ciphertext ct = encrypt(pt);
    std::stringstream ss;
    ss << ct;
    std::string ctstring = ss.str();
    return ctstring;
  };

  Ciphertext Add(Ciphertext a, Ciphertext b) {
    a += b;
    return a;
  }

  Ciphertext Subtract(Ciphertext a, Ciphertext b) {
    a -= b;
    return a;
  }

  Ciphertext Multiply(Ciphertext a, Ciphertext b) {
    a *= b;
    return a;
  }

  Ciphertext Negate(Ciphertext a) {
    if (this->m_bitwidth == 1)  /// special case for binary
      a.addConstant(to_ZZX(1L));
    else
      a.multByConstant(to_ZZX(-1L));

    return a;
  }

  Ciphertext MultByConstant(Ciphertext a, long b) {
    a.multByConstant(to_ZZX(b));
    return a;
  }

  Ciphertext AddConstant(Ciphertext a, long b) {
    a.addConstant(to_ZZX(b));

    return a;
  }

  Ciphertext Select(Ciphertext s, Ciphertext a, Ciphertext b) {
    /// s is 0 or 1
    /// output is s*a + (1-s)*b
    Ciphertext sa = Multiply(s, a);
    Ciphertext one_minus_s = MultByConstant(AddConstant(s, -1L), -1L);
    Ciphertext one_minus_s_times_b = Multiply(one_minus_s, b);
    return Add(sa, one_minus_s_times_b);
  }

  Ciphertext Rotate(Ciphertext a, long n) {
    /// Cyclically rotate the linear array by n positions
    if (n > this->m_nslots) {
      throw std::runtime_error("Error in Rotate: cannot rotate by more than nslots positions");
    }
    //to rotate right, we actually rotate left
    // by (ninputs - n) positions
    if (n > 0) n = n - this->m_ninputs;

    Ciphertext result(a);
    this->m_ea->rotate(result, n);

    return result;
  }

};  /// end of class definition

// (dummy) specializations for double and complex<double>
template <>
class ContextHElib_Fp<double> : public Context<double, Ctxt > {
 public:
  typedef double Plaintext;
  typedef Ctxt  Ciphertext;

  ContextHElib_Fp() {
    throw InputTypeNotSupported();
  }
  Ciphertext encrypt(std::vector<Plaintext> pt) {
    throw InputTypeNotSupported();
  }
  std::vector<Plaintext> decrypt(Ciphertext ct) {
    throw InputTypeNotSupported();
  }
};

template <>
class ContextHElib_Fp<std::complex<double> >: public Context<std::complex<double>, Ctxt > {
 public:
  typedef std::complex<double> Plaintext;
  typedef Ctxt  Ciphertext;

  ContextHElib_Fp() {
    throw InputTypeNotSupported();
  }
  Ciphertext encrypt(std::vector<Plaintext> pt) {
    throw InputTypeNotSupported();
  }
  std::vector<Plaintext> decrypt(Ciphertext ct) {
    throw InputTypeNotSupported();
  }
};

}  // namespace SHEEP

#endif  // CONTEXT_HELIB_HPP


#include <cstdlib>
#include <sstream>
#include <string>

#include <boost/program_options.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>

#include "cell.hpp"

using namespace std;

typedef map<pair<int, int>, int> pcn;   // copy number at a position

int use_grandparent;

// Read posterior distributions of parameters from file
// parameter1	parameter2	distance	weights
void read_params_posterior(string fname, vector<double>& param1, vector<double>& param2, vector<double>& param3, vector<double>& weights, int verbose = 0){
   ifstream fin(fname);
   string line;
   // int i = 0;
   while (getline(fin, line)) {
       // i++;
       // cout << "line " << i << endl;
       // skip comment lines
       if(!isdigit(line.c_str()[0])){
           continue;
       }
       // Split line into tab-separated parts
       vector<string> parts;
       // parameter1	parameter2	distance	weights
       boost::split(parts, line, boost::is_any_of("\t"));
       // cout << parts.size() << "\t" << parts[0] << "\t" << parts[1] << "\t" << parts[2] << endl;

       param1.push_back(stod(parts[0]));
       param2.push_back(stod(parts[1]));
       if(parts.size()>4){
           param3.push_back(stod(parts[2]));    // birth rates
           weights.push_back(stod(parts[4]));
       }else{
           weights.push_back(stod(parts[3]));
       }
   }
   fin.close();

   // Test reading input
   if(verbose > 0){
       cout << "Reading file " << fname << endl;
       cout << param1.size() << "\t" << param2.size() << "\t" << param3.size() << "\t" << weights.size() << "\n";
   }
   // for(int i = 0; i < param1.size(); i++){
   //     cout << param1[i] << "\t" << param2[i] << "\t" << weights[i] << "\n";
   // }
}

void get_params_file(string dir_param, vector<string>& filenames, int verbose = 0){
    for (boost::filesystem::directory_iterator itr(dir_param); itr!=boost::filesystem::directory_iterator(); ++itr)
    {
        string fname = itr->path().filename().string();
        // cout << itr->path().filename() << ' '; // display filename only
        // if (is_regular_file(itr->status())) cout << " [" << file_size(itr->path()) << ']';
        // cout << '\n';
        std::string prefix = "smc-";
        if(fname.compare(0, prefix.size(), prefix) == 0){
            filenames.push_back(itr->path().string());
        }
    }
    if(verbose > 0){
        cout << "Parameter files: " << endl;
        for(int i = 0; i < filenames.size(); i++){
            cout << filenames[i] << endl;
        }
        cout << endl;
    }
}

// Simulate the growth of one cell into a single organoid (clone)
Clone run_sim(double birth_rate, double death_rate, int Nend, double arm_prob, double chr_prob, double multi_prob, int skip, int only_mut, unsigned long rseed, string outdir, int genotype_diff = 0, double chr_weight = 1.0, int chr_sel=0, int use_ablen=0, string suffix="", string prefix="", int model = 0, string fitness_vals="", int num_subclone = 0, double tmin = 0, double tmax = 0, double min_clone_freq = 0, double max_clone_freq = 0, int num_clonal_mutation = 0, string file_cmut = "", int verbose = 0){
    Clone clone;

    double lambda = birth_rate - death_rate;
    double tend = log(Nend)/(lambda);

    double mutation_rate = arm_prob + chr_prob + multi_prob;

    vector<double> fitness;
    if(fitness_vals!=""){
        stringstream ss(fitness_vals);
        int num;   ss >> num;
        for(int i = 0; i < num; i++){
            double d1;
            ss >> d1;
            fitness.push_back(d1);
        }
    }

    vector<double> time_occur;

    if(verbose > 0){
        cout << "\nSimulating tumor growth" << endl;
        cout << "Random seed: " << rseed << endl;
        cout << "Model of evolution: " << model << endl;
        cout << "Initial Net growth rate: " << lambda << endl;
        if(fitness_vals != ""){
          cout << "Fitness values: " << fitness_vals << endl;
        }
        cout << "Mutation rate: " << mutation_rate << endl;
        cout << "\tChr-level CNA rate: " << chr_prob << endl;
        cout << "\tArm-level CNA rate: " << arm_prob << endl;
        cout << "\tMultipolar spindle (hopeful monster) rate: " << multi_prob << endl;
        cout << "\tEffective mutation rate (μ/β): " << mutation_rate/((birth_rate - death_rate)/birth_rate) << endl;
        cout << "Estimated simulation finish time (tumor doublings): " << tend << endl;
    }

    if(tmax > tend){
        tmax = tend;
    }

    if(mutation_rate > 0){
        if(verbose == 1) cout << "\nSimulating CIN" << endl;
        clone.grow_with_cnv(num_subclone, num_clonal_mutation, model, fitness, time_occur, Nend, birth_rate, death_rate, mutation_rate, arm_prob, chr_prob, multi_prob, genotype_diff, chr_weight, chr_sel, file_cmut, verbose);
    }
    else{
        clone.grow(num_subclone, num_clonal_mutation, fitness, time_occur, Nend, birth_rate, death_rate, mutation_rate, verbose);
    }

    // not output anything when simulating bulk samples
    if(verbose < 0) return clone;

    if(verbose > 0){
        const char* path = outdir.c_str();
        boost::filesystem::path dir(path);
        if(boost::filesystem::create_directory(dir))
        {
            if(verbose == 1) cerr << "Directory Created: " << outdir <<endl;
        }
        if(suffix == ""){
            string sep = "-";
            suffix = sep + to_string(num_subclone) + sep + to_string(Nend) + sep + to_string(int(birth_rate*10)) + sep + to_string(int(death_rate*10)) + sep + to_string(int(mutation_rate)) + sep + to_string(num_clonal_mutation) + sep + prefix + sep + to_string(int(tmin)) + sep + to_string(int(rseed));
        }
        string outfile = "";

        // outfile = outdir + "curr_vaf" + suffix;
        // cout << "Computing VAF" << endl;
        // map<int, double> mut_freq = clone.get_allele_freq();
        // clone.print_map(mut_freq, outfile);

        outfile = outdir + "summary" + suffix;
        cout << "Printing summary" << endl;
        clone.print_summary(outfile);

        outfile = outdir + "end_cells_cn" + suffix + ".txt";
        clone.print_obs_cn(clone.curr_cells, outfile, verbose);

        // outfile = outdir + "end_cells_cn" + suffix + "_rvec.txt";
        // clone.write_avg_reciprocal_cn(clone.curr_cells, outfile, false);

        // // The proportion of chr-level and arm-level (p,q) events for each chromosome
        // outfile = outdir + "end_cells_cn" + suffix + "_prop.txt";
        // clone.write_prop(clone.curr_cells, outfile);
        //
        // outfile = outdir + "end_cells_cn" + suffix + "_cnvec.txt";
        // clone.write_avg_cn(clone.curr_cells, outfile);
        //
        // outfile = outdir + "end_cells_cn" + suffix + "_uvec.txt";
        // clone.write_avg_uniq_cn(clone.curr_cells, outfile);
        //
        // outfile = outdir + "end_cells_cn" + suffix + "_avec.txt";
        // clone.write_aggregated_uniq_cn(clone.curr_cells, outfile);
        //
        // outfile = outdir + "end_cells_cn" + suffix + "_lvec.txt";
        // clone.write_relative_reciprocal_cn(clone.curr_cells, outfile, false);

        // Output total number of de novo events in the population
        // outfile = outdir + "summary_stats" + suffix + ".txt";
        // clone.write_summary_stats(sum_stats, outfile);

        if(verbose > 1){
            cout << "Printing out informaton of all cells in a tumor clone" << endl;
            // cout << "file name" << outfile << endl;
            clone.print_lineage(clone.cells, outdir, suffix);

            outfile = outdir + "all_cells_tree" + suffix + ".nwk";
            clone.write_newick(clone.cells, outfile);

            outfile = outdir + "all_cells_tree" + suffix + ".txt";
            clone.write_tree(clone.cells, outfile);
        }
    }

    // vector<double> sum_stats;
    cout.precision(9);
    vector<int> avg_cn(2, 0);
    clone.get_avg_reciprocal_cn(clone.curr_cells, avg_cn, false);
    for(int i = 0; i < avg_cn.size(); i++){
        double ac = (double) avg_cn[i] / clone.curr_cells.size();
        // sum_stats.push_back(ac);
        cout << ac << "\t";
    }

    if(use_ablen == 1){     // Output all the tree lenghts
        if(fitness_vals != ""){
            vector<double> blens;
            clone.get_treelen_vec(blens, clone.cells, skip);
            cout << blens[0];
            for(int i = 1; i < blens.size(); i++){
                cout << "\t" << blens[i];
            }
            cout << "\t";
        }
    }else{
        // Output half tree length (cell lineage tree is symmetrical)
        double tlen = clone.get_treelen(clone.cells, skip);
        // sum_stats.push_back(tlen);
        // cout << tlen << endl;
        cout << tlen << "\t";

        // Output branch length ratios of parent and daughter cells of current cell
        if(verbose > 1) cout << "\ngetting branch length ratios" << endl;
        vector<double> ratios;
        clone.get_treelen_ratios(ratios, clone.cells, skip, only_mut, use_grandparent, verbose);
        double avg_ratio = 0;
        for(auto r : ratios){
         if(verbose > 1)  cout << r << endl;
          avg_ratio += r;
        }
        if(ratios.size() > 0){
          avg_ratio = avg_ratio / ratios.size();
        }
        cout << avg_ratio << "\t";

        // Output half the sum length of branches after immediate CNAs (seem not working well)
        // double elen = clone.get_elen(clone.cells, skip);
        // cout << elen << "\t";
    }

    int ndenovo = 0;
    pcn clone_cnp;      // Group CNPs by arms
    pcn clone_cnc;
    for(auto cell : clone.curr_cells) {
        cell.set_obs_cn();
        for(auto cp : cell.obs_cn_profile){
            pair<int, int> pos = cp.first;
            if(cp.second != 0){
                ndenovo++;

                if(pos.second==0){  // divide whole chromosome to two arms
                    pair<int, int> pos1(pos.first, 1);
                    clone_cnp[pos1] += abs(cp.second);
                    clone_cnc[pos1] += 1;
                    pair<int, int> pos2(pos.first, 2);
                    clone_cnp[pos2] += abs(cp.second);
                    clone_cnc[pos2] += 1;
                }else{
                    clone_cnp[pos] += abs(cp.second);
                    clone_cnc[pos]++;
                }
            }
        }
    }
    int naler_pos_l1sample = 0;
    double sum_avg_cn = 0;
    map<int, int> nalter_chr;   // count the number of altered position for each chromosome (at most 2)
    for(auto cp : clone_cnp){
        pair<int, int> pos = cp.first;
        assert(pos.second!=0);
        nalter_chr[pos.first]++;
        if(clone_cnp[pos] > 1) naler_pos_l1sample += 1;
        // double avg_cn = (double) clone_cnp[pos] / clone_cnc[pos];     // taking average over altered cells
        double avg_cn = (double) clone_cnp[pos] / clone.curr_cells.size();     // taking average over all cells in an organoid
        sum_avg_cn += abs(avg_cn);
    }

    int nalter = 0;
    for(auto nc : nalter_chr){
        assert(nc.second <= 2);
        nalter += nc.second;
    }

    // double prop_alter = (double) nalter / (NUM_CHR * 2);
    // Output summary statistics
    // cout << prop_alter << endl;
    cout << nalter << "\t" << sum_avg_cn  << "\t" << ndenovo << "\t" << naler_pos_l1sample << endl;

    return clone;

}

// set bulk parameters for one run according to random choice
void set_bulk_params_random(vector<double>& params,  double min_birth_rate, double max_birth_rate, double min_arm_prob, double max_arm_prob, double min_chr_prob, double max_chr_prob, double min_mut_rate, double max_mut_rate, double avg_mut_rate, double max_multi_prob) {
    // double mut_rate = runiform(r, min_mut_rate, max_mut_rate);
    double mut_rate_init = gsl_ran_exponential(r, avg_mut_rate);
    double mut_rate = mut_rate_init > max_mut_rate? max_mut_rate : mut_rate_init;

    // // select arm_prob since chr_prob is biased towards small values in real data sets
    // double max_arm_rate = mut_rate < max_arm_prob? mut_rate : max_arm_prob;
    // double min_arm_rate = max_arm_rate < min_arm_prob? max_arm_rate : min_arm_prob;
    // arm_prob = runiform(r, min_arm_rate, max_arm_rate);
    // // chr_prob = runiform(r, min_chr_prob, max_arm_prob);
    // double chr_prob_init = mut_rate - arm_prob;
    // chr_prob = chr_prob_init > max_chr_prob? max_chr_prob:chr_prob_init;

    // select chr_prob since chr_prob is often less than arm_prob in real data sets
    double max_chr_rate = mut_rate < max_chr_prob? mut_rate : max_chr_prob;
    double min_chr_rate = max_chr_rate < min_chr_prob? max_chr_rate : min_chr_prob;
    double chr_prob = runiform(r, min_chr_rate, max_chr_rate);
    // chr_prob = runiform(r, min_chr_prob, max_arm_prob);
    double arm_prob_init = mut_rate - chr_prob;
    double arm_prob = arm_prob_init > max_arm_prob? max_arm_prob : arm_prob_init;

    double multi_prob = 0;
    if(max_multi_prob > 0){
        double multi_prob = runiform(r, 0, max_multi_prob);
    }

    mut_rate = chr_prob + arm_prob + multi_prob;
    double birth_rate = runiform(r, min_birth_rate, max_birth_rate);

    params.push_back(chr_prob);
    params.push_back(arm_prob);
    params.push_back(multi_prob);
    params.push_back(mut_rate);
    params.push_back(birth_rate);
}

// set bulk parameters for one run according to weighted choice from posterior distributions
void set_bulk_params(vector<double>& params, const vector<string>& filenames,  double min_birth_rate, double max_birth_rate, double max_multi_prob, int verbose = 0){
    // randomly pick up a dataset to choose parameters from
    int i = runiform(r, 0, filenames.size());

    string fname = filenames[i];
    vector<double> param1;
    vector<double> param2;
    vector<double> param3;
    vector<double> weights;
    read_params_posterior(fname, param1, param2, param3, weights, verbose);

    double* arr_weights = &weights[0];
    gsl_ran_discrete_t*  dis = gsl_ran_discrete_preproc(weights.size(), arr_weights);
    int sel = gsl_ran_discrete(r, dis);

    double chr_prob, arm_prob, mut_rate, birth_rate, multi_prob = 0;
    chr_prob = param1[sel];
    arm_prob = param2[sel];

    if(param3.size() > 0){
        birth_rate = param3[sel];
    }else{
        birth_rate = runiform(r, min_birth_rate, max_birth_rate);
    }
    assert(birth_rate > 0);

    // Multipolar rate:
    // 1/24=0.0417  divisions in 20190409_1
    // 1/23=0.0435  divisions in 20181127
    // 1/18=0.0556  divisions in 20200403
    if(max_multi_prob > 0){
        // multi_prob = runiform(r, 0, max_multi_prob);
        if(fname.find("20190409_1")!=std::string::npos){
            multi_prob = gsl_ran_exponential(r, 0.0417);
        }else if(fname.find("20181127")!=std::string::npos){
            multi_prob = gsl_ran_exponential(r, 0.0435);
        }else if(fname.find("20200403")!=std::string::npos){
            multi_prob = gsl_ran_exponential(r, 0.0556);
        }else{
            multi_prob = 0;
        }
    }

    // Used for printing
    mut_rate = chr_prob + arm_prob + multi_prob;

    params.push_back(chr_prob);
    params.push_back(arm_prob);
    params.push_back(multi_prob);
    params.push_back(mut_rate);
    params.push_back(birth_rate);

    if(verbose > 0)
        cout << "Selected parameters: " << chr_prob << "\t" << arm_prob << "\t" << multi_prob << "\t" << mut_rate << "\t" << birth_rate << endl;
}

// set single organoid parameters for one run according to weighted choice from posterior distributions
void set_organoid_params(vector<double>& params, string filename, double& birth_rate, int verbose = 0){
    vector<double> param1;
    vector<double> param2;
    vector<double> param3;
    vector<double> weights;
    read_params_posterior(filename, param1, param2, param3, weights, verbose);

    double* arr_weights = &weights[0];
    gsl_ran_discrete_t*  dis = gsl_ran_discrete_preproc(weights.size(), arr_weights);
    int sel = gsl_ran_discrete(r, dis);

    double chr_prob, arm_prob;
    chr_prob = param1[sel];
    arm_prob = param2[sel];

    if(param3.size() > 0){
        birth_rate = param3[sel];
    }
    assert(birth_rate > 0);

    // mut_rate = chr_prob + arm_prob + multi_prob;

    params.push_back(chr_prob);
    params.push_back(arm_prob);
    // params.push_back(mut_rate);
    params.push_back(birth_rate);

    if(verbose > 0)
        cout << "Selected parameters: " << chr_prob << "\t" << arm_prob << "\t" << birth_rate << endl;
}

void get_bulk_stat(pcn& bulk_cnp, pcn& bulk_cnc, int nsample, int ndenovo, int verbose = 0){
    // Take the average of each bulk sample to get the CN profiles of all bulk samples
    double sum_avg_cn = 0;
    // double total_cn = 0;
    map<int, int> nalter_chr;   // count the number of altered position for each chromosome (at most 2)
    int naler_pos_l1sample = 0;    // count the number of  altered position for each chromosome which appear in more than one samples
    // cout << "Bulk CNPs: " << endl;
    assert(bulk_cnp.size() <= 2 * NUM_CHR);
    for(auto cp : bulk_cnp){
        pair<int, int> pos = cp.first;
        assert(pos.second!=0);
        nalter_chr[pos.first]++;
        if(bulk_cnc[pos]>1) naler_pos_l1sample++;
        // double avg_cn = (double) bulk_cnp[pos] / bulk_cnc[pos];     // taking average over all samples
        double avg_cn = (double) bulk_cnp[pos] / nsample;     // taking average over all samples
        // if(verbose>0) cout << pos.first + 1 << "\t" << pos.second << "\t" << cp.second << "\t" << avg_cn << "\t" << bulk_cnc[pos] << endl;
        sum_avg_cn += abs(avg_cn);
        // total_cn += cp.second;
    }

    int nalter_bulk = 0;
    for(auto nc : nalter_chr){
        assert(nc.second <= 2);
        nalter_bulk += nc.second;
    }

    // Compute the proportion of altered positions
    if(verbose>0){
        cout << "Number of CNP in bulk sample is " << bulk_cnp.size() << endl;
        cout << "Number of altered CNP by arm in bulk sample is " << nalter_bulk << endl;
    }
    // double prop_alter = (double) nalter_bulk / (NUM_CHR * 2);
    cout << nalter_bulk << "\t" << sum_avg_cn << "\t" << ndenovo  << "\t" << naler_pos_l1sample << endl;
}

void sim_bulk(int nsample, double min_birth_rate, double max_birth_rate, double death_rate, int min_ncell, int max_ncell, double min_arm_prob, double max_arm_prob, double min_chr_prob, double max_chr_prob, double min_mut_rate, double max_mut_rate, double avg_mut_rate, double max_multi_prob, int skip, int only_mut, unsigned long rseed, string dir_param, string outdir, int genotype_diff = 0, double chr_weight = 1.0, int chr_sel=0, int use_ablen=0, string suffix="", string prefix="", int model = 0, string fitness_vals="", int num_subclone = 0, double tmin = 0, double tmax = 0, double min_clone_freq = 0, double max_clone_freq = 0, int num_clonal_mutation = 0, string file_cmut = "", int verbose = 0){
    pcn bulk_cnp;   // used to compute the average of bulk CNPs
    pcn bulk_cnc;
    int ndenovo = 0;
    // support for moving iostreams was added to GCC 5.1 (not work on cs cluster with default gcc)
    string outfile = outdir + "bulk_cn" + suffix + ".txt";
    string outfile_rate = outdir + "bulk_rate" + suffix + ".txt";
    // ofstream fcn(" "), fcn_rate(" ");
    ofstream fcn(outfile);
    ofstream fcn_rate(outfile_rate);
    vector<string> filenames;

    get_params_file(dir_param, filenames, verbose);

    // Suppress all the output when verbose = -1
    // if(verbose >= 0){
        // fcn = ofstream(outfile);
        // // Output the parameters used for each simulation for double checking
        // fcn_rate = ofstream(outfile_rate);
    // }

    string orig_suffix = suffix;
    double avg_bulk_birth_rate = 0, avg_bulk_mut_rate = 0, avg_bulk_chr_rate = 0, avg_bulk_arm_rate = 0, avg_bulk_mp_rate = 0;
    double avg_bulk_ncell = 0, avg_bulk_num_mut = 0, avg_bulk_first_mut_time = 0, bulk_first_mut_count = 0;

    for(int i = 0; i < nsample; i++){
        int ncell = runiform(r, min_ncell, max_ncell);

        vector<double> params;
        // set_bulk_params_random(params, min_birth_rate, max_birth_rate, min_arm_prob, max_arm_prob, min_chr_prob, max_chr_prob, min_mut_rate, max_mut_rate, avg_mut_rate, max_multi_prob);
        set_bulk_params(params, filenames, min_birth_rate, max_birth_rate, max_multi_prob, verbose);

        double chr_prob = params[0];
        double arm_prob = params[1];
        double multi_prob = params[2];
        double mut_rate = params[3];
        double birth_rate = params[4];

        avg_bulk_birth_rate += birth_rate;
        avg_bulk_mut_rate += mut_rate;
        avg_bulk_chr_rate += chr_prob;
        avg_bulk_arm_rate += arm_prob;
        avg_bulk_mp_rate += multi_prob;

        if(verbose>0){
            cout << "Simulating bulk sample " << i+1 << " with " << ncell << " cells" << endl;
            cout << "\tBirth rate: " << birth_rate << endl;
            cout << "\tMutation rate: " << mut_rate << endl;
            cout << "\tChr-level CNA rate: " << chr_prob << endl;
            cout << "\tArm-level CNA rate: " << arm_prob << endl;
            cout << "\tMultipolar spindle (hopeful monster) rate: " << multi_prob << endl;
            // cout << "\tDeath rate: " << death_rate << endl;
            // cout << "\tEffective mutation rate (μ/β): " << cell.mutation_rate / ((cell.birth_rate-cell.death_rate)/cell.birth_rate) << endl;
        }
        suffix = orig_suffix + "_bulk" + to_string(i+1);
        Clone s1 = run_sim(birth_rate, death_rate, ncell, arm_prob, chr_prob, multi_prob, skip, only_mut, rseed + i, outdir, genotype_diff, chr_weight, chr_sel, use_ablen, suffix, prefix, model, fitness_vals, num_subclone, tmin, tmax, min_clone_freq, max_clone_freq, num_clonal_mutation, file_cmut, verbose);

        avg_bulk_ncell += ncell;
        avg_bulk_num_mut += s1.num_novel_mutation;
        avg_bulk_first_mut_time += s1.time_first_mut;
        if(s1.time_first_mut==1) bulk_first_mut_count += 1;
        if(verbose >= 0)
            fcn_rate << ncell << "\t" << s1.num_novel_mutation << "\t" << s1.time_first_mut << "\t" << birth_rate << "\t" << mut_rate << "\t" << chr_prob << "\t" << arm_prob << "\t" << multi_prob << endl;

        // by default, C++ creates an empty map with value for unknown key being 0
        pcn s1_cnp_split;
        pcn s1_cnc_split;
        pcn s1_cnp_merged;
        pcn s1_cnc_merged;

        // Use s1_cnp_split to merge events on one chromosome across cells in the organoid
        for(auto cell : s1.curr_cells) {
            cell.set_obs_cn();
            for(auto cp : cell.obs_cn_profile){
                pair<int, int> pos = cp.first;
                if(cp.second != 0){
                    if(pos.second==0){  // divide whole chromosome to two arms
                        pair<int, int> pos1(pos.first, 1);
                        s1_cnp_split[pos1] += cp.second;
                        s1_cnc_split[pos1] += 1;

                        pair<int, int> pos2(pos.first, 2);
                        s1_cnp_split[pos2] += cp.second;
                        s1_cnc_split[pos2] += 1;
                    }else{
                        s1_cnp_split[pos] += cp.second;
                        s1_cnc_split[pos] += 1;
                    }
                }
            }
        }
        assert(s1_cnp_split.size() <= 2 * NUM_CHR);

        // Take the average of each organoid sample to get the CN profiles of a bulk sample
        for(auto cp : s1_cnp_split){
            pair<int, int> pos = cp.first;
            assert(pos.second != 0);
            // double acn = (double) s1_cnp_split[pos] / s1_cnc_split[pos];
            double acn = (double) s1_cnp_split[pos] / ncell;        // The detected CN should be based on total number of cells
            // int racn = int(acn);
            // if(round_cn==1)
            int racn = round(acn);
            // if(i==0){
            //     cout << i + 1 << "\t" << pos.first + 1 << "\t" << pos.second << "\t" << s1_cnp_split[pos] << "\t" << s1_cnc_split[pos] << "\t" << acn << "\t" << racn << endl;
            // }
            if(racn == 0) continue;

            // chr-level event on one chr has been recorded
            pair<int, int> pos_chr(pos.first, 0);
            if(s1_cnp_merged.find(pos_chr) != s1_cnp_merged.end()){
                continue;
            }

            pair<int, int> pos2;
            if(pos.second==1){
                pos2 = make_pair(pos.first, 2);
            }else{
                assert(pos.second==2);
                pos2= make_pair(pos.first, 1);
            }
            if(s1_cnp_split.find(pos2) != s1_cnp_split.end()){
                double acn2 = (double) s1_cnp_split[pos2] / s1_cnc_split[pos2];
                // int racn2 = int(acn2);
                // if(round_cn==1){
                int racn2 = round(acn2);
                // }
                if(racn == racn2){
                    s1_cnp_merged[pos_chr] = racn;
                    bulk_cnp[pos] += abs(racn);
                    bulk_cnc[pos] += 1;
                    bulk_cnp[pos2] += abs(racn);
                    bulk_cnc[pos2] += 1;
                    continue;
                }
            }
            s1_cnp_merged[pos] = racn;
            bulk_cnp[pos] += abs(racn);
            bulk_cnc[pos] += 1;
        }

        int nalt = 0;
        for(auto cp : s1_cnp_merged){
            pair<int, int> pos = cp.first;
            int rmcn = s1_cnp_merged[pos];
            if(rmcn == 0) continue;
            nalt++;
            // Output the CN of each bulk sample
            if(verbose >= 0)
                fcn << i + 1 << "\t" << pos.first + 1 << "\t" << pos.second << "\t" << rmcn << endl;
        }

        ndenovo += nalt;

        if(verbose>0) cout << "Number of altered CNP in sample " << i+1 << " is " << nalt << endl;
    }  // Simulate a bulk sample with nsample bulk organoid data

    cout << avg_bulk_ncell / nsample << "\t" << avg_bulk_num_mut / nsample << "\t" << avg_bulk_first_mut_time / nsample << "\t" << bulk_first_mut_count << "\t" << avg_bulk_birth_rate / nsample << "\t" << avg_bulk_mut_rate / nsample << "\t" << avg_bulk_chr_rate / nsample << "\t" << avg_bulk_arm_rate / nsample << "\t" << avg_bulk_mp_rate / nsample << endl;

    get_bulk_stat(bulk_cnp, bulk_cnc, nsample, ndenovo, verbose);

    // if(verbose >= 0){
        fcn.close();
        fcn_rate.close();
    // }
}

// TODO: pass parameters
int main(int argc, char const *argv[]) {
    Clone clone;

    int mode;
    int nsample;
    int min_ncell, max_ncell;
    double min_mut_rate, max_mut_rate, avg_mut_rate;
    double min_arm_prob, max_arm_prob;
    double min_chr_prob, max_chr_prob;
    double max_multi_prob;
    double min_birth_rate, max_birth_rate;
    string dir_param;

    int Nend;
    double birth_rate, death_rate;
    double mutation_rate, arm_prob, chr_prob, multi_prob;
    int multi_nchr;
    string file_param;

    int model;
    string fitness_vals;
    int num_subclone;
    double tmin, tmax;
    // Mutation rate per division per genome
    double min_clone_freq, max_clone_freq;
    int num_clonal_mutation;

    int chr_sel; // level of selection
    int genotype_diff;
    double chr_weight;
    int use_ablen;
    int skip; // number of cells to skip when computing division time
    int only_mut;  // only consider branch ratios when new mutation is introduced
    string outdir, prefix, suffix; // output

    unsigned long seed;

    string tree_file, file_cmut;

    int verbose;

    namespace po = boost::program_options;

    po::options_description generic("Generic options");
    generic.add_options()
      ("version,v", "print version string")
      ("help,h", "produce help message")
      ;

    po::options_description required("Required parameters");
    required.add_options()
      ("odir,o", po::value<string>(&outdir)->required()->default_value("./"), "output directory")
       ;

    po::options_description optional("Optional parameters");
    optional.add_options()
      ("mode", po::value<int>(&mode)->default_value(0), "mode of simulation. 0: single cell data; 1: bulk data")

      ("birth_rate", po::value<double>(&birth_rate)->default_value(1), "birth rate")
      ("death_rate", po::value<double>(&death_rate)->default_value(0), "death rate")
      ("Nend,e", po::value<int>(&Nend)->default_value(100), "size of final cell populations")

      ("nsample", po::value<int>(&nsample)->default_value(100), "number of bulk samples")
      ("min_ncell", po::value<int>(&min_ncell)->default_value(100), "minimum number of cells in a bulk sample")
      ("max_ncell", po::value<int>(&max_ncell)->default_value(100), "maximum number of cells in a bulk sample")
      ("min_mut_rate", po::value<double>(&min_mut_rate)->default_value(0.02), "minimum CNA rate")
      ("max_mut_rate", po::value<double>(&max_mut_rate)->default_value(0.2), "maximum CNA rate")
      ("avg_mut_rate", po::value<double>(&avg_mut_rate)->default_value(0.1), "average CNA rate")
      ("min_chr_prob", po::value<double>(&min_chr_prob)->default_value(0.01), "minimum chr-level CNA rate")
      ("max_chr_prob", po::value<double>(&max_chr_prob)->default_value(0.2), "maximum chr-level CNA rate")
      ("min_arm_prob", po::value<double>(&min_arm_prob)->default_value(0.02), "minimum arm-level CNA rate")
      ("max_arm_prob", po::value<double>(&max_arm_prob)->default_value(0.2), "maximum arm-level CNA rate")
      ("max_multi_prob", po::value<double>(&max_multi_prob)->default_value(0.06), "maximum multiple simultaneous chr-level CNA rate (hopeful monster)")
      ("min_birth_rate", po::value<double>(&min_birth_rate)->default_value(0.2), "minimum cell division rate")
      ("max_birth_rate", po::value<double>(&max_birth_rate)->default_value(0.5), "maximum cell division rate")
      ("dir_param", po::value<string>(&dir_param)->default_value(""), "directory containing posterior distributions of inferred parameters")

      ("model", po::value<int>(&model)->default_value(0), "model of evolution. 0: neutral; 1: gradual; 2: punctuated; 3: positive")
      ("fitness_vals", po::value<string>(&fitness_vals)->default_value(""), "fitness values of mutatants")
      ("chr_sel", po::value<int>(&chr_sel)->default_value(0), "level of selection. 0: selection on all CNAs; 1: selection on chr-level CNAs")
      ("genotype_diff", po::value<int>(&genotype_diff)->default_value(0), "whether or not to use genotype difference (L1 distance) in simulating selection. 0: no; 1: yes")
      ("chr_weight", po::value<double>(&chr_weight)->default_value(1.0), "relative fitness of chr-level CNAs")

      ("use_ablen", po::value<int>(&use_ablen)->default_value(0), "tree summary statistics. 0: half tree length; 1: vector of all branch lengths")

      ("num_subclone,n", po::value<int>(&num_subclone)->default_value(0), "number of subclones to simulate")
      ("min_clone_freq", po::value<double>(&min_clone_freq)->default_value(0), "the minimal frequency of a subclone")
      ("max_clone_freq", po::value<double>(&max_clone_freq)->default_value(0), "the maximal frequency of a subclone")
      ("tmin", po::value<double>(&tmin)->default_value(0), "the earliest time that a subclone occurs")
      ("tmax", po::value<double>(&tmax)->default_value(0), "the latest time that a subclone occurs")

      ("num_clonal_mutation", po::value<int>(&num_clonal_mutation)->default_value(0), "number of clonal mutations")
      ("file_cmut", po::value<string>(&file_cmut)->default_value(""), "the TSV file which contains clonal CNVs, with three columns (chr, arm, cn)")

      ("arm_prob, a", po::value<double>(&arm_prob)->default_value(0.5), "arm-level CNA rate per cell division")
      ("chr_prob, c", po::value<double>(&chr_prob)->default_value(0.5), "chr-level CNA rate per cell division")
      ("multi_prob, m", po::value<double>(&multi_prob)->default_value(0), "multiple simultaneous chr-level CNA rate per cell division")
      ("multi_nchr", po::value<int>(&multi_nchr)->default_value(16), "number of chromosomes affected by multipolar division")
      ("file_param", po::value<string>(&file_param)->default_value(""), "the file containing posterior distributions of inferred parameters")

      ("prefix,p", po::value<string>(&prefix)->default_value("run1"), "prefix of output file (it will be sim-data-N if not specified")
      ("suffix,s", po::value<string>(&suffix)->default_value(""), "suffix of output file")
      ("skip", po::value<int>(&skip)->default_value(3), "number of cells to skip when computing division time")
      ("only_mut", po::value<int>(&only_mut)->default_value(0), "when to consider branch ratios (1: only when there are mutations introduced to the node)")
      ("use_grandparent", po::value<int>(&use_grandparent)->default_value(1), "whether to check grandparent when computing branch ratios")

      ("seed", po::value<unsigned long>(&seed)->default_value(0), "seed used for generating random numbers")
      ("verbose", po::value<int>(&verbose)->default_value(0), "verbose level (0: default, 1: print information of final cells; 2: print information of all cells)")
      ;

    po::options_description cmdline_options;
    cmdline_options.add(generic).add(required).add(optional);
    po::variables_map vm;

    try {
        po::store(po::command_line_parser(argc, argv).options(cmdline_options).run(), vm);
        if(vm.count("help")){
            cout << cmdline_options << endl;
            return 1;
        }
        if(vm.count("version")){
            cout << "sim_cin [version 0.1], a program to simulate copy number variations along a cell division tree" << endl;
            return 1;
        }
        po::notify(vm);
    }
    catch (const exception& e) {
          cerr << e.what() << endl;
          return 1;
    }

    unsigned long rseed = setup_rng(seed);

    if(mode == 0){
        if(file_param!=""){
            vector<double> params;
            set_organoid_params(params, file_param, birth_rate, verbose);

            chr_prob = params[0];
            arm_prob = params[1];
            birth_rate = params[2];
        }
        run_sim(birth_rate, death_rate, Nend, arm_prob, chr_prob, multi_prob, skip, only_mut, rseed, outdir, genotype_diff, chr_weight, chr_sel, use_ablen, suffix, prefix, model, fitness_vals, num_subclone, tmin, tmax, min_clone_freq, max_clone_freq, num_clonal_mutation, file_cmut, verbose);
    }else{
        sim_bulk(nsample, min_birth_rate, max_birth_rate, death_rate, min_ncell, max_ncell, min_arm_prob, max_arm_prob, min_chr_prob, max_chr_prob, min_mut_rate, max_mut_rate, avg_mut_rate, max_multi_prob, skip, only_mut, rseed, dir_param, outdir, genotype_diff, chr_weight, chr_sel, use_ablen, suffix, prefix, model, fitness_vals, num_subclone, tmin, tmax, min_clone_freq, max_clone_freq, num_clonal_mutation, file_cmut, verbose);
    }

    return 0;
}


/*! \file Peridigm_EnergyReleaseDamageModel.cpp */

//@HEADER
// ************************************************************************
//
//                             Peridigm
//                 Copyright (2011) Sandia Corporation
//
// Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
// the U.S. Government retains certain rights in this software.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// 1. Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
//
// 2. Redistributions in binary form must reproduce the above copyright
// notice, this list of conditions and the following disclaimer in the
// documentation and/or other materials provided with the distribution.
//
// 3. Neither the name of the Corporation nor the names of the
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY SANDIA CORPORATION "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL SANDIA CORPORATION OR THE
// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
// Questions?
// David J. Littlewood   djlittl@sandia.gov
// John A. Mitchell      jamitch@sandia.gov
// Michael L. Parks      mlparks@sandia.gov
// Stewart A. Silling    sasilli@sandia.gov
//
// ************************************************************************
//////////////////////////////////////////////////////////////////////////////////////
// Routine developed for Peridigm by
// DLR Composite Structures and Adaptive Systems
//                                __/|__
//                                /_/_/_/
//            www.dlr.de/fa/en      |/ DLR
//////////////////////////////////////////////////////////////////////////////////////
// Questions?
// Christian Willberg  christian.willberg@dlr.de
// Martin Raedel       martin.raedel@dlr.de
// https://github.com/PeriDoX/
//////////////////////////////////////////////////////////////////////////////////////
//@HEADER

#include "Peridigm_EnergyReleaseDamageModel.hpp"
#include "Peridigm_Field.hpp"
#include "material_utilities.h"
#include <thread>
#include <Teuchos_Assert.hpp>
#include <Epetra_SerialComm.h>
#include <Sacado.hpp>
#include <boost/math/special_functions/fpclassify.hpp>

using namespace std;

PeridigmNS::EnergyReleaseDamageModel::EnergyReleaseDamageModel(const Teuchos::ParameterList& params)
: DamageModel(params),
m_applyThermalStrains(false),
m_modelCoordinatesFieldId(-1),
m_coordinatesFieldId(-1),
m_damageFieldId(-1),
m_bondDamageFieldId(-1),
m_deltaTemperatureFieldId(-1),
m_dilatationFieldId(-1),
m_weightedVolumeFieldId(-1),
m_horizonFieldId(-1),
m_damageModelFieldId(-1),
m_planeStrain(false),
m_planeStress(false),
m_onlyTension(false),
m_rot(false),
m_Thickness(-1),
m_OMEGA(PeridigmNS::InfluenceFunction::self().getInfluenceFunction()) {
	m_rot = false;
    if (params.isParameter("Rot Sym")) {
		m_rot = true;
		maxRad = params.get<double>("Rot Sym");
	}
    if (params.isParameter("Critical Energy")) {
        m_criticalEnergyTension = params.get<double>("Critical Energy Tension");
        m_criticalEnergyCompression = params.get<double>("Critical Energy Compression");
        m_criticalEnergyShear = params.get<double>("Critical Energy Shear");

    } else {
        if (params.isParameter("Critical Energy Tension"))
            m_criticalEnergyTension = params.get<double>("Critical Energy Tension");
        else
            m_criticalEnergyTension = -1.0;
        if (params.isParameter("Critical Energy Compression"))
            m_criticalEnergyCompression = params.get<double>("Critical Energy Compression");
        else
            m_criticalEnergyCompression = -1.0;
        if (params.isParameter("Critical Energy Shear"))
            m_criticalEnergyShear = params.get<double>("Critical Energy Shear");
        else
            m_criticalEnergyShear = -1.0;
        m_type = 1;
        if (params.isParameter("Energy Criterion")) {
            if (params.get<bool>("Energy Criterion") == true)
                m_type = 1;
            }

        if (params.isParameter("Power Law")) {
            if (params.get<bool>("Power Law") == true)
                m_type = 2;
            }

        if (params.isParameter("Separated")) {
            if (params.get<bool>("Separated") == true)
                m_type = 3;
            }

    }
    if(params.isParameter("Plane Stress")){
        m_planeStress = params.get<bool>("Plane Stress");
        m_Thickness = params.get<double>("Thickness");
    }
    if(params.isParameter("Plane Strain")){
        m_planeStrain = params.get<bool>("Plane Strain");
        m_Thickness = params.get<double>("Thickness");
    }
    m_pi = 3.14159;
    m_onlyTension = false;
    if(params.isParameter("Only Tension")){
        m_onlyTension = params.get<bool>("Only Tension");

    }

    if (params.isParameter("Thermal Expansion Coefficient")) {
        m_alpha = params.get<double>("Thermal Expansion Coefficient");
        m_applyThermalStrains = true;
    }

    PeridigmNS::FieldManager& fieldManager = PeridigmNS::FieldManager::self();
    m_modelCoordinatesFieldId = fieldManager.getFieldId("Model_Coordinates");
    m_coordinatesFieldId = fieldManager.getFieldId("Coordinates");
    m_volumeFieldId = fieldManager.getFieldId(PeridigmNS::PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Volume");
    m_weightedVolumeFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::CONSTANT, "Weighted_Volume");
    m_dilatationFieldId = fieldManager.getFieldId(PeridigmField::ELEMENT, PeridigmField::SCALAR, PeridigmField::TWO_STEP, "Dilatation");
    m_damageFieldId = fieldManager.getFieldId(PeridigmNS::PeridigmField::ELEMENT, PeridigmNS::PeridigmField::SCALAR, PeridigmNS::PeridigmField::TWO_STEP, "Damage");
    m_bondDamageFieldId = fieldManager.getFieldId(PeridigmNS::PeridigmField::BOND, PeridigmNS::PeridigmField::SCALAR, PeridigmNS::PeridigmField::TWO_STEP, "Bond_Damage");
    m_horizonFieldId = fieldManager.getFieldId(PeridigmNS::PeridigmField::ELEMENT, PeridigmNS::PeridigmField::SCALAR, PeridigmNS::PeridigmField::CONSTANT, "Horizon");
    m_damageModelFieldId = fieldManager.getFieldId(PeridigmNS::PeridigmField::NODE, PeridigmNS::PeridigmField::VECTOR, PeridigmNS::PeridigmField::TWO_STEP, "Damage_Model_Data");
    if(m_applyThermalStrains)
        m_deltaTemperatureFieldId      = fieldManager.getFieldId(PeridigmField::NODE,    PeridigmField::SCALAR,      PeridigmField::TWO_STEP, "Temperature_Change");
    m_fieldIds.push_back(m_volumeFieldId);
    m_fieldIds.push_back(m_modelCoordinatesFieldId);
    m_fieldIds.push_back(m_coordinatesFieldId);
    m_fieldIds.push_back(m_weightedVolumeFieldId);
    m_fieldIds.push_back(m_dilatationFieldId);
    m_fieldIds.push_back(m_volumeFieldId);
    m_fieldIds.push_back(m_damageFieldId);
    m_fieldIds.push_back(m_damageModelFieldId);
    m_fieldIds.push_back(m_bondDamageFieldId);
    m_fieldIds.push_back(m_horizonFieldId);

}

PeridigmNS::EnergyReleaseDamageModel::~EnergyReleaseDamageModel() {
}

void
PeridigmNS::EnergyReleaseDamageModel::initialize(const double dt,
        const int numOwnedPoints,
        const int* ownedIDs,
        const int* neighborhoodList,
        PeridigmNS::DataManager& dataManager) const {
        double *damage, *bondDamage;

    dataManager.getData(m_damageFieldId, PeridigmField::STEP_NP1)->ExtractView(&damage);
    dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamage);
	dataManager.getData(m_damageModelFieldId, PeridigmField::STEP_NP1)->PutScalar(0.0);
    // Initialize damage to zero
    int neighborhoodListIndex(0);
    int bondIndex(0);
    int nodeId, numNeighbors;
    int iID, iNID;

    for (iID = 0; iID < numOwnedPoints; ++iID) {
        nodeId = ownedIDs[iID];
        damage[nodeId] = 0.0;
        numNeighbors = neighborhoodList[neighborhoodListIndex++];
        neighborhoodListIndex += numNeighbors;
        for (iNID = 0; iNID < numNeighbors; ++iNID) {
            bondDamage[bondIndex] = 0.0;
            bondIndex += 1;
        }
    }

}

void
PeridigmNS::EnergyReleaseDamageModel::computeDamage(const double dt,
        const int numOwnedPoints,
        const int* ownedIDs,
        const int* neighborhoodList,
        PeridigmNS::DataManager& dataManager) const {

    double *x, *y, *damage, *bondDamageNP1, *horizon;
    double *cellVolume, *weightedVolume, *damageModel;
    double criticalEnergyTension(-1.0), criticalEnergyCompression(-1.0), criticalEnergyShear(-1.0);
    // for temperature dependencies easy to extent
    double *deltaTemperature = NULL;
    if(m_applyThermalStrains)
        dataManager.getData(m_deltaTemperatureFieldId, PeridigmField::STEP_NP1)->ExtractView(&deltaTemperature);
    double m_alpha = 0;

    dataManager.getData(m_damageFieldId, PeridigmField::STEP_NP1)->ExtractView(&damage);
    dataManager.getData(m_modelCoordinatesFieldId, PeridigmField::STEP_NONE)->ExtractView(&x);
    dataManager.getData(m_coordinatesFieldId, PeridigmField::STEP_NP1)->ExtractView(&y);
    dataManager.getData(m_weightedVolumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&weightedVolume);
    dataManager.getData(m_volumeFieldId, PeridigmField::STEP_NONE)->ExtractView(&cellVolume);
    //////////////////////////////////////////////////////////////
    // transfer of data is done in ComputeDilation --> PeridigmElastic.cpp
    //////////////////////////////////////////////////////////////
    dataManager.getData(m_damageModelFieldId, PeridigmField::STEP_NP1)->ExtractView(&damageModel);
    dataManager.getData(m_horizonFieldId, PeridigmField::STEP_NONE)->ExtractView(&horizon);
    ////////////////////////////////////
    ////////////////////////////////////
    // transfer of data is done in ComputeDilation --> PeridigmMaterial.cpp
    dataManager.getData(m_bondDamageFieldId, PeridigmField::STEP_NP1)->ExtractView(&bondDamageNP1);

    ////////////////////////////////////////////////////
    double trialDamage(0.0);
    int neighborhoodListIndex(0), bondIndex(0);
    int nodeId, numNeighbors, neighborID, iID, iNID;
    double totalDamage, totalVol;
    double alphaP1 = 0.0, alphaP2 = 0.0, gammaP1 = 0.0, gammaP2 = 0.0, kappaP1 = 0.0, kappaP2 = 0.0;
    double nodeInitialX[3], nodeCurrentX[3], relativeExtension(0.0);
    double bondEnergyIsotropic(0.0), bondEnergyDeviatoric(0.0);
    double omegaP1, omegaP2;
    double critDev, critIso, critComp;
    double degradationFactor = 1.0; // Optional parameter if bond should be degradated and not fully destroyed instantaneously
    double avgHorizon, quadhorizon;
    double eiP1, eiP2, tiP1, tiP2;
    double edP1, edP2, tdP1, tdP2;
    double dilatationP1, dilatationP2;
    double BulkModP1, BulkModP2;
    double ShearModP1, ShearModP2;
    double bondEnergy;

    //double thickness;
    //---------------------------
    // INITIALIZE PROCESS STEP t
    //---------------------------

    if (m_criticalEnergyTension > 0.0)
        criticalEnergyTension = m_criticalEnergyTension;
    if (m_criticalEnergyCompression > 0.0)
        criticalEnergyCompression = m_criticalEnergyCompression;
    if (m_criticalEnergyShear > 0.0)
        criticalEnergyShear = m_criticalEnergyShear;

    // Update the bond damage
    // Break bonds if the bond energy potential is greater than the critical bond energy potential
    //---------------------------
    // DAMAGE ANALYSIS
    //---------------------------
    bondIndex = 0;
    //temperatur ist in peridigm_ElasticMaterial gegeben
    for (iID = 0; iID < numOwnedPoints; ++iID) {
        numNeighbors = neighborhoodList[neighborhoodListIndex++];

        nodeId = ownedIDs[iID];
        nodeInitialX[0] = x[nodeId*3];
        nodeInitialX[1] = x[nodeId*3+1];
        nodeInitialX[2] = x[nodeId*3+2];
        nodeCurrentX[0] = y[nodeId*3];
        nodeCurrentX[1] = y[nodeId*3+1];
        nodeCurrentX[2] = y[nodeId*3+2];

        dilatationP1 = damageModel[3*nodeId];
        BulkModP1    = damageModel[3*nodeId+1];     // weightedVolume is already included in the variable --> Perdigm_Elastic.cpp;
        ShearModP1   = damageModel[3*nodeId+2]; // weightedVolume is already included in --> Perdigm_Elastic.cpp;

        if (m_planeStress==false and m_planeStrain==false){
          //  c = 3 * K * (*theta) / weightedVol;
            // kappa = c / (*theta) / gamma / 3 (?) (above eq (7) lammiCJ
            kappaP1 = 9.0 * BulkModP1;
            gammaP1 = 1.0;
            alphaP1 = 15.0 * ShearModP1;
        }
        if (m_planeStress==true and m_planeStrain==false){
            alphaP1 = 8.0 * ShearModP1;
            kappaP1 = 3.0 * BulkModP1;
            gammaP1 = 4.0 * ShearModP1 / (3.0 * BulkModP1 + 4.0 * ShearModP1);
            alphaP1 = 8.0 * ShearModP1;
        }
        if (m_planeStrain==true and m_planeStress==false){
           // c = (12.0*K-4.0*MU) / 9.0 * (*theta) / weightedVol;
            // kappa = c / (*theta) / gamma / 3 (?) (above eq (7) lammiCJ
            //kappa = 3*(12.0*K-4.0*MU) / 18.0 * (*theta) / weightedVol;

            gammaP1 = 2.0/3.0;
            kappaP1 = 6.0*BulkModP1 - 2.0*ShearModP1;
            alphaP1 = 8.0*ShearModP1;
        }
        for (iNID = 0; iNID < numNeighbors; ++iNID) {

            trialDamage = 0.0;
            neighborID = neighborhoodList[neighborhoodListIndex++];

            double zeta =
            distance(nodeInitialX[0], nodeInitialX[1], nodeInitialX[2],
                 x[neighborID*3], x[neighborID*3+1], x[neighborID*3+2]);

            double dY =
            distance(nodeCurrentX[0], nodeCurrentX[1], nodeCurrentX[2],
                 y[neighborID*3], y[neighborID*3+1], y[neighborID*3+2]);

            relativeExtension = (dY - zeta)/zeta;
             // the direction switches between both bonds. This results in a switch of the forces
             // as well. Therefore, all forces and bond deformations are normalized.
            double eP = dY - zeta;
            if(deltaTemperature)
              eP -= 0.5 * m_alpha*(deltaTemperature[nodeId] + deltaTemperature[neighborID])*zeta;

            // the average horizon is taken, if multiple horizons are used
            dilatationP2 = damageModel[3*neighborID];
            BulkModP2    = damageModel[3*neighborID+1]; // weightedVolume is already included in the variable --> Perdigm_Elastic.cpp;
            ShearModP2   = damageModel[3*neighborID+2]; // weightedVolume is already included in --> Perdigm_Elastic.cpp;

            if (m_planeStress){
              //  c = 4.0*K*MU/(3.0*K+4.0*MU) * (*theta) / weightedVol;
                // kappa = c / (*theta) / gamma / 3 (?) (above eq (7) lammiCJ
                kappaP2 = 3.0 * BulkModP2;
                gammaP2 = 4.0 * ShearModP2 / (3.0 * BulkModP2 + 4.0 * ShearModP2);
                alphaP2 = 8.0 * ShearModP2;
            }
            if (m_planeStrain){
               // c = (12.0*K-4.0*MU) / 9.0 * (*theta) / weightedVol;
                // kappa = c / (*theta) / gamma / 3 (?) (above eq (7) lammiCJ
                //kappa = 3*(12.0*K-4.0*MU) / 18.0 * (*theta) / weightedVol;

                gammaP2 = 2.0/3.0;
                kappaP2 =(12.0*BulkModP2 - 4.0*ShearModP2) / 3.0 / gammaP2;
                alphaP2 = 8.0 * ShearModP2;

            }
            if (m_planeStress==false and m_planeStrain==false){
              //  c = 3 * K * (*theta) / weightedVol;
                // kappa = c / (*theta) / gamma / 3 (?) (above eq (7) lammiCJ
                kappaP2 = 9.0 * BulkModP2;
                gammaP2 = 1.0;
                alphaP2 = 15.0 * ShearModP2 ;
            }

            omegaP1 = MATERIAL_EVALUATION::scalarInfluenceFunction(zeta, horizon[nodeId]);
            omegaP2 = MATERIAL_EVALUATION::scalarInfluenceFunction(zeta, horizon[neighborID]);
            avgHorizon = 0.5*(horizon[nodeId]+horizon[neighborID]);

            if(m_planeStrain or m_planeStress){ // m_Thickness is in the volume included (tbd to guarantee this)
            //factor = 16.0 / 5.0 / weightedVol * sqrt(M_PI / 3.0 * pow(horizon,5)); // Simplified with Mathematica
                quadhorizon =  3.0 /(m_Thickness * pow(horizon[neighborID],3) );
				if (m_rot){
					double rad = nodeInitialX[0]*nodeInitialX[0]/maxRad/maxRad;
					//if (rad == 0) rad = 1e-10;
					quadhorizon = quadhorizon*(1+rad);
				}
            }
            else {
                //yieldValue = 25.0 * yieldStress * yieldStress / 8 / M_PI / pow(horizon,5);
                //factor = sqrt(75.0 / 4 / M_PI / pow(horizon,5));        // equation (51) MitchelJA_2011; 1/2 is not needed
                //factor = 15.0 / weightedVol * sqrt(4.0 / 75.0 * M_PI * pow(horizon,5));        // equation (51) MitchelJA_2011; 1/2 is not needed

                quadhorizon =  8.0 /( m_pi * pow(avgHorizon, 4) );
            }

            eiP1 = gammaP1 * dilatationP1 * zeta / 3.0;
            eiP2 = gammaP2 * dilatationP2 * zeta / 3.0;
            tiP1 = kappaP1*eiP1;
            tiP2 = kappaP2*eiP2;

            edP1 = eP - eiP1;
            edP2 = eP - eiP2;
            tdP1 = omegaP1*alphaP1*edP1;
            tdP2 = omegaP2*alphaP2*edP2;

            bondEnergyIsotropic  = (1.0 - bondDamageNP1[bondIndex])*(sqrt(tiP1*eiP1*tiP1*eiP1) + sqrt(tiP2*eiP2*tiP2*eiP2));
            bondEnergyDeviatoric = (1.0 - bondDamageNP1[bondIndex])*(sqrt(tdP1*edP1*tdP1*edP1) + sqrt(tdP2*edP2*tdP2*edP2));

            critIso = 0.0;
            if (relativeExtension>0&&criticalEnergyTension != -1.0){
               critIso = (bondEnergyIsotropic/(criticalEnergyTension*quadhorizon));
            }
            critDev = 0.0;

            if (criticalEnergyShear != -1.0){
               critDev = (bondEnergyDeviatoric/(criticalEnergyShear*quadhorizon));
            }
            critComp = 0.0;
            if (relativeExtension < 0.0 && criticalEnergyCompression != -1.0){
                critComp = (bondEnergyIsotropic/(criticalEnergyCompression*quadhorizon));
                critIso = 0.0;
            }
            if (m_type == 1){ // Energy Criterion by Foster et al.(2009) Journal for Multiscale Computational Engineering;
                bondEnergy = abs(abs(tdP1+tiP1) + abs(tdP2 + tiP2))*eP;

                //bondEnergy = abs(damageModel[3*nodeId]-damageModel[3*neighborID])*zeta / *m;

                critIso = bondEnergy/(criticalEnergyTension*quadhorizon);

                if (m_criticalEnergyTension > 0.0 && critIso > 1.0 ) {
					//std::cout<<BulkModP1<< " "<< weightedVolume[nodeId]<<" "<< ShearModP1<< " "<< ShearModP2<<" "<< BulkModP2<<std::endl;
                    trialDamage = bondDamageNP1[bondIndex] + degradationFactor;
                }
            }

            if (m_type == 2){// Power Law

                if (m_criticalEnergyTension > 0.0 &&critIso*critIso + critDev*critDev + critComp*critComp > 1.0) {
                   trialDamage = bondDamageNP1[bondIndex] + degradationFactor;
                }
            }
            //std::cout<< m_type<< " "<< std::endl;
            if (m_type == 3){ // Separated
                if (m_criticalEnergyTension > 0.0  &&critIso > 1.0) {
                   trialDamage = bondDamageNP1[bondIndex] + degradationFactor;
                }
                if (criticalEnergyShear > 0.0 && critDev > 1.0) {
                   trialDamage = bondDamageNP1[bondIndex] + degradationFactor;

                }
                if (m_criticalEnergyCompression > 0.0  && critComp > 1.0) {
                   trialDamage = bondDamageNP1[bondIndex] + degradationFactor;
                }
            }
            if (m_onlyTension == true){
                if (relativeExtension<0)trialDamage = 0;
            }

            if (trialDamage > bondDamageNP1[bondIndex]) {
               if (trialDamage>1)trialDamage = 1;
                            bondDamageNP1[bondIndex] = trialDamage;
                    }

            bondIndex += 1;

            }

        }

    //  Update the element damage (percent of bonds broken)

    neighborhoodListIndex = 0;
    bondIndex = 0;
    for (iID = 0; iID < numOwnedPoints; ++iID) {
        nodeId = ownedIDs[iID];
        numNeighbors = neighborhoodList[neighborhoodListIndex++];
        //neighborhoodListIndex += numNeighbors;
        damageModel[3*nodeId] = 0.0;
        damageModel[3*nodeId+1] = 0.0;
        damageModel[3*nodeId+2] = 0.0;
        totalDamage = 0.0;
        totalVol = 0.0;
        for (iNID = 0; iNID < numNeighbors; ++iNID) {

            neighborID = neighborhoodList[neighborhoodListIndex++];
            // must be zero to avoid synchronization errors
            damageModel[3*neighborID] = 0.0;
            damageModel[3*neighborID+1] = 0.0;
            damageModel[3*neighborID+2] = 0.0;

            totalDamage += bondDamageNP1[bondIndex]*weightedVolume[nodeId];
            totalVol += weightedVolume[nodeId];
            bondIndex += 1;
        }
        if (numNeighbors > 0)
            totalDamage /= (totalVol);
        else
            totalDamage = 0.0;
        damage[nodeId] = totalDamage;
    }
}


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_SET_TO_0_INCLUDE
#define MTL_SET_TO_0_INCLUDE

#include <algorithm>
#include <cassert>
#include <boost/numeric/mtl/mtl_fwd.hpp>
#include <boost/numeric/mtl/utility/enable_if.hpp>
#include <boost/numeric/mtl/utility/ashape.hpp>
#include <boost/numeric/mtl/utility/tag.hpp>
#include <boost/numeric/mtl/utility/category.hpp>
#include <boost/numeric/mtl/concept/collection.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>
#include <boost/numeric/mtl/interface/vpt.hpp>

namespace mtl {

    // Forward declarations
    namespace matrix {
	template <typename Coll>
	typename mtl::traits::enable_if_matrix<Coll>::type
	set_to_zero(Coll& collection);
    }
    namespace vector {
	template <typename Coll>
	typename mtl::traits::enable_if_vector<Coll>::type
	set_to_zero(Coll& collection);
    }

    namespace impl {

	template <typename Coll>
	void set_to_zero(Coll& collection, tag::vector_ref, ashape::scal)
	{
	    using math::zero;
	    typename Collection<Coll>::value_type  ref, my_zero(zero(ref));
	    for (typename Collection<Coll>::size_type i= 0; i < size(collection); ++i)
		collection[i]= my_zero;
	}

	template <typename Coll>
	void set_to_zero(Coll& collection, tag::contiguous_dense, ashape::scal)
	{
	    using math::zero;
	    typename Collection<Coll>::value_type  ref, my_zero(zero(ref));

	    std::fill(collection.elements(), collection.elements()+collection.used_memory(), my_zero);
	}

	template <typename Coll>
	void set_to_zero(Coll& collection, tag::std_vector, ashape::scal)
	{
	    using math::zero;
	    typename Collection<Coll>::value_type  ref, my_zero(zero(ref));

	    std::fill(collection.begin(), collection.end(), my_zero);
	}

	template <typename Matrix>
	void set_to_zero(Matrix& matrix, tag::morton_dense, ashape::scal)
	{
	    using math::zero;
	    typename Collection<Matrix>::value_type  ref, my_zero(zero(ref));
	    // maybe faster to do it straight
	    // if performance problems we'll take care of the holes
	    // std::cout << "set_to_zero: used_memory = " << matrix.used_memory() << "\n";
	    std::fill(matrix.elements(), matrix.elements() + matrix.used_memory(), my_zero);

#if 0
	    for (int i= 0; i < matrix.num_rows(); i++)
	      for (int j= 0; j < matrix.num_cols(); j++)
		matrix[i][j]= my_zero;
#endif
	}

	// For nested collection, we must consider the dimensions of the elements
	// (Morton-order is included in contiguous_dense)
	template <typename Coll>
	void set_to_zero(Coll& collection, tag::contiguous_dense, ashape::nonscal)
	{
	    for (typename Collection<Coll>::size_type i= 0; i < collection.used_memory(); ++i)
		set_to_zero(collection.value_n(i));
	}

	// Is approbriate for all sparse matrices and vectors (including collections as value_type)
	template <typename Coll>
	void set_to_zero(Coll& collection, tag::sparse, ashape::universe)
	{
	    collection.make_empty();
	}

	// Special treatment for multi_vector
	template <typename Coll>
	void set_to_zero(Coll& collection, tag::multi_vector, ashape::universe)
	{
	    using mtl::vector::set_to_zero;
	    for (typename Collection<Coll>::size_type i= 0; i < num_cols(collection); ++i)
		set_to_zero(collection.vector(i));
	}

	template <typename Coll>
	bool has_strided_data(const Coll&)
	{ return false; }

	template <typename Value, typename Parameter>
	bool has_strided_data(const matrix::dense2D<Value, Parameter>& A)
	{ return A.has_strided_data(); }

	template <typename Matrix>
	void naive_set_to_zero(Matrix& A, tag::matrix, tag::dense)
	{
	    using math::zero;
	    typename Collection<Matrix>::value_type  ref, my_zero(zero(ref));

	    for (unsigned i= 0; i < num_rows(A); i++)
		for (unsigned j= 0; j < num_cols(A); j++)
		    A[i][j]= my_zero;
	}

	template <typename Matrix>
	void naive_set_to_zero(Matrix&, tag::matrix, tag::sparse)
	{
	    assert(true); // must not be called
	}

    }

namespace matrix {

    /// Sets all values of a collection to 0
    /// More spefically the defined multiplicative identity element
    template <typename Coll>
    typename mtl::traits::enable_if_matrix<Coll>::type
    set_to_zero(Coll& collection)
    {
	using mtl::traits::category;
	vampir_trace<3031> tracer;
	typedef typename Collection<Coll>::value_type value_type;
	if (mtl::impl::has_strided_data(collection))
	    mtl::impl::naive_set_to_zero(collection, typename category<Coll>::type(), typename category<Coll>::type());
	else
	    mtl::impl::set_to_zero(collection, typename category<Coll>::type(),typename ashape::ashape<value_type>::type()); // 2. ashape ???
    }
}

namespace vector {

    /// Sets all values of a collection to 0
    /// More spefically the defined multiplicative identity element
    template <typename Coll>
    typename mtl::traits::enable_if_vector<Coll>::type
    set_to_zero(Coll& collection)
    {
	using mtl::traits::category;
	vampir_trace<2029> tracer;
	typedef typename Collection<Coll>::value_type value_type;
	mtl::impl::set_to_zero(collection, typename category<Coll>::type(),typename ashape::ashape<value_type>::type());
    }

}

} // namespace mtl

#endif // MTL_SET_TO_0_INCLUDE


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

#include <boost/algorithm/string/predicate.hpp>

#include <fstream>
#include <iostream>
#include <sstream>
#include <string>

#include "kahypar/definitions.h"
#include "kahypar/io/hypergraph_io.h"
#include "kahypar/io/partitioning_output.h"
#include "kahypar/macros.h"
#include "kahypar/partition/context.h"
#include "kahypar/partition/metrics.h"
#include "tools/mtx_to_hgr_conversion.h"

using namespace kahypar;
using mtxconversion::Matrix;

static inline double imb(const Hypergraph& hypergraph, const PartitionID k) {
  HypernodeWeight max_weight = hypergraph.partWeight(0);
  for (PartitionID i = 1; i != k; ++i) {
    max_weight = std::max(max_weight, hypergraph.partWeight(i));
  }
  return static_cast<double>(max_weight) /
         ceil(static_cast<double>(hypergraph.totalWeight()) / k) - 1.0;
}

static inline Hypergraph createHypergraphFromMtx(const std::string& filename,
                                                  const PartitionID num_parts) {
  // we use the row-net model
  Matrix matrix = mtxconversion::readMatrix(filename);
  HypernodeID num_hypernodes = matrix.info.num_columns;
  HyperedgeID num_hyperedges = matrix.info.num_rows;
  HyperedgeIndexVector index_vector;
  HyperedgeVector edge_vector;

  index_vector.push_back(edge_vector.size());
  for (const auto& hyperedge : matrix.data.entries) {
    if (hyperedge.size() != 0) {
      for (const auto& pin : hyperedge) {
        edge_vector.push_back(pin);
      }
      index_vector.push_back(edge_vector.size());
    }
  }
  ALWAYS_ASSERT(matrix.info.object == mtxconversion::MatrixObjectType::WEIGHTED_MATRIX
                || matrix.data.weights.empty(), "Weights not allowed");
  return Hypergraph(num_hypernodes, num_hyperedges, index_vector, edge_vector,
                    num_parts, HyperedgeWeightVector{ }, matrix.data.weights);
}

int main(int argc, char* argv[]) {
  if (argc != 2 && argc != 3) {
    std::cout << "No .hgr file specified" << std::endl;
    std::cout << "Usage: EvaluateMondriaanPartiton <.hgr/.mtx> <partition file>" << std::endl;
    exit(0);
  }
  const std::string hypergraph_filename(argv[1]);
  Hypergraph hypergraph;
  if (boost::algorithm::ends_with(hypergraph_filename, ".mtx")) {
    hypergraph = createHypergraphFromMtx(hypergraph_filename, 2);
  } else {
    hypergraph = io::createHypergraphFromFile(hypergraph_filename, 2);
  }

  PartitionID max_part = 1;
  std::vector<PartitionID> partition(hypergraph.initialNumNodes(), -1);
  if (argc == 3) {
    const std::string partition_filename(argv[2]);
    std::cout << "Reading partition file: " << partition_filename << std::endl;
    std::ifstream file(partition_filename);
    if (file) {
      HypernodeID vertex = -1;
      PartitionID part = -1;
      // header
      file >> vertex >> max_part;
      ALWAYS_ASSERT(vertex == hypergraph.initialNumNodes());
      while (file >> vertex >> part) {
        // one-based
        partition[vertex - 1] = part - 1;
      }
      file.close();
    } else {
      std::cerr << "Error: File not found: " << std::endl;
    }
    ALWAYS_ASSERT(std::none_of(std::begin(partition), std::end(partition),
                               [](PartitionID i) {
      return i == -1;
    }));
  }

  if (partition.size() != 0 && partition.size() != hypergraph.initialNumNodes()) {
    std::cout << "partition file has incorrect size. Exiting." << std::endl;
    exit(-1);
  }

  LOG << V(max_part);
  hypergraph.changeK(max_part);

  for (size_t index = 0; index < partition.size(); ++index) {
    hypergraph.setNodePart(index, partition[index]);
  }

  Context context;
  context.partition.k = max_part;

  for (PartitionID i = 0; i < context.partition.k; ++i) {
    LOG << i << V(hypergraph.partSize(i)) << V(hypergraph.partWeight(i));
  }

  std::cout << "***********************" << hypergraph.k()
            << "-way Partition Result************************" << std::endl;
  std::cout << "cut=" << metrics::hyperedgeCut(hypergraph) << std::endl;
  std::cout << "soed=" << metrics::soed(hypergraph) << std::endl;
  std::cout << "km1= " << metrics::km1(hypergraph) << std::endl;
  std::cout << "absorption= " << metrics::absorption(hypergraph) << std::endl;
  std::cout << "imbalance= " << imb(hypergraph, context.partition.k)
            << std::endl;

  std::cout << "RESULT"
            << " graph=" << hypergraph_filename.substr(hypergraph_filename.find_last_of('/') + 1)
            << " k=" << context.partition.k
            << " imbalance=" << imb(hypergraph, context.partition.k)
            << " cut=" << metrics::hyperedgeCut(hypergraph)
            << " soed=" << metrics::soed(hypergraph)
            << " km1=" << metrics::km1(hypergraph) << std::endl;

  return 0;
}


#pragma once

#include <iostream>

#include <Eigen/Geometry>

#include <ipc/utils/eigen_ext.hpp>
#include <ipc/utils/logger.hpp>

namespace ipc {

template <typename DerivedP, typename DerivedE0, typename DerivedE1>
PointEdgeDistanceType point_edge_distance_type(
    const Eigen::MatrixBase<DerivedP>& p,
    const Eigen::MatrixBase<DerivedE0>& e0,
    const Eigen::MatrixBase<DerivedE1>& e1)
{
    assert(p.size() == 2 || p.size() == 3);
    assert(e0.size() == 2 || e0.size() == 3);
    assert(e1.size() == 2 || e1.size() == 3);

    const auto e = e1 - e0;
    const auto e_length_sqr = e.squaredNorm();
    if (e_length_sqr == 0) {
        IPC_LOG(warn("Degenerate edge in point_edge_distance_type!"));
        return PointEdgeDistanceType::P_E0; // WARNING: use arbitrary end-point
    }
    auto ratio = e.dot(p - e0) / e_length_sqr;
    if (ratio < 0) {
        return PointEdgeDistanceType::P_E0; // PP (p-e0)
    } else if (ratio > 1) {
        return PointEdgeDistanceType::P_E1; // PP (p-e1)
    } else {
        return PointEdgeDistanceType::P_E; // PE
    }
}

template <
    typename DerivedP,
    typename DerivedT0,
    typename DerivedT1,
    typename DerivedT2>
PointTriangleDistanceType point_triangle_distance_type(
    const Eigen::MatrixBase<DerivedP>& p,
    const Eigen::MatrixBase<DerivedT0>& t0,
    const Eigen::MatrixBase<DerivedT1>& t1,
    const Eigen::MatrixBase<DerivedT2>& t2)
{
    typedef typename DerivedP::Scalar T;

    assert(p.size() == 3);
    assert(t0.size() == 3);
    assert(t1.size() == 3);
    assert(t2.size() == 3);

    Eigen::Matrix<T, 2, 3> basis;
    basis.row(0) = t1 - t0;
    basis.row(1) = t2 - t0;

    auto normal = cross(basis.row(0), basis.row(1));

    Eigen::Matrix<T, 2, 3> param;

    basis.row(1) = cross(basis.row(0), normal);
    param.col(0) =
        (basis * basis.transpose()).ldlt().solve(basis * Vector3<T>(p - t0));
    if (param(0, 0) > 0.0 && param(0, 0) < 1.0 && param(1, 0) >= 0.0) {
        return PointTriangleDistanceType::P_E0; // edge 0 is the closest
    } else {
        basis.row(0) = t2 - t1;
        basis.row(1) = cross(basis.row(0), normal);
        param.col(1) = (basis * basis.transpose())
                           .ldlt()
                           .solve(basis * Vector3<T>(p - t1));
        if (param(0, 1) > 0.0 && param(0, 1) < 1.0 && param(1, 1) >= 0.0) {
            return PointTriangleDistanceType::P_E1; // edge 1 is the closest
        } else {
            basis.row(0) = t0 - t2;

            basis.row(1) = cross(basis.row(0), normal);
            param.col(2) = (basis * basis.transpose())
                               .ldlt()
                               .solve(basis * Vector3<T>(p - t2));
            if (param(0, 2) > 0.0 && param(0, 2) < 1.0 && param(1, 2) >= 0.0) {
                return PointTriangleDistanceType::P_E2; // edge 2 is the closest
            } else {
                if (param(0, 0) <= 0.0 && param(0, 2) >= 1.0) {
                    // vertex 0 is the closest
                    return PointTriangleDistanceType::P_T0;
                } else if (param(0, 1) <= 0.0 && param(0, 0) >= 1.0) {
                    // vertex 1 is the closest
                    return PointTriangleDistanceType::P_T1;
                } else if (param(0, 2) <= 0.0 && param(0, 1) >= 1.0) {
                    // vertex 2 is the closest
                    return PointTriangleDistanceType::P_T2;
                } else {
                    return PointTriangleDistanceType::P_T;
                }
            }
        }
    }
}

// A more robust implementation of http://geomalgorithms.com/a07-_distance.html
template <
    typename DerivedEA0,
    typename DerivedEA1,
    typename DerivedEB0,
    typename DerivedEB1>
EdgeEdgeDistanceType edge_edge_distance_type(
    const Eigen::MatrixBase<DerivedEA0>& ea0,
    const Eigen::MatrixBase<DerivedEA1>& ea1,
    const Eigen::MatrixBase<DerivedEB0>& eb0,
    const Eigen::MatrixBase<DerivedEB1>& eb1)
{
    assert(ea0.size() == 3);
    assert(ea1.size() == 3);
    assert(eb0.size() == 3);
    assert(eb1.size() == 3);

    auto u = ea1 - ea0;
    auto v = eb1 - eb0;
    auto w = ea0 - eb0;

    auto a = u.squaredNorm(); // always >= 0
    auto b = u.dot(v);
    auto c = v.squaredNorm(); // always >= 0
    auto d = u.dot(w);
    auto e = v.dot(w);
    auto D = a * c - b * b; // always >= 0
    auto tD = D;            // tc = tN / tD, default tD = D >= 0

    EdgeEdgeDistanceType defaultCase = EdgeEdgeDistanceType::EA_EB;

    // compute the line parameters of the two closest points
    auto sN = (b * e - c * d);
    decltype(sN) tN;
    if (sN <= 0.0) { // sc < 0 => the s=0 edge is visible
        tN = e;
        tD = c;
        defaultCase = EdgeEdgeDistanceType::EA0_EB;
    } else if (sN >= D) { // sc > 1  => the s=1 edge is visible
        tN = e + b;
        tD = c;
        defaultCase = EdgeEdgeDistanceType::EA1_EB;
    } else {
        tN = (a * e - b * d);
        if (tN > 0.0 && tN < tD
            && (cross(u, v).squaredNorm() < 1.0e-20 * a * c)) {
            // avoid nearly parallel EE
            if (sN < D / 2) {
                tN = e;
                tD = c;
                defaultCase = EdgeEdgeDistanceType::EA0_EB;
            } else {
                tN = e + b;
                tD = c;
                defaultCase = EdgeEdgeDistanceType::EA1_EB;
            }
        }
        // else defaultCase stays EdgeEdgeDistanceType::EA_EB
    }

    if (tN <= 0.0) { // tc < 0 => the t=0 edge is visible
        // recompute sc for this edge
        if (-d <= 0.0) {
            return EdgeEdgeDistanceType::EA0_EB0;
        } else if (-d >= a) {
            return EdgeEdgeDistanceType::EA1_EB0;
        } else {
            return EdgeEdgeDistanceType::EA_EB0;
        }
    } else if (tN >= tD) { // tc > 1  => the t=1 edge is visible
        // recompute sc for this edge
        if ((-d + b) <= 0.0) {
            return EdgeEdgeDistanceType::EA0_EB1;
        } else if ((-d + b) >= a) {
            return EdgeEdgeDistanceType::EA1_EB1;
        } else {
            return EdgeEdgeDistanceType::EA_EB1;
        }
    }

    return defaultCase;
}

} // namespace ipc