// Gmsh - Copyright (C) 1997-2019 C. Geuzaine, J.-F. Remacle
//
// See the LICENSE.txt file for license information. Please report all
// issues on https://gitlab.onelab.info/gmsh/gmsh/issues.

#include <utility>

#include "GModel.h"
#include "OS.h"
#include "MPoint.h"
#include "MLine.h"
#include "MTriangle.h"
#include "MQuadrangle.h"
#include "MTetrahedron.h"
#include "MHexahedron.h"
#include "MPrism.h"
#include "MPyramid.h"
#include "StringUtils.h"

std::string getMATLABName(int type);

void writeElementsMATLAB(FILE *fp, const GEntity *entity, int filetype,
                         bool binary, int physical)
{
  if(filetype != 0) return;
  for(std::size_t j = 0; j < entity->getNumMeshElements(); j++) {
    if(entity->getMeshElement(j)->getTypeForMSH())
      entity->getMeshElement(j)->writeMATLAB(fp, filetype, entity->tag(),
                                             physical, binary);
  }
  return;
}

void storePairMATLAB(
  const GEntity *entity, int physical,
  std::vector<std::vector<std::pair<MElement *, int> > > &elems)
{
  for(std::size_t j = 0; j < entity->getNumMeshElements(); j++) {
    int elemtype = entity->getMeshElement(j)->getTypeForMSH();
    if(elemtype)
      elems[elemtype - 1].push_back(
        std::make_pair(entity->getMeshElement(j), physical));
  }
  return;
}

int GModel::writeMATLAB(const std::string &name, bool binary, bool saveAll,
                        double scalingFactor, int filetype)
{
  // File type
  int SIMPLE = 0; // very simple with only triangles
  int LOAD_GMSH2 = 1; // structure same as utils/converters/matlab/load_gmsh2.m

  // Hardcoded...
  filetype = LOAD_GMSH2;

  if(binary) {
    // TODO
    Msg::Warning(
      "Binary format not available for Matlab, saving into ASCII format");
    binary = false;
  }

  FILE *fp = Fopen(name.c_str(), binary ? "wb" : "w");
  if(!fp) {
    Msg::Error("Unable to open file '%s'", name.c_str());
    return 0;
  }

  // if there are no physicals we save all the elements
  if(noPhysicalGroups()) saveAll = true;

  // get the number of vertices and index the vertices in a continuous
  // sequence
  int numVertices = indexMeshVertices(saveAll);

  fprintf(fp, "\%\%  Matlab mesh\n");
  fprintf(fp, "\%\% %s, Created by Gmsh\n", getName().c_str());
  if(binary)
    fprintf(fp, "\%\% BINARY\n");
  else
    fprintf(fp, "\%\% ASCII\n");

  // get all the entities in the model
  std::vector<GEntity *> entities;
  getEntities(entities);

  // Print mesh vertices
  if(filetype == SIMPLE) {
    fprintf(fp, "gVertices = %d;\n", numVertices);
    fprintf(fp, "gXYZ = [\n");
    for(std::size_t i = 0; i < entities.size(); i++)
      for(std::size_t j = 0; j < entities[i]->mesh_vertices.size(); j++)
        entities[i]->mesh_vertices[j]->writeMATLAB(fp, filetype, binary,
                                                   scalingFactor);
    fprintf(fp, "];\n");
    // triangles
    fprintf(fp, "\%\%\n");
    fprintf(fp, "gTri=[\n");
    for(std::size_t i = 0; i < entities.size(); i++) {
      if(saveAll)
        writeElementsMATLAB(fp, entities[i], filetype, binary, 0);
      else if(entities[i]->getPhysicalEntities().size()) {
        for(std::size_t k = 0; k < entities[i]->getPhysicalEntities().size();
            k++) {
          int physical = entities[i]->getPhysicalEntities()[k];
          writeElementsMATLAB(fp, entities[i], filetype, binary, physical);
        }
      }
    }
    fprintf(fp, "];\n");
    fprintf(fp, "nTri=size(gTri,1);\n");
  }

  //  int nelemtype = 0; //number of element type
  if(filetype == LOAD_GMSH2) {
    fprintf(fp, "clear msh;\n");
    fprintf(fp, "msh.nbNod = %d;\n", numVertices);
    fprintf(fp, "msh.POS = [\n");
    for(std::size_t i = 0; i < entities.size(); i++)
      for(std::size_t j = 0; j < entities[i]->mesh_vertices.size(); j++)
        entities[i]->mesh_vertices[j]->writeMATLAB(fp, filetype, binary,
                                                   scalingFactor);
    fprintf(fp, "];\n");
    fprintf(fp, "msh.MAX = max(msh.POS);\n");
    fprintf(fp, "msh.MIN = min(msh.POS);\n");
    // We have to store every element in an array and then store them in the
    // file.
    std::vector<std::vector<std::pair<MElement *, int> > > elems(MSH_MAX_NUM);
    // loop on each element and fill the vectors of connectivity
    for(std::size_t i = 0; i < entities.size(); i++) {
      if(saveAll)
        storePairMATLAB(entities[i], 0, elems);
      else {
        for(std::size_t iphys = 0;
            iphys < entities[i]->getPhysicalEntities().size(); iphys++) {
          int physical = entities[i]->getPhysicalEntities()[iphys];
          storePairMATLAB(entities[i], physical, elems);
        }
      }
    }
    // Store in Matlab structure
    for(int elemtype = 1; elemtype < MSH_MAX_NUM + 1; elemtype++) {
      if(elems[elemtype - 1].empty()) continue;
      fprintf(fp, "msh.%s =[\n", getMATLABName(elemtype).c_str());
      // writes
      for(std::size_t i = 0; i < elems[elemtype - 1].size(); i++) {
        elems[elemtype - 1][i].first->writeMATLAB(
          fp, filetype, elems[elemtype - 1][i].second, binary);
      }
      fprintf(fp, "];\n");
    }
  }

  fclose(fp);
  return 1;
}

