#include "Mesh.hpp"
#include <algorithm>
#include <iomanip>

const Mesh::Faces& Mesh::Faces::render() const {
  for (
    const_iterator i = begin();
    i < end();
    i++
  ) {
    (*i)->render();
  }
  return *this;
}

const bool Mesh::Faces::isUsed(const Vertex* v) const {
  for (
    const_iterator i = begin();
    i < end();
    i++
  ) {
    if (
      (*i)->contain(v)
    ) {
      return true;
    }
  }
  return false;
}

Mesh::Faces& Mesh::Faces::removeDegenerated() {
  Faces degenerated;
  for (
    iterator i = begin();
    i < end();
    i++
  ) {
    if (
      (*i)->isDegenerated()
    ) {
      degenerated.push_back(*i);
    }
  }
  for (
    iterator i = degenerated.begin();
    i < degenerated.end();
    i++
  ) {
    erase(
      std::find(
        begin(),
        end(),
        *i
      )
    );
    delete *i;
  }
  return *this;
}

Mesh& Mesh::randomizeXZ(const double factor) {
  static Vector v;
  return randomize(
    v.set(
      factor,
      0,
      factor
    )
  );
}

Mesh::~Mesh() {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    delete *i;
  }
  vertices.clear();
  for (
    Faces::iterator i = faces.begin();
    i < faces.end();
    i++
  ) {
    delete *i;
  }
  faces.clear();
}

Vertex* Mesh::getVertex(const Vector& vector) const {
  if (
    vertices.size() > 0
  ) {
    struct {
      Vertices::const_iterator i;
      double d;
    } ret;
    ret.i = vertices.begin();
    ret.d = (*ret.i)->getDistance(vector);
    Vertices::const_iterator i = vertices.begin();
    while (
      ++i != vertices.end()
    ) {
      const double d = (*i)->getDistance(vector);
      if (d < ret.d) {
        ret.i = i;
        ret.d = d;
      }
    }
    return *ret.i;
  }
  throw std::runtime_error("Zero size Mesh::vertices");
}

Mesh& Mesh::setVector(
  const Vector& vector,
  const Vector& v
) {
  *getVertex(vector) = v;
  return *this;
}

Mesh& Mesh::setVector(
  const double x,
  const double y,
  const double z,
  const double x2,
  const double y2,
  const double z2
) {
  return setVector(
    Vector(
      x,
      y,
      z
    ),
    Vector(
      x2,
      y2,
      z2
    )
  );
}

Mesh& Mesh::setColor(
  const Vector& vector,
  const Color& c
) {
  *getVertex(vector) = c;
  return *this;
}

Mesh& Mesh::setColor(
  const double x,
  const double y,
  const double z,
  const Color& c
) {
  return setColor(
    Vector(
      x,
      y,
      z
    ),
    c
  );
}

Mesh& Mesh::setColor(const Color& c) {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    **i = c;
  }
  return *this;
}

Mesh& Mesh::translate(
  const double x,
  const double y,
  const double z
) {
  return *this += Vector(
    x,
    y,
    z
  );
}

Mesh& Mesh::translate(const double y) {
  return translate(
    0,
    y
  );
}

Mesh& Mesh::scale(
  const double x,
  const double y,
  const double z
) {
  return *this *= Vector(
    x,
    y,
    z
  );
}

Mesh& Mesh::rotateX(const double rad) {
  static Matrix m;
  return *this *= m.setXRotation(rad);
}

Mesh& Mesh::rotateY(const double rad) {
  static Matrix m;
  return *this *= m.setYRotation(rad);
}

Mesh& Mesh::rotateZ(const double rad) {
  static Matrix m;
  return *this *= m.setZRotation(rad);
}

Vertex* Mesh::addVertex(Vertex* vertex) {
  vertices.push_back(vertex);
  return vertex;
}

Face* Mesh::addFace(Face* face) {
  faces.push_back(face);
  return face;
}

void Mesh::render() const {
  faces.render();
}

Mesh& Mesh::removeNotUsedVertices() {
  Vertices notUsed;
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    if (
      !faces.isUsed(*i)
    ) {
      notUsed.push_back(*i);
    }
  }
  for (
    Vertices::iterator i = notUsed.begin();
    i < notUsed.end();
    i++
  ) {
    vertices.erase(
      std::find(
        vertices.begin(),
        vertices.end(),
        *i
      )
    );
    delete *i;
  }
  return *this;
}

