/*************************************************************************/
/*  texture_layer_merger.cpp                                             */
/*************************************************************************/
/*                         This file is part of:                         */
/*                          PANDEMONIUM ENGINE                           */
/*             https://github.com/Relintai/pandemonium_engine            */
/*************************************************************************/
/* Copyright (c) 2022-present Péter Magyar.                              */
/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */
/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */
/*                                                                       */
/* Permission is hereby granted, free of charge, to any person obtaining */
/* a copy of this software and associated documentation files (the       */
/* "Software"), to deal in the Software without restriction, including   */
/* without limitation the rights to use, copy, modify, merge, publish,   */
/* distribute, sublicense, and/or sell copies of the Software, and to    */
/* permit persons to whom the Software is furnished to do so, subject to */
/* the following conditions:                                             */
/*                                                                       */
/* The above copyright notice and this permission notice shall be        */
/* included in all copies or substantial portions of the Software.       */
/*                                                                       */
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
/*************************************************************************/

#include "texture_layer_merger.h"

int TextureLayerMerger::get_width() const {
	return _width;
}

void TextureLayerMerger::set_width(const int p_value) {
	_width = p_value;
}

int TextureLayerMerger::get_height() const {
	return _height;
}

void TextureLayerMerger::set_height(const int p_value) {
	_height = p_value;
}

uint32_t TextureLayerMerger::get_texture_flags() const {
	return _texture_flags;
}
void TextureLayerMerger::set_texture_flags(uint32_t p_flags) {
	_texture_flags = p_flags;
}

Color TextureLayerMerger::get_base_color() const {
	return _base_color;
}
void TextureLayerMerger::set_base_color(const Color &p_color) {
	_base_color = p_color;
}

Ref<Image> TextureLayerMerger::get_data() const {
	return _image;
}

void TextureLayerMerger::set_data(const Ref<Image> &p_image) {
	ERR_FAIL_COND(p_image.is_null());

	_image = p_image;
}

Ref<ImageTexture> TextureLayerMerger::get_result_as_texture() const {
	ERR_FAIL_COND_V(!_image.is_valid(), Ref<ImageTexture>());

	Ref<ImageTexture> tex;
	tex.instance();
	tex->create_from_image(_image);

	return tex;
}

void TextureLayerMerger::add_texture(const Ref<Texture> &p_texture, const Color &p_color, const Vector2 &p_position, Rect2 p_rect) {
	ERR_FAIL_COND(!p_texture.is_valid());

	LayerMergerEntry entry;

	entry.texture = p_texture;
	entry.color = p_color;
	entry.position = p_position;

	if (p_rect.size.x <= 0)
		p_rect.size.x = p_texture->get_width();

	if (p_rect.size.y <= 0)
		p_rect.size.y = p_texture->get_height();

	entry.rect = p_rect;

	if (_width == 0 || _height == 0) {
		Ref<AtlasTexture> at = p_texture;

		int w = 0;
		int h = 0;

		if (at.is_valid()) {
			w = at->get_region().size.x;
			h = at->get_region().size.y;
		} else {
			w = p_texture->get_width();
			h = p_texture->get_height();
		}

		if (_width == 0)
			_width = w;

		if (_height == 0)
			_height = h;
	}

	_entries.push_back(entry);
}

Ref<Texture> TextureLayerMerger::get_texture(const int p_index) {
	ERR_FAIL_INDEX_V(p_index, _entries.size(), Ref<Texture>());

	return _entries.get(p_index).texture;
}
void TextureLayerMerger::set_texture(const int p_index, const Ref<Texture> &p_texture) {
	ERR_FAIL_INDEX(p_index, _entries.size());

	_entries.get(p_index).texture = p_texture;
}

Color TextureLayerMerger::get_color(const int p_index) {
	ERR_FAIL_INDEX_V(p_index, _entries.size(), Color());

	return _entries.get(p_index).color;
}
void TextureLayerMerger::set_color(const int p_index, const Color &p_color) {
	ERR_FAIL_INDEX(p_index, _entries.size());

	_entries.get(p_index).color = p_color;
}

Vector2 TextureLayerMerger::get_position(const int p_index) {
	ERR_FAIL_INDEX_V(p_index, _entries.size(), Vector2());

	return _entries.get(p_index).position;
}
void TextureLayerMerger::set_position(const int p_index, const Vector2 &p_position) {
	ERR_FAIL_INDEX(p_index, _entries.size());

	_entries.get(p_index).position = p_position;
}

