/*
    QGLView
    Copyright (C) 2017  Ivan Pelipenko peri4ko@yandex.ru

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU 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 General Public License for more details.

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

#include "glcamera.h"
#include "qglview.h"

QGLWidget * currentQGLView;
QMutex globMutex;


QString readCharsUntilNull(QDataStream & s) {
	QString str;
	char ch;
	s.readRawData(&ch, 1);
	while (ch != '\0') {
		str += ch;
		s.readRawData(&ch, 1);
	}
	return str;
}


QString findFile(const QString & file, const QStringList & pathes) {
	QFileInfo fi(QString(file).replace("\\", "/"));
	//qDebug() << "search" << file << "in" << pathes;
	if (fi.exists()) return fi.absoluteFilePath();
	QString fn = fi.fileName();
	if (fn.contains("/")) fn = fn.mid(fn.lastIndexOf("/"));
	foreach (QString p, pathes) {
		QFileInfoList fil = QDir(p).entryInfoList(QStringList(fn), QDir::Files | QDir::NoDotAndDotDot);
		//qDebug() << "findFile" << fn << "in" << p << "->" << fil.size();
		if (!fil.isEmpty())
			return fil[0].absoluteFilePath();
	}
	return QString();
}


void glDrawQuad(QGLShaderProgram * prog, QVector4D * corner_dirs, GLfloat x, GLfloat y, GLfloat w, GLfloat h) {
	glResetAllTransforms();
	glSetPolygonMode(GL_FILL);
	int loc = prog ? prog->attributeLocation("qgl_Color") : 0,
		locv = prog ? prog->attributeLocation("qgl_Vertex") : 0,
		loct = prog ? prog->attributeLocation("qgl_Texture") : 0,
		locc = prog ? prog->attributeLocation("view_corner") : 0;
	glBegin(GL_QUADS);
	if (prog) glVertexAttrib3f(loc, 1.f, 1.f, 1.f); glColor3f(1.f, 1.f, 1.f);
	if (prog) {if (corner_dirs) prog->setAttributeValue(locc, corner_dirs[0]); glVertexAttrib2f(loct, 0.f, 0.f); glVertexAttrib2f(locv, x, y);} glTexCoord2f(0.f, 0.f); glVertex2f(x, y);
	if (prog) {if (corner_dirs) prog->setAttributeValue(locc, corner_dirs[1]); glVertexAttrib2f(loct, 1.f, 0.f); glVertexAttrib2f(locv, x + w, y);} glTexCoord2f(1.f, 0.f); glVertex2f(x + w, y);
	if (prog) {if (corner_dirs) prog->setAttributeValue(locc, corner_dirs[2]); glVertexAttrib2f(loct, 1.f, 1.f); glVertexAttrib2f(locv, x + w, y + h);} glTexCoord2f(1.f, 1.f); glVertex2f(x + w, y + h);
	if (prog) {if (corner_dirs) prog->setAttributeValue(locc, corner_dirs[3]); glVertexAttrib2f(loct, 0.f, 1.f); glVertexAttrib2f(locv, x, y + h);} glTexCoord2f(0.f, 1.f); glVertex2f(x, y + h);
	glEnd();
}


QMatrix4x4 getGLMatrix(GLenum matrix) {
	GLdouble gm[16];
	glGetDoublev(matrix, gm);
	qreal qm[16];
	for (int i = 0; i < 16; ++i)
		qm[i] = gm[i];
	return QMatrix4x4(qm).transposed();
}


void setGLMatrix(QMatrix4x4 matrix) {
	GLdouble gm[16];
	qreal qm[16];
	matrix.transposed().copyDataTo(qm);
	for (int i = 0; i < 16; ++i)
		gm[i] = qm[i];
	glLoadMatrixd(gm);
}


void qglMultMatrix(const QMatrix4x4 & m) {
	GLdouble gm[16];
	qreal qm[16];
	m.transposed().copyDataTo(qm);
	for (int i = 0; i < 16; ++i)
		gm[i] = qm[i];
	glMultMatrixd(gm);
}


void createGLTexture(GLuint & tex, int width, int height, const GLenum & format, const GLenum & target) {
	glClearError();
	if (tex == 0) {
		glGenTextures(1, &tex);
		glBindTexture(target, tex);
	}
    //glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	if (format == GL_DEPTH_COMPONENT || format == GL_DEPTH_COMPONENT16 || format == GL_DEPTH_COMPONENT24 || format == GL_DEPTH_COMPONENT32)
		glTexImage2D(target, 0, format, width, height, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_BYTE, 0);
	else {
		int t = GL_UNSIGNED_BYTE;
		int f = GL_RGBA;
		if (format == GL_RGB32F || format == GL_RGB16F || format == GL_RGBA32F || format == GL_RGBA16F)
			t = GL_FLOAT;
		if (format == GL_RGB32F || format == GL_RGB16F || format == GL_RGB8 || format == GL_RGB)
			f = GL_RGB;
		glTexImage2D(target, 0, format, width, height, 0, f, t, 0);
		//glGenerateMipmap(target);
		//qDebug() << "glTexImage2D" << width << height << QString::number(t, 16);
	}
	//qDebug() << QString::number(glGetError(), 16);
}


void createGLTexture(GLuint & tex, const QImage & image, const GLenum & format, const GLenum & target) {
	if (tex == 0) {
		glGenTextures(1, &tex);
		glBindTexture(target, tex);
	}
	QImage im = QGLWidget::convertToGLFormat(image);
	//const QImage & cim(im);
	//glClearError();
	//glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
	if (target == GL_TEXTURE_1D || target == GL_TEXTURE_2D || target == GL_TEXTURE_3D) {
		glTexParameteri(target, GL_TEXTURE_WRAP_T, GL_REPEAT);
		glTexParameteri(target, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(target, GL_TEXTURE_WRAP_R, GL_REPEAT);
		glTexParameteri(target, GL_TEXTURE_MIN_FILTER, GL_LINEAR_MIPMAP_LINEAR);
		glTexParameteri(target, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(target, GL_GENERATE_MIPMAP_SGIS, GL_TRUE);
	}
	glTexImage2D(target, 0, format, im.width(), im.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, im.bits());
	//qDebug() << tex << im.width() << im.height() << im.bits() << glGetError();
}


QMatrix4x4 glMatrixPerspective(double angle, double aspect, double near_, double far_) {
	QMatrix4x4 ret;
	double t = 1. / (tan(angle * deg2rad / 2.)), e = 2.4e-7;
	ret(0, 0) = t / aspect;
	ret(1, 1) = t;
	ret(2, 2) = e - 1.;//far_ / (far_ - near_) - 1.;
	ret(2, 3) = (e - 2.) * near_;//2. * far_ * near_ / (far_ - near_);
	ret(3, 2) = -1.;
	ret(3, 3) = 0.;
	return ret;
}


QImage rotateQImageLeft(const QImage & im) {
	QImage ri(im.height(), im.width(), im.format());
	QPainter p(&ri);
	p.rotate(90);
	p.drawImage(0, -im.height(), im);
	p.end();
	return ri;
}


QImage rotateQImageRight(const QImage & im) {
	QImage ri(im.height(), im.width(), im.format());
	QPainter p(&ri);
	p.rotate(-90);
	p.drawImage(-im.width(), 0, im);
	p.end();
	return ri;
}




QColor colorFromString(const QString & str) {
	QString s = str.trimmed();
	int i = s.indexOf("\t");
	double r, g, b;
	r = s.left(i).toFloat(); s = s.right(s.length() - i - 1); i = s.indexOf("\t");
	g = s.left(i).toFloat(); s = s.right(s.length() - i - 1);
	b = s.toFloat();
	return QColor(r * 255., g * 255., b * 255.);
}


QVector3D orthToVector(const QVector3D & v, const double & scale) {
	if (v.isNull()) return QVector3D();
	QVector3D rv, fn, sn;
	if (v.x() != 0) rv.setZ(1.);
	else if (v.y() != 0) rv.setX(1.);
	else rv.setY(1.);
	fn = QVector3D::crossProduct(v, rv).normalized();
	sn = QVector3D::crossProduct(v, fn).normalized();
	return fn * urand(scale) + sn * urand(scale);
}


QVector3D rotateVector(const QVector3D & v, const QVector3D & a) {
	QMatrix4x4 m;
	m.rotate(a.z(), 0., 0., 1.);
	m.rotate(a.y(), 0., 1., 0.);
	m.rotate(a.x(), 1., 0., 0.);
	return m * v;
}


void setVectorLength(QVector3D & v, const double & l) {
	double vl = v.length();
	if (vl == 0.) return;
	double c = l / vl;
	v *= c;
}


void lengthenVector(QVector3D & v, const double & l) {
	double vl = v.length();
	if (l == 0. || vl == 0.) return;
	double c = 1. + l / vl;
	v *= c;
}


Vector3d::Vector3d(const QString & str) {
	QString s = str.trimmed();
	int i = s.indexOf("\t");
	x = s.left(i).toFloat(); s = s.right(s.length() - i - 1); i = s.indexOf("\t");
	y = s.left(i).toFloat(); s = s.right(s.length() - i - 1);
	z = s.toFloat();
}


Vector3i::Vector3i(const QString & str) {
	QString s = str.trimmed();
	int i = s.indexOf("\t");
	p0 = s.left(i).toInt(); s = s.right(s.length() - i - 1); i = s.indexOf("\t");
	p1 = s.left(i).toInt(); s = s.right(s.length() - i - 1);
	p2 = s.toInt();
}


void glEnableDepth() {
	glEnable(GL_DEPTH_TEST);
	//glDepthFunc(GL_GREATER);
	glDepthFunc(GL_LESS);
	glDepthMask(GL_TRUE);
}


void glDisableDepth() {
	glDisable(GL_DEPTH_TEST);
	glDepthMask(GL_FALSE);
}


void glClearFramebuffer(const QColor & color, bool depth) {
	glClearColor(color.redF(), color.greenF(), color.blueF(), color.alphaF());
	//glClearDepth(0.);
	if (depth)
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	else
		glClear(GL_COLOR_BUFFER_BIT);
}




QGLViewBase::QGLViewBase() {
	camera_ = new Camera();
}


Camera & QGLViewBase::camera() {
	return *camera_;
}


const Camera & QGLViewBase::camera() const {
	return *camera_;
}


void QGLViewBase::setCamera(const Camera & camera) {
	*camera_ = camera;
}
