// This may look like C code, but it's really -*- C++ -*-
/*
 * Copyright (C) 2013 Emweb bvba, Leuven, Belgium.
 *
 * See the LICENSE file for terms of use.
 */
#ifndef WCLIENTGLWIDGET
#define WCLIENTGLWIDGET

#include <Wt/WAbstractGLImplementation>

namespace Wt {

class WMemoryResource;
class WRasterPaintDevice;

class WClientGLWidget : public WAbstractGLImplementation {

#ifdef WT_TARGET_JAVA
  typedef float FloatArray;
  typedef int IntArray;
  typedef double MatrixType;
#endif

public:
  WClientGLWidget(WGLWidget *glInterface);

  void debugger();

  void activeTexture(WGLWidget::GLenum texture);

  void attachShader(WGLWidget::Program program, WGLWidget::Shader shader);

  void bindAttribLocation(WGLWidget::Program program, unsigned index,
			  const std::string &name);

  void bindBuffer(WGLWidget::GLenum target, WGLWidget::Buffer buffer);

  void bindFramebuffer(WGLWidget::GLenum target, WGLWidget::Framebuffer buffer);

  void bindRenderbuffer(WGLWidget::GLenum target,
			WGLWidget::Renderbuffer buffer);

  void bindTexture(WGLWidget::GLenum target, WGLWidget::Texture texture);

  void blendColor(double red, double green, double blue, double alpha);

  void blendEquation(WGLWidget::GLenum mode);

  void blendEquationSeparate(WGLWidget::GLenum modeRGB,
			     WGLWidget::GLenum modeAlpha);

  void blendFunc(WGLWidget::GLenum sfactor, WGLWidget::GLenum dfactor);

  void blendFuncSeparate(WGLWidget::GLenum srcRGB, WGLWidget::GLenum dstRGB,
			 WGLWidget::GLenum srcAlpha,WGLWidget::GLenum dstAlpha);

  void bufferData(WGLWidget::GLenum target, int size, WGLWidget::GLenum usage);

  void bufferData(WGLWidget::GLenum target, WGLWidget::ArrayBuffer res,
		  WGLWidget::GLenum usage);

  void bufferData(WGLWidget::GLenum target, WGLWidget::ArrayBuffer res,
		  unsigned arrayBufferOffset, unsigned arrayBufferSize,
		  WGLWidget::GLenum usage);

  void bufferSubData(WGLWidget::GLenum target, unsigned offset,
		     WGLWidget::ArrayBuffer res);

  void bufferSubData(WGLWidget::GLenum target, unsigned offset,
		     WGLWidget::ArrayBuffer res,
		     unsigned arrayBufferOffset, unsigned size);

  void bufferDatafv(WGLWidget::GLenum target, const FloatBuffer &v, WGLWidget::GLenum usage, bool binary);
#ifdef WT_TARGET_JAVA
  void bufferDatafv(WGLWidget::GLenum target, const FloatNotByteBuffer &buffer, WGLWidget::GLenum usage);
#endif

  void bufferSubDatafv(WGLWidget::GLenum target, unsigned offset, const FloatBuffer &buffer, bool binary);
#ifdef WT_TARGET_JAVA
  void bufferSubDatafv(WGLWidget::GLenum target, unsigned offset, const FloatNotByteBuffer &buffer);
#endif

  void bufferDataiv(WGLWidget::GLenum target, IntBuffer &buffer, WGLWidget::GLenum usage, WGLWidget::GLenum type);

  void bufferSubDataiv(WGLWidget::GLenum target,
		       unsigned offset, IntBuffer &buffer, 
		       WGLWidget::GLenum type);

  void clearBinaryResources();

  void clear(WFlags<WGLWidget::GLenum> mask);

  void clearColor(double r, double g, double b, double a);

  void clearDepth(double depth);

  void clearStencil(int s);

  void colorMask(bool red, bool green, bool blue, bool alpha);

  void compileShader(WGLWidget::Shader shader);

  void copyTexImage2D(WGLWidget::GLenum target, int level,
		      WGLWidget::GLenum internalFormat,
		      int x, int y,
		      unsigned width, unsigned height, 
		      int border);

  void copyTexSubImage2D(WGLWidget::GLenum target, int level,
			 int xoffset, int yoffset,
			 int x, int y,
			 unsigned width, unsigned height);

  WGLWidget::Buffer createBuffer();

  WGLWidget::ArrayBuffer createAndLoadArrayBuffer(const std::string &url);

  WGLWidget::Framebuffer createFramebuffer();

  WGLWidget::Program createProgram();

  WGLWidget::Renderbuffer createRenderbuffer();

  WGLWidget::Shader createShader(WGLWidget::GLenum shader);

  WGLWidget::Texture createTexture();