Mesh& Mesh::mergeVertices(const double e) {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    for (
      Vertices::iterator j = vertices.begin();
      j < vertices.end();
      j++
    ) {
      if (
        j > i
        &&
        (*i)->getDistance(**j) < e
      ) {
        for (
          Faces::iterator k = faces.begin();
          k < faces.end();
          k++
        ) {
          (*k)->swap(
            *j,
            *i
          );
        }
      }
    }
  }
  faces.removeDegenerated();
  return removeNotUsedVertices();
}

Mesh* Mesh::clone() const {
  Mesh* ret = new Mesh();
  *ret += *this;
  return ret;
}

const Box& Mesh::getBox() const {
  static Box box;
  box.min.zero();
  box.max.zero();
  #define SET_MIN(a)\
    if (\
      i == vertices.begin() || (*i)->a < box.min.a\
    ) {\
      box.min.a = (*i)->a;\
    }
  #define SET_MAX(a)\
    if (\
      i == vertices.begin() || (*i)->a > box.max.a\
    ) {\
      box.max.a = (*i)->a;\
    }
  for (
    Vertices::const_iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    SET_MIN(x);
    SET_MIN(y);
    SET_MIN(z);
    SET_MAX(x);
    SET_MAX(y);
    SET_MAX(z);
  }
  #undef SET_MAX
  #undef SET_MIN
  return box;
}

Mesh& Mesh::centralize() {
  static Vector v;
  return *this += (
    v = getBox().getCenter()
  ).neg();
}

Mesh& Mesh::normalize() {
  const Vector& v = getBox().getSize();
  return scale(
    1.0 / v.x,
    1.0 / v.y,
    1.0 / v.z
  );
}

Mesh& Mesh::randomize(const Vector& factor) {
  for (
    Vertices::const_iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    (*i)->randomize(factor);
  }
  return *this;
}

Mesh& Mesh::round(const double precision) {
  for (
    Vertices::const_iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    (*i)->round(precision);
  }
  return mergeVertices(1.0 / precision / 10.0);
}

Mesh& Mesh::setLight(const Color& bottomColor) {
  const Box& b = getBox();
  const Vector& size = b.getSize();
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    const double f = (
      (*i)->y - b.min.y
    ) / size.y;
    #define SET(c) \
      (*i)->c = bottomColor.c + (\
        (*i)->c - bottomColor.c\
      ) * f;
    SET(r);
    SET(g);
    SET(b);
    #undef SET
  }
  return *this;
}

Mesh& Mesh::operator *= (const double factor) {
  return scale(
    factor,
    factor,
    factor
  );
}

Mesh& Mesh::operator *= (const Vector& v) {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    (*i)->scale(v);
  }
  return *this;
}

Mesh& Mesh::operator *= (const Matrix& m) {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    **i *= m;
  }
  return *this;
}

Mesh& Mesh::operator += (const Vector& v) {
  for (
    Vertices::iterator i = vertices.begin();
    i < vertices.end();
    i++
  ) {
    **i += v;
  }
  return *this;
}

Mesh& Mesh::operator += (const Mesh& m) {
  for (
    Faces::const_iterator i = m.faces.begin();
    i < m.faces.end();
    i++
  ) {
    addFace(
      (*i)->clone()
    );
  }
  for (
    Vertices::const_iterator j = m.vertices.begin();
    j < m.vertices.end();
    j++
  ) {
    const Vertex* v = addVertex(
      new Vertex(**j)
    );
    for (
      Faces::iterator i = faces.begin();
      i < faces.end();
      i++
    ) {
      (*i)->swap(
        *j,
        v
      );
    }
  }
  return mergeVertices();
}

std::ostream& Mesh::operator >> (std::ostream& os) const {
  for (
    unsigned int i = 0;
    i < vertices.size();
    i++
  ) {
    os << std::setw(8) << i << ": " << *vertices.at(i) << std::endl;
  }
  return os << std::endl << std::setw(9) << vertices.size() << " " << std::setw(8) << faces.size();
}

std::ostream& operator << (
  std::ostream& os,
  const Mesh& m
) {
  return m >> os;
}

TriangleMesh::TriangleMesh() {
  addFace(
    new TriangleFace(
      addVertex(
        new Vertex()
      ),
      addVertex(
        new Vertex(
          0,
          1
        )
      ),
      addVertex(
        new Vertex(1)
      )
    )
  );
}

RectMesh::RectMesh() {
  Mesh* m = clone();
  *this += m->rotateZ(Math::PI).translate(
    1,
    1
  );
  delete m;
}

GridMesh::GridMesh(
  unsigned int n,
  unsigned int k
) {
  Mesh* m = clone();
  for (
    unsigned int i = 1;
    i < n;
    i++
  ) {
    *this += (*m += Vector::X);
  }
  delete m;
  m = clone();
  for (
    unsigned int i = 1;
    i < k;
    i++
  ) {
    *this += (*m += Vector::Y);
  }
  delete m;
}

