#pragma once

class Buffer;
class ShaderProgram;
class Shader;


using namespace glm;

enum class TextAnchorr : uint8_t {
	CENTER, TOP, TOP_RIGHT, TOP_LEFT, BOT,
	BOT_RIGHT, BOT_LEFT, RIGHT, LEFT
};

class Font;
struct Trans {
	mat4 mat;
	bool is_identity = true;
	uint64_t local_id;
	static uint64_t global_id;
	Trans();

	Trans rot(vec3 axis, float angle);
	Trans scale(vec3 scale);
	Trans trans(vec3 trans);
	Trans apply_other_trans(Trans other_trans);
    Trans skew(uint pushAxis, uint byAxis, float amount);
};



struct PointDescr {
	vec3 pos = vec3(0);
	float sz = 0.02;
	vec4 col = vec4(0,0,0,1);
	bool const_size = true;
	Trans trans = Trans();
};

struct QuadDescr {
	float rotation;
	vec2 sz;
	vec3 pos = vec3(0);
	vec3 posb = vec3(0);
	vec3 color = vec3(0);
};

struct TriDescr {
	std::array<vec3,3> positions;
	std::array<vec4,3> colours;
	std::array<vec2,3> uvs = {vec2(-9999), vec2(-9999), vec2(-9999)};
	Trans trans = Trans();
};

struct CurvePt {
	vec3 pos;
	float sz;
	vec4 col = vec4(1,1, 1,1);
};

struct LineDescr {
	std::array<vec3,2> positions;
	float width;
	std::array<vec4,2> cols;
	bool const_size = true;
	Trans trans = Trans();
};

struct RectangDescr {
	vec3 pos;
	vec2 sz;
	vec4 col;
	bool const_size = true;
	Trans trans = Trans();
};

// enum class TextAnchor {
// 	CENTER , TOP , TOP_RIGHT , TOP_LEFT , BOT , BOT_RIGHT , BOT_LEFT , RIGHT , LEFT
// };

struct TextDescr{
	vec3 pos = vec3(0);
	vec4 col = vec4(1.0);
	float sz = 0.194;
	float kern_scale = 0.722;
	float whitespace_scale = 2.667;
	bool do_kerning = true;
	bool outline = true;
	vec4 bg_col = vec4(0);
	TextAnchorr anchor = TextAnchorr::CENTER;
	Trans trans = Trans();
	bool centered = false;
	bool const_size = true;
	SIMDString<512> text ;
};

struct Shape {
	bool dirty = true;
	
	using Point = std::array<float, 3>;
	
	int shape_idx = 0;
	
	std::vector<vec3>* vertices;
	std::vector<std::vector<Point>> mapbox_polygon;
	std::vector<vec3> triangulated_vertices;
	
	Shape();

	void push_vert(vec3 pos);

	void triangulate();

	void reset();
	
	void start_next_shape();
};

struct ShapeDescr {
	Shape* shape;
	vec4 col = vec4(1,1,1,1);
	Trans trans = Trans();
	vec3 proj_on_axis;
};



struct Drawer {
	int verts_drawn_this_frame_count = 0;
	int drawcalls_this_frame_count = 0;
	struct TransformsContainer {
		Buffer* buff_transforms;
		int idx_buff_transforms = 0;
		int prev_trans_id = -1;
		int curr_trans_buff_idx = -1;
		TransformsContainer( );

		void try_push_transform(Trans trans);

		void flush_transforms();

		// int get_transforms_buff_idx(Trans trans){
		// 	return trans.is_identity ? -1 : this->idx_buff_transforms - 1;
		// }
		
		void reset();
	};
	struct TriangleDrawer {
		Buffer* buff_verts;
		
		float* buff_ptr;
		float* prev_buff_ptr;

		TransformsContainer* transforms_container;
		ShaderProgram* shader_prog;
		int vert_cnt = 0;
		int vert_cnt_accum = 0;
		
		const int floats_per_vert = 12;
		const int bytes_per_vert = floats_per_vert * sizeof(float);
		
		TriangleDrawer(TransformsContainer* _transforms_container);

		void push_vert(vec3 pos, vec4 colour, vec2 uv, Trans trans);

		void draw_triangle(TriDescr tri_descr);

		void draw_triangles(TriDescr* tri_descr, Trans trans, int tri_cnt);

		bool flush();

		void reset();
	};
	struct LineDrawer {
		Buffer* buff_verts;
		int vert_cnt = 0;
		int vert_idx_curr_polyline_start = 0;
		
		const int floats_per_vert = 16;
		const int bytes_per_vert = floats_per_vert * sizeof(float);

		std::vector<std::array<int, 2>> polyline_start_and_ends;
		TransformsContainer* transforms_container;
		ShaderProgram* shader_prog;

		void push_vert(
			vec3 pos, vec4 col, float sz, bool constant_width, vec3 norm,
			Trans trans
		);

		LineDrawer(TransformsContainer* _transforms_container);

		void draw_line(LineDescr line_descr);

		bool flush();