// This should be updated whenever a new element is available in GMSH
// See Common/GmshDefines.h
std::string getMATLABName(int type)
{
  switch(type) {
  case MSH_LIN_2: return "LINES";
  case MSH_TRI_3: return "TRIANGLES";
  case MSH_QUA_4: return "QUADS";
  case MSH_TET_4: return "TETS";
  case MSH_HEX_8: return "HEXAS";
  case MSH_PRI_6: return "PRISMS";
  case MSH_PYR_5: return "PYRAMIDS";
  case MSH_LIN_3: return "LINES3";
  case MSH_TRI_6: return "TRIANGLES6";
  case MSH_QUA_9: return "QUADS9";
  case MSH_TET_10: return "TETS10";
  case MSH_HEX_27: return "HEXAS27";
  case MSH_PRI_18: return "PRISMS18";
  case MSH_PYR_14: return "PYRAMIDS14";
  case MSH_PNT: return "PNT";
  case MSH_QUA_8: return "QUADS8";
  case MSH_HEX_20: return "HEXAS20";
  case MSH_PRI_15: return "PRISMS15";
  case MSH_PYR_13: return "PYRAMIDS13";
  case MSH_TRI_9: return "TRIANGLES9";
  case MSH_TRI_10: return "TRIANGLES10";
  case MSH_TRI_12: return "TRIANGLES12";
  case MSH_TRI_15: return "TRIANGLES15";
  case MSH_TRI_15I: return "TRIANGLES15I";
  case MSH_TRI_21: return "TRIANGLES21";
  case MSH_LIN_4: return "LINES4";
  case MSH_LIN_5: return "LINES5";
  case MSH_LIN_6: return "LINES6";
  case MSH_TET_20: return "TETS20";
  case MSH_TET_35: return "TETS35";
  case MSH_TET_56: return "TETS56";
  case MSH_TET_22: return "TETS22";
  case MSH_TET_28: return "TETS28";
  case MSH_POLYG_: return "POLYG_";
  case MSH_POLYH_: return "POLYH_";
  case MSH_QUA_16: return "QUADS16";
  case MSH_QUA_25: return "QUADS25";
  case MSH_QUA_36: return "QUADS36";
  case MSH_QUA_12: return "QUADS12";
  case MSH_QUA_16I: return "QUADS16I";
  case MSH_QUA_20: return "QUADS20";
  case MSH_TRI_28: return "TRIANGLES28";
  case MSH_TRI_36: return "TRIANGLES36";
  case MSH_TRI_45: return "TRIANGLES45";
  case MSH_TRI_55: return "TRIANGLES55";
  case MSH_TRI_66: return "TRIANGLES66";
  case MSH_QUA_49: return "QUADS49";
  case MSH_QUA_64: return "QUADS64";
  case MSH_QUA_81: return "QUADS81";
  case MSH_QUA_100: return "QUADS100";
  case MSH_QUA_121: return "QUADS121";
  case MSH_TRI_18: return "TRIANGLES18";
  case MSH_TRI_21I: return "TRIANGLES21I";
  case MSH_TRI_24: return "TRIANGLES24";
  case MSH_TRI_27: return "TRIANGLES27";
  case MSH_TRI_30: return "TRIANGLES30";
  case MSH_QUA_24: return "QUADS24";
  case MSH_QUA_28: return "QUADS28";
  case MSH_QUA_32: return "QUADS32";
  case MSH_QUA_36I: return "QUADS36I";
  case MSH_QUA_40: return "QUADS40";
  case MSH_LIN_7: return "LINES7";
  case MSH_LIN_8: return "LINES8";
  case MSH_LIN_9: return "LINES9";
  case MSH_LIN_10: return "LINES10";
  case MSH_LIN_11: return "LINES11";
  case MSH_LIN_B: return "LINESB";
  case MSH_TRI_B: return "TRIANGLESB";
  case MSH_POLYG_B: return "POLYG_B";
  case MSH_LIN_C: return "LINESC";
  case MSH_TET_84: return "TETS84";
  case MSH_TET_120: return "TETS120";
  case MSH_TET_165: return "TETS165";
  case MSH_TET_220: return "TETS220";
  case MSH_TET_286: return "TETS286";
  case MSH_TET_34: return "TETS34";
  case MSH_TET_40: return "TETS40";
  case MSH_TET_46: return "TETS46";
  case MSH_TET_52: return "TETS52";
  case MSH_TET_58: return "TETS58";
  case MSH_LIN_1: return "LINES1";
  case MSH_TRI_1: return "TRIANGLES1";
  case MSH_QUA_1: return "QUADS1";
  case MSH_TET_1: return "TETS1";
  case MSH_HEX_1: return "HEXAS1";
  case MSH_PRI_1: return "PRISMS1";
  case MSH_PRI_40: return "PRISMS40";
  case MSH_PRI_75: return "PRISMS75";
  case MSH_HEX_64: return "HEXAS64";
  case MSH_HEX_125: return "HEXAS125";
  case MSH_HEX_216: return "HEXAS216";
  case MSH_HEX_343: return "HEXAS343";
  case MSH_HEX_512: return "HEXAS512";
  case MSH_HEX_729: return "HEXAS729";
  case MSH_HEX_1000: return "HEXAS1000";
  case MSH_HEX_32: return "HEXAS32";
  case MSH_HEX_44: return "HEXAS44";
  case MSH_HEX_56: return "HEXAS56";
  case MSH_HEX_68: return "HEXAS68";
  case MSH_HEX_80: return "HEXAS80";
  case MSH_HEX_92: return "HEXAS92";
  case MSH_HEX_104: return "HEXAS104";
  case MSH_PRI_126: return "PRISMS126";
  case MSH_PRI_196: return "PRISMS196";
  case MSH_PRI_288: return "PRISMS288";
  case MSH_PRI_405: return "PRISMS405";
  case MSH_PRI_550: return "PRISMS550";
  case MSH_PRI_24: return "PRISMS24";
  case MSH_PRI_33: return "PRISMS33";
  case MSH_PRI_42: return "PRISMS42";
  case MSH_PRI_51: return "PRISMS51";
  case MSH_PRI_60: return "PRISMS60";
  case MSH_PRI_69: return "PRISMS69";
  case MSH_PRI_78: return "PRISMS78";
  case MSH_PYR_30: return "PYRAMIDS30";
  case MSH_PYR_55: return "PYRAMIDS55";
  case MSH_PYR_91: return "PYRAMIDS91";
  case MSH_PYR_140: return "PYRAMIDS140";
  case MSH_PYR_204: return "PYRAMIDS204";
  case MSH_PYR_285: return "PYRAMIDS285";
  case MSH_PYR_385: return "PYRAMIDS385";
  case MSH_PYR_21: return "PYRAMIDS21";
  case MSH_PYR_29: return "PYRAMIDS29";
  case MSH_PYR_37: return "PYRAMIDS37";
  case MSH_PYR_45: return "PYRAMIDS45";
  case MSH_PYR_53: return "PYRAMIDS53";
  case MSH_PYR_61: return "PYRAMIDS61";
  case MSH_PYR_69: return "PYRAMIDS69";
  case MSH_PYR_1: return "PYRAMIDS1";
  case MSH_PNT_SUB: return "PNT_SUB";
  case MSH_LIN_SUB: return "LINESSUB";
  case MSH_TRI_SUB: return "TRIANGLESSUB";
  case MSH_TET_SUB: return "TETSSUB";
  case MSH_TET_16: return "TETS16";
  case MSH_TRI_MINI: return "TRIANGLESMINI";
  case MSH_TET_MINI: return "TETSMINI";
  case MSH_TRIH_4: return "TRIH_4";
  }
  return "";
}