// 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.

// Program.h: Defines the Program class. Implements GL program objects
// and related functionality. [OpenGL ES 2.0.24] section 2.10.3 page 28.

#ifndef LIBGLESV2_PROGRAM_H_
#define LIBGLESV2_PROGRAM_H_

#include "Shader.h"
#include "Context.h"
#include "Shader/PixelShader.hpp"
#include "Shader/VertexShader.hpp"

#include <string>
#include <vector>
#include <set>
#include <map>

namespace es2
{
	class Device;
	class ResourceManager;
	class FragmentShader;
	class VertexShader;

	// Helper struct representing a single shader uniform
	struct Uniform
	{
		struct BlockInfo
		{
			BlockInfo(const glsl::Uniform& uniform, int blockIndex);

			int index = -1;
			int offset = -1;
			int arrayStride = -1;
			int matrixStride = -1;
			bool isRowMajorMatrix = false;
		};

		Uniform(const glsl::Uniform &uniform, const BlockInfo &blockInfo);

		~Uniform();

		bool isArray() const;
		int size() const;
		int registerCount() const;

		const GLenum type;
		const GLenum precision;
		const std::string name;
		const unsigned int arraySize;
		const BlockInfo blockInfo;
		std::vector<glsl::ShaderVariable> fields;

		unsigned char *data = nullptr;
		bool dirty = true;

		short psRegisterIndex = -1;
		short vsRegisterIndex = -1;
	};

	// Helper struct representing a single shader uniform block
	struct UniformBlock
	{
		// use GL_INVALID_INDEX for non-array elements
		UniformBlock(const std::string &name, unsigned int elementIndex, unsigned int dataSize, std::vector<unsigned int> memberUniformIndexes);

		void setRegisterIndex(GLenum shader, unsigned int registerIndex);

		bool isArrayElement() const;
		bool isReferencedByVertexShader() const;
		bool isReferencedByFragmentShader() const;

		const std::string name;
		const unsigned int elementIndex;
		const unsigned int dataSize;

		std::vector<unsigned int> memberUniformIndexes;

		unsigned int psRegisterIndex;
		unsigned int vsRegisterIndex;
	};

	// Struct used for correlating uniforms/elements of uniform arrays to handles
	struct UniformLocation
	{
		UniformLocation(const std::string &name, unsigned int element, unsigned int index);

		std::string name;
		unsigned int element;
		unsigned int index;
	};

	struct LinkedVarying
	{
		LinkedVarying();
		LinkedVarying(const std::string &name, GLenum type, GLsizei size, int reg, int col);

		// Original GL name
		std::string name;

		GLenum type;
		GLsizei size;

		int reg;    // First varying register, assigned during link
		int col;    // First register element, assigned during link
	};

	class Program
	{
	public:
		Program(ResourceManager *manager, GLuint handle);

		~Program();

		bool attachShader(Shader *shader);
		bool detachShader(Shader *shader);
		int getAttachedShadersCount() const;

		sw::PixelShader *getPixelShader();
		sw::VertexShader *getVertexShader();

		void bindAttributeLocation(GLuint index, const char *name);
		GLint getAttributeLocation(const char *name);
		int getAttributeStream(int attributeIndex);

		GLint getSamplerMapping(sw::SamplerType type, unsigned int samplerIndex);
		TextureType getSamplerTextureType(sw::SamplerType type, unsigned int samplerIndex);

		GLuint getUniformIndex(const std::string &name) const;
		GLuint getUniformBlockIndex(const std::string &name) const;
		void bindUniformBlock(GLuint uniformBlockIndex, GLuint uniformBlockBinding);
		GLuint getUniformBlockBinding(GLuint uniformBlockIndex) const;
		void getActiveUniformBlockiv(GLuint uniformBlockIndex, GLenum pname, GLint *params) const;

