// Copyright 2023 The Manifold Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "manifold/manifoldc.h"

#include <sstream>
#include <vector>

#include "conv.h"
#include "manifold/common.h"
#include "manifold/cross_section.h"
#include "manifold/manifold.h"
#include "manifold/polygon.h"
#include "manifold/types.h"

using namespace manifold;

namespace {
ManifoldManifold* level_set(
    void* mem, double (*sdf_context)(double, double, double, void*),
    ManifoldBox* bounds, double edge_length, double level, double tolerance,
    bool seq, void* ctx) {
  // Bind function with context argument to one without
  using namespace std::placeholders;
  std::function<double(double, double, double)> sdf =
      std::bind(sdf_context, _1, _2, _3, ctx);
  std::function<double(vec3)> fun = [sdf](vec3 v) {
    return (sdf(v.x, v.y, v.z));
  };
  return to_c(new (mem) Manifold(Manifold::LevelSet(
      fun, *from_c(bounds), edge_length, level, tolerance, !seq)));
}
}  // namespace

#ifdef __cplusplus
extern "C" {
#endif

ManifoldSimplePolygon* manifold_simple_polygon(void* mem, ManifoldVec2* ps,
                                               size_t length) {
  auto vec = new (mem) std::vector<vec2>;
  for (size_t i = 0; i < length; ++i) {
    vec->push_back({ps[i].x, ps[i].y});
  }
  return to_c(vec);
}

ManifoldPolygons* manifold_polygons(void* mem, ManifoldSimplePolygon** ps,
                                    size_t length) {
  auto vec = new (mem) std::vector<SimplePolygon>;
  auto polys = reinterpret_cast<SimplePolygon**>(ps);
  for (size_t i = 0; i < length; ++i) {
    vec->push_back(*polys[i]);
  }
  return to_c(vec);
}

size_t manifold_simple_polygon_length(ManifoldSimplePolygon* p) {
  return from_c(p)->size();
}

size_t manifold_polygons_length(ManifoldPolygons* ps) {
  return from_c(ps)->size();
}

size_t manifold_polygons_simple_length(ManifoldPolygons* ps, size_t idx) {
  return (*from_c(ps))[idx].size();
}

ManifoldVec2 manifold_simple_polygon_get_point(ManifoldSimplePolygon* p,
                                               size_t idx) {
  return to_c((*from_c(p))[idx]);
}

ManifoldSimplePolygon* manifold_polygons_get_simple(void* mem,
                                                    ManifoldPolygons* ps,
                                                    size_t idx) {
  auto sp = (*from_c(ps))[idx];
  return to_c(new (mem) SimplePolygon(sp));
}

ManifoldVec2 manifold_polygons_get_point(ManifoldPolygons* ps,
                                         size_t simple_idx, size_t pt_idx) {
  return to_c((*from_c(ps))[simple_idx][pt_idx]);
}

ManifoldManifoldVec* manifold_manifold_empty_vec(void* mem) {
  return to_c(new (mem) ManifoldVec());
}

ManifoldManifoldVec* manifold_manifold_vec(void* mem, size_t sz) {
  return to_c(new (mem) ManifoldVec(sz));
}

void manifold_manifold_vec_reserve(ManifoldManifoldVec* ms, size_t sz) {
  from_c(ms)->reserve(sz);
}

size_t manifold_manifold_vec_length(ManifoldManifoldVec* ms) {
  return from_c(ms)->size();
}

ManifoldManifold* manifold_manifold_vec_get(void* mem, ManifoldManifoldVec* ms,
                                            size_t idx) {
  auto m = (*from_c(ms))[idx];
  return to_c(new (mem) Manifold(m));
}

void manifold_manifold_vec_set(ManifoldManifoldVec* ms, size_t idx,
                               ManifoldManifold* m) {
  (*from_c(ms))[idx] = *from_c(m);
}

void manifold_manifold_vec_push_back(ManifoldManifoldVec* ms,
                                     ManifoldManifold* m) {
  return from_c(ms)->push_back(*from_c(m));
}

ManifoldManifold* manifold_boolean(void* mem, ManifoldManifold* a,
                                   ManifoldManifold* b, ManifoldOpType op) {
  auto m = from_c(a)->Boolean(*from_c(b), from_c(op));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_batch_boolean(void* mem, ManifoldManifoldVec* ms,
                                         ManifoldOpType op) {
  auto m = Manifold::BatchBoolean(*from_c(ms), from_c(op));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_union(void* mem, ManifoldManifold* a,
                                 ManifoldManifold* b) {
  auto m = (*from_c(a)) + (*from_c(b));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_difference(void* mem, ManifoldManifold* a,
                                      ManifoldManifold* b) {
  auto m = (*from_c(a)) - (*from_c(b));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_intersection(void* mem, ManifoldManifold* a,
                                        ManifoldManifold* b) {
  auto m = (*from_c(a)) ^ (*from_c(b));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifoldPair manifold_split(void* mem_first, void* mem_second,
                                    ManifoldManifold* a, ManifoldManifold* b) {
  auto pair = from_c(a)->Split(*from_c(b));
  auto first = new (mem_first) Manifold(pair.first);
  auto second = new (mem_second) Manifold(pair.second);
  return {to_c(first), to_c(second)};
}

ManifoldManifoldPair manifold_split_by_plane(void* mem_first, void* mem_second,
                                             ManifoldManifold* m,
                                             double normal_x, double normal_y,
                                             double normal_z, double offset) {
  auto normal = vec3(normal_x, normal_y, normal_z);
  auto pair = from_c(m)->SplitByPlane(normal, offset);
  auto first = new (mem_first) Manifold(pair.first);
  auto second = new (mem_second) Manifold(pair.second);
  return {to_c(first), to_c(second)};
}

ManifoldManifold* manifold_trim_by_plane(void* mem, ManifoldManifold* m,
                                         double normal_x, double normal_y,
                                         double normal_z, double offset) {
  auto normal = vec3(normal_x, normal_y, normal_z);
  auto trimmed = from_c(m)->TrimByPlane(normal, offset);
  return to_c(new (mem) Manifold(trimmed));
}

ManifoldManifold* manifold_minkowski_sum(void* mem, ManifoldManifold* a,
                                         ManifoldManifold* b) {
  auto m = (*from_c(a)).MinkowskiSum(*from_c(b));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_minkowski_difference(void* mem, ManifoldManifold* a,
                                                ManifoldManifold* b) {
  auto m = (*from_c(a)).MinkowskiDifference(*from_c(b));
  return to_c(new (mem) Manifold(m));
}

ManifoldPolygons* manifold_slice(void* mem, ManifoldManifold* m,
                                 double height) {
  auto poly = from_c(m)->Slice(height);
  return to_c(new (mem) Polygons(poly));
}

ManifoldPolygons* manifold_project(void* mem, ManifoldManifold* m) {
  auto poly = from_c(m)->Project();
  return to_c(new (mem) Polygons(poly));
}

ManifoldManifold* manifold_hull(void* mem, ManifoldManifold* m) {
  auto hulled = from_c(m)->Hull();
  return to_c(new (mem) Manifold(hulled));
}

ManifoldManifold* manifold_batch_hull(void* mem, ManifoldManifoldVec* ms) {
  auto hulled = Manifold::Hull(*from_c(ms));
  return to_c(new (mem) Manifold(hulled));
}

ManifoldManifold* manifold_hull_pts(void* mem, ManifoldVec3* ps,
                                    size_t length) {
  std::vector<vec3> vec(length);
  for (size_t i = 0; i < length; ++i) {
    vec[i] = {ps[i].x, ps[i].y, ps[i].z};
  }
  auto hulled = Manifold::Hull(vec);
  return to_c(new (mem) Manifold(hulled));
}

ManifoldManifold* manifold_translate(void* mem, ManifoldManifold* m, double x,
                                     double y, double z) {
  auto v = vec3(x, y, z);
  auto translated = from_c(m)->Translate(v);
  return to_c(new (mem) Manifold(translated));
}

ManifoldManifold* manifold_rotate(void* mem, ManifoldManifold* m, double x,
                                  double y, double z) {
  auto rotated = from_c(m)->Rotate(x, y, z);
  return to_c(new (mem) Manifold(rotated));
}

ManifoldManifold* manifold_scale(void* mem, ManifoldManifold* m, double x,
                                 double y, double z) {
  auto s = vec3(x, y, z);
  auto scaled = from_c(m)->Scale(s);
  return to_c(new (mem) Manifold(scaled));
}

ManifoldManifold* manifold_transform(void* mem, ManifoldManifold* m, double x1,
                                     double y1, double z1, double x2, double y2,
                                     double z2, double x3, double y3, double z3,
                                     double x4, double y4, double z4) {
  auto mat = mat3x4({x1, y1, z1}, {x2, y2, z2}, {x3, y3, z3}, {x4, y4, z4});
  auto transformed = from_c(m)->Transform(mat);
  return to_c(new (mem) Manifold(transformed));
}

ManifoldManifold* manifold_mirror(void* mem, ManifoldManifold* m, double nx,
                                  double ny, double nz) {
  auto mirrored = from_c(m)->Mirror({nx, ny, nz});
  return to_c(new (mem) Manifold(mirrored));
}

ManifoldManifold* manifold_warp(void* mem, ManifoldManifold* m,
                                ManifoldVec3 (*fun)(double, double, double,
                                                    void*),
                                void* ctx) {
  // Bind function with context argument to one without
  using namespace std::placeholders;
  std::function<ManifoldVec3(double, double, double)> f3 =
      std::bind(fun, _1, _2, _3, ctx);
  std::function<void(vec3 & v)> warp = [f3](vec3& v) {
    v = from_c(f3(v.x, v.y, v.z));
  };
  auto warped = from_c(m)->Warp(warp);
  return to_c(new (mem) Manifold(warped));
}

ManifoldManifold* manifold_level_set(
    void* mem, double (*sdf)(double, double, double, void*),
    ManifoldBox* bounds, double edge_length, double level, double tolerance,
    void* ctx) {
  return level_set(mem, sdf, bounds, edge_length, level, tolerance, false, ctx);
}

ManifoldManifold* manifold_level_set_seq(
    void* mem, double (*sdf)(double, double, double, void*),
    ManifoldBox* bounds, double edge_length, double level, double tolerance,
    void* ctx) {
  return level_set(mem, sdf, bounds, edge_length, level, tolerance, true, ctx);
}

ManifoldManifold* manifold_smooth_by_normals(void* mem, ManifoldManifold* m,
                                             int normalIdx) {
  auto smoothed = from_c(m)->SmoothByNormals(normalIdx);
  return to_c(new (mem) Manifold(smoothed));
}

ManifoldManifold* manifold_smooth_out(void* mem, ManifoldManifold* m,
                                      double minSharpAngle,
                                      double minSmoothness) {
  auto smoothed = from_c(m)->SmoothOut(minSharpAngle, minSmoothness);
  return to_c(new (mem) Manifold(smoothed));
}

ManifoldManifold* manifold_refine(void* mem, ManifoldManifold* m, int refine) {
  auto refined = from_c(m)->Refine(refine);
  return to_c(new (mem) Manifold(refined));
}

ManifoldManifold* manifold_refine_to_length(void* mem, ManifoldManifold* m,
                                            double length) {
  auto refined = from_c(m)->RefineToLength(length);
  return to_c(new (mem) Manifold(refined));
}

ManifoldManifold* manifold_refine_to_tolerance(void* mem, ManifoldManifold* m,
                                               double tolerance) {
  auto refined = from_c(m)->RefineToTolerance(tolerance);
  return to_c(new (mem) Manifold(refined));
}

ManifoldManifold* manifold_empty(void* mem) {
  return to_c(new (mem) Manifold());
}

ManifoldManifold* manifold_copy(void* mem, ManifoldManifold* m) {
  return to_c(new (mem) Manifold(*from_c(m)));
}

ManifoldManifold* manifold_tetrahedron(void* mem) {
  auto m = Manifold::Tetrahedron();
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_cube(void* mem, double x, double y, double z,
                                int center) {
  auto size = vec3(x, y, z);
  auto m = Manifold::Cube(size, center);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_cylinder(void* mem, double height, double radius_low,
                                    double radius_high, int circular_segments,
                                    int center) {
  auto m = Manifold::Cylinder(height, radius_low, radius_high,
                              circular_segments, center);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_sphere(void* mem, double radius,
                                  int circular_segments) {
  auto m = Manifold::Sphere(radius, circular_segments);
  return to_c(new (mem) Manifold(m));
}

ManifoldMeshGL* manifold_meshgl(void* mem, float* vert_props, size_t n_verts,
                                size_t n_props, uint32_t* tri_verts,
                                size_t n_tris) {
  auto mesh = new (mem) MeshGL();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
  return to_c(mesh);
}

ManifoldMeshGL* manifold_meshgl_w_tangents(void* mem, float* vert_props,
                                           size_t n_verts, size_t n_props,
                                           uint32_t* tri_verts, size_t n_tris,
                                           float* halfedge_tangent) {
  auto mesh = new (mem) MeshGL();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
  mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
  return to_c(mesh);
}

ManifoldMeshGL* manifold_meshgl_w_options(void* mem, float* vert_props,
                                          size_t n_verts, size_t n_props,
                                          uint32_t* tri_verts, size_t n_tris,
                                          ManifoldMeshGLOptions* options) {
  auto mesh = new (mem) MeshGL();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);

  if (options->halfedge_tangents != nullptr) {
    mesh->halfedgeTangent =
        vector_of_array(options->halfedge_tangents, n_tris * 3 * 4);
  }

  if (options->run_indices != nullptr) {
    mesh->runIndex =
        vector_of_array(options->run_indices, options->run_indices_length);
  }

  if (options->run_original_ids != nullptr) {
    mesh->runOriginalID = vector_of_array(options->run_original_ids,
                                          options->run_original_ids_length);
  }

  if (options->merge_from_vert != nullptr) {
    mesh->mergeFromVert =
        vector_of_array(options->merge_from_vert, options->merge_verts_length);
  }

  if (options->merge_to_vert != nullptr) {
    mesh->mergeToVert =
        vector_of_array(options->merge_to_vert, options->merge_verts_length);
  }

  return to_c(mesh);
}

ManifoldMeshGL64* manifold_meshgl64(void* mem, double* vert_props,
                                    size_t n_verts, size_t n_props,
                                    uint64_t* tri_verts, size_t n_tris) {
  auto mesh = new (mem) MeshGL64();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
  return to_c(mesh);
}

ManifoldMeshGL64* manifold_meshgl64_w_tangents(void* mem, double* vert_props,
                                               size_t n_verts, size_t n_props,
                                               uint64_t* tri_verts,
                                               size_t n_tris,
                                               double* halfedge_tangent) {
  auto mesh = new (mem) MeshGL64();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);
  mesh->halfedgeTangent = vector_of_array(halfedge_tangent, n_tris * 3 * 4);
  return to_c(mesh);
}

ManifoldMeshGL64* manifold_meshgl64_w_options(
    void* mem, double* vert_props, size_t n_verts, size_t n_props,
    uint64_t* tri_verts, size_t n_tris, ManifoldMeshGL64Options* options) {
  auto mesh = new (mem) MeshGL64();
  mesh->numProp = n_props;
  mesh->vertProperties = vector_of_array(vert_props, n_verts * n_props);
  mesh->triVerts = vector_of_array(tri_verts, n_tris * 3);

  if (options->halfedge_tangents != nullptr) {
    mesh->halfedgeTangent =
        vector_of_array(options->halfedge_tangents, n_tris * 3 * 4);
  }

  if (options->run_indices != nullptr) {
    mesh->runIndex =
        vector_of_array(options->run_indices, options->run_indices_length);
  }

  if (options->run_original_ids != nullptr) {
    mesh->runOriginalID = vector_of_array(options->run_original_ids,
                                          options->run_original_ids_length);
  }

  if (options->merge_from_vert != nullptr) {
    mesh->mergeFromVert =
        vector_of_array(options->merge_from_vert, options->merge_verts_length);
  }

  if (options->merge_to_vert != nullptr) {
    mesh->mergeToVert =
        vector_of_array(options->merge_to_vert, options->merge_verts_length);
  }

  return to_c(mesh);
}

ManifoldManifold* manifold_smooth(void* mem, ManifoldMeshGL* mesh,
                                  size_t* half_edges, double* smoothness,
                                  size_t n_edges) {
  auto smooth = std::vector<Smoothness>();
  for (size_t i = 0; i < n_edges; ++i) {
    smooth.push_back({half_edges[i], smoothness[i]});
  }
  auto m = Manifold::Smooth(*from_c(mesh), smooth);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_smooth64(void* mem, ManifoldMeshGL64* mesh,
                                    size_t* half_edges, double* smoothness,
                                    size_t n_edges) {
  auto smooth = std::vector<Smoothness>();
  for (size_t i = 0; i < n_edges; ++i) {
    smooth.push_back({half_edges[i], smoothness[i]});
  }
  auto m = Manifold::Smooth(*from_c(mesh), smooth);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_of_meshgl(void* mem, ManifoldMeshGL* mesh) {
  auto m = Manifold(*from_c(mesh));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_of_meshgl64(void* mem, ManifoldMeshGL64* mesh) {
  auto m = Manifold(*from_c(mesh));
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_extrude(void* mem, ManifoldPolygons* cs,
                                   double height, int slices,
                                   double twist_degrees, double scale_x,
                                   double scale_y) {
  auto scale = vec2(scale_x, scale_y);
  auto m = Manifold::Extrude(*from_c(cs), height, slices, twist_degrees, scale);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_revolve(void* mem, ManifoldPolygons* cs,
                                   int circular_segments,
                                   double revolve_degrees) {
  auto m = Manifold::Revolve(*from_c(cs), circular_segments, revolve_degrees);
  return to_c(new (mem) Manifold(m));
}

ManifoldManifold* manifold_compose(void* mem, ManifoldManifoldVec* ms) {
  auto composed = Manifold::BatchBoolean(*from_c(ms), OpType::Add);
  return to_c(new (mem) Manifold(composed));
}

ManifoldManifoldVec* manifold_decompose(void* mem, ManifoldManifold* m) {
  auto comps = from_c(m)->Decompose();
  return to_c(new (mem) std::vector<Manifold>(comps));
}

ManifoldMeshGL* manifold_get_meshgl(void* mem, ManifoldManifold* m) {
  auto mesh = from_c(m)->GetMeshGL();
  return to_c(new (mem) MeshGL(mesh));
}

ManifoldMeshGL* manifold_get_meshgl_w_normals(void* mem, ManifoldManifold* m,
                                              int32_t normalIdx) {
  auto mesh = from_c(m)->GetMeshGL(normalIdx);
  return to_c(new (mem) MeshGL(mesh));
}

ManifoldMeshGL* manifold_meshgl_copy(void* mem, ManifoldMeshGL* m) {
  return to_c(new (mem) MeshGL(*from_c(m)));
}

ManifoldMeshGL* manifold_meshgl_merge(void* mem, ManifoldMeshGL* m) {
  auto duplicate = new (mem) MeshGL(*from_c(m));
  if (duplicate->Merge()) {
    return to_c(duplicate);
  }
  duplicate->~MeshGL();
  return m;
}

ManifoldMeshGL64* manifold_get_meshgl64(void* mem, ManifoldManifold* m) {
  auto mesh = from_c(m)->GetMeshGL64();
  return to_c(new (mem) MeshGL64(mesh));
}

ManifoldMeshGL64* manifold_get_meshgl64_w_normals(void* mem,
                                                  ManifoldManifold* m,
                                                  int32_t normalIdx) {
  auto mesh = from_c(m)->GetMeshGL64(normalIdx);
  return to_c(new (mem) MeshGL64(mesh));
}

ManifoldMeshGL64* manifold_meshgl64_copy(void* mem, ManifoldMeshGL64* m) {
  return to_c(new (mem) MeshGL64(*from_c(m)));
}

ManifoldMeshGL64* manifold_meshgl64_merge(void* mem, ManifoldMeshGL64* m) {
  auto duplicate = new (mem) MeshGL64(*from_c(m));
  if (duplicate->Merge()) {
    return to_c(duplicate);
  }
  duplicate->~MeshGL64();
  return m;
}

size_t manifold_meshgl_num_prop(ManifoldMeshGL* m) {
  return from_c(m)->numProp;
}
size_t manifold_meshgl_num_vert(ManifoldMeshGL* m) {
  return from_c(m)->NumVert();
}
size_t manifold_meshgl_num_tri(ManifoldMeshGL* m) {
  return from_c(m)->NumTri();
}

size_t manifold_meshgl_vert_properties_length(ManifoldMeshGL* m) {
  return from_c(m)->vertProperties.size();
}

size_t manifold_meshgl_tri_length(ManifoldMeshGL* m) {
  return from_c(m)->triVerts.size();
}

size_t manifold_meshgl_merge_length(ManifoldMeshGL* m) {
  return from_c(m)->mergeFromVert.size();
}

size_t manifold_meshgl_run_index_length(ManifoldMeshGL* m) {
  return from_c(m)->runIndex.size();
}

size_t manifold_meshgl_run_original_id_length(ManifoldMeshGL* m) {
  return from_c(m)->runOriginalID.size();
}

size_t manifold_meshgl_run_transform_length(ManifoldMeshGL* m) {
  return from_c(m)->runTransform.size();
}

size_t manifold_meshgl_face_id_length(ManifoldMeshGL* m) {
  return from_c(m)->faceID.size();
}

size_t manifold_meshgl_tangent_length(ManifoldMeshGL* m) {
  return from_c(m)->halfedgeTangent.size();
}

float* manifold_meshgl_vert_properties(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->vertProperties);
}

uint32_t* manifold_meshgl_tri_verts(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->triVerts);
}

uint32_t* manifold_meshgl_merge_from_vert(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->mergeFromVert);
}

uint32_t* manifold_meshgl_merge_to_vert(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->mergeToVert);
}

uint32_t* manifold_meshgl_run_index(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->runIndex);
}

uint32_t* manifold_meshgl_run_original_id(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->runOriginalID);
}

float* manifold_meshgl_run_transform(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->runTransform);
}

uint32_t* manifold_meshgl_face_id(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->faceID);
}

float* manifold_meshgl_halfedge_tangent(void* mem, ManifoldMeshGL* m) {
  return copy_data(mem, from_c(m)->halfedgeTangent);
}

size_t manifold_meshgl64_num_prop(ManifoldMeshGL64* m) {
  return from_c(m)->numProp;
}
size_t manifold_meshgl64_num_vert(ManifoldMeshGL64* m) {
  return from_c(m)->NumVert();
}
size_t manifold_meshgl64_num_tri(ManifoldMeshGL64* m) {
  return from_c(m)->NumTri();
}
size_t manifold_meshgl64_vert_properties_length(ManifoldMeshGL64* m) {
  return from_c(m)->vertProperties.size();
}
size_t manifold_meshgl64_tri_length(ManifoldMeshGL64* m) {
  return from_c(m)->triVerts.size();
}
size_t manifold_meshgl64_merge_length(ManifoldMeshGL64* m) {
  return from_c(m)->mergeFromVert.size();
}
size_t manifold_meshgl64_run_index_length(ManifoldMeshGL64* m) {
  return from_c(m)->runIndex.size();
}
size_t manifold_meshgl64_run_original_id_length(ManifoldMeshGL64* m) {
  return from_c(m)->runOriginalID.size();
}
size_t manifold_meshgl64_run_transform_length(ManifoldMeshGL64* m) {
  return from_c(m)->runTransform.size();
}
size_t manifold_meshgl64_face_id_length(ManifoldMeshGL64* m) {
  return from_c(m)->faceID.size();
}
size_t manifold_meshgl64_tangent_length(ManifoldMeshGL64* m) {
  return from_c(m)->halfedgeTangent.size();
}
double* manifold_meshgl64_vert_properties(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->vertProperties);
}
uint64_t* manifold_meshgl64_tri_verts(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->triVerts);
}
uint64_t* manifold_meshgl64_merge_from_vert(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->mergeFromVert);
}
uint64_t* manifold_meshgl64_merge_to_vert(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->mergeToVert);
}
uint64_t* manifold_meshgl64_run_index(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->runIndex);
}
uint32_t* manifold_meshgl64_run_original_id(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->runOriginalID);
}
double* manifold_meshgl64_run_transform(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->runTransform);
}
uint64_t* manifold_meshgl64_face_id(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->faceID);
}
double* manifold_meshgl64_halfedge_tangent(void* mem, ManifoldMeshGL64* m) {
  return copy_data(mem, from_c(m)->halfedgeTangent);
}

