//////////////////////////////////////////////////
//opengl2Dlib.h				1.2
//Copyright(c) 2020 Luta Vlad
//https://github.com/meemknight/gl2d
//
//
//	dependences: glew, glm, stb_image, stb_trueType
//
//
//	features: 
//	
//	draw shapes with rotation color \
//		texture transparency
//	draw text with font and shadows
//	camera
//	shaders
//	setVsync
//	texture atlases and loading textures with \
//		padding to fix visual bugs when using \
//		pixel art sprite atlases
//	draw to screen of frame buffer that	can \
//		be used as a texture
//
//	a particle system that can use a custom \
//	shader and apply a pixelate effect
//
//
//////////////////////////////////////////////////

#pragma once

//enable simd functions
//set GL2D_SIMD to 0 if it doesn't work on your platform
#ifdef _WIN32
#define GL2D_SIMD 1
#else
#define GL2D_SIMD 0
#endif



#include <glad/glad.h>
#include <glm/glm.hpp>
#include <random>
#include <stb_image/stb_image.h>
#include <stb_truetype/stb_truetype.h>

namespace gl2d
{

	//todo proper cmake, rename repo

	void init();

	void defaultErrorFunc(const char *msg);

	using errorFuncType = decltype(defaultErrorFunc);

	errorFuncType *setErrorFuncCallback(errorFuncType *newFunc);

	struct Font;

	//returns false on fail
	bool setVsync(bool b);

	namespace internal
	{
		struct ShaderProgram
		{
			GLuint id;
			int u_sampler;
		};

		float positionToScreenCoordsX(const float position, float w);
		float positionToScreenCoordsY(const float position, float h);

		stbtt_aligned_quad fontGetGlyphQuad(const Font font, const char c);
		glm::vec4 fontGetGlyphTextureCoords(const Font font, const char c);

	}

	///////////////////// COLOR ///////////////////
#pragma region Color

	using Color4f = glm::vec4;
#define Colors_Red (gl2d::Color4f{ 1, 0, 0, 1 })
#define Colors_Green (gl2d::Color4f{ 0, 1, 0, 1 })
#define Colors_Blue (gl2d::Color4f{ 0, 0, 1, 1 })
#define Colors_Black (gl2d::Color4f{ 0, 0, 0, 1 })
#define Colors_White (gl2d::Color4f{ 1, 1, 1, 1 })
#define Colors_Yellow (gl2d::Color4f{ 1, 1, 0, 1 })
#define Colors_Magenta (gl2d::Color4f{ 1, 0, 1, 1 })
#define Colors_Turqoise (gl2d::Color4f{ 0, 1, 1, 1 })
#define Colors_Orange (gl2d::Color4f{ 1, (float)0x7F / 255.0f, 0, 1 })
#define Colors_Purple (gl2d::Color4f{ 101.0f / 255.0f, 29.0f / 255.0f, 173.0f / 255.0f, 1 })
#define Colors_Gray (gl2d::Color4f{ (float)0x7F / 255.0f, (float)0x7F / 255.0f, (float)0x7F / 255.0f, 1 })

#pragma endregion

	///////////////////// MATH ////////////////////
#pragma region math

	using Rect = glm::vec4;
	glm::vec2 rotateAroundPoint(glm::vec2 vec, glm::vec2 point, const float degrees);
	glm::vec2 scaleAroundPoint(glm::vec2 vec, glm::vec2 point, float scale);

#pragma endregion

	///////////////////// Texture /////////////////////
#pragma region Texture

	struct Texture
	{
		GLuint id = 0;

		Texture() {};
		Texture(const char *file) { loadFromFile(file); }

		glm::ivec2 GetSize();

		//Note: This function expects a buffer of bytes in GL_RGBA format
		void createFromBuffer(const char *image_data, const int width, const int height);
		void create1PxSquare(const char *b = 0);
		void createFromFileData(const unsigned char *image_file_data, const size_t image_file_size);
		void createFromFileDataWithPixelPadding(const unsigned char *image_file_data,
			const size_t image_file_size, int blockSize);

		void loadFromFile(const char *fileName);
		void loadFromFileWithPixelPadding(const char *fileName, int blockSize);

		void bind(const unsigned int sample = 0);
		void unbind();

		void cleanup();
	};

	struct TextureRegion
	{
		Texture texture;
		glm::vec4 textureCoords;
	};

#pragma endregion

	///////////////////// Font /////////////////////
#pragma region Font
#define Default_Font_Characters_Range_Begin cast(char, ' ')
#define Default_Font_Characters_Range_End cast(char, '~')
#define Default_Font_Characters_Range_Size cast(isize, Default_Font_Characters_Range_End - Default_Font_Characters_Range_Begin)

	typedef float Font_Size;

	typedef struct Font Font;
	struct Font
	{
		Texture           texture;
		glm::ivec2        size;
		stbtt_packedchar *packedCharsBuffer;
		int               packedCharsBufferSize;
		float             max_height;

