/*
    QGL TextureManager
    Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

#include "gltexture_manager.h"

#include "gltexturearray.h"
#include "gltypes.h"

QStringList TextureManager::search_pathes(".");


QVector3D colorVector(QRgb c) {
	return QVector3D(((uchar *)(&c))[0] / 255.f, ((uchar *)(&c))[1] / 255.f, ((uchar *)(&c))[2] / 255.f);
}


void TextureManager::addSearchPath(const QString & path) {
	if (!search_pathes.contains(path)) search_pathes << path;
}


QString TextureManager::findFile(const QString & path) {
	return ::findFile(path, search_pathes);
}

/*
GLuint TextureManager::loadTexture(const QString & path, bool is_normal, MapBakeOptions opts) {
	QString p = findFile(path);
	if (p.isEmpty()) return 0;
	uint hash = mapHash(p, is_normal, opts);
	int tid = cache_loaded.value(hash, 0);
	if (tid > 0) {
		qDebug() << "[TextureManager] Found" << path << "as" << tid;
		return tid;
	}
	QImage image(p);
	if (is_normal) convertToNormal(image);
	applyBakeOption(image, opts);
	// qDebug() << p << image.width() << image.height() << image.format() << is_normal;
	GLuint tid_ = tid;
	createGLTexture(f, tid_, image);
	tid = tid_;
	if (tid == 0) {
		qDebug() << "[TextureManager] Can`t load" << p;
		return tid;
	}
	qDebug() << "[TextureManager] Loaded" << p << "as" << tid;
	tex_ids[is_normal ? 1 : 0].insert(p, tid);
	tex_im[is_normal ? 1 : 0].insert(p, image);
	return tid;
}
*/

QImage TextureManager::loadTextureImage(const QString & path, bool is_normal, MapBakeOptions opts, uint * result_hash) {
	if (result_hash) *result_hash = 0;
	if (path.isEmpty()) return QImage();
	QString p = findFile(path);
	if (p.isEmpty()) {
		missed << path;
		// p = findFile(path);
		// if (p.isEmpty()) return QImage();
		return QImage();
	}
	uint hash    = mapHash(p, is_normal, opts);
	QImage image = cache_image.value(hash);
	if (!image.isNull()) {
		if (result_hash) *result_hash = hash;
		return image;
	}
	image = QImage(p).convertToFormat(QImage::Format_ARGB32);
	if (image.isNull()) return image;
	if (is_normal) convertToNormal(image);
	applyBakeOption(image, opts);
	cache_image[hash] = image;
	if (result_hash) *result_hash = hash;
	return image;
}

/*
GLuint TextureManager::loadTexture(const QImage & im, bool ownership, bool is_normal, MapBakeOptions opts) {
	if (im.isNull()) return 0;
	QImage image(im);
	if (is_normal) convertToNormal(image);
	applyBakeOption(image, opts);
	GLuint tid = 0;
	createGLTexture(f, tid, im);
	if (tid == 0) {
		qDebug() << "[TextureManager] Can`t load image";
		return tid;
	}
	qDebug() << "[TextureManager] Loaded image as" << tid;
	return tid;
}


void TextureManager::reloadTexture(GLuint tid, const QString & path) {
	QString p = findFile(path);
	if (p.isEmpty() || (tid == 0)) return;
	QImage image(p);
	createGLTexture(f, tid, image);
	if (tid == 0) {
		qDebug() << "[TextureManager] Can`t load" << p;
		return;
	}
	qDebug() << "[TextureManager] Reloaded" << p << "as" << tid;
}


void TextureManager::reloadTexture(GLuint tid, const QImage & im) {
	if (im.isNull() || (tid == 0)) return;
	QImage image(im);
	createGLTexture(f, tid, image);
	qDebug() << "[TextureManager] Reloaded" << tid;
}
*/

int TextureManager::textureID(const QString & path, bool is_normal, MapBakeOptions opts) {
	return cache_loaded.value(mapHash(findFile(path), is_normal, opts), 0);
}


QImage TextureManager::textureImage(const QString & path, bool is_normal, MapBakeOptions opts) {
	return cache_image.value(mapHash(findFile(path), is_normal, opts));
}


