/*
    QGLEngineShaders
    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 "glshaders.h"

#include "glshaders_headers.h"
#include "gltypes.h"
#include "qglview.h"

using namespace QGLEngineShaders;


bool addShader(QOpenGLShaderProgram * prog,
               QOpenGLShader::ShaderType type,
               QString & content,
               const QString & file,
               bool add_qgl,
               const QString & defs) {
	if (type == 0 || content.isEmpty()) {
		content.clear();
		return true;
	}
	// qDebug() << "[QGLEngine] Shader" << file << "found" << (QOpenGLShader::ShaderTypeBit)(int)type << "section ...";
	if (add_qgl) {
		switch (type) {
		case QOpenGLShader::Fragment:
			content.prepend(qgl_material_head);
			content.prepend(qgl_fragment_head);
			content.prepend(qgl_uniform_light);
			content.prepend(qgl_uniform_material);
			content.prepend(qgl_structs);
			break;
		case QOpenGLShader::Vertex:
			content.prepend(qgl_material_head);
			content.prepend(qgl_vertex_head);
			content.prepend(qgl_uniform_material);
			content.prepend(qgl_structs);
			break;
		case QOpenGLShader::Geometry: content.prepend(qgl_geometry_head); break;
		}
	}
	content.prepend(defs);
	content.prepend(qgl_common_head);
	bool ret = prog->addShaderFromSourceCode(type, content.toLatin1());
	if (!ret) qDebug() << "[QGLEngine] Shader" << file << "Compile error:\n" << prog->log().toLocal8Bit().constData();
	content.clear();
	return ret;
}


QString prepareDefines(const QStringList & defines) {
	if (defines.isEmpty()) return QString();
	QString ret;
	foreach(QString s, defines)
		ret.append("#define " + s + "\n");
	return ret;
}


bool QGLEngineShaders::loadShadersMulti(QOpenGLShaderProgram *& prog,
										const QString & file,
										bool add_qgl,
										const QStringList & defines,
										const QString & custom_header) {
	if (!prog) prog = new QOpenGLShaderProgram();
	prog->removeAllShaders();
	QFile f(file);
	if (!f.open(QIODevice::ReadOnly)) {
		qDebug() << "[QGLEngine] Shader" << file << "Error: can`t open file!";
		return false;
	}
	QTextStream ts(&f);
	QString cur_shader, line, pl, defs = prepareDefines(defines) + "\n" + custom_header;
	QOpenGLShader::ShaderType type = QOpenGLShader::ShaderType();
	while (!ts.atEnd()) {
		line = ts.readLine();
		pl   = line.trimmed().remove(' ').remove('\t').mid(2).toLower();
		pl.chop(2);
		if (pl == "vertex" || pl == "vert") {
			if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
			type = QOpenGLShader::Vertex;
			continue;
		}
		if (pl == "fragment" || pl == "frag") {
			if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
			type = QOpenGLShader::Fragment;
			continue;
		}
		if (pl == "geometry" || pl == "geom") {
			if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
			type = QOpenGLShader::Geometry;
			continue;
		}
		if (pl == "tessellation_control") {
			if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
			type = QOpenGLShader::TessellationControl;
			continue;
		}
		if (pl == "tessellation_evaluation") {
			if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
			type = QOpenGLShader::TessellationEvaluation;
			continue;
		}
		cur_shader.append("\n");
		cur_shader.append(line);
	}
	if (!addShader(prog, type, cur_shader, file, add_qgl, defs)) return false;
	if (!prog->link()) {
		qDebug() << "[QGLEngine] Shader" << file << "Link error:\n" << prog->log().toLocal8Bit().constData();
		return false;
	}
	qDebug() << "[QGLEngine] Shader" << file << "ok";
	return true;
}


bool QGLEngineShaders::loadShaders(QOpenGLShaderProgram *& prog,
								   const QStringList & files,
								   bool add_qgl,
								   const QStringList & defines,
								   const QString & custom_header) {
	if (!prog) prog = new QOpenGLShaderProgram();
	prog->removeAllShaders();
	QString cur_shader, defs = prepareDefines(defines) + "\n" + custom_header;
	foreach(QString f, files) {
		QFileInfo fi(f);
		QOpenGLShader::ShaderType type = QOpenGLShader::ShaderType();
		if (fi.suffix().toLower() == "vert") type = QOpenGLShader::Vertex;
		if (fi.suffix().toLower() == "frag") type = QOpenGLShader::Fragment;
		if (fi.suffix().toLower() == "geom") type = QOpenGLShader::Geometry;
		if (type == 0) continue;
		QFile file(f);
		if (!file.open(QIODevice::ReadOnly)) {
			qDebug() << "[QGLEngine] Shader" << f << "Error: can`t open file!";
			return false;
		}
		cur_shader = file.readAll();
		if (!addShader(prog, type, cur_shader, f, add_qgl, defs)) return false;
	}
	if (!prog->link()) {
		qDebug() << "[QGLEngine] Shader" << files << "Link error:\n" << prog->log().toLocal8Bit().constData();
		return false;
	}
	qDebug() << "[QGLEngine] Shader" << files << "ok";
	return true;
}
