/*
    QGLView
    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 "gltypes.h"
#include "qglview.h"

QStringList GLTextureManagerBase::search_pathes(".");


bool GLCubeTexture::create() {
	// qDebug("create");
	destroy();
	glGenTextures(1, &id_);
	glBindTexture(GL_TEXTURE_CUBE_MAP, id_);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR /*_MIPMAP_LINEAR*/);
	glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	// glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
	// glClearError();
	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format_, size, size, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
	// qDebug() << glGetError();
	changed_ = false;
	return id_ > 0;
}


void GLCubeTexture::load() {
	if (isEmpty()) return;
	create();
	if (!path(0).isEmpty()) loadFront(path(0));
	if (!path(1).isEmpty()) loadBack(path(1));
	if (!path(2).isEmpty()) loadLeft(path(2));
	if (!path(3).isEmpty()) loadRight(path(3));
	if (!path(4).isEmpty()) loadTop(path(4));
	if (!path(5).isEmpty()) loadBottom(path(5));
}


void GLCubeTexture::loadFromDirectory(const QString & dir) {
	QDir d(dir);
	QFileInfoList sl;
	sl = d.entryInfoList(QStringList("front.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadFront(sl[0].absoluteFilePath());
	sl = d.entryInfoList(QStringList("back.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadBack(sl[0].absoluteFilePath());
	sl = d.entryInfoList(QStringList("left.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadLeft(sl[0].absoluteFilePath());
	sl = d.entryInfoList(QStringList("right.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadRight(sl[0].absoluteFilePath());
	sl = d.entryInfoList(QStringList("top.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadTop(sl[0].absoluteFilePath());
	sl = d.entryInfoList(QStringList("bottom.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) loadBottom(sl[0].absoluteFilePath());
}


void GLCubeTexture::loadPathesFromDirectory(const QString & dir) {
	QDir d(dir);
	QFileInfoList sl;
	sl = d.entryInfoList(QStringList("front.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[0] = sl[0].absoluteFilePath();
	sl = d.entryInfoList(QStringList("back.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[1] = sl[0].absoluteFilePath();
	sl = d.entryInfoList(QStringList("left.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[2] = sl[0].absoluteFilePath();
	sl = d.entryInfoList(QStringList("right.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[3] = sl[0].absoluteFilePath();
	sl = d.entryInfoList(QStringList("top.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[4] = sl[0].absoluteFilePath();
	sl = d.entryInfoList(QStringList("bottom.*"), QDir::Files | QDir::NoDotAndDotDot);
	if (!sl.isEmpty()) pathes[5] = sl[0].absoluteFilePath();
}


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


GLuint GLTextureManagerBase::loadTexture(const QString & path, bool ownership, bool bump) {
	QString p = findFile(path);
	if (p.isEmpty()) return 0;
	int tid = textureID(p, bump);
	if (tid > 0) {
		// qDebug() << "[TextureManager] Found" << path << "as" << tid;
		return tid;
	}
	QImage image(p);
	if (bump) convertToNormal(image);
	// qDebug() << p << image.width() << image.height() << image.format() << bump;
	/// tid = currentQGLView->bindTexture(image, GL_TEXTURE_2D/*, GL_RGBA, __GLContext__::MipmapBindOption*/);
	// GLuint tid = 0;
	GLuint _tid = tid;
	createGLTexture(_tid, image); /// currentQGLView->bindTexture(image, GL_TEXTURE_2D);
	tid = _tid;
	if (tid == 0) {
		qDebug() << "[TextureManager] Can`t load" << p;
		return tid;
	}
	qDebug() << "[TextureManager] Loaded" << p << "as" << tid;
	if (ownership) tex_ids[bump ? 1 : 0].insert(p, tid);
	return tid;
}


GLuint GLTextureManagerBase::loadTexture(const QImage & im, bool ownership, bool bump) {
	if (im.isNull()) return 0;
	QImage image(im);
	if (bump) convertToNormal(image);
	GLuint tid = 0;
	createGLTexture(tid, im); /// currentQGLView->bindTexture(image, GL_TEXTURE_2D);
	if (tid == 0) {
		qDebug() << "[TextureManager] Can`t load image";
		return tid;
	}
	// qDebug() << "[TextureManager] Loaded image as" << tid;
	if (ownership) tex_ids[bump ? 1 : 0].insert(QString(), tid);
	return tid;
}


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


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


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


void GLTextureManagerBase::convertToNormal(QImage & im) {
	if (im.isNull()) return;
	QImage sim       = im.convertToFormat(QImage::Format_ARGB32);
	float sum[3]     = {0., 0., 0.};
	llong a          = 0;
	const uchar * sd = sim.constBits();
	for (int i = 0; i < sim.height(); i++) {
		for (int j = 0; j < sim.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 = sim.width() * sim.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 bump";
	QImage dim = QImage(sim.width(), sim.height(), QImage::Format_ARGB32);
	int tx, ty, w = sim.width(), h = sim.height();
	a          = 0;
	uchar * dd = dim.bits();
	for (int i = 0; i < sim.height(); i++) {
		for (int j = 0; j < sim.width(); j++) {
			tx = j - 1;
			tx = tx < 0 ? w + tx : tx % w;
			ty = i - 1;
			ty = ty < 0 ? h + ty : ty % h;
			Vector3d p[3], res;
			p[0]  = colorVector(sim.pixel(j, i));
			p[1]  = colorVector(sim.pixel(j, ty));
			p[2]  = colorVector(sim.pixel(tx, i));
			res.y = piClamp(0.5f + (p[0].length() - p[1].length()) / 2.f, 0.f, 1.f);
			res.x = 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(sim.pixel(j, ty));
			p[2]  = colorVector(sim.pixel(tx, i));
			res.y = piClamp(0.5f + (p[0].length() - p[1].length()) / 2.f, 0.f, 1.f);
			res.x = piClamp(0.5f + (p[0].length() - p[2].length()) / 2.f, 0.f, 1.f);
			res.z = 1.;
			dd[a] = res.z * 255;
			++a;
			dd[a] = res.x * 255;
			++a;
			dd[a] = res.y * 255;
			++a;
			dd[a] = 255;
			++a;
		}
	}
	im = dim;
	// im.save("_bump.png");
}


Material::Material(): map_reflection(512) {
	color_diffuse = color_specular = Qt::white;
	color_self_illumination        = Qt::black;
	glass                          = false;
	transparency = reflectivity  = 0.f;
	map_specularity.color_amount = 0.5f;
	map_specular.color_amount    = 1.f;
	iof                          = 1.f;
	dispersion                   = 0.05f;
}


void Material::apply(QOpenGLShaderProgram * prog) {
	if (prog) {
		setUniformMaterial(prog, *this);
	} else {
		GLfloat mat_diffuse[4]  = {1.0f, 1.0f, 1.0f, 1.0f};
		GLfloat mat_specular[4] = {0.9f, 0.9f, 0.9f, 1.0f};
		GLfloat mat_emission[4] = {0.f, 0.f, 0.f, 1.0f};
		mat_diffuse[0]          = map_diffuse.color_amount * color_diffuse.redF();
		mat_diffuse[1]          = map_diffuse.color_amount * color_diffuse.greenF();
		mat_diffuse[2]          = map_diffuse.color_amount * color_diffuse.blueF();
		mat_diffuse[3]          = map_diffuse.color_amount * color_diffuse.alphaF() * (1.f - transparency);
		mat_specular[0]         = map_specular.color_amount * color_specular.redF();
		mat_specular[1]         = map_specular.color_amount * color_specular.greenF();
		mat_specular[2]         = map_specular.color_amount * color_specular.blueF();
		mat_emission[0]         = map_self_illumination.color_amount * color_self_illumination.redF();
		mat_emission[1]         = map_self_illumination.color_amount * color_self_illumination.greenF();
		mat_emission[2]         = map_self_illumination.color_amount * color_self_illumination.blueF();
		glColor4f(mat_diffuse[0], mat_diffuse[1], mat_diffuse[2], mat_diffuse[3]);
		glMaterialfv(GL_FRONT, GL_DIFFUSE, mat_diffuse);
		glMaterialfv(GL_FRONT, GL_SPECULAR, mat_specular);
		// qDebug() << (map_specularity.color_amount)*128.;
		glMaterialf(GL_FRONT, GL_SHININESS, (map_specularity.color_amount) * 128.f);
		glMaterialfv(GL_FRONT, GL_EMISSION, mat_emission);
		glMaterialfv(GL_FRONT, GL_AMBIENT, mat_diffuse);
	}
}


void Material::loadTextures(GLTextureManagerBase * tm) {
	// qDebug() << "load textures";
	if (!tm) return;
	if (!map_diffuse.bitmap_path.isEmpty()) map_diffuse.bitmap_id = tm->loadTexture(map_diffuse.bitmap_path);
	if (!map_normal.bitmap_path.isEmpty()) map_normal.bitmap_id = tm->loadTexture(map_normal.bitmap_path, true, true);
	if (!map_relief.bitmap_path.isEmpty()) map_relief.bitmap_id = tm->loadTexture(map_relief.bitmap_path);
	if (!map_specularity.bitmap_path.isEmpty()) map_specularity.bitmap_id = tm->loadTexture(map_specularity.bitmap_path);
	if (!map_specular.bitmap_path.isEmpty()) map_specular.bitmap_id = tm->loadTexture(map_specular.bitmap_path);
	if (!map_self_illumination.bitmap_path.isEmpty()) map_self_illumination.bitmap_id = tm->loadTexture(map_self_illumination.bitmap_path);
	// if (!map_diffuse_2.bitmap_path.isEmpty()) map_diffuse_2.bitmap_id = tm->loadTexture(map_diffuse_2.bitmap_path);
	map_reflection.load();
}