  WGLWidget::Texture createTextureAndLoad(const std::string &url);

  WPaintDevice* createPaintDevice(const WLength& width,
				  const WLength& height);

  void cullFace(WGLWidget::GLenum mode);

  void deleteBuffer(WGLWidget::Buffer buffer);

  void deleteFramebuffer(WGLWidget::Framebuffer buffer);

  void deleteProgram(WGLWidget::Program program);

  void deleteRenderbuffer(WGLWidget::Renderbuffer buffer);

  void deleteShader(WGLWidget::Shader shader);

  void deleteTexture(WGLWidget::Texture texture);

  void depthFunc(WGLWidget::GLenum func);

  void depthMask(bool flag);

  void depthRange(double zNear, double zFar);

  void detachShader(WGLWidget::Program program,
		    WGLWidget::Shader shader);

  void disable(WGLWidget::GLenum cap);

  void disableVertexAttribArray(WGLWidget::AttribLocation index);

  void drawArrays(WGLWidget::GLenum mode, int first, unsigned count);

  void drawElements(WGLWidget::GLenum mode, unsigned count, WGLWidget::GLenum type, unsigned offset);

  void enable(WGLWidget::GLenum cap);

  void enableVertexAttribArray(WGLWidget::AttribLocation index);

  void finish();

  void flush();

  void framebufferRenderbuffer(WGLWidget::GLenum target,
			       WGLWidget::GLenum attachment,
			       WGLWidget::GLenum renderbuffertarget,
			       WGLWidget::Renderbuffer renderbuffer);

  void framebufferTexture2D(WGLWidget::GLenum target,
			    WGLWidget::GLenum attachment,
			    WGLWidget::GLenum textarget,
			    WGLWidget::Texture texture, int level);

  void frontFace(WGLWidget::GLenum mode);

  void generateMipmap(WGLWidget::GLenum target);

  WGLWidget::AttribLocation getAttribLocation(WGLWidget::Program program, const std::string &attrib);

  WGLWidget::UniformLocation getUniformLocation(WGLWidget::Program program, const std::string location);

  void hint(WGLWidget::GLenum target, WGLWidget::GLenum mode);

  void lineWidth(double width);

  void linkProgram(WGLWidget::Program program);

  void pixelStorei(WGLWidget::GLenum pname, int param);

  void polygonOffset(double factor, double units);

  void renderbufferStorage(WGLWidget::GLenum target,
			   WGLWidget::GLenum internalformat,
			   unsigned width, unsigned height);

  void sampleCoverage(double value, bool invert);

  void scissor(int x, int y, unsigned width, unsigned height);

  void shaderSource(WGLWidget::Shader shader, const std::string &src);

  void stencilFunc(WGLWidget::GLenum func, int ref, unsigned mask);

  void stencilFuncSeparate(WGLWidget::GLenum face,
			   WGLWidget::GLenum func, int ref,
			   unsigned mask);

  void stencilMask(unsigned mask);

  void stencilMaskSeparate(WGLWidget::GLenum face, unsigned mask);

  void stencilOp(WGLWidget::GLenum fail, WGLWidget::GLenum zfail,
		 WGLWidget::GLenum zpass);