Rect2 TextureLayerMerger::get_rect(const int p_index) {
	ERR_FAIL_INDEX_V(p_index, _entries.size(), Rect2());

	return _entries.get(p_index).rect;
}
void TextureLayerMerger::set_rect(const int p_index, const Rect2 &p_rect) {
	ERR_FAIL_INDEX(p_index, _entries.size());

	_entries.get(p_index).rect = p_rect;
}

void TextureLayerMerger::remove_texture(const int p_index) {
	ERR_FAIL_INDEX(p_index, _entries.size());

	return _entries.remove(p_index);
}

int TextureLayerMerger::get_texture_count() {
	return _entries.size();
}

void TextureLayerMerger::clear() {
	_entries.clear();
}

void TextureLayerMerger::merge() {
	ERR_FAIL_COND(_width <= 0 || _height <= 0);

	if (!_image.is_valid()) {
		_image.instance();
	}

	PoolVector<uint8_t> data;
	data.resize(_width * _height * 4);

	write_base_color_to_array(data);

	for (int i = 0; i < _entries.size(); ++i) {
		const LayerMergerEntry &e = _entries[i];

		ERR_CONTINUE(!e.texture.is_valid());

		int rx = e.rect.position.x + 0.1;
		int ry = e.rect.position.y + 0.1;
		int rw = e.rect.size.x + 0.1;
		int rh = e.rect.size.y + 0.1;

		int posx = e.position.x + 0.1;
		int posy = e.position.y + 0.1;

		int atlas_x = 0;
		int atlas_y = 0;

		if (posx > _width || posy > _height)
			continue;

		Ref<AtlasTexture> altas_texture = e.texture;
		Ref<Image> input_image;

		if (altas_texture.is_valid()) {
			Ref<Texture> atlas = altas_texture->get_atlas();

			ERR_CONTINUE(!atlas.is_valid());

			input_image = atlas->get_data();

			Rect2 region = altas_texture->get_region();

			atlas_x = region.position.x + 0.1;
			atlas_y = region.position.y + 0.1;

			int atlas_w = region.size.x + 0.1;
			int atlas_h = region.size.y + 0.1;

			if (rw > atlas_w)
				rw = atlas_w;

			if (rh > atlas_h)
				rh = atlas_h;
		} else {
			input_image = e.texture->get_data();
		}

		ERR_CONTINUE(!input_image.is_valid());

		int iiw = input_image->get_width();
		int iih = input_image->get_height();
		PoolVector<uint8_t> input_image_data = input_image->get_data();

		const Color &blendcolor = e.color;

		float blend_arr[] = {
			blendcolor.r,
			blendcolor.g,
			blendcolor.b,
			blendcolor.a
		};

		ERR_CONTINUE(iiw == 0 || iih == 0);
		ERR_CONTINUE(rx > iiw || ry > iih);

		//clamp width, and height if bigger (for ease of use)
		if (atlas_x + rx + rw >= iiw)
			rw -= (atlas_x + rx + rw) - iiw;

		if (atlas_y + ry + rh >= iih)
			rh -= (atlas_y + ry + rh) - iih;

		//Let's take position into account
		if (rx + rw + posx >= _width)
			rw -= (rx + rw + posx) - _width;

		if (ry + rh + posy >= _height)
			rh -= (ry + rh + posy) - _height;

		if (rw <= 0 || rh <= 0)
			continue;

		int elen = 0;

		if (input_image->get_format() == Image::FORMAT_RGBA8) {
			elen = 4;
		} else if (input_image->get_format() == Image::FORMAT_RGB8) {
			elen = 3;
		}

		ERR_CONTINUE_MSG(elen == 0, "Unsupported image format! Format: " + String::num(input_image->get_format()));

		for (int y = 0; y < rh; ++y) {
			int img_gen_index = (posx + ((y + posy) * _width)) * 4;
			int img_input_index = (rx + atlas_x + ((y + atlas_y + ry) * iiw)) * elen;

			for (int x = 0; x < rw; ++x) {
				float blend_alpha = blendcolor.a;

				if (elen == 4) {
					float orig_alpha = input_image_data.get(img_input_index + 3) / 255.0;

					blend_alpha -= 1.0 - orig_alpha;

					if (blend_alpha < 0)
						blend_alpha = 0;

					blend_alpha = orig_alpha;
				}

				for (int sp = 0; sp < elen; ++sp) {
					int main_index = img_gen_index + sp;

					int main_val = data.get(main_index);
					int input_val = input_image_data.get(img_input_index + sp);

					main_val = (input_val * blend_arr[sp]) * blend_alpha + (main_val * (1 - blend_alpha));

					if (main_val > 255)
						main_val = 255;

					data.set(main_index, main_val);
				}

				img_gen_index += 4;
				img_input_index += elen;
			}
		}
	}

	_image->create(_width, _height, (_texture_flags & Texture::FLAG_MIPMAPS) != 0, Image::FORMAT_RGBA8, data);
}

