// Copyright 2016 The SwiftShader Authors. All Rights Reserved.
//
// 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.

// Context.h: Defines the Context class, managing all GL state and performing
// rendering operations. It is the GLES2 specific implementation of EGLContext.

#ifndef LIBGLES_CM_CONTEXT_H_
#define LIBGLES_CM_CONTEXT_H_

#include "libEGL/Context.hpp"
#include "ResourceManager.h"
#include "common/NameSpace.hpp"
#include "common/Object.hpp"
#include "common/Image.hpp"
#include "Renderer/Sampler.hpp"
#include "common/MatrixStack.hpp"

#include <GLES/gl.h>
#include <GLES/glext.h>
#include <EGL/egl.h>

#include <map>
#include <string>

namespace gl { class Surface; }

namespace egl
{
class Display;
class Config;
}

namespace es1
{
struct TranslatedAttribute;
struct TranslatedIndexData;

class Device;
class Buffer;
class Texture;
class Texture2D;
class TextureExternal;
class Framebuffer;
class Renderbuffer;
class RenderbufferStorage;
class Colorbuffer;
class Depthbuffer;
class StreamingIndexBuffer;
class Stencilbuffer;
class DepthStencilbuffer;
class VertexDataManager;
class IndexDataManager;

enum
{
	MAX_VERTEX_ATTRIBS = sw::MAX_VERTEX_INPUTS,
	MAX_VARYING_VECTORS = 10,
	MAX_TEXTURE_UNITS = 2,
	MAX_DRAW_BUFFERS = 1,
	MAX_LIGHTS = 8,
	MAX_CLIP_PLANES = sw::MAX_CLIP_PLANES,

	MAX_MODELVIEW_STACK_DEPTH = 32,
	MAX_PROJECTION_STACK_DEPTH = 2,
	MAX_TEXTURE_STACK_DEPTH = 2,
};

const GLenum compressedTextureFormats[] =
{
	GL_ETC1_RGB8_OES,
#if (S3TC_SUPPORT)
	GL_COMPRESSED_RGB_S3TC_DXT1_EXT,
	GL_COMPRESSED_RGBA_S3TC_DXT1_EXT,
#endif
};

const GLint NUM_COMPRESSED_TEXTURE_FORMATS = sizeof(compressedTextureFormats) / sizeof(compressedTextureFormats[0]);

const GLint multisampleCount[] = {4, 2, 1};
const GLint NUM_MULTISAMPLE_COUNTS = sizeof(multisampleCount) / sizeof(multisampleCount[0]);
const GLint IMPLEMENTATION_MAX_SAMPLES = multisampleCount[0];

const float ALIASED_LINE_WIDTH_RANGE_MIN = 1.0f;
const float ALIASED_LINE_WIDTH_RANGE_MAX = 1.0f;
const float ALIASED_POINT_SIZE_RANGE_MIN = 0.125f;
const float ALIASED_POINT_SIZE_RANGE_MAX = 8192.0f;
const float SMOOTH_LINE_WIDTH_RANGE_MIN = 1.0f;
const float SMOOTH_LINE_WIDTH_RANGE_MAX = 1.0f;
const float SMOOTH_POINT_SIZE_RANGE_MIN = 0.125f;
const float SMOOTH_POINT_SIZE_RANGE_MAX = 8192.0f;
const float MAX_TEXTURE_MAX_ANISOTROPY = 16.0f;

struct Color
{
	float red;
	float green;
	float blue;
	float alpha;
};

struct Point
{
	float x;
	float y;
	float z;
	float w;
};

struct Vector
{
	float x;
	float y;
	float z;
};

struct Attenuation
{
	float constant;
	float linear;
	float quadratic;
};

struct Light
{
	bool enabled;
	Color ambient;
	Color diffuse;
	Color specular;
	Point position;
	Vector direction;
	Attenuation attenuation;
	float spotExponent;
	float spotCutoffAngle;
};

// Helper structure describing a single vertex attribute
class VertexAttribute
{
public:
	VertexAttribute() : mType(GL_FLOAT), mSize(4), mNormalized(false), mStride(0), mPointer(nullptr), mArrayEnabled(false)
	{
		mCurrentValue[0] = 0.0f;
		mCurrentValue[1] = 0.0f;
		mCurrentValue[2] = 0.0f;
		mCurrentValue[3] = 1.0f;
	}