  void stencilOpSeparate(WGLWidget::GLenum face, WGLWidget::GLenum fail,
			 WGLWidget::GLenum zfail, WGLWidget::GLenum zpass);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  unsigned width, unsigned height, int border,
		  WGLWidget::GLenum format);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  WGLWidget::GLenum format, WGLWidget::GLenum type,
		  WImage *image);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  WGLWidget::GLenum format, WGLWidget::GLenum type,
		  WVideo *video);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  WGLWidget::GLenum format, WGLWidget::GLenum type,
		  std::string image);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  WGLWidget::GLenum format, WGLWidget::GLenum type,
		  WPaintDevice *paintdevice);

  void texImage2D(WGLWidget::GLenum target, int level,
		  WGLWidget::GLenum internalformat,
		  WGLWidget::GLenum format, WGLWidget::GLenum type,
		  WGLWidget::Texture texture);

  void texParameteri(WGLWidget::GLenum target, WGLWidget::GLenum pname,
		     WGLWidget::GLenum param);

  void uniform1f(const WGLWidget::UniformLocation &location, double x);

  void uniform1fv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY float *value);

  void uniform1fv(const WGLWidget::UniformLocation &location,
		  const WGLWidget::JavaScriptVector &v);

  void uniform1i(const WGLWidget::UniformLocation &location,
		 int x);

  void uniform1iv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY int *value);

  void uniform2f(const WGLWidget::UniformLocation &location,
		 double x, double y);

  void uniform2fv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY float *value);

  void uniform2fv(const WGLWidget::UniformLocation &location,
		  const WGLWidget::JavaScriptVector &v);

  void uniform2i(const WGLWidget::UniformLocation &location,
		 int x, int y);

  void uniform2iv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY int *value);

  void uniform3f(const WGLWidget::UniformLocation &location,
		 double x, double y, double z);

  void uniform3fv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY float *value);

  void uniform3fv(const WGLWidget::UniformLocation &location,
		  const WGLWidget::JavaScriptVector &v);

  void uniform3i(const WGLWidget::UniformLocation &location,
		 int x, int y, int z);

  void uniform3iv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY int *value);

  void uniform4f(const WGLWidget::UniformLocation &location,
		 double x, double y, double z, double w);

  void uniform4fv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY float *value);

  void uniform4fv(const WGLWidget::UniformLocation &location,
		  const WGLWidget::JavaScriptVector &v);

  void uniform4i(const WGLWidget::UniformLocation &location,
		 int x, int y, int z, int w);

  void uniform4iv(const WGLWidget::UniformLocation &location,
		  const WT_ARRAY int *value);

  void uniformMatrix2fv(const WGLWidget::UniformLocation &location,
			bool transpose, const WT_ARRAY double *value);

  void uniformMatrix2(const WGLWidget::UniformLocation &location,
		      const WGenericMatrix<double, 2, 2> &m);

  void uniformMatrix3fv(const WGLWidget::UniformLocation &location,
			bool transpose,	const WT_ARRAY double *value);

  void uniformMatrix3(const WGLWidget::UniformLocation &location,
		      const WGenericMatrix<double, 3, 3> &m);

  void uniformMatrix4fv(const WGLWidget::UniformLocation &location,
			bool transpose, const WT_ARRAY double *value);

  void uniformMatrix4(const WGLWidget::UniformLocation &location,
		      const WGenericMatrix<double, 4, 4> &m);

  void uniformMatrix4(const WGLWidget::UniformLocation &location,
		      const WGLWidget::JavaScriptMatrix4x4 &m);

  void useProgram(WGLWidget::Program program);

  void validateProgram(WGLWidget::Program program);

  void vertexAttrib1f(WGLWidget::AttribLocation location, double x);

  void vertexAttrib2f(WGLWidget::AttribLocation location,
		      double x, double y);

  void vertexAttrib3f(WGLWidget::AttribLocation location,
		      double x, double y, double z);

  void vertexAttrib4f(WGLWidget::AttribLocation location,
		      double x, double y, double z, double w);

  void vertexAttribPointer(WGLWidget::AttribLocation location, int size,
    WGLWidget::GLenum type, bool normalized, unsigned stride, unsigned offset);

  void viewport(int x, int y, unsigned width, unsigned height);

  void initJavaScriptMatrix4(WGLWidget::JavaScriptMatrix4x4 &jsm);

  void setJavaScriptMatrix4(WGLWidget::JavaScriptMatrix4x4 &jsm,
			    const WGenericMatrix<double, 4, 4> &m);

  void initJavaScriptVector(WGLWidget::JavaScriptVector &jsv);

  void setJavaScriptVector(WGLWidget::JavaScriptVector &jsv,
			   const std::vector<float> &v);

  void setClientSideMouseHandler(const std::string& handlerCode);

  void setClientSideLookAtHandler(const WGLWidget::JavaScriptMatrix4x4 &m,
				  double ctrX, double ctrY, double ctrZ,
				  double uX, double uY, double uZ,
				  double pitchRate, double yawRate);
    
  void setClientSideWalkHandler(const WGLWidget::JavaScriptMatrix4x4 &m,
				double frontStep, double rotStep);

  JsArrayType arrayType() const;

  void injectJS(const std::string & jsString);

  void restoreContext(const std::string &jsRef);

  void render(const std::string& jsRef, WFlags<RenderFlag> flags);

