// Copyright 2006-2015 Tobias Sargeant (tobias.sargeant@gmail.com).
//
// This file is part of the Carve CSG Library (http://carve-csg.com/)
//
// Permission is hereby granted, free of charge, to any person
// obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without
// restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies
// of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
// BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

#pragma once

namespace carve {
namespace line {

inline PolylineEdge::PolylineEdge(Polyline* _parent, size_t _edgenum,
                                  Vertex* _v1, Vertex* _v2)
    : tagable(), parent(_parent), edgenum(_edgenum), v1(_v1), v2(_v2) {}

inline carve::geom3d::AABB PolylineEdge::aabb() const {
  carve::geom3d::AABB a;
  a.fit(v1->v, v2->v);
  return a;
}

inline PolylineEdge* PolylineEdge::prevEdge() const {
  if (edgenum) {
    return parent->edge(edgenum - 1);
  } else {
    if (parent->closed) {
      return parent->edge(parent->edgeCount() - 1);
    } else {
      return nullptr;
    }
  }
}

inline PolylineEdge* PolylineEdge::nextEdge() const {
  if (edgenum + 1 < parent->edgeCount()) {
    return parent->edge(edgenum + 1);
  } else {
    if (parent->closed) {
      return parent->edge(0);
    } else {
      return nullptr;
    }
  }
}

inline Polyline::Polyline() : edges() {}

inline size_t Polyline::vertexCount() const {
  return edgeCount() + (closed ? 0 : 1);
}

inline size_t Polyline::edgeCount() const {
  return edges.size();
}

inline const PolylineEdge* Polyline::edge(size_t e) const {
  return edges[e % edges.size()];
}

inline PolylineEdge* Polyline::edge(size_t e) {
  return edges[e % edges.size()];
}

inline const Vertex* Polyline::vertex(size_t v) const {
  if (closed) {
    v %= edgeCount();
  } else if (v >= edgeCount()) {
    return v == edgeCount() ? edges.back()->v2 : nullptr;
  }
  return edges[v]->v1;
}

inline Vertex* Polyline::vertex(size_t v) {
  if (closed) {
    v %= edgeCount();
  } else if (v >= edgeCount()) {
    return v == edgeCount() ? edges.back()->v2 : nullptr;
  }
  return edges[v]->v1;
}

inline bool Polyline::isClosed() const {
  return closed;
}

template <typename iter_t>
void Polyline::_init(bool c, iter_t begin, iter_t end,
                     std::vector<Vertex>& vertices) {
  closed = c;

  PolylineEdge* e;
  if (begin == end) {
    return;
  }
  size_t v1 = (size_t)*begin++;
  if (begin == end) {
    return;
  }

  while (begin != end) {
    size_t v2 = (size_t)*begin++;
    e = new PolylineEdge(this, edges.size(), &vertices[v1], &vertices[v2]);
    edges.push_back(e);
    v1 = v2;
  }

  if (closed) {
    e = new PolylineEdge(this, edges.size(), edges.back()->v2,
                         edges.front()->v1);
    edges.push_back(e);

    edges.front()->v1->addEdgePair(edges.back(), edges.front());
    for (size_t i = 1; i < edges.size(); ++i) {
      edges[i]->v1->addEdgePair(edges[i - 1], edges[i]);
    }
  } else {
    edges.front()->v1->addEdgePair(nullptr, edges.front());
    for (size_t i = 1; i < edges.size(); ++i) {
      edges[i]->v1->addEdgePair(edges[i - 1], edges[i]);
    }
    edges.back()->v2->addEdgePair(edges.back(), nullptr);
  }
}

template <typename iter_t>
void Polyline::_init(bool closed, iter_t begin, iter_t end,
                     std::vector<Vertex>& vertices, std::forward_iterator_tag) {
  _init(closed, begin, end, vertices);
}

template <typename iter_t>
void Polyline::_init(bool closed, iter_t begin, iter_t end,
                     std::vector<Vertex>& vertices,
                     std::random_access_iterator_tag) {
  edges.reserve(end - begin - (closed ? 0 : 1));
  _init(closed, begin, end, vertices);
}

template <typename iter_t>
Polyline::Polyline(bool closed, iter_t begin, iter_t end,
                   std::vector<Vertex>& vertices) {
  _init(closed, begin, end, vertices,
        typename std::iterator_traits<iter_t>::iterator_category());
}

template <typename iter_t>
void PolylineSet::addPolyline(bool closed, iter_t begin, iter_t end) {
  Polyline* p = new Polyline(closed, begin, end, vertices);
  lines.push_back(p);
}

inline size_t PolylineSet::vertexToIndex_fast(const Vertex* v) const {
  return (size_t)(v - &vertices[0]);
}
}  // namespace line
}  // namespace carve