	int typeSize() const
	{
		switch(mType)
		{
		case GL_BYTE:           return mSize * sizeof(GLbyte);
		case GL_UNSIGNED_BYTE:  return mSize * sizeof(GLubyte);
		case GL_SHORT:          return mSize * sizeof(GLshort);
		case GL_UNSIGNED_SHORT: return mSize * sizeof(GLushort);
		case GL_FIXED:          return mSize * sizeof(GLfixed);
		case GL_FLOAT:          return mSize * sizeof(GLfloat);
		default: UNREACHABLE(mType); return mSize * sizeof(GLfloat);
		}
	}

	GLsizei stride() const
	{
		return mStride ? mStride : typeSize();
	}

	// From glVertexAttribPointer
	GLenum mType;
	GLint mSize;
	bool mNormalized;
	GLsizei mStride;   // 0 means natural stride

	union
	{
		const void *mPointer;
		intptr_t mOffset;
	};

	gl::BindingPointer<Buffer> mBoundBuffer;   // Captured when glVertexAttribPointer is called.

	bool mArrayEnabled;   // From glEnable/DisableVertexAttribArray
	float mCurrentValue[4];   // From glVertexAttrib
};

typedef VertexAttribute VertexAttributeArray[MAX_VERTEX_ATTRIBS];

struct TextureUnit
{
	Color color;
	GLenum environmentMode;
	GLenum combineRGB;
	GLenum combineAlpha;
	GLenum src0RGB;
	GLenum src0Alpha;
	GLenum src1RGB;
	GLenum src1Alpha;
	GLenum src2RGB;
	GLenum src2Alpha;
	GLenum operand0RGB;
	GLenum operand0Alpha;
	GLenum operand1RGB;
	GLenum operand1Alpha;
	GLenum operand2RGB;
	GLenum operand2Alpha;
};

// Helper structure to store all raw state
struct State
{
	Color colorClearValue;
	GLclampf depthClearValue;
	int stencilClearValue;

	bool cullFaceEnabled;
	GLenum cullMode;
	GLenum frontFace;
	bool depthTestEnabled;
	GLenum depthFunc;
	bool blendEnabled;
	GLenum sourceBlendRGB;
	GLenum destBlendRGB;
	GLenum sourceBlendAlpha;
	GLenum destBlendAlpha;
	GLenum blendEquationRGB;
	GLenum blendEquationAlpha;
	bool stencilTestEnabled;
	GLenum stencilFunc;
	GLint stencilRef;
	GLuint stencilMask;
	GLenum stencilFail;
	GLenum stencilPassDepthFail;
	GLenum stencilPassDepthPass;
	GLuint stencilWritemask;
	bool polygonOffsetFillEnabled;
	GLfloat polygonOffsetFactor;
	GLfloat polygonOffsetUnits;
	bool sampleAlphaToCoverageEnabled;
	bool sampleCoverageEnabled;
	GLclampf sampleCoverageValue;
	bool sampleCoverageInvert;
	bool scissorTestEnabled;
	bool ditherEnabled;
	GLenum shadeModel;

	GLfloat lineWidth;

	GLenum generateMipmapHint;
	GLenum perspectiveCorrectionHint;
	GLenum fogHint;

	GLint viewportX;
	GLint viewportY;
	GLsizei viewportWidth;
	GLsizei viewportHeight;
	float zNear;
	float zFar;

	GLint scissorX;
	GLint scissorY;
	GLsizei scissorWidth;
	GLsizei scissorHeight;

	bool colorMaskRed;
	bool colorMaskGreen;
	bool colorMaskBlue;
	bool colorMaskAlpha;
	bool depthMask;