private:
  std::stringstream js_;

  unsigned shaders_;
  unsigned programs_;
  unsigned attributes_;
  unsigned uniforms_;
  unsigned buffers_;
  unsigned arrayBuffers_;

  unsigned framebuffers_;
  unsigned renderbuffers_;
  unsigned textures_;
  unsigned images_;
  unsigned canvas_;

  WGLWidget::Buffer currentlyBoundBuffer_;
  WGLWidget::Texture currentlyBoundTexture_;
  std::vector<WMemoryResource*> binaryResources_;
  struct PreloadImage {
    PreloadImage(const std::string& r, const std::string& u, unsigned i) :
      jsRef(r), url(u), id(i) {}
    std::string jsRef; 
    std::string url;
    unsigned id; // each image has separate id, so multiple images can be 
                 // made properties of a texture
  };
  std::vector<PreloadImage> preloadImages_;

  struct PreloadArrayBuffer {
    PreloadArrayBuffer(const std::string& ref, const std::string& u) :
      jsRef(ref), url(u) {}
    std::string jsRef;
    std::string url;
  };
  std::vector<PreloadArrayBuffer> preloadArrayBuffers_;

  static const char *makeFloat(double d, char *buf);
  static const char *makeInt(int i, char *buf);

  static const char *toString(WGLWidget::GLenum e);

  WMemoryResource *rpdToMemResource(WRasterImage *rpd);

  static void renderiv(std::ostream &os, 
		       const IntBuffer& a, 
		       WGLWidget::GLenum type)
  {
    switch (type) {
    case WGLWidget::BYTE:
      os << "new Int8Array([";
      break;
    case WGLWidget::UNSIGNED_BYTE:
      os << "new Uint8Array([";
      break;
    case WGLWidget::SHORT:
      os << "new Int16Array([";
      break;
    case WGLWidget::UNSIGNED_SHORT:
      os << "new Uint16Array([";
      break;
    case WGLWidget::INT:
      os << "new Int32Array([";
      break;
    default:
      // Should we warn?
    case WGLWidget::UNSIGNED_INT:
      os << "new Uint32Array([";
      break;
    }
    char buf[30];
    unsigned i;
    for (i = 0; i < a.size(); i++)
      os << (i == 0 ? "" : ",") << makeInt(a[i], buf);
    os << "])";
  }

  static void renderiv(std::ostream &os,
		       const WT_ARRAY int *a, unsigned size, 
		       WGLWidget::GLenum type)
  {
    switch (type) {
    case WGLWidget::BYTE:
      os << "new Int8Array([";
      break;
    case WGLWidget::UNSIGNED_BYTE:
      os << "new Uint8Array([";
      break;
    case WGLWidget::SHORT:
      os << "new Int16Array([";
      break;
    case WGLWidget::UNSIGNED_SHORT:
      os << "new Uint16Array([";
      break;
    case WGLWidget::INT:
      os << "new Int32Array([";
      break;
    default:
      // Should we warn?
    case WGLWidget::UNSIGNED_INT:
      os << "new Uint32Array([";
      break;
    }
    char buf[30];
    unsigned i;
    for (i = 0; i < size; i++)
      os << (i == 0 ? "" : ",") << makeInt(a[i], buf);
    os << "])";
  }

#if !defined(WT_TARGET_JAVA)
  template<typename Array>
  static void renderfv(std::ostream &os, Array *a, unsigned size,
		       JsArrayType arrayType)
  {
    renderfv(os, a, a + size, arrayType);
  }

  template<typename Iterator>
  static void renderfv(std::ostream &os, Iterator begin, Iterator end,
		       JsArrayType arrayType)
  {
    char buf[30];
    switch (arrayType) {
    case Array:
      os << "new Array(";
      for (Iterator i = begin; i != end; ++i) {
	os << (i == begin ? "" : ",") << makeFloat(*i, buf);
      }
      os << ")";
      break;
    case Float32Array:
      os << "new Float32Array([";
      for (Iterator i = begin; i != end; ++i) {
	os << (i == begin ? "" : ",") << makeFloat(*i, buf);
      }
      os << "])";
      break;
    default:
      throw WException("WClientGLWidget: cannot render this javascript type");
    }
  }

  template<typename T, std::size_t Cols, std::size_t Rows>
  static void renderfv(std::ostream &os, WGenericMatrix<T, Cols, Rows> t,
		       JsArrayType arrayType)
  {
    renderfv(os, t.data().begin(), t.data().end(), arrayType);
  }
  
  template<typename Iterator>
  static void renderiv(std::ostream &os, Iterator begin, Iterator end,
		       WGLWidget::GLenum type)
  {
    renderiv(os, &(*begin), end - begin, type);
  }
#else
  static void renderfv(std::ostream &os, 
		       const WT_ARRAY FloatArray *a, 
		       unsigned size,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, 
		       const WT_ARRAY MatrixType *a, 
		       unsigned size,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, 
		       const WT_ARRAY IntArray *a, 
		       unsigned size,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, WGenericMatrix<MatrixType, 4, 4> t,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, WGenericMatrix<MatrixType, 3, 3> t,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, WGenericMatrix<MatrixType, 2, 2> t,
		       JsArrayType arrayType);
  static void renderfv(std::ostream &os, FloatNotByteBuffer buffer,
		       JsArrayType arrayType);
  

  static void renderiv(std::ostream &os, 
		       const WT_ARRAY IntArray *a, 
		       WGLWidget::GLenum type);
#endif //WT_TARGET_JAVA

  std::string glObjJsRef(const std::string& jsRef);

  void initializeGL(const std::string &jsRef, std::stringstream &ss);

  friend class WGLWidget;
  friend class WServerGLWidget;
};

}

#endif