ManifoldManifold* manifold_as_original(void* mem, ManifoldManifold* m) {
  auto orig = from_c(m)->AsOriginal();
  return to_c(new (mem) Manifold(orig));
}

int manifold_original_id(ManifoldManifold* m) {
  return from_c(m)->OriginalID();
}

int manifold_is_empty(ManifoldManifold* m) { return from_c(m)->IsEmpty(); }

ManifoldError manifold_status(ManifoldManifold* m) {
  auto error = from_c(m)->Status();
  return to_c(error);
}

size_t manifold_num_vert(ManifoldManifold* m) { return from_c(m)->NumVert(); }
size_t manifold_num_edge(ManifoldManifold* m) { return from_c(m)->NumEdge(); }
size_t manifold_num_tri(ManifoldManifold* m) { return from_c(m)->NumTri(); }
size_t manifold_num_prop(ManifoldManifold* m) { return from_c(m)->NumProp(); };
int manifold_genus(ManifoldManifold* m) { return from_c(m)->Genus(); }

double manifold_surface_area(ManifoldManifold* m) {
  return from_c(m)->SurfaceArea();
}
double manifold_volume(ManifoldManifold* m) { return from_c(m)->Volume(); }

ManifoldBox* manifold_bounding_box(void* mem, ManifoldManifold* m) {
  auto box = from_c(m)->BoundingBox();
  return to_c(new (mem) Box(box));
}

