// Copyright 2011-2021 the Polygon Mesh Processing Library developers.
// Distributed under a MIT-style license, see LICENSE.txt for details.

#include "MyViewer.h"

#include <imgui.h>

#include <pmp/algorithms/SurfaceSubdivision.h>

using namespace pmp;

SurfaceMesh plane(size_t resolution)
{
  SurfaceMesh mesh;

  // generate vertices
  Point p(0, 0, 0);
  for (size_t i = 0; i < resolution + 1; i++) {
    for (size_t j = 0; j < resolution + 1; j++) {
      mesh.add_vertex(p);
      p[1] += 1.0 / resolution;
    }
    p[1] = 0;
    p[0] += 1.0 / resolution;
  }

  // generate faces
  for (size_t i = 0; i < resolution; i++) {
    for (size_t j = 0; j < resolution; j++) {
      auto v0 = Vertex(j + i * (resolution + 1));
      auto v1 = Vertex(v0.idx() + resolution + 1);
      auto v2 = Vertex(v0.idx() + resolution + 2);
      auto v3 = Vertex(v0.idx() + 1);
      mesh.add_quad(v0, v1, v2, v3);
    }
  }

  return mesh;
}

SurfaceMesh cone(size_t resolution,
                 Scalar radius,
                 Scalar height)
{
  SurfaceMesh mesh;

  // add vertices subdividing a circle
  std::vector<Vertex> base_vertices;
  for (size_t i = 0; i < resolution; i++) {
    Scalar ratio = static_cast<Scalar>(i) / (resolution);
    Scalar r = ratio * (M_PI * 2.0);
    Scalar x = std::cos(r) * radius;
    Scalar y = std::sin(r) * radius;
    auto v = mesh.add_vertex(Point(x, y, 0.0));
    base_vertices.push_back(v);
  }

  // add the tip of the cone
  auto v0 = mesh.add_vertex(Point(0.0, 0.0, height));

  // generate triangular faces
  for (size_t i = 0; i < resolution; i++) {
    auto ii = (i + 1) % resolution;
    mesh.add_triangle(v0, Vertex(i), Vertex(ii));
  }

  // reverse order for consistent face orientation
  std::reverse(base_vertices.begin(), base_vertices.end());

  // add polygonal base face
  mesh.add_face(base_vertices);

  return mesh;
}

SurfaceMesh cylinder(size_t resolution,
                     Scalar radius,
                     Scalar height)
{
  SurfaceMesh mesh;

  // generate vertices
  std::vector<Vertex> bottom_vertices;
  std::vector<Vertex> top_vertices;
  for (size_t i = 0; i < resolution; i++) {
    Scalar ratio = static_cast<Scalar>(i) / (resolution);
    Scalar r = ratio * (M_PI * 2.0);
    Scalar x = std::cos(r) * radius;
    Scalar y = std::sin(r) * radius;
    Vertex v = mesh.add_vertex(Point(x, y, 0.0));
    bottom_vertices.push_back(v);
    v = mesh.add_vertex(Point(x, y, height));
    top_vertices.push_back(v);
  }

  // add faces around the cylinder
  for (size_t i = 0; i < resolution; i++) {
    auto ii = i * 2;
    auto jj = (ii + 2) % (resolution * 2);
    auto kk = (ii + 3) % (resolution * 2);
    auto ll = ii + 1;
    mesh.add_quad(Vertex(ii), Vertex(jj), Vertex(kk), Vertex(ll));
  }

  // add top polygon
  mesh.add_face(top_vertices);

  // reverse order for consistent face orientation
  std::reverse(bottom_vertices.begin(), bottom_vertices.end());

  // add bottom polygon
  mesh.add_face(bottom_vertices);

  return mesh;
}

SurfaceMesh torus(size_t radial_resolution,
                  size_t tubular_resolution,
                  Scalar radius,
                  Scalar thickness)
{
  SurfaceMesh mesh;

  // generate vertices
  for (size_t i = 0; i < radial_resolution; i++) {
    for (size_t j = 0; j < tubular_resolution; j++) {
      Scalar u = (Scalar)j / tubular_resolution * M_PI * 2.0;
      Scalar v = (Scalar)i / radial_resolution * M_PI * 2.0;
      Scalar x = (radius + thickness * std::cos(v)) * std::cos(u);
      Scalar y = (radius + thickness * std::cos(v)) * std::sin(u);
      Scalar z = thickness * std::sin(v);
      mesh.add_vertex(Point(x, y, z));
    }
  }

  // add quad faces
  for (size_t i = 0; i < radial_resolution; i++) {
    auto i_next = (i + 1) % radial_resolution;
    for (size_t j = 0; j < tubular_resolution; j++) {
      auto j_next = (j + 1) % tubular_resolution;
      auto i0 = i * tubular_resolution + j;
      auto i1 = i * tubular_resolution + j_next;
      auto i2 = i_next * tubular_resolution + j_next;
      auto i3 = i_next * tubular_resolution + j;
      mesh.add_quad(Vertex(i0), Vertex(i1), Vertex(i2), Vertex(i3));
    }
  }

  return mesh;
}

void MyViewer::keyboard(int key, int scancode, int action, int mods)
{
    if (action != GLFW_PRESS) // only react on key press events
        return;

    switch (key)
    {
        case GLFW_KEY_G:
        {
            // mesh_.assign(plane(10));
            // mesh_.assign(cone(10,1.0,2.5));
            // mesh_.assign(cylinder(10,1.0,2.5));
            mesh_.assign(torus(10,20,1.0,0.4));

            BoundingBox bb = mesh_.bounds();
            set_scene((vec3)bb.center(), 0.5 * bb.size());
            set_draw_mode("Hidden Line");
            update_mesh();
            break;
        }
        // add your own keyboard action here
        default:
        {
            MeshViewer::keyboard(key, scancode, action, mods);
            break;
        }
    }
}