	unsigned int activeSampler;   // Active texture unit selector - GL_TEXTURE0
	gl::BindingPointer<Buffer> arrayBuffer;
	gl::BindingPointer<Buffer> elementArrayBuffer;
	GLuint framebuffer;
	gl::BindingPointer<Renderbuffer> renderbuffer;

	VertexAttribute vertexAttribute[MAX_VERTEX_ATTRIBS];
	gl::BindingPointer<Texture> samplerTexture[TEXTURE_TYPE_COUNT][MAX_TEXTURE_UNITS];

	GLint unpackAlignment;
	GLint packAlignment;

	TextureUnit textureUnit[MAX_TEXTURE_UNITS];
};

class [[clang::lto_visibility_public]] Context : public egl::Context
{
public:
	Context(egl::Display *display, const Context *shareContext, const egl::Config *config);

	void makeCurrent(gl::Surface *surface) override;
	EGLint getClientVersion() const override;
	EGLint getConfigID() const override;

	void finish() override;

	void markAllStateDirty();

	// State manipulation
	void setClearColor(float red, float green, float blue, float alpha);
	void setClearDepth(float depth);
	void setClearStencil(int stencil);

	void setCullFaceEnabled(bool enabled);
	bool isCullFaceEnabled() const;
	void setCullMode(GLenum mode);
	void setFrontFace(GLenum front);

	void setDepthTestEnabled(bool enabled);
	bool isDepthTestEnabled() const;
	void setDepthFunc(GLenum depthFunc);
	void setDepthRange(float zNear, float zFar);

	void setAlphaTestEnabled(bool enabled);
	bool isAlphaTestEnabled() const;
	void setAlphaFunc(GLenum alphaFunc, GLclampf reference);

	void setBlendEnabled(bool enabled);
	bool isBlendEnabled() const;
	void setBlendFactors(GLenum sourceRGB, GLenum destRGB, GLenum sourceAlpha, GLenum destAlpha);
	void setBlendEquation(GLenum rgbEquation, GLenum alphaEquation);

	void setStencilTestEnabled(bool enabled);
	bool isStencilTestEnabled() const;
	void setStencilParams(GLenum stencilFunc, GLint stencilRef, GLuint stencilMask);
	void setStencilWritemask(GLuint stencilWritemask);
	void setStencilOperations(GLenum stencilFail, GLenum stencilPassDepthFail, GLenum stencilPassDepthPass);

	void setPolygonOffsetFillEnabled(bool enabled);
	bool isPolygonOffsetFillEnabled() const;
	void setPolygonOffsetParams(GLfloat factor, GLfloat units);

	void setSampleAlphaToCoverageEnabled(bool enabled);
	bool isSampleAlphaToCoverageEnabled() const;
	void setSampleCoverageEnabled(bool enabled);
	bool isSampleCoverageEnabled() const;
	void setSampleCoverageParams(GLclampf value, bool invert);

	void setShadeModel(GLenum mode);
	void setDitherEnabled(bool enabled);
	bool isDitherEnabled() const;
	void setLightingEnabled(bool enabled);
	bool isLightingEnabled() const;
	void setLightEnabled(int index, bool enable);
	bool isLightEnabled(int index) const;
	void setLightAmbient(int index, float r, float g, float b, float a);
	void setLightDiffuse(int index, float r, float g, float b, float a);
	void setLightSpecular(int index, float r, float g, float b, float a);
	void setLightPosition(int index, float x, float y, float z, float w);
	void setLightDirection(int index, float x, float y, float z);
	void setLightAttenuationConstant(int index, float constant);
	void setLightAttenuationLinear(int index, float linear);
	void setLightAttenuationQuadratic(int index, float quadratic);
	void setSpotLightExponent(int index, float exponent);
	void setSpotLightCutoff(int index, float cutoff);

	void setGlobalAmbient(float red, float green, float blue, float alpha);
	void setMaterialAmbient(float red, float green, float blue, float alpha);
	void setMaterialDiffuse(float red, float green, float blue, float alpha);
	void setMaterialSpecular(float red, float green, float blue, float alpha);
	void setMaterialEmission(float red, float green, float blue, float alpha);
	void setMaterialShininess(float shininess);
	void setLightModelTwoSide(bool enable);

