/*
    QGL CubeTexture
    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 "glcubemap.h"

#include "gltypes.h"
#include "hdr_p.h"

using namespace QGLEngineShaders;


QVector<QVector3D> loadFileHDR(const QString & path, QSize * size) {
	if (size) *size = QSize();
	QVector<QVector3D> ret;
	QFile f(path);
	if (!f.open(QIODevice::ReadOnly)) {
		qDebug() << "[QGLEngine] Can`t open" << path;
		return ret;
	}
	QTextStream ts(&f);
	QString line = ts.readLine(256);
	if (line != "#?RADIANCE") return ret;
	QSize sz;
	while (!ts.atEnd()) {
		line = ts.readLine(256);
		if (line.startsWith("FORMAT")) {
			line.remove(0, 7);
			if (!line.startsWith("32-bit")) {
				qDebug() << "[QGLEngine] File" << path << "has unknown format!";
				return ret;
			}
		}
		if (line.mid(1, 2) == "Y ") {
			QStringList sl = line.trimmed().split(" ");
			sl.removeAll("");
			if (sl.size() != 4) {
				qDebug() << "[QGLEngine] File" << path << "has unknown size!";
				return ret;
			}
			sz.setWidth(sl[3].toInt());
			sz.setHeight(sl[1].toInt());
			// qDebug() << "found size" << sz;
			break;
		}
	}
	if (sz.isEmpty()) return ret;
	f.seek(ts.pos());
	QDataStream ds(&f);
	int count = sz.width() * sz.height();
	QVector<float> data(count * 3);
	if (!RGBE_ReadPixels_RLE(&ds, data.data(), sz.width(), sz.height())) return ret;

	if (size) *size = sz;
	ret.resize(count);
	// QColor col;
	// QImage im(sz, QImage::Format_ARGB32);
	// QRgb * imdata = (QRgb*)im.bits();
	for (int i = 0; i < count; ++i) {
		QVector3D p(pow(data[i * 3 + 2], 1. / 2.2), pow(data[i * 3 + 1], 1. / 2.2), pow(data[i * 3 + 0], 1. / 2.2));
		ret[i] = p;
		// col = QColor::fromRgbF(piClamp(p[0], 0.f, 1.f),
		//					   piClamp(p[1], 0.f, 1.f),
		//					   piClamp(p[2], 0.f, 1.f));
		// imdata[i] = col.rgb();
	}
	// im.save("_hdr.png");

	return ret;
}


QVector<QVector3D> faceHDR(const QVector<QVector3D> & data, QSize sz, QSize & fsz, int face) {
	QVector<QVector3D> ret;
	if (data.isEmpty() || sz.isNull()) return ret;
	QRect fr;
	int fw = sz.width() / 4;
	int fh = sz.height() / 3;
	fsz    = QSize(fw, fh);
	ret.reserve(fw * fh);
	switch (face) {
	case GL_TEXTURE_CUBE_MAP_POSITIVE_X:
		fr.setRect(fw, fh, fw, fh);
		for (int x = fr.left(); x <= fr.right(); ++x) {
			for (int y = fr.top(); y <= fr.bottom(); ++y) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_X:
		fr.setRect(fw * 3, fh, fw, fh);
		for (int x = fr.right(); x >= fr.left(); --x) {
			for (int y = fr.bottom(); y >= fr.top(); --y) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	case GL_TEXTURE_CUBE_MAP_POSITIVE_Y:
		fr.setRect(0, fh, fw, fh);
		for (int y = fr.bottom(); y >= fr.top(); --y) {
			for (int x = fr.left(); x <= fr.right(); ++x) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_Y:
		fr.setRect(fw * 2, fh, fw, fh);
		for (int y = fr.top(); y <= fr.bottom(); ++y) {
			for (int x = fr.right(); x >= fr.left(); --x) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	case GL_TEXTURE_CUBE_MAP_POSITIVE_Z:
		fr.setRect(fw, 0, fw, fh);
		for (int x = fr.left(); x <= fr.right(); ++x) {
			for (int y = fr.top(); y <= fr.bottom(); ++y) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	case GL_TEXTURE_CUBE_MAP_NEGATIVE_Z:
		fr.setRect(fw, fh * 2, fw, fh);
		for (int x = fr.left(); x <= fr.right(); ++x) {
			for (int y = fr.top(); y <= fr.bottom(); ++y) {
				ret << data[y * sz.width() + x];
			}
		}
		break;
	default: break;
	}
	if (fr.isEmpty()) return ret;
	// qDebug() << ret.size() << fr;
	return ret;
}


CubeTexture::CubeTexture(QOpenGLExtraFunctions * f_, int _size, const GLenum & _format): f(f_) {
	size     = _size;
	format_  = _format;
	id_      = 0;
	changed_ = false;
}


bool CubeTexture::init() {
	if (isInit()) return true;
	f->glGenTextures(1, &id_);
	f->glBindTexture(GL_TEXTURE_CUBE_MAP, id_);
	f->glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
	f->glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
	f->glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
	f->glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
	f->glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
	changed_ = false;
	return id_ > 0;
}


void CubeTexture::destroy() {
	if (!isInit()) return;
	f->glDeleteTextures(1, &id_);
	id_ = 0;
}


void CubeTexture::bind(int channel) {
	init();
	f->glActiveTexture(GL_TEXTURE0 + channel);
	f->glBindTexture(GL_TEXTURE_CUBE_MAP, id_);
}


void CubeTexture::release() {
	f->glBindTexture(GL_TEXTURE_CUBE_MAP, 0);
}


void CubeTexture::loadHDR(const QVector<QVector3D> & data, QSize sz) {
	bind();
	QSize fsz;
	QVector<QVector3D> fd;
	for (int i = 0; i < 6; ++i) {
		fd = faceHDR(data, sz, fsz, GL_TEXTURE_CUBE_MAP_POSITIVE_X + i);
		// qDebug() << "load cube" << fd[0];
		f->glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i,
		                0,
		                format_,
		                fsz.width(),
		                fsz.height(),
		                0,
		                GL_RGB,
		                GL_FLOAT,
		                fd.isEmpty() ? 0 : fd.constData());
		// qDebug() << QString::number(GetLastError(), 16);
	}
	f->glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
}


void CubeTexture::setFileHDR(const QString & path) {
	hdr_path = path;
	changed_ = true;
}


void CubeTexture::load() {
	if (!changed_) return;
	init();
	if (!hdr_path.isEmpty()) {
		QSize sz;
		QVector<QVector3D> data = loadFileHDR(hdr_path, &sz);
		loadHDR(data, sz);
	} else {
		destroy();
		bind();
		for (int i = 0; i < 6; ++i) {
			f->glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X + i, 0, format_, 1, 1, 0, GL_RGB, GL_FLOAT, 0);
		}
		f->glGenerateMipmap(GL_TEXTURE_CUBE_MAP);
	}
	changed_ = false;
}
