#ifndef GLERMINAL_PRIVATE_H
#define GLERMINAL_PRIVATE_H

#include "glerminal.h"
#include "miniaudio.h"

#include <stb_image.h>
#include <glad/glad.h>
#include <GLFW/glfw3.h>

#include <iostream>
#include <map>
#include <string>

namespace glerminal
{
	constexpr unsigned int MAX_SPRITES_ROW = 64;
	constexpr unsigned int MAX_SPRITES = MAX_SPRITES_ROW * MAX_SPRITES_ROW;
	constexpr unsigned int GRID_WIDTH = ::GRID_WIDTH;
	constexpr unsigned int GRID_HEIGHT = ::GRID_HEIGHT;
	constexpr unsigned int LAYER_COUNT = ::LAYER_COUNT;
	constexpr unsigned int CELL_SCALE = ::CELL_SCALE;
	constexpr unsigned int CELL_SIZE = ::CELL_SIZE;
	constexpr unsigned int GRID_AREA = GRID_WIDTH * GRID_HEIGHT;
	constexpr unsigned int SCREEN_WIDTH = GRID_WIDTH * CELL_SIZE * CELL_SCALE;
	constexpr unsigned int SCREEN_HEIGHT = GRID_HEIGHT * CELL_SIZE * CELL_SCALE;
	constexpr unsigned int SOUND_CHANNELS = 8;

	constexpr unsigned int GRID_AREA_2 = (GRID_WIDTH + 2) * (GRID_HEIGHT + 2);

	class glerminal
	{
	public:
		explicit glerminal(glerminal_init_params params);
		~glerminal();

		glerminal(const glerminal&) = delete;
		glerminal(glerminal&&) = delete;

		glerminal& operator=(const glerminal&) = delete;
		glerminal& operator=(glerminal&&) = delete;

		void run();
		void quit();

		void flush();

		void set(int x, int y, int layer, unsigned short sprite);
		unsigned short get(int x, int y, int layer) const;
		void offset(int x, int y, int layer, float x_offset, float y_offset);
		void color(int x, int y, int layer, unsigned int color);
		void scale(int x,int y, int layer, float scale);
		void load_atlas(unsigned char w, unsigned char h, const unsigned int* data);
		bool load_sound(const char* name);
		void play_sound(const char* name);

	private:
		// glfw data

		GLFWwindow* m_window;

		// opengl handles

		unsigned int m_vbo;
		unsigned int m_sprites_instance_vbo;
		unsigned int m_offsets_instance_vbo;
		unsigned int m_vao;
		unsigned int m_screen_vao;
		unsigned int m_program;
		unsigned int m_screen_program;
		unsigned int m_sprites_texture;
		unsigned int m_framebuffer;
		unsigned int m_framebuffer_backing_texture;
		unsigned int m_screen_framebuffer;
		unsigned int m_screen_framebuffer_backing_texture;
		unsigned int m_colors_instance_vbo;
		unsigned int m_scales_instance_vbo;

		// per-cell data

		unsigned short m_cells[GRID_AREA_2 * LAYER_COUNT];
		float m_offsets[GRID_AREA_2 * LAYER_COUNT * 2];
		unsigned char m_colors[GRID_AREA_2 * LAYER_COUNT * 4];
		float m_scales[GRID_AREA_2 * LAYER_COUNT];

		// library state

		unsigned int m_sprites[(CELL_SIZE + 2) * (CELL_SIZE + 2) * MAX_SPRITES];
		glerminal_main_cb m_main;
		glerminal_keys_cb m_keypressed, m_keyreleased;
		glerminal_mousemoved_cb m_mousemoved;
		glerminal_mousepress_cb m_mousepressed, m_mousereleased;

		ma_engine m_audio_engine;
		std::map<std::string, ma_sound> m_sounds;

#ifdef GLERMINAL_OPENGL_DEBUG_CONTEXT
		mutable std::ofstream m_log;
#endif
		void log(GLenum type, GLuint id, GLenum severity, const char* message) const;

		void init_glfw();
		void init_gl();

		void init_audio();

		void deinit_glfw();
		void deinit_gl();

		void deinit_audio();

		void update_sprites();
		void update_colors();
		void update_scales();

		static void glfw_key_handler(GLFWwindow* window, int key, int scancode, int action, int mods);
		static void glfw_mousemoved_handler(GLFWwindow* window, double x, double y);
		static void glfw_mousepress_handler(GLFWwindow* window, int button, int action, int mods);
	};
}

#endif//GLERMINAL_PRIVATE_H
