/* -*- 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 <iostream>
#include <fstream>
#include <algorithm>
#include <vector>
#include <string>

#include <glib.h>

// very wired thing, this should work automatically ...
#ifdef G_HAVE_INLINE
#define G_CAN_INLINE 1
#endif

#include <gts.h>

#include <libmia/plugin_handler.hh>
#include <libmia/triangularMesh.hh>
#include <libmia/monaFileIO.hh>

namespace gts_mesh_io
{
using namespace mia;
using namespace std;

static char const *format = "gts";

class TGtsMeshIO: public CTriangleMeshIO
{
public:
       TGtsMeshIO();
       virtual CTriangleMesh *load(string const&  filename, bool, CProgressCallback *cb)const;
       virtual bool save(const CTriangleMesh& data, string const&  filename, bool, CProgressCallback *cb)const;
       virtual const string short_descr()const;
};


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


TGtsMeshIO::TGtsMeshIO(): CTriangleMeshIO(format)
{
}

const string  TGtsMeshIO::short_descr()const
{
       return string("GNU Triangulates Surface input/output support");
}

typedef struct  {
       CTriangleMesh::vertex_iterator vi;
       guint32  c_index;
} vertex_data_t;


typedef struct {
       CTriangleMesh::triangle_iterator ti;
       guint32 c_index;
} triangle_data_t;

static void copy_vertices(GtsPoint *p, vertex_data_t *v)
{
       CTriangleMesh::vertexfield_type::value_type& vec = *v->vi;
       vec.x = p->x;
       vec.y = p->y;
       vec.z = p->z;
       GTS_OBJECT(p)->reserved = GUINT_TO_POINTER(v->c_index++);
       ++v->vi;
}

static void copy_triangles(GtsTriangle *tri, CTriangleMesh::triangle_iterator *ti)
{
       GtsVertex *v1;
       GtsVertex *v2;
       GtsVertex *v3;
       gts_triangle_vertices(tri, &v1, &v2, &v3);
       (**ti).x = GPOINTER_TO_UINT( GTS_OBJECT(v1)->reserved);
       (**ti).y = GPOINTER_TO_UINT( GTS_OBJECT(v2)->reserved);
       (**ti).z = GPOINTER_TO_UINT( GTS_OBJECT(v3)->reserved);
       ++(*ti);
}

CTriangleMesh *TGtsMeshIO::load(string const&  filename, bool from_stdin, CProgressCallback *cb) const
{
       CTriangleMesh *result = NULL;
       CInputFile f(filename, from_stdin);

       if (!f)
              return NULL;

       GtsSurface *surface = gts_surface_new (gts_surface_class (), gts_face_class (),
                                              gts_edge_class (), gts_vertex_class ());
       GtsFile *gtsf = gts_file_new(f);

       if (gts_surface_read (surface, gtsf) == 0) {
              int n_vertices = gts_surface_vertex_number(surface);
              int n_faces = gts_surface_face_number(surface);
              CTriangleMesh::vertexfield_type *vertices  = new  CTriangleMesh::vertexfield_type(n_vertices);
              CTriangleMesh::trianglefield_type *triangles = new  CTriangleMesh::trianglefield_type(n_faces);
              vertex_data_t vdata = {vertices->begin(), 0};
              gts_surface_foreach_vertex(surface, (GtsFunc)copy_vertices, &vdata);
              CTriangleMesh::triangle_iterator ti = triangles->begin();
              gts_surface_foreach_face(surface, (GtsFunc)copy_triangles, &ti);
              result = new CTriangleMesh(triangles, vertices, NULL, NULL, NULL);
       } else {
              cvdebug() << "no gts mesh\n";
       }

       if (GTS_IS_SURFACE(surface))
              gts_object_destroy((GtsObject *)surface);

       gts_file_destroy(gtsf);
       return result;
}

class FCopyVertex
{
       vector<GtsVertex *>::iterator target;
public:
       FCopyVertex(vector<GtsVertex *>::iterator i): target(i) {}
       void operator()(const CTriangleMesh::vertexfield_type::value_type& vertex)
       {
              *target = gts_vertex_new(gts_vertex_class(), vertex.x, vertex.y, vertex.z);
              ++target;
       }
};

class FBuildSurface
{
       vector<GtsVertex *>& v;
       GtsSurface *s;
public:
       FBuildSurface(vector<GtsVertex *>& _v, GtsSurface *_s):
              v(_v), s(_s) {}

       GtsEdge *get_edge(GtsVertex *a, GtsVertex *b)
       {
              GtsSegment *seg = gts_vertices_are_connected (a, b);

              if (GTS_IS_EDGE (seg))
                     return GTS_EDGE(seg);
              else
                     return gts_edge_new (s->edge_class, a, b);
       }

       void operator()(const CTriangleMesh::trianglefield_type::value_type& t)
       {
              GtsVertex *v1 = v[t.x];
              GtsVertex *v2 = v[t.y];
              GtsVertex *v3 = v[t.z];
              GtsEdge *e1 = get_edge(v1, v2);
              GtsEdge *e2 = get_edge(v2, v3);
              GtsEdge *e3 = get_edge(v3, v1);
              gts_surface_add_face (s, gts_face_new (s->face_class, e1, e2, e3));
       }
};

bool TGtsMeshIO::save(const CTriangleMesh& mesh, string const&  filename, bool to_stdout, CProgressCallback *cb) const
{
       COutputFile f(filename, to_stdout);

       if (!f)
              return false;

       GtsSurface *surface = gts_surface_new (gts_surface_class (), gts_face_class (),
                                              gts_edge_class (), gts_vertex_class ());
       vector<GtsVertex *> GtsV(mesh.vertices_size());
       for_each(mesh.vertices_begin(), mesh.vertices_end(), FCopyVertex(GtsV.begin()));
       for_each(mesh.triangles_begin(), mesh.triangles_end(), FBuildSurface(GtsV, surface));
       GtsFile *gtsf = gts_file_new(f);
       gts_surface_write(surface, f);
       gts_file_destroy(gtsf);
       gts_object_destroy(GTS_OBJECT(surface));
       return true;
}

}
