// Copyright 2018-2023 the openage authors. See copying.md for legal info.

#include "texture_array.h"

#include <epoxy/gl.h>

#include <tuple>

#include "../../datastructure/constexpr_map.h"
#include "../../error/error.h"
#include "../../log/log.h"

#include "../resources/texture_data.h"
#include "lookup.h"
#include "render_target.h"


namespace openage {
namespace renderer {
namespace opengl {

GlTexture2dArray::GlTexture2dArray(const std::shared_ptr<GlContext> &context,
                                   const std::vector<resources::Texture2dData> &data)
	// Call the data-less constructor first.
	:
	GlTexture2dArray(context, data.size(), data[0].get_info()) {
	auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format());
	auto size = this->layer_info.get_size();

	// Fill the array with given data
	size_t i = 0;
	for (auto const &tex : data) {
		glTexSubImage3D(GL_TEXTURE_2D_ARRAY,
		                0, // mipmap number
		                0,
		                0,
		                i, // xoffset, yoffset, zoffset
		                size.first,
		                size.second,
		                1, // width, height, depth
		                std::get<1>(fmt_in_out), // format
		                std::get<2>(fmt_in_out), // type
		                tex.get_data() // data
		);

		i += 1;
	}
}

GlTexture2dArray::GlTexture2dArray(const std::shared_ptr<GlContext> &context,
                                   size_t n_layers,
                                   resources::Texture2dInfo const &layer_info) :
	Texture2dArray(layer_info),
	GlSimpleObject(context,
                   [](GLuint handle) { glDeleteTextures(1, &handle); }),
	n_layers(n_layers) {
	GLuint handle;
	glGenTextures(1, &handle);
	this->handle = handle;

	glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle);

	auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format());
	auto size = this->layer_info.get_size();

	// Create empty image
	glTexImage3D(GL_TEXTURE_2D_ARRAY,
	             0, // mipmap level
	             std::get<0>(fmt_in_out), // gpu texel format
	             size.first, // width
	             size.second, // height
	             n_layers, // depth
	             0, // border
	             std::get<1>(fmt_in_out), // cpu pixel format
	             std::get<2>(fmt_in_out), // cpu pixel type
	             nullptr // data
	);

	// TODO these are outdated, use sampler settings
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
	glTexParameteri(GL_TEXTURE_2D_ARRAY, GL_TEXTURE_MAG_FILTER, GL_LINEAR);

	log::log(MSG(dbg) << "Created an OpenGL texture array.");
}

void GlTexture2dArray::upload(size_t layer, resources::Texture2dData const &data) {
	if (layer >= this->n_layers) {
		throw Error(MSG(err) << "Cannot upload to layer " << layer << " in texture array with "
		                     << this->n_layers << " layers.");
	}

	glBindTexture(GL_TEXTURE_2D_ARRAY, *this->handle);

	auto fmt_in_out = GL_PIXEL_FORMAT.get(this->layer_info.get_format());
	auto size = this->layer_info.get_size();

	glTexSubImage3D(GL_TEXTURE_2D_ARRAY,
	                0, // mipmap number
	                0,
	                0,
	                layer, // xoffset, yoffset, zoffset
	                size.first,
	                size.second,
	                1, // width, height, depth
	                std::get<1>(fmt_in_out), // format
	                std::get<2>(fmt_in_out), // type
	                data.get_data() // data
	);
}

} // namespace opengl
} // namespace renderer
} // namespace openage