		Font() {}
		explicit Font(const char *file) { createFromFile(file); }

		void createFromTTF(const unsigned char *ttf_data, const size_t ttf_data_size);
		void createFromFile(const char *file);
	};

#pragma endregion

	///////////////////// Camera /////////////////////
#pragma region Camera

	struct Camera;
	Camera cameraCreateDefault();

	struct Camera
	{
		glm::vec2  position;
		//glm::vec2  offset;   // Camera offset (displacement from target)
		glm::vec2  target;   // Camera target (rotation and zoom origin)
		float rotation; // Camera rotation in degrees
		float zoom;     // Camera zoom (scaling), should be 1.0f by default

		void setDefault() { *this = cameraCreateDefault(); }
		glm::mat3 getMatrix();

		void follow(glm::vec2 pos, float speed, float max, float w, float h);

		glm::vec2 convertPoint(const glm::vec2 &p, float windowW, float windowH);
	};


#pragma endregion

	///////////////////// Renderer2d /////////////////////
#pragma region Renderer2d

	typedef glm::vec2 Position2D;
	typedef glm::vec4 Texture_Coords;

	struct FrameBuffer
	{
		unsigned int fbo;
		Texture texture;

		void create(unsigned int w, unsigned int h);
		void resize(unsigned int w, unsigned int h);

		//clears resources
		void cleanup();

		//clears colors
		void clear();
	};


#define Renderer2D_Max_Buffer_Capacity 25000
#define DefaultTextureCoords (glm::vec4{ 0, 1, 1, 0 })

	enum Renderer2DBufferType
	{
		quadPositions,
		quadColors,
		texturePositions,

		bufferSize
	};

	typedef struct Renderer2D Renderer2D;
	struct Renderer2D
	{
		Renderer2D() {};

		void create();

		GLuint buffers[Renderer2DBufferType::bufferSize];
		GLuint vao;

		//Note: Just for testing purposes
		//4 elements each component
		glm::vec2 spritePositions[Renderer2D_Max_Buffer_Capacity];
		glm::vec4 spriteColors[Renderer2D_Max_Buffer_Capacity];
		glm::vec2 texturePositions[Renderer2D_Max_Buffer_Capacity];
		Texture   spriteTextures[Renderer2D_Max_Buffer_Capacity];

		int spritePositionsCount = 0;
		int spriteColorsCount = 0;
		int texturePositionsCount = 0;
		int spriteTexturesCount = 0;

		Texture white1pxSquareTexture;

		internal::ShaderProgram currentShader;
		Camera currentCamera;

		//window metrics, should be up to date at all times
		int windowW = 0;
		int windowH = 0;
		void updateWindowMetrics(int w, int h) { windowW = w; windowH = h; }

		//converts pixels to screen (top left) (bottom right)
		glm::vec4 toScreen(const glm::vec4 &transform);

		inline void clearDrawData()
		{
			spritePositionsCount = 0;
			spriteColorsCount = 0;
			spriteTexturesCount = 0;
			texturePositionsCount = 0;
		}

		glm::vec2 getTextSize(const char *text, const Font font, const float size = 1.5f,
			const float spacing = 4, const float line_space = 3);

		// The origin will be the bottom left corner since it represents the line for the text to be drawn
		//Pacing and lineSpace are influenced by size
		void renderText(glm::vec2 position, const char *text, const Font font, const Color4f color, const float size = 1.5f,
			const float spacing = 4, const float line_space = 3, bool showInCenter = 1, const Color4f ShadowColor = { 0.1,0.1,0.1,1 }
		, const Color4f LightColor = {});

		//todo color overloads
		void renderRectangle(const Rect transforms, const Color4f colors[4], const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords);
		inline void renderRectangle(const Rect transforms, const Color4f colors, const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords)
		{
			Color4f c[4] = { colors,colors,colors,colors };
			renderRectangle(transforms, c, origin, rotation, texture, textureCoords);
		}

		void renderRectangleAbsRotation(const Rect transforms, const Color4f colors[4], const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords);
		inline void renderRectangleAbsRotation(const Rect transforms, const Color4f colors, const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords)
		{
			Color4f c[4] = { colors,colors,colors,colors };
			renderRectangleAbsRotation(transforms, c, origin, rotation, texture, textureCoords);
		}

		void renderRectangle(const Rect transforms, const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords);
		void renderRectangleAbsRotation(const Rect transforms, const glm::vec2 origin, const float rotation, const Texture texture, const glm::vec4 textureCoords = DefaultTextureCoords);

		void renderRectangle(const Rect transforms, const Color4f colors[4], const glm::vec2 origin = { 0,0 }, const float rotation = 0);
		inline void renderRectangle(const Rect transforms, const Color4f colors, const glm::vec2 origin = { 0,0 }, const float rotation = 0)
		{
			Color4f c[4] = { colors,colors,colors,colors };
			renderRectangle(transforms, c, origin, rotation);
		}