void TextureManager::convertToNormal(QImage & im) {
	if (im.isNull()) return;
	float sum[3]     = {0., 0., 0.};
	llong a          = 0;
	const uchar * sd = im.constBits();
	for (int i = 0; i < im.height(); i++) {
		for (int j = 0; j < im.width(); j++) {
			sum[2] += sd[a] / 255.f - 0.5f;
			++a;
			sum[1] += sd[a] / 255.f - 0.5f;
			++a;
			sum[0] += sd[a] / 255.f - 0.5f;
			++a;
			++a;
		}
	}
	float wh = im.width() * im.height();
	sum[0] /= wh;
	sum[1] /= wh;
	sum[2] /= wh;
	// qDebug() << sum[0] << sum[1] << sum[2];
	if ((qAbs(sum[0]) <= 0.05f) && (qAbs(sum[1]) <= 0.05f) && (sum[2] >= 0.25f)) /// already normal
		return;
	// qDebug() << "convert to normal";
	QImage dim = QImage(im.width(), im.height(), QImage::Format_ARGB32);
	int tx, ty, w = im.width(), h = im.height();
	a          = 0;
	uchar * dd = dim.bits();
	for (int i = 0; i < im.height(); i++) {
		for (int j = 0; j < im.width(); j++) {
			tx = j - 1;
			tx = tx < 0 ? w + tx : tx % w;
			ty = i - 1;
			ty = ty < 0 ? h + ty : ty % h;
			QVector3D p[3], res;
			p[0] = colorVector(im.pixel(j, i));
			p[1] = colorVector(im.pixel(j, ty));
			p[2] = colorVector(im.pixel(tx, i));
			res.setY(piClamp(0.5f + (p[0].length() - p[1].length()) / 2.f, 0.f, 1.f));
			res.setX(piClamp(0.5f - (p[0].length() - p[2].length()) / 2.f, 0.f, 1.f));
			tx   = (j + 1) % w;
			ty   = (i + 1) % h;
			p[1] = colorVector(im.pixel(j, ty));
			p[2] = colorVector(im.pixel(tx, i));
			res.setY(piClamp(0.5f + (p[0].length() - p[1].length()) / 2.f, 0.f, 1.f));
			res.setX(piClamp(0.5f - (p[0].length() - p[2].length()) / 2.f, 0.f, 1.f));
			res.setZ(1.f);
			dd[a] = res.z() * 255;
			++a;
			dd[a] = res.y() * 255;
			++a;
			dd[a] = res.x() * 255;
			++a;
			dd[a] = 255;
			++a;
		}
	}
	im = dim;
	// im.save("_normal.png");
}


void TextureManager::applyBakeOption(QImage & im, MapBakeOptions opts) {
	if (im.isNull()) return;
	if (!opts.invert_R && !opts.invert_G && !opts.invert_B) return;
	QRgb * data = (QRgb *)im.bits();
	uint pixels = im.width() * im.height();
	for (uint i = 0; i < pixels; ++i) {
		uchar * p = (uchar *)&(data[i]);
		if (opts.invert_B) p[0] = 255 - p[0];
		if (opts.invert_G) p[1] = 255 - p[1];
		if (opts.invert_R) p[2] = 255 - p[2];
	}
}


uint TextureManager::mapHash(const QString & path, bool is_normal, MapBakeOptions opts) {
	return qHash(path) ^ ((uint)is_normal << 8) ^ opts.hash();
}

/*
bool TextureManager::loadTextures() {
	QFileInfoList fil;
	foreach(const QString & i, tex_pathes)
		loadTexture(i, true);
	tex_pathes.clear();
	return true;
}


void TextureManager::deleteTextures() {
	for (int i = 0; i < 2; ++i) {
		QList<GLuint> texs = tex_ids[i].values();
		qDebug() << "[TextureManager] Delete" << texs.size() << "textures";
		if (!texs.isEmpty()) f->glDeleteTextures(texs.size(), &texs[0]);
		tex_ids[i].clear();
		tex_im[i].clear();
	}
}


void TextureManager::deleteTexture(const QString & name) {
	for (int i = 0; i < 2; ++i) {
		if (tex_ids[i].contains(name)) {
			GLuint id = tex_ids[i][name];
			f->glDeleteTextures(1, &id);
			tex_ids[i].remove(name);
			tex_im[i].remove(name);
		}
	}
}
*/

void TextureManager::clearImageCache() {
	cache_image.clear();
}


void TextureManager::clearMissed() {
	missed.clear();
}


void TextureManager::loadToTexture2DArray(Texture2DArray * array, QSize map_size) {
	array->resize(f, map_size, texturesCount());
	array_layers.clear();
	QMapIterator<uint, QImage> it(cache_image);
	int cl = -1;
	while (it.hasNext()) {
		it.next();
		array->load(f, it.value(), ++cl);
		array_layers[it.key()] = cl;
	}
	array->mipmaps(f);
	emit filesUsed(texturesCount());
}
