// Begin License:
// Copyright (C) 2006-2008 Tobias Sargeant (tobias.sargeant@gmail.com).
// All rights reserved.
//
// This file is part of the Carve CSG Library (http://carve-csg.com/)
//
// This file may be used under the terms of the GNU General Public
// License version 2.0 as published by the Free Software Foundation
// and appearing in the file LICENSE.GPL2 included in the packaging of
// this file.
//
// This file is provided "AS IS" with NO WARRANTY OF ANY KIND,
// INCLUDING THE WARRANTIES OF DESIGN, MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE.
// End:

#include <carve/csg.hpp>
#include <carve/tag.hpp>
#include <carve/triangulator.hpp>
#include <deque>

namespace carve {
  namespace csg {

    namespace detail {
      template<bool with_improvement>
      class CarveTriangulator : public csg::CSG::Hook {

      public:
        CarveTriangulator() {
        }

        virtual ~CarveTriangulator() {
        }

        virtual void processOutputFace(std::vector<poly::Polyhedron::face_t *> &faces,
                                       const poly::Polyhedron::face_t *orig,
                                       bool flipped) {
          std::vector<poly::Polyhedron::face_t *> out_faces;

          size_t n_tris = 0;
          for (size_t f = 0; f < faces.size(); ++f) {
            CARVE_ASSERT(faces[f]->nVertices() >= 3);
            n_tris += faces[f]->nVertices() - 2;
          }

          out_faces.reserve(n_tris);

          for (size_t f = 0; f < faces.size(); ++f) {
            poly::Polyhedron::face_t *face = faces[f];

            if (face->nVertices() == 3) {
              out_faces.push_back(face);
              continue;
            }

            std::vector<triangulate::tri_idx> result;

            std::vector<const poly::Polyhedron::vertex_t *> vloop;
            face->getVertexLoop(vloop);

            triangulate::triangulate(face->projector(), vloop, result);
            if (with_improvement) {
              triangulate::improve(face->projector(), vloop, result);
            }

            std::vector<const poly::Polyhedron::vertex_t *> fv;
            fv.resize(3);
            for (size_t i = 0; i < result.size(); ++i) {
              fv[0] = vloop[result[i].a];
              fv[1] = vloop[result[i].b];
              fv[2] = vloop[result[i].c];
              out_faces.push_back(face->create(fv, false));
            }
            delete face;
          }
          std::swap(faces, out_faces);
        }
      };
    }

    typedef detail::CarveTriangulator<false> CarveTriangulator;
    typedef detail::CarveTriangulator<true> CarveTriangulatorWithImprovement;

    class CarveTriangulationImprover : public csg::CSG::Hook {
    public:
      CarveTriangulationImprover() {
      }

      virtual ~CarveTriangulationImprover() {
      }

      virtual void processOutputFace(std::vector<poly::Polyhedron::face_t *> &faces,
                                     const poly::Polyhedron::face_t *orig,
                                     bool flipped) {
        if (faces.size() == 1) return;

        // doing improvement as a separate hook is much messier than
        // just incorporating it into the triangulation hook.

        typedef std::map<const poly::Polyhedron::vertex_t *, size_t> vert_map_t;
        std::vector<poly::Polyhedron::face_t *> out_faces;
        vert_map_t vert_map;

        out_faces.reserve(faces.size());

        poly::p2_adapt_project<3> projector(faces[0]->project);

        std::vector<triangulate::tri_idx> result;

        for (size_t f = 0; f < faces.size(); ++f) {
          poly::Polyhedron::face_t *face = faces[f];
          if (face->nVertices() != 3) {
            out_faces.push_back(face);
          } else {
            triangulate::tri_idx tri;
            for (size_t i = 0; i < 3; ++i) {
              size_t v = 0;
              vert_map_t::iterator j = vert_map.find(face->vertex(i));
              if (j == vert_map.end()) {
                v = vert_map.size();
                vert_map[face->vertex(i)] = v;
              } else {
                v = (*j).second;
              }
              tri.v[i] = v;
            }
            result.push_back(tri);
            delete face;
          }
        }

        std::vector<const poly::Polyhedron::vertex_t *> verts;
        verts.resize(vert_map.size());
        for (vert_map_t::iterator i = vert_map.begin(); i != vert_map.end(); ++i) {
          verts[(*i).second] = (*i).first;
        }
 
        triangulate::improve(projector, verts, result);

        std::vector<const poly::Polyhedron::vertex_t *> fv;
        fv.resize(3);
        for (size_t i = 0; i < result.size(); ++i) {
          fv[0] = verts[result[i].a];
          fv[1] = verts[result[i].b];
          fv[2] = verts[result[i].c];
          out_faces.push_back(orig->create(fv, false));
        }

        std::swap(faces, out_faces);
      }
    };

    class CarveTriangulationQuadMerger : public csg::CSG::Hook {
      // this code is incomplete.
      typedef std::map<V2, F2> edge_map_t;