		GLint getUniformLocation(const std::string &name) const;
		bool setUniform1fv(GLint location, GLsizei count, const GLfloat *v);
		bool setUniform2fv(GLint location, GLsizei count, const GLfloat *v);
		bool setUniform3fv(GLint location, GLsizei count, const GLfloat *v);
		bool setUniform4fv(GLint location, GLsizei count, const GLfloat *v);
		bool setUniformMatrix2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix2x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix2x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix3x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix3x4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix4fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix4x2fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniformMatrix4x3fv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value);
		bool setUniform1iv(GLint location, GLsizei count, const GLint *v);
		bool setUniform2iv(GLint location, GLsizei count, const GLint *v);
		bool setUniform3iv(GLint location, GLsizei count, const GLint *v);
		bool setUniform4iv(GLint location, GLsizei count, const GLint *v);
		bool setUniform1uiv(GLint location, GLsizei count, const GLuint *v);
		bool setUniform2uiv(GLint location, GLsizei count, const GLuint *v);
		bool setUniform3uiv(GLint location, GLsizei count, const GLuint *v);
		bool setUniform4uiv(GLint location, GLsizei count, const GLuint *v);

		bool getUniformfv(GLint location, GLsizei *bufSize, GLfloat *params);
		bool getUniformiv(GLint location, GLsizei *bufSize, GLint *params);
		bool getUniformuiv(GLint location, GLsizei *bufSize, GLuint *params);

		void dirtyAllUniforms();
		void applyUniforms(Device *device);
		void applyUniformBuffers(Device *device, BufferBinding* uniformBuffers);
		void applyTransformFeedback(Device *device, TransformFeedback* transformFeedback);

		void link();
		bool isLinked() const;
		size_t getInfoLogLength() const;
		void getInfoLog(GLsizei bufSize, GLsizei *length, char *infoLog);
		void getAttachedShaders(GLsizei maxCount, GLsizei *count, GLuint *shaders);

		GLint getFragDataLocation(const GLchar *name);

		void getActiveAttribute(GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, GLchar *name) const;
		size_t getActiveAttributeCount() const;
		GLint getActiveAttributeMaxLength() const;

		void getActiveUniform(GLuint index, GLsizei bufsize, GLsizei *length, GLint *size, GLenum *type, GLchar *name) const;
		size_t getActiveUniformCount() const;
		GLint getActiveUniformMaxLength() const;
		GLint getActiveUniformi(GLuint index, GLenum pname) const;

		void getActiveUniformBlockName(GLuint index, GLsizei bufSize, GLsizei *length, GLchar *name) const;
		size_t getActiveUniformBlockCount() const;
		GLint getActiveUniformBlockMaxLength() const;

		void setTransformFeedbackVaryings(GLsizei count, const GLchar *const *varyings, GLenum bufferMode);
		void getTransformFeedbackVarying(GLuint index, GLsizei bufSize, GLsizei *length, GLsizei *size, GLenum *type, GLchar *name) const;
		GLsizei getTransformFeedbackVaryingCount() const;
		GLsizei getTransformFeedbackVaryingMaxLength() const;
		GLenum getTransformFeedbackBufferMode() const;

		void addRef();
		void release();
		unsigned int getRefCount() const;
		void flagForDeletion();
		bool isFlaggedForDeletion() const;

		void validate(Device* device);
		bool validateSamplers(bool logErrors);
		bool isValidated() const;

		unsigned int getSerial() const;

		bool getBinaryRetrievableHint() const { return retrievableBinary; }
		void setBinaryRetrievable(bool retrievable) { retrievableBinary = retrievable; }
		GLint getBinaryLength() const;

	private:
		void unlink();
		void resetUniformBlockBindings();

		bool linkVaryings();
		bool linkTransformFeedback();

		bool linkAttributes();
		bool linkAttribute(const glsl::Attribute &attribute, int location, unsigned int &usedLocations);
		int getAttributeLocation(const std::string &name);

