//
// This file is part of MorphoGraphX - http://www.MorphoGraphX.org
// Copyright (C) 2012-2015 Richard S. Smith and collaborators.
//
// If you use MorphoGraphX in your work, please cite:
//   http://dx.doi.org/10.7554/eLife.05864
//
// MorphoGraphX is free software, and is licensed under under the terms of the 
// GNU General (GPL) Public License version 2.0, http://www.gnu.org/licenses.
// 
#include "MeshProcessMulti.hpp"
#include "Information.hpp"
#include "Progress.hpp"

#include "GraphUtils.hpp"

namespace mgx 
{
  bool CopySwapMeshes::run(const QString& actionStr)
  {
    Mesh* mesh1 = mesh(0);
    if(!mesh1) {
      setErrorMessage("Error, mesh 1 empty");
      return false;
    }
    Mesh* mesh2 = mesh(1);
    if(!mesh2) {
      setErrorMessage("Error, mesh 2 empty");
      return false;
    }
  
    if(actionStr == "1 -> 2") {
      QByteArray ba;
      QBuffer bf(&ba);
      bf.open(QIODevice::WriteOnly);
      mesh1->write(bf, false);
      bf.close();

      mesh2->reset();
      bf.open(QIODevice::ReadOnly);
      mesh2->read(bf, false);
      mesh2->updateAll();
    } else if(actionStr == "1 <- 2") {
      QByteArray ba;
      QBuffer bf(&ba);
      bf.open(QIODevice::WriteOnly);
      mesh2->write(bf, false);
      bf.close();

      mesh1->reset();
      bf.open(QIODevice::ReadOnly);
      mesh1->read(bf, false);
      mesh1->updateAll();
    } else if(actionStr == "1 <-> 2") {
      QByteArray ba1, ba2;
      QBuffer bf1(&ba1), bf2(&ba2);

      bf1.open(QIODevice::WriteOnly);
      mesh1->write(bf1, false);
      bf1.close();

      bf2.open(QIODevice::WriteOnly);
      mesh2->write(bf2, false);
      bf2.close();

      mesh1->reset();
      bf2.open(QIODevice::ReadOnly);
      mesh1->read(bf2, false);

      mesh2->reset();
      bf1.open(QIODevice::ReadOnly);
      mesh2->read(bf1, false);

      mesh1->updateAll();
      mesh2->updateAll();
    } else {
      setErrorMessage("Error, bad action:" + actionStr);
      return false;
    }
    return true;
  }
  REGISTER_PROCESS(CopySwapMeshes);


  int getVectorIndex(vertex v, std::vector<vertex>& vtxVec, std::map<vertex, int>& vtxIdxMap)
  {

    int idx = 0;


      if(vtxIdxMap.find(v) == vtxIdxMap.end()){
        idx = vtxVec.size();

        vertex vNew;
        vNew->label = v->label;
        vNew->pos = v->pos;

        vtxVec.push_back(vNew);
        vtxIdxMap[v] = idx;
      } else {
        idx = vtxIdxMap[v];
      }

    return idx;

  }


  bool MeshAddOtherMesh::run(const Stack *s1, const Stack *s2, Mesh* m, Mesh* m2, bool relabel)
  {

  vvGraph& S = m->graph();
  vvGraph& S2 = m2->graph();

  std::map<vertex, int> vtxIdxMap;
  std::vector<vertex> vtxVec;
  std::vector<Point3i> triVec;
  

  std::set<int> allLabels = findAllLabelsSet(S);
  std::set<int> allLabels2 = findAllLabelsSet(S2);

  Matrix4d rotMatrixS1, rotMatrixS2;
  s1->getFrame().getMatrix(rotMatrixS1.data());
  s2->getFrame().getMatrix(rotMatrixS2.data());

  // fix the rotations
  Matrix4d mGLTot = transpose(inverse(rotMatrixS1)) * transpose(rotMatrixS2);

  std::map<int, int> labelConversionMap;

  int maxLabel = -1;

  forall(int l, allLabels){
    if(l > maxLabel) maxLabel = l;
  }

  forall(int l, allLabels2){
    if(l < 1) continue;
    if(allLabels.find(l) == allLabels.end()){
      labelConversionMap[l] = l;
    } else {
      maxLabel++;
      labelConversionMap[l] = maxLabel;
    }
  }

  forall(vertex v, S2){
    forall(const vertex &n, S2.neighbors(v)){
      if(!progressAdvance()) continue;
      const vertex& m = S2.nextTo(v,n);
      if(!S2.uniqueTri(v, n, m)) continue;

      int idx1, idx2, idx3;

      idx1 = getVectorIndex(v,vtxVec, vtxIdxMap);
      idx2 = getVectorIndex(n,vtxVec, vtxIdxMap);
      idx3 = getVectorIndex(m,vtxVec, vtxIdxMap);

      if(relabel){
        vtxVec[vtxIdxMap[v]]->label = labelConversionMap[v->label];
        vtxVec[vtxIdxMap[n]]->label = labelConversionMap[n->label];
        vtxVec[vtxIdxMap[m]]->label = labelConversionMap[m->label];
      }

      vtxVec[vtxIdxMap[v]]->pos = multMatrix4Point3(mGLTot, v->pos);
      vtxVec[vtxIdxMap[n]]->pos = multMatrix4Point3(mGLTot, n->pos);
      vtxVec[vtxIdxMap[m]]->pos = multMatrix4Point3(mGLTot, m->pos);
      
      triVec.push_back(Point3i(idx1,idx2,idx3));
    }
  }


    if(!Mesh::meshFromTriangles(S, vtxVec, triVec)) {
      return setErrorMessage("Something went wrong!");
    }

    m->updateAll();

    return true;
  }
  REGISTER_PROCESS(MeshAddOtherMesh);


}
