#ifndef Mesh_hpp
#define Mesh_hpp

#include <math/Matrix.hpp>
#include <math/Box.hpp>
#include "Face.hpp"
#include <vector>

class Mesh {
  struct Faces : std::vector<Face*> {
    const Faces& render() const;
    const bool isUsed(const Vertex* v) const;
    Faces& removeDegenerated();
  } faces;
  Mesh& removeNotUsedVertices();
protected:
  struct Vertices : std::vector<Vertex*> {
  } vertices;
  Vertex* addVertex(Vertex* vertex);
  Vertex* getVertex(const Vector& vector) const;
  Face* addFace(Face* face);
public:
  virtual ~Mesh();
  void render() const;
  const Box& getBox() const;
  Mesh* clone() const;
  Mesh& setVector(
    const Vector& vector,
    const Vector& v
  );
  Mesh& setVector(
    const double x,
    const double y,
    const double z,
    const double x2,
    const double y2,
    const double z2
  );
  Mesh& setColor(
    const Vector& vector,
    const Color& c
  );
  Mesh& setColor(
    const double x,
    const double y,
    const double z,
    const Color& c
  );
  Mesh& setColor(const Color& c);
  Mesh& setLight(const Color& bottomColor = Color::Black);
  Mesh& translate(
    const double x,
    const double y,
    const double z = 0
  );
  Mesh& translate(const double y);
  Mesh& scale(
    const double x,
    const double y,
    const double z
  );
  Mesh& rotateX(const double rad);
  Mesh& rotateY(const double rad);
  Mesh& rotateZ(const double rad);
  Mesh& centralize();
  Mesh& normalize();
  Mesh& randomize(const Vector& factor);
  Mesh& randomizeXZ(const double factor);
  Mesh& round(const double precision = 1);
  Mesh& mergeVertices(const double e = 0.001);
  Mesh& operator *= (const double factor);
  Mesh& operator *= (const Vector& v);
  Mesh& operator *= (const Matrix& m);
  Mesh& operator += (const Vector& v);
  Mesh& operator += (const Mesh& m);
  std::ostream& operator >> (std::ostream& os) const;
};

std::ostream& operator << (
  std::ostream& os,
  const Mesh& m
);

struct DotMesh : Mesh {
  DotMesh();
};

struct LineMesh : Mesh {
  LineMesh(
    const double x1 = 0,
    const double y1 = 0,
    const double z1 = 0,
    const double x2 = 1,
    const double y2 = 0,
    const double z2 = 0
  );
};

struct RingMesh : LineMesh {
  RingMesh(const unsigned int n = 3);
};

struct TriangleMesh : Mesh {
  TriangleMesh();
};

struct RectMesh : TriangleMesh {
  RectMesh();
};

struct GridMesh : RectMesh {
  GridMesh(
    unsigned int n = 1,
    unsigned int k = 1
  );
};

struct BoxMesh : GridMesh {
  BoxMesh(
    const bool closed = true,
    unsigned int n = 1,
    unsigned int k = 1,
    unsigned int t = 1
  );
};

struct ConeMesh : Mesh {
  ConeMesh(
    const unsigned int n = 3,
    const double h = 1,
    const bool closed = false
  );
};

struct CylinderMesh : RectMesh {
  CylinderMesh(
    const unsigned int n = 3,
    const unsigned int k = 1,
    const bool top = false,
    const bool bottom = false
  );
};

struct SphereMesh : CylinderMesh {
  SphereMesh(
    const unsigned int n = 8,
    const unsigned int k = 4
  );
};

#endif