	void setFogEnabled(bool enabled);
	bool isFogEnabled() const;
	void setFogMode(GLenum mode);
	void setFogDensity(float fogDensity);
	void setFogStart(float fogStart);
	void setFogEnd(float fogEnd);
	void setFogColor(float r, float g, float b, float a);

	void setTexture2Denabled(bool enabled);
	bool isTexture2Denabled() const;
	void setTextureExternalEnabled(bool enabled);
	bool isTextureExternalEnabled() const;
	void clientActiveTexture(GLenum texture);
	GLenum getClientActiveTexture() const;
	unsigned int getActiveTexture() const;

	void setTextureEnvMode(GLenum texEnvMode);
	void setTextureEnvColor(GLclampf red, GLclampf green, GLclampf blue, GLclampf alpha);
	void setCombineRGB(GLenum combineRGB);
	void setCombineAlpha(GLenum combineAlpha);
	void setOperand0RGB(GLenum operand);
	void setOperand1RGB(GLenum operand);
	void setOperand2RGB(GLenum operand);
	void setOperand0Alpha(GLenum operand);
	void setOperand1Alpha(GLenum operand);
	void setOperand2Alpha(GLenum operand);
	void setSrc0RGB(GLenum src);
	void setSrc1RGB(GLenum src);
	void setSrc2RGB(GLenum src);
	void setSrc0Alpha(GLenum src);
	void setSrc1Alpha(GLenum src);
	void setSrc2Alpha(GLenum src);

	void setLineWidth(GLfloat width);

	void setGenerateMipmapHint(GLenum hint);
	void setPerspectiveCorrectionHint(GLenum hint);
	void setFogHint(GLenum hint);

	void setViewportParams(GLint x, GLint y, GLsizei width, GLsizei height);

	void setScissorTestEnabled(bool enabled);
	bool isScissorTestEnabled() const;
	void setScissorParams(GLint x, GLint y, GLsizei width, GLsizei height);

	void setColorMask(bool red, bool green, bool blue, bool alpha);
	void setDepthMask(bool mask);

	void setActiveSampler(unsigned int active);

	GLuint getFramebufferName() const;
	GLuint getRenderbufferName() const;

	GLuint getArrayBufferName() const;

	void setVertexAttribArrayEnabled(unsigned int attribNum, bool enabled);
	const VertexAttribute &getVertexAttribState(unsigned int attribNum);
	void setVertexAttribState(unsigned int attribNum, Buffer *boundBuffer, GLint size, GLenum type,
	                          bool normalized, GLsizei stride, const void *pointer);
	const void *getVertexAttribPointer(unsigned int attribNum) const;

	const VertexAttributeArray &getVertexAttributes();

	void setUnpackAlignment(GLint alignment);
	GLint getUnpackAlignment() const;

	void setPackAlignment(GLint alignment);
	GLint getPackAlignment() const;

	// These create and destroy methods are merely pass-throughs to
	// ResourceManager, which owns these object types
	GLuint createBuffer();
	GLuint createTexture();
	GLuint createRenderbuffer();

	void deleteBuffer(GLuint buffer);
	void deleteTexture(GLuint texture);
	void deleteRenderbuffer(GLuint renderbuffer);

	// Framebuffers are owned by the Context, so these methods do not pass through
	GLuint createFramebuffer();
	void deleteFramebuffer(GLuint framebuffer);

	void bindArrayBuffer(GLuint buffer);
	void bindElementArrayBuffer(GLuint buffer);
	void bindTexture2D(GLuint texture);
	void bindTextureExternal(GLuint texture);
	void bindFramebuffer(GLuint framebuffer);
	void bindRenderbuffer(GLuint renderbuffer);

	void setFramebufferZero(Framebuffer *framebuffer);

	void setRenderbufferStorage(RenderbufferStorage *renderbuffer);

	void setVertexAttrib(GLuint index, GLfloat x, GLfloat y, GLfloat z, GLfloat w);

