/*****
* glrender.h
* Render 3D Bezier paths and surfaces.
*****/

#ifndef GLRENDER_H
#define GLRENDER_H

#include "common.h"
#include "triple.h"
#include "pen.h"

#ifdef HAVE_LIBGLM
#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/transform.hpp>
#endif

#ifdef HAVE_GL

#include <csignal>
#ifdef __APPLE__
#define GL_SILENCE_DEPRECATION
#endif
#include <GL/glew.h>

#ifdef __APPLE__
#include <OpenGL/gl.h>

#ifdef HAVE_LIBGLUT
#include <GLUT/glut.h>
#ifndef GLUT_3_2_CORE_PROFILE
#undef HAVE_GL
#endif

#endif

#ifdef HAVE_LIBOSMESA
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef GLAPI
#define GLAPI
#endif
#define GLEW_OSMESA
#include <GL/osmesa.h>
#endif

#else

#ifdef HAVE_LIBGLUT
#include <GL/glut.h>
#endif

#ifdef HAVE_LIBOSMESA
#ifndef APIENTRY
#define APIENTRY
#endif
#ifndef GLAPI
#define GLAPI
#endif
#include <GL/osmesa.h>
#endif

#endif

#else
typedef unsigned int GLuint;
typedef int GLint;
typedef float GLfloat;
typedef double GLdouble;
typedef unsigned char GLubyte;
typedef unsigned int GLenum;
#define GL_POINTS                               0x0000
#define GL_LINES                                0x0001
#define GL_TRIANGLES                            0x0004
#endif

#ifdef HAVE_LIBGLM
#include "material.h"
#endif

namespace camp {
class picture;

inline void store(GLfloat *f, double *C)
{
 f[0]=C[0];
 f[1]=C[1];
 f[2]=C[2];
}

inline void store(GLfloat *control, const camp::triple& v)
{
 control[0]=v.getx();
 control[1]=v.gety();
 control[2]=v.getz();
}

inline void store(GLfloat *control, const triple& v, double weight)
{
 control[0]=v.getx()*weight;
 control[1]=v.gety()*weight;
 control[2]=v.getz()*weight;
 control[3]=weight;
}

}

namespace gl {

extern bool outlinemode;
extern bool wireframeMode;

extern bool orthographic;

// 2D bounds
extern double xmin,xmax;
extern double ymin,ymax;

// 3D bounds
extern double Xmin,Xmax;
extern double Ymin,Ymax;
extern double Zmin,Zmax;

extern int fullWidth,fullHeight;
extern double Zoom0;
extern double Angle;
extern camp::pair Shift;
extern camp::pair Margin;

extern camp::triple *Lights;
extern size_t nlights;
extern double *Diffuse;
extern double Background[4];

struct projection
{
public:
 bool orthographic;
 camp::triple camera;
 camp::triple up;
 camp::triple target;
 double zoom;
 double angle;
 camp::pair viewportshift;

 projection(bool orthographic=false, camp::triple camera=0.0,
            camp::triple up=0.0, camp::triple target=0.0,
            double zoom=0.0, double angle=0.0,
            camp::pair viewportshift=0.0) :
   orthographic(orthographic), camera(camera), up(up), target(target),
   zoom(zoom), angle(angle), viewportshift(viewportshift) {}
};

#ifdef HAVE_GL
extern GLuint ubo;
GLuint initHDR();
#endif

projection camera(bool user=true);

struct GLRenderArgs
{
 string prefix;
 camp::picture* pic;
 string format;
 double width;
 double height;
 double angle;
 double zoom;
 camp::triple m;
 camp::triple M;
 camp::pair shift;
 camp::pair margin;
 double *t;
 double *tup;
 double *background;
 size_t nlights;
 camp::triple *lights;
 double *diffuse;
 double *specular;
 bool view;
};

void glrender(GLRenderArgs const& args, int oldpid=0);

extern const double *dprojView;
extern const double *dView;

extern double BBT[9];
extern double T[16];

extern bool format3dWait;

}

namespace camp {

struct Billboard {
 double cx,cy,cz;

 void init(const triple& center) {
   cx=center.getx();
   cy=center.gety();
   cz=center.getz();
 }