		void reset();
	};
	
	struct PolyLineDrawer {
		Buffer* buff_verts;
		int vert_cnt = 0;
		int vert_idx_curr_polyline_start = 0;
		
		const int floats_per_vert = 16;
		const int bytes_per_vert = floats_per_vert * sizeof(float);

		std::vector<std::array<int, 2>> polyline_start_and_ends;
		TransformsContainer* transforms_container;
		ShaderProgram* shader_prog;
		
		PolyLineDrawer(TransformsContainer* _transforms_container);

		void push_vert(CurvePt pt_descr);

		void end_polyline(TransformsContainer* transforms_container, Trans trans = Trans());

		bool flush();

		void reset();
	};
	
	struct TextDrawer3D {
		Font* font = nullptr;
		Buffer* text_buff;
		float* buff_ptr;

		int char_cnt = 0;
		int char_cnt_accum = 0;
		
		ShaderProgram* shader_prog;
		TransformsContainer* transforms_container;
		
		static constexpr int verts_per_char = 6;
		static constexpr int floats_per_vert = 20;
		static constexpr int floats_per_char = verts_per_char * floats_per_vert;
		static constexpr int bytes_per_vert = floats_per_vert * 4;

		TextDrawer3D (TransformsContainer* transforms_container);

		inline void update_verts() { }
		
		void set_font(Font* _font);

		bool flush();
		void reset_end_of_frame();

		void draw(TextDescr& desc);
	};
	
	struct PointDrawer {
		Buffer* buff;
		float* buff_ptr;
		float* prev_buff_ptr;

		ShaderProgram* shader_prog;
		int vert_cnt = 0;
		int vert_cnt_accum = 0;
		
		const int floats_per_vert = 12;
		const int bytes_per_vert = floats_per_vert * sizeof(float);

		TransformsContainer* transforms_container;
		
		PointDrawer(TransformsContainer* _transforms_container);

		void push_vert(vec3 pos, vec4 colour, float sz, bool is_constant_size, Trans trans);

		void draw_point(PointDescr point_descr);

		bool flush();

		void reset();
	};
		
	enum DrawType {
		POINT, LINE, RECT, TRIANGLE, SHAPE, POLYLINE
	};
	DrawType last_draw_type;
	
	TextDrawer3D* text_drawer;
	TriangleDrawer* tri_drawer;
	PointDrawer* point_drawer;
	LineDrawer* line_drawer;
	PolyLineDrawer* poly_line_drawer;
	TransformsContainer* transforms_container;

	std::vector<CurvePt> current_curve_pts;

	Drawer();

	void reset();

	void flush();
	void flush_tris();
	void flush_points();
	void flush_lines();
	void flush_curves();
	void flush_text();

	
	void push_curve_pt(CurvePt curve_pt);
	void end_curve(Trans trans = Trans());

	void draw_text(TextDescr text_descr);
	void draw_point( PointDescr point_descr );
	void draw_line( LineDescr line_descr );
	void draw_rect(RectangDescr rect_descr);
	void draw_tri( TriDescr tri_descr );
	void draw_shape(ShapeDescr shape_descr);
	
	void set_font(Font* _font);
};





// struct Triangulator {
// 	using Point = std::array<float, 2>;
// 	std::vector<std::vector<Point>> mapbox_polygon;
// 	std::vector<vec3> triangulated_mesh;
// 	
// 	Triangulator() {
// 		mapbox_polygon.push_back(std::vector<Point>());
// 	}
// 	
// 	void push_vert( vec2 pos ) {
// 		mapbox_polygon[0].push_back({float(pos.x), float(pos.y)});
// 	}
// 	
// 	std::vector<vec3>* triangulate() {
// 		this->triangulated_mesh.resize(0);
// 		std::vector<uint32_t> indices = mapbox::earcut<uint32_t>(mapbox_polygon);
//
// 		for(int i = 0; i < indices.size()/3; i++) {
// 			int idx_a = indices[i*3];
// 			int idx_b = indices[i*3 + 1];
// 			int idx_c = indices[i*3 + 2];
// 			
// 			vec3 pos_a = vec3(mapbox_polygon[0][idx_a][0], mapbox_polygon[0][idx_a][1], 0);
// 			vec3 pos_b = vec3(mapbox_polygon[0][idx_b][0], mapbox_polygon[0][idx_b][1], 0);
// 			vec3 pos_c = vec3(mapbox_polygon[0][idx_c][0], mapbox_polygon[0][idx_c][1], 0);
//
// 			this->triangulated_mesh.push_back(pos_a);
// 			this->triangulated_mesh.push_back(pos_b);
// 			this->triangulated_mesh.push_back(pos_c);
// 		}
// 		this->reset();
// 		return &this->triangulated_mesh;
// 	}
// 	void reset() {
// 		mapbox_polygon[0].resize(0);
// 	}
// };




// ConstCol = GL_CONSTANT_COLOR,
// OneMinusConstCol = GL_ONE_MINUS_CONSTANT_COLOR,
// Color::
