double manifold_epsilon(ManifoldManifold* m) { return from_c(m)->GetEpsilon(); }

uint32_t manifold_reserve_ids(uint32_t n) { return Manifold::ReserveIDs(n); }

ManifoldManifold* manifold_set_properties(
    void* mem, ManifoldManifold* m, int num_prop,
    void (*fun)(double* new_prop, ManifoldVec3 position, const double* old_prop,
                void* ctx),
    void* ctx) {
  // Bind function with context argument to one without
  using namespace std::placeholders;
  std::function<void(double*, ManifoldVec3, const double*)> f3 =
      std::bind(fun, _1, _2, _3, ctx);
  std::function<void(double*, vec3, const double*)> f =
      [f3](double* new_prop, vec3 v, const double* old_prop) {
        return (f3(new_prop, to_c(v), old_prop));
      };
  auto man = from_c(m)->SetProperties(num_prop, f);
  return to_c(new (mem) Manifold(man));
};

ManifoldManifold* manifold_calculate_curvature(void* mem, ManifoldManifold* m,
                                               int gaussian_idx, int mean_idx) {
  auto man = from_c(m)->CalculateCurvature(gaussian_idx, mean_idx);
  return to_c(new (mem) Manifold(man));
}

double manifold_min_gap(ManifoldManifold* m, ManifoldManifold* other,
                        double searchLength) {
  return from_c(m)->MinGap(*from_c(other), searchLength);
}