		Uniform *getUniform(const std::string &name) const;
		bool linkUniforms(const Shader *shader);
		bool linkUniformBlocks(const Shader *vertexShader, const Shader *fragmentShader);
		bool areMatchingUniformBlocks(const glsl::UniformBlock &block1, const glsl::UniformBlock &block2, const Shader *shader1, const Shader *shader2);
		bool areMatchingFields(const std::vector<glsl::ShaderVariable>& fields1, const std::vector<glsl::ShaderVariable>& fields2, const std::string& name);
		bool validateUniformStruct(GLenum shader, const glsl::Uniform &newUniformStruct);
		bool defineUniform(GLenum shader, const glsl::Uniform &uniform, const Uniform::BlockInfo& blockInfo);
		bool defineUniformBlock(const Shader *shader, const glsl::UniformBlock &block);
		bool applyUniform(Device *device, GLint location, float* data);
		bool applyUniform1bv(Device *device, GLint location, GLsizei count, const GLboolean *v);
		bool applyUniform2bv(Device *device, GLint location, GLsizei count, const GLboolean *v);
		bool applyUniform3bv(Device *device, GLint location, GLsizei count, const GLboolean *v);
		bool applyUniform4bv(Device *device, GLint location, GLsizei count, const GLboolean *v);
		bool applyUniform1fv(Device *device, GLint location, GLsizei count, const GLfloat *v);
		bool applyUniform2fv(Device *device, GLint location, GLsizei count, const GLfloat *v);
		bool applyUniform3fv(Device *device, GLint location, GLsizei count, const GLfloat *v);
		bool applyUniform4fv(Device *device, GLint location, GLsizei count, const GLfloat *v);
		bool applyUniformMatrix2fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix2x3fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix2x4fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix3fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix3x2fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix3x4fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix4fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix4x2fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniformMatrix4x3fv(Device *device, GLint location, GLsizei count, const GLfloat *value);
		bool applyUniform1iv(Device *device, GLint location, GLsizei count, const GLint *v);
		bool applyUniform2iv(Device *device, GLint location, GLsizei count, const GLint *v);
		bool applyUniform3iv(Device *device, GLint location, GLsizei count, const GLint *v);
		bool applyUniform4iv(Device *device, GLint location, GLsizei count, const GLint *v);
		bool applyUniform1uiv(Device *device, GLint location, GLsizei count, const GLuint *v);
		bool applyUniform2uiv(Device *device, GLint location, GLsizei count, const GLuint *v);
		bool applyUniform3uiv(Device *device, GLint location, GLsizei count, const GLuint *v);
		bool applyUniform4uiv(Device *device, GLint location, GLsizei count, const GLuint *v);

		bool setUniformfv(GLint location, GLsizei count, const GLfloat *v, int numElements);
		bool setUniformMatrixfv(GLint location, GLsizei count, GLboolean transpose, const GLfloat *value, GLenum type);
		bool setUniformiv(GLint location, GLsizei count, const GLint *v, int numElements);
		bool setUniformuiv(GLint location, GLsizei count, const GLuint *v, int numElements);

		void appendToInfoLog(const char *info, ...);
		void resetInfoLog();

		static unsigned int issueSerial();

		FragmentShader *fragmentShader;
		VertexShader *vertexShader;

		sw::PixelShader *pixelBinary;
		sw::VertexShader *vertexBinary;

		std::map<std::string, GLuint> attributeBinding;
		std::map<std::string, GLuint> linkedAttributeLocation;
		std::vector<glsl::Attribute> linkedAttribute;
		int attributeStream[MAX_VERTEX_ATTRIBS];

		GLuint uniformBlockBindings[MAX_UNIFORM_BUFFER_BINDINGS];

		std::vector<std::string> transformFeedbackVaryings;
		GLenum transformFeedbackBufferMode;
		size_t totalLinkedVaryingsComponents;

		struct Sampler
		{
			bool active;
			GLint logicalTextureUnit;
			TextureType textureType;
		};

		Sampler samplersPS[MAX_TEXTURE_IMAGE_UNITS];
		Sampler samplersVS[MAX_VERTEX_TEXTURE_IMAGE_UNITS];

		typedef std::vector<Uniform*> UniformArray;
		UniformArray uniforms;
		typedef std::vector<Uniform> UniformStructArray;
		UniformStructArray uniformStructs;
		typedef std::vector<UniformLocation> UniformIndex;
		UniformIndex uniformIndex;
		typedef std::vector<UniformBlock*> UniformBlockArray;
		UniformBlockArray uniformBlocks;
		typedef std::vector<LinkedVarying> LinkedVaryingArray;
		LinkedVaryingArray transformFeedbackLinkedVaryings;

		bool linked;
		bool orphaned;   // Flag to indicate that the program can be deleted when no longer in use
		char *infoLog;
		bool validated;
		bool retrievableBinary;

		unsigned int referenceCount;
		const unsigned int serial;

		static unsigned int currentSerial;

		ResourceManager *resourceManager;
		const GLuint handle;
	};
}

#endif   // LIBGLESV2_PROGRAM_H_