#include <igl/avg_edge_length.h>
#include <igl/barycenter.h>
#include <igl/conjugate_frame_fields.h>
#include <igl/ConjugateFFSolverData.h>
#include <igl/dot_row.h>
#include <igl/jet.h>
#include <igl/local_basis.h>
#include <igl/n_polyvector.h>
#include <igl/readDMAT.h>
#include <igl/readOBJ.h>
#include <igl/viewer/Viewer.h>
#include <vector>
#include <cstdlib>

#include "tutorial_shared_path.h"

// Input mesh
Eigen::MatrixXd V;
Eigen::MatrixXi F;

// Face barycenters
Eigen::MatrixXd B;

// Scale for visualizing the fields
double global_scale;

// Input constraints
Eigen::VectorXi b;
Eigen::MatrixXd bc;

Eigen::MatrixXd smooth_pvf;
Eigen::MatrixXd conjugate_pvf;
Eigen::VectorXd conjugacy_s;
Eigen::VectorXd conjugacy_c;


bool key_down(igl::viewer::Viewer& viewer, unsigned char key, int modifier)
{
  using namespace std;
  using namespace Eigen;

  if (key <'1' || key >'5')
    return false;

  viewer.data.lines.resize(0,9);
  // Highlight in red the constrained faces
  MatrixXd C = MatrixXd::Constant(F.rows(),3,1);
  for (unsigned i=0; i<b.size();++i)
      C.row(b(i)) << 1, 0, 0;

  double maxC = std::max(conjugacy_c.maxCoeff(), conjugacy_s.maxCoeff());
  double minC = std::min(conjugacy_c.minCoeff(), conjugacy_s.minCoeff());

  Eigen::VectorXd valS = conjugacy_s;
  // Eigen::VectorXd valS = (valS.array() - minC)/(maxC-minC);
  // valS = 1 - valS.array();
  Eigen::VectorXd valC = conjugacy_c;
  // Eigen::VectorXd valC = (valC.array() - minC)/(maxC-minC);
  // valC = 1 - valC.array();
  MatrixXd CS, CC;
  igl::jet(valS, 0, 0.004, CS);
  igl::jet(valC, 0, 0.004, CC);

  if (key == '1')
  {
    // Frame field constraints
    MatrixXd F1_t = MatrixXd::Zero(F.rows(),3);
    MatrixXd F2_t = MatrixXd::Zero(F.rows(),3);

    for (unsigned i=0; i<b.size();++i)
    {
      F1_t.row(b(i)) = bc.block(i,0,1,3);
      F2_t.row(b(i)) = bc.block(i,3,1,3);
    }

    viewer.data.add_edges(B - global_scale*F1_t, B + global_scale*F1_t , Eigen::RowVector3d(0,0,1));
    viewer.data.add_edges(B - global_scale*F2_t, B + global_scale*F2_t , Eigen::RowVector3d(0,0,1));
    viewer.data.set_colors(C);
  }

  if (key == '2')
  {
    // Interpolated result
    viewer.data.add_edges(B - global_scale*smooth_pvf.block(0,0,F.rows(),3),
                      B + global_scale*smooth_pvf.block(0,0,F.rows(),3),
                      Eigen::RowVector3d(0,0,1));
    viewer.data.add_edges(B - global_scale*smooth_pvf.block(0,3,F.rows(),3),
                      B + global_scale*smooth_pvf.block(0,3,F.rows(),3),
                      Eigen::RowVector3d(0,0,1));
    viewer.data.set_colors(C);
  }

  if (key == '3')
  {
    // Interpolated result
    viewer.data.set_colors(CS);
  }

  if (key == '4')
  {
    // Conjugate field
    viewer.data.add_edges(B - global_scale*conjugate_pvf.block(0,0,F.rows(),3),
                      B + global_scale*conjugate_pvf.block(0,0,F.rows(),3),
                      Eigen::RowVector3d(0,0,1));
    viewer.data.add_edges(B - global_scale*conjugate_pvf.block(0,3,F.rows(),3),
                      B + global_scale*conjugate_pvf.block(0,3,F.rows(),3),
                      Eigen::RowVector3d(0,0,1));
    viewer.data.set_colors(C);
  }
  if (key == '5')
  {
    // Conjugate field
    viewer.data.set_colors(CC);
  }

  return false;
}

int main(int argc, char *argv[])
{
  using namespace Eigen;
  using namespace std;

  // Load a mesh in OBJ format
  igl::readOBJ(TUTORIAL_SHARED_PATH "/inspired_mesh.obj", V, F);
  // Compute face barycenters
  igl::barycenter(V, F, B);

  // Local bases (needed for conjugacy)
  Eigen::MatrixXd B1, B2, B3;
  igl::local_basis(V, F, B1, B2, B3);

  // Compute scale for visualizing fields
  global_scale =  .4*igl::avg_edge_length(V, F);

  // Load constraints
  igl::readDMAT(TUTORIAL_SHARED_PATH "/inspired_mesh_b.dmat",b);
  igl::readDMAT(TUTORIAL_SHARED_PATH "/inspired_mesh_bc.dmat",bc);

  // Interpolate to get a smooth field
  igl::n_polyvector(V, F, b, bc, smooth_pvf);

  // Initialize conjugate field with smooth field
  igl::ConjugateFFSolverData<Eigen::MatrixXd, Eigen::MatrixXi> csdata(V,F);
  conjugate_pvf = smooth_pvf;


  // Optimize the field
  int conjIter = 20;
  double lambdaOrtho = .1;
  double lambdaInit = 100;
  double lambdaMultFactor = 1.01;
  bool doHardConstraints = true;
  VectorXi isConstrained = VectorXi::Constant(F.rows(),0);
  for (unsigned i=0; i<b.size(); ++i)
    isConstrained(b(i)) = 1;

  double lambdaOut = igl::conjugate_frame_fields(csdata, isConstrained, conjugate_pvf, conjugate_pvf, conjIter, lambdaOrtho, lambdaInit, lambdaMultFactor, doHardConstraints);

  // local representations of field vectors
  Eigen::Matrix<double, Eigen::Dynamic, 2> pvU, pvV;
  pvU.resize(F.rows(),2); pvV.resize(F.rows(),2);
  //smooth
  const Eigen::MatrixXd &Us = smooth_pvf.leftCols(3);
  const Eigen::MatrixXd &Vs = smooth_pvf.rightCols(3);
  pvU << igl::dot_row(Us,B1), igl::dot_row(Us,B2);
  pvV << igl::dot_row(Vs,B1), igl::dot_row(Vs,B2);
  csdata.evaluateConjugacy(pvU, pvV, conjugacy_s);
  //conjugate
  const Eigen::MatrixXd &Uc = conjugate_pvf.leftCols(3);
  const Eigen::MatrixXd &Vc = conjugate_pvf.rightCols(3);
  pvU << igl::dot_row(Uc,B1), igl::dot_row(Uc,B2);
  pvV << igl::dot_row(Vc,B1), igl::dot_row(Vc,B2);
  csdata.evaluateConjugacy(pvU, pvV, conjugacy_c);
  // Launch the viewer
  igl::viewer::Viewer viewer;
  viewer.core.invert_normals = true;
  viewer.core.show_lines = false;
  viewer.core.show_texture = false;
  viewer.data.set_mesh(V, F);
  viewer.callback_key_down = &key_down;
  key_down(viewer,'1',0);
  viewer.launch();
}