ManifoldManifold* manifold_calculate_normals(void* mem, ManifoldManifold* m,
                                             int normal_idx,
                                             double min_sharp_angle) {
  auto man = from_c(m)->CalculateNormals(normal_idx, min_sharp_angle);
  return to_c(new (mem) Manifold(man));
}

// Static Quality Globals

void manifold_set_min_circular_angle(double degrees) {
  Quality::SetMinCircularAngle(degrees);
}

void manifold_set_min_circular_edge_length(double length) {
  Quality::SetMinCircularEdgeLength(length);
}

void manifold_set_circular_segments(int number) {
  Quality::SetCircularSegments(number);
}

int manifold_get_circular_segments(double radius) {
  return Quality::GetCircularSegments(radius);
}

void manifold_reset_to_circular_defaults() { Quality::ResetToDefaults(); }

ManifoldTriangulation* manifold_triangulate(void* mem, ManifoldPolygons* ps,
                                            double epsilon) {
  auto triangulation = manifold::Triangulate(*from_c(ps), epsilon);
  return to_c(new (mem) std::vector<ivec3>(triangulation));
}

size_t manifold_triangulation_num_tri(ManifoldTriangulation* m) {
  return from_c(m)->size();
}

int* manifold_triangulation_tri_verts(void* mem, ManifoldTriangulation* m) {
  return reinterpret_cast<int*>(copy_data<ivec3>(mem, *from_c(m)));
}