 triple transform(const triple& v) const {
   double x=v.getx()-cx;
   double y=v.gety()-cy;
   double z=v.getz()-cz;

   return triple(x*gl::BBT[0]+y*gl::BBT[3]+z*gl::BBT[6]+cx,
                 x*gl::BBT[1]+y*gl::BBT[4]+z*gl::BBT[7]+cy,
                 x*gl::BBT[2]+y*gl::BBT[5]+z*gl::BBT[8]+cz);
 }
};

extern Billboard BB;

#ifdef HAVE_LIBGLM
typedef mem::map<const Material,size_t> MaterialMap;

extern std::vector<Material> materials;
extern MaterialMap materialMap;
extern size_t materialIndex;
extern int MaterialIndex;

extern const size_t Nbuffer; // Initial size of 2D dynamic buffers
extern const size_t nbuffer; // Initial size of 0D & 1D dynamic buffers

class vertexData
{
public:
 GLfloat position[3];
 GLfloat normal[3];
 GLint material;
 vertexData() {};
 vertexData(const triple& v, const triple& n) {
   position[0]=v.getx();
   position[1]=v.gety();
   position[2]=v.getz();
   normal[0]=n.getx();
   normal[1]=n.gety();
   normal[2]=n.getz();
   material=MaterialIndex;
 }
};

class VertexData
{
public:
 GLfloat position[3];
 GLfloat normal[3];
 GLint material;
 GLfloat color[4];
 VertexData() {};
 VertexData(const triple& v, const triple& n) {
   position[0]=v.getx();
   position[1]=v.gety();
   position[2]=v.getz();
   normal[0]=n.getx();
   normal[1]=n.gety();
   normal[2]=n.getz();
   material=MaterialIndex;
 }
 VertexData(const triple& v, const triple& n, GLfloat *c) {
   position[0]=v.getx();
   position[1]=v.gety();
   position[2]=v.getz();
   normal[0]=n.getx();
   normal[1]=n.gety();
   normal[2]=n.getz();
   material=MaterialIndex;
   color[0]=c[0];
   color[1]=c[1];
   color[2]=c[2];
   color[3]=c[3];
 }
};

class vertexData0 {
public:
 GLfloat position[3];
 GLfloat width;
 GLint material;
 vertexData0() {};
 vertexData0(const triple& v, double width) : width(width) {
   position[0]=v.getx();
   position[1]=v.gety();
   position[2]=v.getz();
   material=MaterialIndex;
 }
};

class vertexBuffer {
public:
 GLenum type;

 GLuint verticesBuffer;
 GLuint VerticesBuffer;
 GLuint vertices0Buffer;
 GLuint indicesBuffer;
 GLuint materialsBuffer;

 std::vector<vertexData> vertices;
 std::vector<VertexData> Vertices;
 std::vector<vertexData0> vertices0;
 std::vector<GLuint> indices;

 std::vector<Material> materials;
 std::vector<GLint> materialTable;

 bool rendered; // Are all patches in this buffer fully rendered?
 bool partial;  // Does buffer contain incomplete data?

 vertexBuffer(GLint type=GL_TRIANGLES) : type(type),
                                         verticesBuffer(0),
                                         VerticesBuffer(0),
                                         vertices0Buffer(0),
                                         indicesBuffer(0),
                                         materialsBuffer(0),
                                         rendered(false),
                                         partial(false)
 {}

 void clear() {
   vertices.clear();
   Vertices.clear();
   vertices0.clear();
   indices.clear();
   materials.clear();
   materialTable.clear();
 }

 void reserve0() {
   vertices0.reserve(nbuffer);
 }

 void reserve() {
   vertices.reserve(Nbuffer);
   indices.reserve(Nbuffer);
 }

 void Reserve() {
   Vertices.reserve(Nbuffer);
   indices.reserve(Nbuffer);
 }

// Store the vertex v and its normal vector n.
 GLuint vertex(const triple &v, const triple& n) {
   size_t nvertices=vertices.size();
   vertices.push_back(vertexData(v,n));
   return nvertices;
 }

// Store the vertex v and its normal vector n, without an explicit color.
 GLuint tvertex(const triple &v, const triple& n) {
   size_t nvertices=Vertices.size();
   Vertices.push_back(VertexData(v,n));
   return nvertices;
 }

// Store the vertex v, its normal vector n, and colors c.
 GLuint Vertex(const triple &v, const triple& n, GLfloat *c) {
   size_t nvertices=Vertices.size();
   Vertices.push_back(VertexData(v,n,c));
   return nvertices;
 }

// Store the pixel v and its width.
 GLuint vertex0(const triple &v, double width) {
   size_t nvertices=vertices0.size();
   vertices0.push_back(vertexData0(v,width));
   return nvertices;
 }

 // append array b onto array a with offset
 void appendOffset(std::vector<GLuint>& a,
                   const std::vector<GLuint>& b, size_t offset) {
   size_t n=a.size();
   size_t m=b.size();
   a.resize(n+m);
   for(size_t i=0; i < m; ++i)
     a[n+i]=b[i]+offset;
 }

 void append(const vertexBuffer& b) {
   appendOffset(indices,b.indices,vertices.size());
   vertices.insert(vertices.end(),b.vertices.begin(),b.vertices.end());
 }

 void Append(const vertexBuffer& b) {
   appendOffset(indices,b.indices,Vertices.size());
   Vertices.insert(Vertices.end(),b.Vertices.begin(),b.Vertices.end());
 }

 void append0(const vertexBuffer& b) {
   appendOffset(indices,b.indices,vertices0.size());
   vertices0.insert(vertices0.end(),b.vertices0.begin(),b.vertices0.end());
 }
};

extern vertexBuffer material0Data;   // pixels
extern vertexBuffer material1Data;   // material Bezier curves
extern vertexBuffer materialData;    // material Bezier patches & triangles
extern vertexBuffer colorData;       // colored Bezier patches & triangles
extern vertexBuffer triangleData;    // opaque indexed triangles
extern vertexBuffer transparentData; // transparent patches & triangles

void drawBuffer(vertexBuffer& data, GLint shader, bool color=false);
void drawBuffers();

void clearMaterials();
void clearCenters();

typedef void draw_t();
void setMaterial(vertexBuffer& data, draw_t *draw);

void drawMaterial0();
void drawMaterial1();
void drawMaterial();
void drawColor();
void drawTriangle();
void drawTransparent();

#endif

}

#endif