BoxMesh::BoxMesh(
  const bool closed,
  const unsigned int n,
  const unsigned int k,
  const unsigned int t
) : GridMesh(
  n,
  k
) {
  Mesh* m = clone();
  *this += m->rotateX(Math::PI).translate(
    0,
    k,
    t
  );
  delete m;
  m = new GridMesh(
    t,
    k
  );
  *this += (
    m->rotateY(Math::PIdiv2) += Vector(n)
  );
  *this += m->rotateZ(Math::PI).translate(
    n,
    k
  );
  delete m;
  if (closed) {
    m = new GridMesh(
      n,
      t
    );
    *this += m->rotateX(- Math::PIdiv2).translate(k);
    *this += m->rotateX(Math::PI).translate(
      0,
      k,
      t
    );
    delete m;
  }
  round();
}

ConeMesh::ConeMesh(
  const unsigned int n,
  const double h,
  const bool closed
) {
  Vertex* center = addVertex(
    new Vertex()
  );
  Vertex* first = nullptr;
  Vertex* last = nullptr;
  for (
    unsigned int i = 0;
    i < n;
    i++
  ) {
    static Vector v;
    v.set(1).rotateY(- Math::PImul2 * i / n);
    Vertex* current = addVertex(
      new Vertex(
        v.x,
        0,
        v.z
      )
    );
    if (i == 0) {
      first = last = current;
    } else {
      addFace(
        new TriangleFace(
          center,
          last,
          current
        )
      );
      last = current;
      if (i == n - 1) {
        addFace(
          new TriangleFace(
            center,
            last,
            first
          )
        );
      }
    }
  }
  center->y = h;
  if (closed) {
    ConeMesh* m = new ConeMesh(
      n,
      0
    );
    *this += m->rotateX(Math::PI);
    delete m;
  }
}

DotMesh::DotMesh() {
  addFace(
    new Face(
      addVertex(
        new Vertex()
      )
    )
  );
}

LineMesh::LineMesh(
  const double x1,
  const double y1,
  const double z1,
  const double x2,
  const double y2,
  const double z2
) {
  addFace(
    new LineFace(
      addVertex(
        new Vertex(
          x1,
          y1,
          z1
        )
      ),
      addVertex(
        new Vertex(
          x2,
          y2,
          z2
        )
      )
    )
  );
}

RingMesh::RingMesh(const unsigned int n) {
  const double rad = - Math::PI * 2.0 / n;
  static Vector v;
  setVector(
    Vector::X,
    (v = Vector::X).rotateY(rad)
  ).setVector(
    Vector::Zero,
    Vector::X
  );
  Mesh* m = clone();
  for (
    unsigned int i = 1;
    i < n;
    i++
  ) {
    *this += m->rotateY(rad);
  }
  delete m;
}

CylinderMesh::CylinderMesh(
  const unsigned int n,
  const unsigned int k,
  const bool top,
  const bool bottom
) {
  const double rad = - Math::PI * 2.0 / n;
  for (
    Vertices::iterator i = vertices.begin();
    i != vertices.end();
    i++
  ) {
    if (
      (*i)->x == 0
    ) {
      (*i)->x = 1;
      (*i)->rotateY(rad);
    }
  }
  {
    Mesh* m = clone();
    for (
      unsigned int i = 1;
      i < n;
      i++
    ) {
      *this += m->rotateY(rad);
    }
    delete m;
  }
  if (k > 1) {
    Mesh* m = clone();
    for (
      unsigned int i = 1;
      i < k;
      i++
    ) {
      *this += (*m += Vector::Y);
    }
    delete m;
  }
  if (top || bottom) {
    const Vector v = Vector(
      0,
      k
    );
    Mesh* m = new ConeMesh(
      n,
      0
    );
    *m += v;
    if (top) {
      *this += *m;
    }
    if (bottom) {
      *this += (
        m->rotateX(Math::PI) += v
      );
    }
    delete m;
  }
}

SphereMesh::SphereMesh(
  const unsigned int n,
  const unsigned int k
) : CylinderMesh(
  n,
  k,
  false
) {
  const double h = k / 2.0;
  for (
    Vertices::iterator i = vertices.begin();
    i != vertices.end();
    i++
  ) {
    const double f = sqrt(
      h * h - pow(
        (*i)->y - h,
        2
      )
    ) / h / 2.0;
    (*i)->scale(
      Vector(
        f,
        1.0,
        f
      )
    );
  }
  mergeVertices();
  scale(
    1.0,
    1.0 / k,
    1.0
  );
}