// memory size
size_t manifold_cross_section_size() { return sizeof(CrossSection); }
size_t manifold_cross_section_vec_size() {
  return sizeof(std::vector<CrossSection>);
}
size_t manifold_simple_polygon_size() { return sizeof(SimplePolygon); }
size_t manifold_polygons_size() { return sizeof(Polygons); }
size_t manifold_manifold_size() { return sizeof(Manifold); }
size_t manifold_manifold_vec_size() { return sizeof(std::vector<Manifold>); }
size_t manifold_manifold_pair_size() { return sizeof(ManifoldManifoldPair); }
size_t manifold_meshgl_size() { return sizeof(MeshGL); }
size_t manifold_meshgl64_size() { return sizeof(MeshGL64); }
size_t manifold_box_size() { return sizeof(Box); }
size_t manifold_rect_size() { return sizeof(Rect); }
size_t manifold_triangulation_size() { return sizeof(std::vector<ivec3>); }

// allocation
ManifoldManifold* manifold_alloc_manifold() {
  return to_c(new manifold::Manifold);
}
ManifoldManifoldVec* manifold_alloc_manifold_vec() {
  return to_c(new std::vector<manifold::Manifold>);
}
ManifoldCrossSection* manifold_alloc_cross_section() {
  return to_c(new CrossSection);
}
ManifoldCrossSectionVec* manifold_alloc_cross_section_vec() {
  return to_c(new std::vector<CrossSection>);
}
ManifoldSimplePolygon* manifold_alloc_simple_polygon() {
  return to_c(new SimplePolygon);
}
ManifoldPolygons* manifold_alloc_polygons() {
  return to_c(new std::vector<SimplePolygon>);
}
ManifoldMeshGL* manifold_alloc_meshgl() { return to_c(new MeshGL); }
ManifoldMeshGL64* manifold_alloc_meshgl64() { return to_c(new MeshGL64); }
ManifoldBox* manifold_alloc_box() { return to_c(new Box); }
ManifoldRect* manifold_alloc_rect() { return to_c(new Rect); }
ManifoldTriangulation* manifold_alloc_triangulation() {
  return to_c(new std::vector<ivec3>);
}

