/* -*- mia-c++  -*-
 *
 * This file is part of MIA - a toolbox for medical image analysis
 * Copyright (c) Leipzig, Madrid 1999-2017 Gert Wollny
 *
 * MIA 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.
 *
 * This program 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 MIA; if not, see <http://www.gnu.org/licenses/>.
 *
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include <map>
#include <fstream>
#include <iostream>

#include <mia/core/file.hh>
#include <mia/core/filter.hh>
#include <mia/core/msgstream.hh>
#include <mia/mesh/triangularMesh.hh>


NS_BEGIN(stl_mesh_io)

NS_MIA_USE;
using namespace std;


static char const *const format = "stl";

class CSTLMeshIO: public CMeshIOPlugin
{
public:
       CSTLMeshIO();

private:
       virtual PTriangleMesh do_load(string const&   filename)const;
       virtual bool do_save(string const&   filename, const CTriangleMesh& data)const;
       virtual const std::string  do_get_descr()const;

       struct vless {
              bool operator ()(const C3DFVector& a, const C3DFVector& b)const
              {
                     return (a.z < b.z) ||
                            ((a.z == b.z) && ((a.y < b.y) ||
                                              ((a.y == b.y) && (a.x < b.x))
                                             )
                            );
              }
       };

       typedef map<CTriangleMesh::vertex_type, size_t, vless> CVertexMap;
       PTriangleMesh load_ascii(istream& f)const;
       bool read_vertex(CVertexMap& vmap, unsigned int& index, istream& f)const;
       bool save_ascii(ostream& of, const CTriangleMesh& data)const;
};


extern "C" EXPORT CPluginBase *get_plugin_interface()
{
       return new CSTLMeshIO;
}

const string start_solid("solid");
const string end_solid("endsolid");
const string start_face("facet");
const string end_face("endfacet");
const string vertex_tag("vertex");
const string normal_tag("normal");
const string start_loop("outer");
const string tag_loop("loop");
const string end_loop("endloop");

CSTLMeshIO::CSTLMeshIO():
       CMeshIOPlugin(format)
{
       add_suffix(".stl");
       add_suffix(".STL");
}

bool CSTLMeshIO::read_vertex(CVertexMap& vmap, unsigned int& index, istream& f)const
{
       string tag;
       C3DFVector vertex;
       f >> tag >> vertex.x >> vertex.y >> vertex.z;

       // coverity[TAINTED_SCALAR]
       if (tag != vertex_tag) {
              cverr() << "not a valid ascii STL file\n";
              return false;
       }

       cvdebug() << "Read vertex: " << vertex << "\n";
       CVertexMap::const_iterator v = vmap.find(vertex);

       if (v != vmap.end()) {
              index = v->second;
       } else {
              index = vmap.size();
              vmap[vertex] = index;
       }

       return true;
}

PTriangleMesh CSTLMeshIO::load_ascii(istream& f)const
{
       string attribute;
       string tag;
       string solid_name;
       CVertexMap vmap;
       vector<CTriangleMesh::triangle_type> faces;
       f >> attribute;

       // coverity is complaining when a tainted string is used in
       // std::string::operator !=
       //
       // coverity[TAINTED_SCALAR]
       if (attribute != start_solid)
              return PTriangleMesh();

       f >> tag;

       // coverity[TAINTED_SCALAR]
       if (tag != start_face) {
              cvdebug() << "Loading solid " << tag  << "\n";
              f >> tag;
       }

       // coverity[TAINTED_SCALAR]
       while (tag != end_solid) {
              CTriangleMesh::triangle_type face;
              C3DFVector face_normal;

              // coverity[TAINTED_SCALAR]
              if (tag != start_face)
                     goto fail;

              cverb << "start face ";
              f >> tag;

              // coverity[TAINTED_SCALAR]
              if (tag != normal_tag)
                     goto fail;

              f >> face_normal.x >> face_normal.y >> face_normal.z;
              //	face_normals.push_back(face_normal);
              cverb << " normal " << face_normal;
              f >> tag;

              // coverity[TAINTED_SCALAR]
              if (tag != start_loop)
                     goto fail;

              f >> tag;

              // coverity[TAINTED_SCALAR]
              if (tag != tag_loop)
                     goto fail;

              if (!read_vertex(vmap, face.x, f))
                     goto fail;

              if (!read_vertex(vmap, face.y, f))
                     goto fail;

              if (!read_vertex(vmap, face.z, f))
                     goto fail;

              f >> tag;

              // coverity[TAINTED_SCALAR]
              if (tag != end_loop)
                     goto fail;

              f >> tag;

              // coverity[TAINTED_SCALAR]
              if (tag != end_face)
                     goto fail;

              cverb << " finished\n";
              faces.push_back(face);
              f >> tag;

              if (!f.good())
                     goto fail;
       }

       {
              cvmsg() << "Got a mesh with " << vmap.size() << " vertices and " <<
                      faces.size() << " faces\n";
              CTriangleMesh::PVertexfield vertices(new CTriangleMesh::CVertexfield(vmap.size()));

              // remap the vertices
              for (CVertexMap::const_iterator i = vmap.begin();
                   i != vmap.end(); ++i)
                     (*vertices)[i->second] = i->first;

              cvinfo() << "STL: For now the face normals given in the file are thrown away\n";
              CTriangleMesh::PTrianglefield tri(new CTriangleMesh::CTrianglefield(faces.size()));
              copy(faces.begin(), faces.end(), tri->begin());
              return PTriangleMesh(new CTriangleMesh(tri, vertices));
       }

fail:
       throw create_exception<runtime_error>("STL: not a valid ascii STL mesh file\n");
}

PTriangleMesh CSTLMeshIO::do_load(string const&   filename)const
{
       PTriangleMesh result;
       cvdebug() << "try stl mesh\n";

       if (filename != "-") {
              ifstream inf(filename.c_str());
              result = load_ascii(inf);
       } else
              result = load_ascii(cin);

       if (!result) { // probably a binary file
              cvinfo() << "If this is a binary STL mesh then it is not (yet) supported\n";
              return PTriangleMesh();
       }

       return PTriangleMesh(result);
}

bool CSTLMeshIO::save_ascii(ostream& of, const CTriangleMesh& data)const
{
       of << start_solid << '\n';
       //	int s = data.triangle_size();
       int i = 0;

       for (CTriangleMesh::const_triangle_iterator t = data.triangles_begin();
            t != data.triangles_end(); ++t, ++i) {
              const C3DFVector& vx = data.vertex_at(t->x);
              const C3DFVector& vy = data.vertex_at(t->y);
              const C3DFVector& vz = data.vertex_at(t->z);
              C3DFVector e1 = vx - vy;
              C3DFVector e2 = vz - vy;
              C3DFVector normal = e2 ^ e1;
              double n = normal.norm2();

              if (n > 0.0)
                     normal /= sqrt(n);

              of << "  " << start_face << " " << normal_tag << " "
                 << normal.x << " " << normal.y << " " << normal.z << "\n";
              of << "    " << start_loop << " " << tag_loop << "\n";
              of << "      "  << vertex_tag << " "
                 << vx.x << " " << vx.y << " " << vx.z << "\n";
              of << "      "  << vertex_tag << " "
                 << vy.x << " " << vy.y << " " << vy.z << "\n";
              of << "      "  << vertex_tag << " "
                 << vz.x << " " << vz.y << " " << vz.z << "\n";
              of << "    "  << end_loop << "\n";
              of << "  "  << end_face << "\n";
       }

       of << end_solid << "\n";
       return of.good();
}

bool CSTLMeshIO::do_save(string const&   filename, const CTriangleMesh& data)const
{
       if (filename != "-") {
              ofstream of(filename.c_str());
              return save_ascii(of, data);
       } else
              return save_ascii(cout, data);
}

const string CSTLMeshIO::do_get_descr()const
{
       return "STL mesh io plugin";
}

NS_END