		void renderRectangleAbsRotation(const Rect transforms, const Color4f colors[4], const glm::vec2 origin = { 0,0 }, const float rotation = 0);
		inline void renderRectangleAbsRotation(const Rect transforms, const Color4f colors, const glm::vec2 origin = { 0,0 }, const float rotation = 0)
		{
			Color4f c[4] = { colors,colors,colors,colors };
			renderRectangleAbsRotation(transforms, c, origin, rotation);
		}

		void render9Patch(const Rect position, const int borderSize, const Color4f color, const glm::vec2 origin, const float rotation, const Texture texture, const Texture_Coords textureCoords, const Texture_Coords inner_texture_coords);
		void render9Patch2(const Rect position, const int borderSize, const Color4f color, const glm::vec2 origin, const float rotation, const Texture texture, const Texture_Coords textureCoords, const Texture_Coords inner_texture_coords);

		void clearScreen(const Color4f color = Colors_Black);

		void setShaderProgram(const internal::ShaderProgram shader);
		void setCamera(const Camera camera);

		void resetCameraAndShader();

		//draws to the screen
		void flush();
		void flushFBO(FrameBuffer frameBuffer);
	};

	//this should be called if the user changes the gl state by hand
	void enableNecessaryGLFeatures();

#pragma endregion

	///////////////////// TextureAtlas /////////////////////
#pragma region TextureAtlas

	glm::vec4 computeTextureAtlas(int xCount, int yCount, int x, int y, bool flip = 0);

	glm::vec4 computeTextureAtlasWithPadding(int mapXsize, int mapYsize, int xCount, int yCount, int x, int y, bool flip = 0);

	struct TextureAtlas
	{
		TextureAtlas() {};
		TextureAtlas(int x, int y):xCount(x), yCount(y) {};

		int xCount;
		int yCount;

		glm::vec4 get(int x, int y, bool flip = 0)
		{
			return computeTextureAtlas(xCount, yCount, x, y, flip);
		}
	};

	struct TextureAtlasPadding
	{
		TextureAtlasPadding() {};
		//count count size size
		TextureAtlasPadding(int x, int y, int xSize, int ySize):xCount(x), yCount(y)
			, xSize(xSize), ySize(ySize)
		{
		};

		int xCount;
		int yCount;
		int xSize;
		int ySize;

		glm::vec4 get(int x, int y, bool flip = 0)
		{
			return computeTextureAtlasWithPadding(xSize, ySize, xCount, yCount, x, y, flip);
		}
	};
	// Get default internal texture (white texture)
#pragma endregion

	///////////////////// ParticleSysyem /////////////////////
#pragma region ParticleSysyem

	struct ParticleApearence
	{
		glm::vec2 size;
		glm::vec4 color1;
		glm::vec4 color2;
	};

	enum TRANZITION_TYPES
	{
		none = 0,
		linear,
		curbe,
		abruptCurbe,
		wave,
		wave2,
		delay,
		delay2
	};


	struct ParticleSettings
	{
		ParticleSettings *deathRattle = nullptr;
		ParticleSettings *subemitParticle = nullptr;

		int onCreateCount = 0;

		glm::vec2 subemitParticleTime = {};

		glm::vec2 positionX = {};
		glm::vec2 positionY = {};

		glm::vec2 particleLifeTime = {}; // move
		glm::vec2 directionX = {};
		glm::vec2 directionY = {};
		glm::vec2 dragX = {};
		glm::vec2 dragY = {};

		glm::vec2 rotation = {};
		glm::vec2 rotationSpeed = {};
		glm::vec2 rotationDrag = {};

		ParticleApearence createApearence = {};
		ParticleApearence createEndApearence = {};

		gl2d::Texture *texturePtr = 0;

		int tranzitionType = TRANZITION_TYPES::linear;
	};


	struct ParticleSystem
	{
		void initParticleSystem(int size);
		void cleanup();

		void emitParticleWave(ParticleSettings *ps, glm::vec2 pos);


		void applyMovement(float deltaTime);

		void draw(Renderer2D &r);

		bool postProcessing = true;
		float pixelateFactor = 2;

	private:

		int size = 0;

		float *posX = 0;
		float *posY = 0;

		float *directionX = 0;
		float *directionY = 0;

		float *rotation = 0;

		float *sizeXY = 0;

		float *dragX = 0;
		float *dragY = 0;

		float *duration = 0;
		float *durationTotal = 0;

		glm::vec4 *color = 0;

		float *rotationSpeed = 0;
		float *rotationDrag = 0;

		float *emitTime = 0;

		char *tranzitionType = 0;
		ParticleSettings **deathRattle = 0;
		ParticleSettings **thisParticleSettings = 0;
		ParticleSettings **emitParticle = 0;

		gl2d::Texture **textures = 0;

		std::mt19937 random{ std::random_device{}() };

		gl2d::FrameBuffer fb;

		float rand(glm::vec2 v);
	};


#pragma endregion




};