    public:
      CarveTriangulationQuadMerger() {
      }

      virtual ~CarveTriangulationQuadMerger() {
      }

      double scoreQuad(edge_map_t::iterator i, edge_map_t &edge_map) {
        if (!(*i).second.first || !(*i).second.second) return -1;
      }

      poly::Polyhedron::face_t *mergeQuad(edge_map_t::iterator i, edge_map_t &edge_map) {
        return NULL;
      }

      void recordEdge(const poly::Polyhedron::vertex_t *v1,
                      const poly::Polyhedron::vertex_t *v2,
                      const poly::Polyhedron::face_t *f,
                      edge_map_t &edge_map) {
        if (v1 < v2) {
          edge_map[V2(v1, v2)].first = f;
        } else {
          edge_map[V2(v2, v1)].second = f;
        }
      }

      virtual void processOutputFace(std::vector<poly::Polyhedron::face_t *> &faces,
                                     const poly::Polyhedron::face_t *orig,
                                     bool flipped) {
        if (faces.size() == 1) return;

        std::vector<poly::Polyhedron::face_t *> out_faces;
        edge_map_t edge_map;

        out_faces.reserve(faces.size());

        poly::p2_adapt_project<3> projector(faces[0]->project);

        for (size_t f = 0; f < faces.size(); ++f) {
          poly::Polyhedron::face_t *face = faces[f];
          if (face->nVertices() != 3) {
            out_faces.push_back(face);
          } else {
            recordEdge(face->vertex(0), face->vertex(1), face, edge_map);
            recordEdge(face->vertex(1), face->vertex(2), face, edge_map);
            recordEdge(face->vertex(2), face->vertex(0), face, edge_map);
          }
        }

        for (edge_map_t::iterator i = edge_map.begin(); i != edge_map.end();) {
          if ((*i).second.first && (*i).second.second) {
            ++i;
          } else {
            edge_map.erase(i++);
          }
        }

        while (edge_map.size()) {
          edge_map_t::iterator i = edge_map.begin();
          edge_map_t::iterator best = i;
          double best_score = scoreQuad(i, edge_map);
          for (++i; i != edge_map.end(); ++i) {
            double score = scoreQuad(i, edge_map);
            if (score > best_score) best = i;
          }
          if (best_score < 0) break;
          out_faces.push_back(mergeQuad(best, edge_map));
        }

        if (edge_map.size()) {
          tagable::tag_begin();
          for (edge_map_t::iterator i = edge_map.begin(); i != edge_map.end(); ++i) {
            poly::Polyhedron::face_t *a = const_cast<poly::Polyhedron::face_t *>((*i).second.first);
            poly::Polyhedron::face_t *b = const_cast<poly::Polyhedron::face_t *>((*i).second.first);
            if (a && a->tag_once()) out_faces.push_back(a);
            if (b && b->tag_once()) out_faces.push_back(b);
          }
        }

        std::swap(faces, out_faces);
      }
    };

    class CarveHoleResolver : public csg::CSG::Hook {

    public:
      CarveHoleResolver() {
      }

      virtual ~CarveHoleResolver() {
      }

      bool findRepeatedEdges(const std::vector<const poly::Polyhedron::vertex_t *> &vertices,
                             std::list<std::pair<size_t, size_t> > &edge_pos) {
        std::map<V2, size_t> edges;
        for (size_t i = 0; i < vertices.size() - 1; ++i) {
          edges[std::make_pair(vertices[i], vertices[i+1])] = i;
        }
        edges[std::make_pair(vertices[vertices.size()-1], vertices[0])] = vertices.size() - 1;

        for (std::map<V2, size_t>::iterator i = edges.begin(); i != edges.end(); ++i) {
          V2 rev = V2((*i).first.second, (*i).first.first);
          std::map<V2, size_t>::iterator j = edges.find(rev);
          if (j != edges.end()) {
            edge_pos.push_back(std::make_pair((*i).second, (*j).second));
          }
        }
        return edge_pos.size() > 0;
      }

      void flood(size_t t1,
                 size_t t2,
                 size_t old_grp,
                 size_t new_grp_1,
                 size_t new_grp_2,
                 std::vector<size_t> &grp,
                 const std::vector<triangulate::tri_idx> &tris,
                 const std::map<std::pair<size_t, size_t>, size_t> &tri_edge) {
        grp[t1] = new_grp_1;
        grp[t2] = new_grp_2;

        std::deque<size_t> to_visit;
        to_visit.push_back(t1);
        to_visit.push_back(t2);
        std::vector<std::pair<size_t, size_t> > rev;
        rev.resize(3);
        while (to_visit.size()) {
          size_t curr = to_visit.front();
          to_visit.pop_front();
          triangulate::tri_idx ct = tris[curr];
          rev[0] = std::make_pair(ct.b, ct.a);
          rev[1] = std::make_pair(ct.c, ct.b);
          rev[2] = std::make_pair(ct.a, ct.c);

          for (size_t i = 0; i < 3; ++i) {
            std::map<std::pair<size_t, size_t>, size_t>::const_iterator adj = tri_edge.find(rev[i]);
            if (adj == tri_edge.end()) continue;
            size_t next = (*adj).second;
            if (grp[next] != old_grp) continue;
            grp[next] = grp[curr];
            to_visit.push_back(next);
          }
        }
      }

