#ifndef NOO_IMAGE_H
#define NOO_IMAGE_H

#include "shim5/main.h"

namespace noo {

namespace gfx {

class Shader;

typedef unsigned char *(*image_loader)(std::string filename, util::Size<int> &out_size, SDL_Color *out_palette, util::Point<int> *opaque_topleft, util::Point<int> *opaque_bottomright, bool *has_alpha, bool load_from_filesystem);
typedef bool (*image_saver)(std::string filename, unsigned char *loaded_data, util::Size<int> size, bool _save_rgba);

struct TGA_Header {
	char idlength;
	char colourmaptype;
	char datatypecode;
	Uint16 colourmaporigin;
	Uint16 colourmaplength;
	char colourmapdepth;
	Uint16 x_origin;
	Uint16 y_origin;
	Uint16 width;
	Uint16 height;
	char bitsperpixel;
	char imagedescriptor;
	SDL_Color palette[256];
};

class Image {
public:
	friend class Shader;
	friend class Vertex_Cache;

	enum Flags {
		FLIP_H = 1,
		FLIP_V = 2
	};

	static void static_start();
	static void release_all(bool include_managed = false);
	static void reload_all(bool include_managed = false);
	static int get_unfreed_count();
	static void audit();
	SHIM5_EXPORT static unsigned char *read_backbuffer(bool include_black_bars = true, int *out_w = nullptr, int *out_h = nullptr);
	SHIM5_EXPORT static unsigned char *read_texture(gfx::Image *image);
	static unsigned char find_colour_in_palette(unsigned char *p);

	static unsigned char *read_png(std::string filename, util::Size<int> &out_size, SDL_Color *out_palette, util::Point<int> *opaque_topleft, util::Point<int> *opaque_bottomright, bool *has_alpha, bool load_from_filesystem);
	static unsigned char *read_tga(std::string filename, util::Size<int> &out_size, SDL_Color *out_palette, util::Point<int> *opaque_topleft, util::Point<int> *opaque_bottomright, bool *has_alpha, bool load_from_filesystem);
	static bool save_png(std::string filename, unsigned char *data, util::Size<int> size, bool _save_rgba);
	static bool save_tga(std::string filename, unsigned char *loaded_data, util::Size<int> size, bool _save_rgba);

	SHIM5_EXPORT static unsigned char *load_image(std::string filename, util::Size<int> &out_size, SDL_Color *out_palette = 0, util::Point<int> *opaque_topleft = 0, util::Point<int> *opaque_bottomright = 0, bool *has_alpha = 0, bool load_from_filesystem = false);
	SHIM5_EXPORT static bool save_image(std::string filename, unsigned char *loaded_data, util::Size<int> size, bool _save_rgba = true);

	SHIM5_EXPORT static void register_image_loader(std::string ext, image_loader func);
	SHIM5_EXPORT static void register_image_saver(std::string ext, image_saver func);

	// These parameters affect newly created images
	SHIM5_EXPORT static bool dumping_colours;
	SHIM5_EXPORT static bool keep_data;
	SHIM5_EXPORT static bool save_rle;
	SHIM5_EXPORT static bool ignore_palette;
	SHIM5_EXPORT static bool create_depth_buffer;
	SHIM5_EXPORT static bool create_stencil_buffer;
	SHIM5_EXPORT static bool premultiply_alpha;
	SHIM5_EXPORT static bool save_rgba;
	SHIM5_EXPORT static bool save_palettes;

	std::string filename;
	util::Size<int> size;

	SHIM5_EXPORT Image(std::string filename, bool is_absolute_path = false, bool load_from_filesystem = false);
	SHIM5_EXPORT Image(Uint8 *data, util::Size<int> size, bool destroy_data = false);
	SHIM5_EXPORT Image(SDL_Surface *surface);
	SHIM5_EXPORT Image(util::Size<int> size);
	SHIM5_EXPORT Image(util::Size<int> size, unsigned char *pixels);
	SHIM5_EXPORT Image(Image *parent, util::Point<int> offset, util::Size<int> size);
	SHIM5_EXPORT virtual ~Image();

	SHIM5_EXPORT void release();
	SHIM5_EXPORT void reload(bool load_from_filesystem = false);

	SHIM5_EXPORT bool save(std::string filename);

	// these are not normally to be used, use gfx::set_target_image and gfx::set_target_backbuffer instead
	SHIM5_EXPORT void set_target();
	SHIM5_EXPORT void release_target();

	SHIM5_EXPORT void get_bounds(util::Point<int> &topleft, util::Point<int> &bottomright);
	SHIM5_EXPORT void set_bounds(util::Point<int> topleft, util::Point<int> bottomright);

	SHIM5_EXPORT void destroy_data();

	SHIM5_EXPORT Image *get_root();

	SHIM5_EXPORT unsigned char *get_loaded_data();