	Buffer *getBuffer(GLuint handle);
	virtual Texture *getTexture(GLuint handle);
	Framebuffer *getFramebuffer(GLuint handle);
	virtual Renderbuffer *getRenderbuffer(GLuint handle);

	Buffer *getArrayBuffer();
	Buffer *getElementArrayBuffer();
	Texture2D *getTexture2D();
	TextureExternal *getTextureExternal();
	Texture *getSamplerTexture(unsigned int sampler, TextureType type);
	Framebuffer *getFramebuffer();

	bool getFloatv(GLenum pname, GLfloat *params);
	bool getIntegerv(GLenum pname, GLint *params);
	bool getBooleanv(GLenum pname, GLboolean *params);
	bool getPointerv(GLenum pname, const GLvoid **params);

	int getQueryParameterNum(GLenum pname);
	bool isQueryParameterInt(GLenum pname);
	bool isQueryParameterFloat(GLenum pname);
	bool isQueryParameterBool(GLenum pname);
	bool isQueryParameterPointer(GLenum pname);

	void drawArrays(GLenum mode, GLint first, GLsizei count);
	void drawElements(GLenum mode, GLsizei count, GLenum type, const void *indices);
	void drawTexture(GLfloat x, GLfloat y, GLfloat z, GLfloat width, GLfloat height);
	void blit(sw::Surface *source, const sw::SliceRect &sRect, sw::Surface *dest, const sw::SliceRect &dRect) override;
	void readPixels(GLint x, GLint y, GLsizei width, GLsizei height, GLenum format, GLenum type, GLsizei *bufSize, void* pixels);
	void clear(GLbitfield mask);
	void flush();

	void recordInvalidEnum();
	void recordInvalidValue();
	void recordInvalidOperation();
	void recordOutOfMemory();
	void recordInvalidFramebufferOperation();
	void recordMatrixStackOverflow();
	void recordMatrixStackUnderflow();

	GLenum getError();

	static int getSupportedMultisampleCount(int requested);

	void bindTexImage(gl::Surface *surface) override;
	EGLenum validateSharedImage(EGLenum target, GLuint name, GLuint textureLevel) override;
	egl::Image *createSharedImage(EGLenum target, GLuint name, GLuint textureLevel) override;
	egl::Image *getSharedImage(GLeglImageOES image);

	Device *getDevice();

	void setMatrixMode(GLenum mode);
	void loadIdentity();
	void load(const GLfloat *m);
	void pushMatrix();
	void popMatrix();
	void rotate(GLfloat angle, GLfloat x, GLfloat y, GLfloat z);
	void translate(GLfloat x, GLfloat y, GLfloat z);
	void scale(GLfloat x, GLfloat y, GLfloat z);
	void multiply(const GLfloat *m);
	void frustum(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar);
	void ortho(GLfloat left, GLfloat right, GLfloat bottom, GLfloat top, GLfloat zNear, GLfloat zFar);

	void setClipPlane(int index, const float plane[4]);
	void setClipPlaneEnabled(int index, bool enable);
	bool isClipPlaneEnabled(int index) const;

	void setColorLogicOpEnabled(bool enable);
	bool isColorLogicOpEnabled() const;
	void setLogicalOperation(GLenum logicOp);

	void setPointSmoothEnabled(bool enable);
	bool isPointSmoothEnabled() const;

	void setLineSmoothEnabled(bool enable);
	bool isLineSmoothEnabled() const;

	void setColorMaterialEnabled(bool enable);
	bool isColorMaterialEnabled() const;

	void setNormalizeEnabled(bool enable);
	bool isNormalizeEnabled() const;

	void setRescaleNormalEnabled(bool enable);
	bool isRescaleNormalEnabled() const;

	void setVertexArrayEnabled(bool enable);
	bool isVertexArrayEnabled() const;

	void setNormalArrayEnabled(bool enable);
	bool isNormalArrayEnabled() const;

	void setColorArrayEnabled(bool enable);
	bool isColorArrayEnabled() const;