void TextureLayerMerger::write_base_color_to_array(PoolVector<uint8_t> &data) {
	int cr = _base_color.r * 255;
	int cg = _base_color.g * 255;
	int cb = _base_color.b * 255;
	int ca = _base_color.a * 255;

	for (int i = 0; i < data.size(); i += 4) {
		data.set(i, cr);
		data.set(i + 1, cg);
		data.set(i + 2, cb);
		data.set(i + 3, ca);
	}
}

void TextureLayerMerger::_bind_methods() {
	ClassDB::bind_method(D_METHOD("get_width"), &TextureLayerMerger::get_width);
	ClassDB::bind_method(D_METHOD("set_width", "image"), &TextureLayerMerger::set_width);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "width"), "set_width", "get_width");

	ClassDB::bind_method(D_METHOD("get_height"), &TextureLayerMerger::get_height);
	ClassDB::bind_method(D_METHOD("set_height", "image"), &TextureLayerMerger::set_height);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "height"), "set_height", "get_height");

	ClassDB::bind_method(D_METHOD("get_texture_flags"), &TextureLayerMerger::get_texture_flags);
	ClassDB::bind_method(D_METHOD("set_texture_flags", "image"), &TextureLayerMerger::set_texture_flags);
	ADD_PROPERTY(PropertyInfo(Variant::INT, "texture_flags", PROPERTY_HINT_FLAGS, "Mipmaps,Repeat,Filter"), "set_texture_flags", "get_texture_flags");

	ClassDB::bind_method(D_METHOD("get_base_color"), &TextureLayerMerger::get_base_color);
	ClassDB::bind_method(D_METHOD("set_base_color", "color"), &TextureLayerMerger::set_base_color);
	ADD_PROPERTY(PropertyInfo(Variant::COLOR, "base_color"), "set_base_color", "get_base_color");

	ClassDB::bind_method(D_METHOD("get_data"), &TextureLayerMerger::get_data);
	ClassDB::bind_method(D_METHOD("set_data", "image"), &TextureLayerMerger::set_data);
	ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "data", PROPERTY_HINT_RESOURCE_TYPE, "Image"), "set_data", "get_data");

	ClassDB::bind_method(D_METHOD("get_result_as_texture"), &TextureLayerMerger::get_result_as_texture);

	ClassDB::bind_method(D_METHOD("add_texture", "texture", "color", "position", "rect"), &TextureLayerMerger::add_texture, DEFVAL(Color(1, 1, 1, 1)), DEFVAL(Vector2()), DEFVAL(Rect2()));

	ClassDB::bind_method(D_METHOD("get_texture", "index"), &TextureLayerMerger::get_texture);
	ClassDB::bind_method(D_METHOD("set_texture", "index", "texture"), &TextureLayerMerger::set_texture);

	ClassDB::bind_method(D_METHOD("get_color", "index"), &TextureLayerMerger::get_color);
	ClassDB::bind_method(D_METHOD("set_color", "index", "color"), &TextureLayerMerger::set_color);

	ClassDB::bind_method(D_METHOD("get_position", "index"), &TextureLayerMerger::get_position);
	ClassDB::bind_method(D_METHOD("set_position", "index", "position"), &TextureLayerMerger::set_position);

	ClassDB::bind_method(D_METHOD("get_rect", "index"), &TextureLayerMerger::get_rect);
	ClassDB::bind_method(D_METHOD("set_rect", "index", "rect"), &TextureLayerMerger::set_rect);

	ClassDB::bind_method(D_METHOD("remove_texture", "index"), &TextureLayerMerger::remove_texture);
	ClassDB::bind_method(D_METHOD("get_texture_count"), &TextureLayerMerger::get_texture_count);
	ClassDB::bind_method(D_METHOD("clear"), &TextureLayerMerger::clear);

	ClassDB::bind_method(D_METHOD("merge"), &TextureLayerMerger::merge);
}

TextureLayerMerger::TextureLayerMerger() {
	_width = 0;
	_height = 0;

	_texture_flags = 0;
}

TextureLayerMerger::~TextureLayerMerger() {
	_image.unref();
	_entries.clear();
}