#ifndef __AMSCUDAFRACTALLEVELSET_HPP__
#define __AMSCUDAFRACTALLEVELSET_HPP__

//Std Lib Includes
#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <stdint.h>
#include <time.h>

//C++ standard library headers
#include <new>
#include <thread>
#include <functional>
#include <mutex>

#include <cuda_runtime_api.h> //where all the cuda functions live
#include <cuda_runtime.h>
#include <cuda.h>

#include <amsculib3/amsculib3.hpp>

//Dependencies

//Predeclarations
class cuvect2;
class cuvect3;
class cuvect4;
class cuvec2f;
class cuvec3f;
class cuvec4f;

//Need a way to define the same symbols using both host and device code
//A solution was found here: https://stackoverflow.com/questions/9457572/cuda-host-and-device-using-same-constant-memory
// #ifdef __CUDA_ARCH__
// #define AMSCU_CONST __constant__
// #else
// #define AMSCU_CONST
// #endif

#define AMSCLS_MANDELBROT
//#define AMSCLS_PPKFRACTAL


namespace amscuda
{
namespace fractallevelset
{

__device__ __host__ int isignf(float f);

#include <amscudafractallevelset/amscfls_functiondefs.cuh>

__host__ __device__ float main_function(cuvec3f pos, main_functionpars &pars);

class bagoftriangles
{
    public:
    cuarray<float> vertices;

    __host__ int ntris() const {return vertices.length/9;}
    __host__ int nverts() const {return vertices.length/3;}
    __host__ int resize_tris(const int ntris);

    __host__ bagoftriangles();
    __host__ ~bagoftriangles();

    __host__ int save_stl(const char *fname);

};

class triangle
{
public:
    cuvec3f p0;
    cuvec3f p1;
    cuvec3f p2;
    
    __device__ __host__ cuvec3f normal() const;
    __device__ __host__ float area() const;
    __device__ __host__ triangle();
    __device__ __host__ ~triangle();
};

class tetrahedron
{
    public:
    cuvec3f p0;
    cuvec3f p1;
    cuvec3f p2;
    cuvec3f p3;

    __device__ __host__ float volume();
    __device__ __host__ int volume_sign();

    __device__ __host__ tetrahedron();
    __device__ __host__ ~tetrahedron();
};

class tetrahedron_feinds
{
    public:
    int index0;
    int index1;
    int index2;
    int index3;
    float eval0;
    float eval1;
    float eval2;
    float eval3;

    __device__ __host__ tetrahedron_feinds();
    __device__ __host__ ~tetrahedron_feinds();
    __device__ __host__ int trianglecount();

};

__device__ __host__ int mt_count_triangles(const tetrahedron_feinds &fevals);
__device__ __host__ int mt_find_triangles(const tetrahedron &tet, const tetrahedron_feinds &fevals, triangle &tri1, triangle &tri2);

class mtcube
{
    public:
    cuvec3f xyz_min;
    cuvec3f xyz_max;

    float fevals[15];

    __device__ __host__ cuvec3f get_point(const int node_index) const; //node_index [0,15)
    //corner nodes (0,1,2,3,4,5,6,7)
    //center node 8
    //face center nodes 9,10,11,12,13,14
    __device__ __host__ tetrahedron get_tetrahedron(const int tet_index) const; //tet_index [0,24)
    __device__ __host__ tetrahedron_feinds get_feinds(const int tet_index) const; //tet_index [0,24)
    __device__ __host__ void eval_function(main_functionpars &pars);         //calls a function under consideration at each of the 15 node points

    __device__ __host__ int count_triangles();
    __device__ __host__ void fill_trianglebuffer(float *buffer, int64_t &offset); //fills from offset to offset+ntris*3*3 with the triangles calculated from fevals. Increments offset.
    __device__ __host__ float count_triarea(); //counts triangle area without filling a buffer or retaining the mesh

    __device__ __host__ mtcube();
    __device__ __host__ ~mtcube();
};

struct marchingtet_pars
{
    public:
    cuvec3f xyz_min;
    cuvec3f xyz_max;
    int Nx;
    int Ny;
    int Nz;

    main_functionpars pars;

    __device__ __host__ marchingtet_pars();
};


void marchingtets_gpu(marchingtet_pars mtpars, bagoftriangles *mesh, int nblockdiv=32, int nthreads=256, int verbose=0);

int64_t marchingtets_tricount_gpu(marchingtet_pars mtpars, int nblockdiv=32, int nthreads=256, int verbose=0);

float marchingtets_area_gpu(marchingtet_pars mtpars, int nblockdiv=32, int nthreads=256, int verbose=0);

}; //end namespaces
};

#include <amscudafractallevelset/amscfls_parseinput.cuh>

namespace amscuda
{
namespace fractallevelset
{
///////////
// Tests //
///////////
void test_tetorientation();
void test_mtcube_triangulation();
void test_marchingtets();
void hausdorff_test();



};//end namespaces
};

#endif