	void setPointSizeArrayEnabled(bool enable);
	bool isPointSizeArrayEnabled() const;

	void setTextureCoordArrayEnabled(bool enable);
	bool isTextureCoordArrayEnabled() const;

	void setMultisampleEnabled(bool enable);
	bool isMultisampleEnabled() const;

	void setSampleAlphaToOneEnabled(bool enable);
	bool isSampleAlphaToOneEnabled() const;

	void setPointSpriteEnabled(bool enable);
	bool isPointSpriteEnabled() const;
	void setPointSizeMin(float min);
	void setPointSizeMax(float max);
	void setPointDistanceAttenuation(float a, float b, float c);
	void setPointFadeThresholdSize(float threshold);

private:
	~Context() override;

	bool applyRenderTarget();
	void applyState(GLenum drawMode);
	GLenum applyVertexBuffer(GLint base, GLint first, GLsizei count);
	GLenum applyIndexBuffer(const void *indices, GLsizei count, GLenum mode, GLenum type, TranslatedIndexData *indexInfo);
	void applyTextures();
	void applyTexture(int sampler, Texture *texture);

	void detachBuffer(GLuint buffer);
	void detachTexture(GLuint texture);
	void detachFramebuffer(GLuint framebuffer);
	void detachRenderbuffer(GLuint renderbuffer);

	bool cullSkipsDraw(GLenum drawMode);
	bool isTriangleMode(GLenum drawMode);

	const egl::Config *const config;

	State mState;

	gl::BindingPointer<Texture2D> mTexture2DZero;
	gl::BindingPointer<TextureExternal> mTextureExternalZero;

	gl::NameSpace<Framebuffer> mFramebufferNameSpace;

	VertexDataManager *mVertexDataManager;
	IndexDataManager *mIndexDataManager;

	bool lightingEnabled;
	Light light[MAX_LIGHTS];
	Color globalAmbient;
	Color materialAmbient;
	Color materialDiffuse;
	Color materialSpecular;
	Color materialEmission;
	GLfloat materialShininess;
	bool lightModelTwoSide;

	// Recorded errors
	bool mInvalidEnum;
	bool mInvalidValue;
	bool mInvalidOperation;
	bool mOutOfMemory;
	bool mInvalidFramebufferOperation;
	bool mMatrixStackOverflow;
	bool mMatrixStackUnderflow;

	bool mHasBeenCurrent;

	// state caching flags
	bool mDepthStateDirty;
	bool mMaskStateDirty;
	bool mBlendStateDirty;
	bool mStencilStateDirty;
	bool mPolygonOffsetStateDirty;
	bool mSampleStateDirty;
	bool mFrontFaceDirty;
	bool mDitherStateDirty;

	sw::MatrixStack &currentMatrixStack();
	GLenum matrixMode;
	sw::MatrixStack modelViewStack;
	sw::MatrixStack projectionStack;
	sw::MatrixStack textureStack0;
	sw::MatrixStack textureStack1;

	bool texture2Denabled[MAX_TEXTURE_UNITS];
	bool textureExternalEnabled[MAX_TEXTURE_UNITS];
	GLenum clientTexture;

	int clipFlags;

	bool alphaTestEnabled;
	GLenum alphaTestFunc;
	float alphaTestRef;

	bool fogEnabled;
	GLenum fogMode;
	float fogDensity;
	float fogStart;
	float fogEnd;
	Color fogColor;

	bool lineSmoothEnabled;
	bool colorMaterialEnabled;
	bool normalizeEnabled;
	bool rescaleNormalEnabled;
	bool multisampleEnabled;
	bool sampleAlphaToOneEnabled;

	bool pointSpriteEnabled;
	bool pointSmoothEnabled;
	float pointSizeMin;
	float pointSizeMax;
	Attenuation pointDistanceAttenuation;
	float pointFadeThresholdSize;

	bool colorLogicOpEnabled;
	GLenum logicalOperation;

	Device *device;
	ResourceManager *mResourceManager;
};
}

#endif   // INCLUDE_CONTEXT_H_