	SHIM5_EXPORT void start_batch(bool repeat = false);
	SHIM5_EXPORT void end_batch();

	SHIM5_EXPORT void stretch_region_tinted_repeat(SDL_Color tint, util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, util::Size<int> dest_size, int flags = 0);
	SHIM5_EXPORT void stretch_region_tinted(SDL_Color tint, util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, util::Size<int> dest_size, int flags = 0);
	SHIM5_EXPORT void stretch_region(util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, util::Size<int> dest_size, int flags = 0);
	SHIM5_EXPORT void draw_region_lit_z_range(SDL_Color colours[4], util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z_top, float z_bottom, int flags = 0);
	SHIM5_EXPORT void draw_region_lit_z(SDL_Color colours[4], util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z, int flags = 0);
	SHIM5_EXPORT void draw_region_tinted_z_range(SDL_Color tint, util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z_top, float z_bottom, int flags = 0);
	SHIM5_EXPORT void draw_region_tinted_z(SDL_Color tint, util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z, int flags = 0);
	SHIM5_EXPORT void draw_region_tinted(SDL_Color tint, util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, int flags = 0);
	SHIM5_EXPORT void draw_region_z_range(util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z_top, float z_bottom, int flags = 0);
	SHIM5_EXPORT void draw_region_z(util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, float z, int flags = 0);
	SHIM5_EXPORT void draw_region(util::Point<float> source_position, util::Size<int> source_size, util::Point<float> dest_position, int flags = 0);
	SHIM5_EXPORT void draw_z(util::Point<float> dest_position, float z, int flags = 0);
	SHIM5_EXPORT void draw_tinted(SDL_Color tint, util::Point<float> dest_position, int flags = 0);
	SHIM5_EXPORT void draw(util::Point<float> dest_position, int flags = 0);
	SHIM5_EXPORT void draw_tinted_rotated(SDL_Color tint, util::Point<float> centre, util::Point<float> dest_position, float angle, int flags = 0);
	SHIM5_EXPORT void draw_tinted_rotated_scaledxy_z(SDL_Color tint, util::Point<float> centre, util::Point<float> dest_position, float angle, float scale_x, float scale_y, float z, int flags = 0);
	SHIM5_EXPORT void draw_tinted_rotated_scaled_z(SDL_Color tint, util::Point<float> centre, util::Point<float> dest_position, float angle, float scale, float z, int flags = 0);
	SHIM5_EXPORT void draw_rotated_scaled_z(util::Point<float> centre, util::Point<float> dest_position, float angle, float scale, float z, int flags = 0);
	SHIM5_EXPORT void draw_tinted_rotated_scaled(SDL_Color tint, util::Point<float> centre, util::Point<float> dest_position, float angle, float scale, int flags = 0);
	SHIM5_EXPORT void draw_tinted_rotated_scaledxy(SDL_Color tint, util::Point<float> centre, util::Point<float> dest_position, float angle, float scale_x, float scale_y, int flags = 0);
	SHIM5_EXPORT void draw_rotated(util::Point<float> centre, util::Point<float> dest_position, float angle, int flags = 0);
	SHIM5_EXPORT void draw_rotated_scaled(util::Point<float> centre, util::Point<float> dest_position, float angle, float scale, int flags = 0);

	SHIM5_EXPORT GLuint get_opengl_texture();

	SHIM5_EXPORT bool is_sub_image();
	SHIM5_EXPORT util::Point<int> get_offset();

	SHIM5_EXPORT void update(unsigned char *pixels);

protected:
	static bool merge_bytes(unsigned char *pixel, unsigned char *p, int bytes, TGA_Header *header, bool *alpha);

	struct SHIM5_EXPORT Internal {
		Internal(std::string filename, bool keep_data, bool support_render_to_texture = false, bool load_from_filesystem = false);
		Internal(unsigned char *pixels, util::Size<int> size, bool support_render_to_texture = false);
		Internal();
		~Internal();

		void upload(unsigned char *pixels);

		void release();
		unsigned char *reload(bool keep_data, bool load_from_filesystem = false);
		void unbind();

		void destroy_data();
		bool is_transparent(util::Point<int> position);

		unsigned char *loaded_data;

		std::string filename;
		util::Size<int> size;
		int refcount;

		bool has_render_to_texture;

		GLuint texture;
		GLuint fbo;
		GLuint depth_buffer;
		GLuint stencil_buffer;

		// For sub-images
		Image *parent;
		util::Point<int> offset;

		// Position of outmost opaque pixels
		util::Point<int> opaque_topleft;
		util::Point<int> opaque_bottomright;

		bool create_depth_buffer;
		bool create_stencil_buffer;

		bool has_alpha;
	};

	static std::vector<Internal *> loaded_images;

	bool batching;

	Internal *internal;

	bool flipped;

	Uint8 *data_to_destroy;
};

} // End namespace gfx

} // End namespace noo

#endif // NOO_IMAGE_H