// pointer free + destruction
void manifold_delete_cross_section(ManifoldCrossSection* c) {
  delete from_c(c);
}
void manifold_delete_cross_section_vec(ManifoldCrossSectionVec* csv) {
  delete from_c(csv);
}
void manifold_delete_simple_polygon(ManifoldSimplePolygon* p) {
  delete from_c(p);
}
void manifold_delete_polygons(ManifoldPolygons* p) { delete from_c(p); }
void manifold_delete_manifold(ManifoldManifold* m) { delete from_c(m); }
void manifold_delete_manifold_vec(ManifoldManifoldVec* ms) {
  delete from_c(ms);
}
void manifold_delete_meshgl(ManifoldMeshGL* m) { delete from_c(m); }
void manifold_delete_meshgl64(ManifoldMeshGL64* m) { delete from_c(m); }
void manifold_delete_box(ManifoldBox* b) { delete from_c(b); }
void manifold_delete_rect(ManifoldRect* r) { delete from_c(r); }
void manifold_delete_triangulation(ManifoldTriangulation* m) {
  delete from_c(m);
}

// destruction
void manifold_destruct_cross_section(ManifoldCrossSection* cs) {
  from_c(cs)->~CrossSection();
}
void manifold_destruct_cross_section_vec(ManifoldCrossSectionVec* csv) {
  from_c(csv)->~CrossSectionVec();
}
void manifold_destruct_simple_polygon(ManifoldSimplePolygon* p) {
  from_c(p)->~SimplePolygon();
}
void manifold_destruct_polygons(ManifoldPolygons* p) { from_c(p)->~Polygons(); }
void manifold_destruct_manifold(ManifoldManifold* m) { from_c(m)->~Manifold(); }
void manifold_destruct_manifold_vec(ManifoldManifoldVec* ms) {
  from_c(ms)->~ManifoldVec();
}
void manifold_destruct_meshgl(ManifoldMeshGL* m) { from_c(m)->~MeshGL(); }
void manifold_destruct_meshgl64(ManifoldMeshGL64* m) { from_c(m)->~MeshGL64(); }
void manifold_destruct_box(ManifoldBox* b) { from_c(b)->~Box(); }
void manifold_destruct_rect(ManifoldRect* r) { from_c(r)->~Rect(); }
void manifold_destruct_triangulation(ManifoldTriangulation* m) {
  from_c(m)->~vector<ivec3>();
}

// IO

ManifoldManifold* manifold_read_obj(void* mem, char* obj_file) {
  std::istringstream iss(obj_file);
  Manifold m = Manifold::ReadOBJ(iss);
  return to_c(new (mem) Manifold(m));
}

ManifoldMeshGL64* manifold_meshgl64_read_obj(void* mem, char* obj_file) {
  std::istringstream iss(obj_file);
  MeshGL64 m = ReadOBJ(iss);
  return to_c(new (mem) MeshGL64(std::move(m)));
}

void manifold_write_obj(ManifoldManifold* manifold,
                        void (*callback)(char*, void*), void* args) {
  std::stringstream ss;
  const Manifold* m = from_c(manifold);
  m->WriteOBJ(ss);
  callback(ss.str().data(), args);
}

void manifold_meshgl64_write_obj(ManifoldMeshGL64* mesh,
                                 void (*callback)(char*, void*), void* args) {
  std::stringstream ss;
  const MeshGL64* m = from_c(mesh);
  WriteOBJ(ss, *m);
  callback(ss.str().data(), args);
}

#ifdef __cplusplus
}
#endif