      void findPerimeter(const std::vector<triangulate::tri_idx> &tris,
                         const std::vector<const poly::Polyhedron::vertex_t *> &verts,
                         std::vector<const poly::Polyhedron::vertex_t *> &out) {
        std::map<std::pair<size_t, size_t>, size_t> edges;
        for (size_t i = 0; i < tris.size(); ++i) {
          edges[std::make_pair(tris[i].a, tris[i].b)] = i;
          edges[std::make_pair(tris[i].b, tris[i].c)] = i;
          edges[std::make_pair(tris[i].c, tris[i].a)] = i;
        }
        std::map<size_t, size_t> unpaired;
        for (std::map<std::pair<size_t, size_t>, size_t>::iterator i = edges.begin(); i != edges.end(); ++i) {
          if (edges.find(std::make_pair((*i).first.second, (*i).first.first)) == edges.end()) {
            CARVE_ASSERT(unpaired.find((*i).first.first) == unpaired.end());
            unpaired[(*i).first.first] = (*i).first.second;
          }
        }
        out.clear();
        out.reserve(unpaired.size());
        size_t start = (*unpaired.begin()).first;
        size_t vert = start;
        do {
          out.push_back(verts[vert]);
          CARVE_ASSERT(unpaired.find(vert) != unpaired.end());
          vert = unpaired[vert];
        } while (vert != start);
      }

      virtual void processOutputFace(std::vector<poly::Polyhedron::face_t *> &faces,
                                     const poly::Polyhedron::face_t *orig,
                                     bool flipped) {
        std::vector<poly::Polyhedron::face_t *> out_faces;

        for (size_t f = 0; f < faces.size(); ++f) {
          poly::Polyhedron::face_t *face = faces[f];

          if (face->nVertices() == 3) {
            out_faces.push_back(face);
            continue;
          }

          std::vector<const poly::Polyhedron::vertex_t *> vloop;
          face->getVertexLoop(vloop);

          std::list<std::pair<size_t, size_t> > rep_edges;
          if (!findRepeatedEdges(vloop, rep_edges)) {
            out_faces.push_back(face);
            continue;
          }

          std::vector<triangulate::tri_idx> result;
          triangulate::triangulate(face->projector(), vloop, result);

          std::map<std::pair<size_t, size_t>, size_t> tri_edge;
          for (size_t i = 0; i < result.size(); ++i) {
            tri_edge[std::make_pair(result[i].a, result[i].b)] = i;
            tri_edge[std::make_pair(result[i].b, result[i].c)] = i;
            tri_edge[std::make_pair(result[i].c, result[i].a)] = i;
          }

          std::vector<size_t> grp;
          grp.resize(result.size(), 0);

          size_t grp_max = 0;

          while (rep_edges.size()) {
            std::pair<size_t, size_t> e1, e2;

            e1.first = rep_edges.front().first;
            e1.second = (e1.first + 1) % vloop.size();

            e2.first = rep_edges.front().second;
            e2.second = (e2.first + 1) % vloop.size();

            rep_edges.pop_front();

            CARVE_ASSERT(tri_edge.find(e1) != tri_edge.end());
            size_t t1 = tri_edge[e1];
            CARVE_ASSERT(tri_edge.find(e2) != tri_edge.end());
            size_t t2 = tri_edge[e2];

            if (grp[t1] != grp[t2]) {
              continue;
            }

            size_t t1g = ++grp_max;
            size_t t2g = ++grp_max;

            flood(t1, t2, grp[t1], t1g, t2g, grp, result, tri_edge);
          }

          std::set<size_t> groups;
          std::copy(grp.begin(), grp.end(), std::inserter(groups, groups.begin()));

          // now construct perimeters for each group.
          std::vector<triangulate::tri_idx> grp_tris;
          grp_tris.reserve(result.size());
          for (std::set<size_t>::iterator i = groups.begin(); i != groups.end(); ++i) {
            size_t grp_id = *i;
            grp_tris.clear();
            for (size_t j = 0; j < grp.size(); ++j) {
              if (grp[j] == grp_id) {
                grp_tris.push_back(result[j]);
              }
            }
            std::vector<const poly::Polyhedron::vertex_t *> grp_perim;
            findPerimeter(grp_tris, vloop, grp_perim);
            out_faces.push_back(face->create(grp_perim, false));
          }
        }
        std::swap(faces, out_faces);
      }
    };
  }
}
