/*
    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/>.
*/

#ifndef GLMATERIAL_H
#define GLMATERIAL_H

#include "chunkstream.h"
#include "gltypes.h"

class GLTexture {
public:
	GLTexture(int _width, int _height, const GLenum & _format = GL_RGBA8, const GLenum & _target = GL_TEXTURE_2D) {
		wid     = _width;
		hei     = _height;
		format_ = _format;
		target_ = _target;
		id_     = 0;
	}
	bool create() {
		destroy();
		createGLTexture(id_, wid, hei, format_, target_);
		return id_ > 0;
	}
	void destroy() {
		if (id_ > 0) glDeleteTextures(1, &id_);
		id_ = 0;
	}
	void bind() {
		if (id_ > 0) glBindTexture(target_, id_);
	}
	void release() { glBindTexture(target_, 0); }
	int width() const { return wid; }
	int height() const { return hei; }
	GLenum format() const { return format_; }
	GLenum target() const { return target_; }
	GLuint id() const { return id_; }

private:
	int wid, hei;
	GLenum format_, target_;
	GLuint id_;
};


class GLCubeTexture {
public:
	GLCubeTexture(int _size, const GLenum & _format = GL_RGBA8) {
		size     = _size;
		format_  = _format;
		id_      = 0;
		changed_ = false;
		pathes.resize(6);
	}
	bool create();
	void destroy() {
		if (id_ > 0) glDeleteTextures(1, &id_);
		id_ = 0;
	}
	void bind() {
		if (changed_) {
			changed_ = false;
			create();
		}
		if (id_ > 0) glBindTexture(GL_TEXTURE_CUBE_MAP, id_);
	}
	void release() { glBindTexture(GL_TEXTURE_CUBE_MAP, 0); }
	void resize(int _size) {
		size     = _size;
		changed_ = true;
	}
	void loadFromDirectory(const QString & dir);
	void loadFront(const QString & path) {
		bind();
		pathes[0] = path;
		createGLTexture(id_,
		                rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_POSITIVE_X);
	}
	void loadBack(const QString & path) {
		bind();
		pathes[1] = path;
		createGLTexture(id_,
		                rotateQImageRight(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_NEGATIVE_X);
	}
	void loadLeft(const QString & path) {
		bind();
		pathes[2] = path;
		createGLTexture(id_,
		                QImage(path).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_NEGATIVE_Y);
	}
	void loadRight(const QString & path) {
		bind();
		pathes[3] = path;
		createGLTexture(id_,
		                rotateQImage180(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_POSITIVE_Y);
	}
	void loadTop(const QString & path) {
		bind();
		pathes[4] = path;
		createGLTexture(id_,
		                rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_NEGATIVE_Z);
	}
	void loadBottom(const QString & path) {
		bind();
		pathes[5] = path;
		createGLTexture(id_,
		                rotateQImageLeft(QImage(path)).scaled(size, size, Qt::IgnoreAspectRatio, Qt::SmoothTransformation),
		                format_,
		                GL_TEXTURE_CUBE_MAP_POSITIVE_Z);
	}
	void load();
	bool isEmpty() const {
		foreach(const QString & i, pathes)
			if (!i.isEmpty()) return false;
		return true;
	}
	GLenum format() const { return format_; }
	void setFormat(GLenum f) {
		format_  = f;
		changed_ = true;
	}
	GLuint id() const { return id_; }
	const QString & path(int side) const { return pathes[side]; }
	void setPath(int side, const QString & p) { pathes[side] = p; }
	void loadPathesFromDirectory(const QString & dir);

private:
	bool changed_;
	int size;
	GLenum format_;
	GLuint id_;
	QVector<QString> pathes;
};

class GLTextureManager;

class GLTextureManagerBase {
public:
	GLTextureManagerBase() {}
	virtual ~GLTextureManagerBase() {}
	void addSearchPath(const QString & path) { search_pathes << path; }
	static QStringList searchPathes() { return search_pathes; }
	QString findFile(const QString & path);
	GLuint loadTexture(const QString & path, bool ownership = true, bool bump = false);
	GLuint loadTexture(const QImage & image, bool ownership = true, bool bump = false);
	void reloadTexture(GLuint tid, const QString & path);
	void reloadTexture(GLuint tid, const QImage & image);
	int textureID(const QString & path, bool bump = false) { return tex_ids[bump ? 1 : 0][path]; }
	virtual void addTexture(const QString & path)                        = 0;
	virtual void addAnimation(const QString & dir, const QString & name) = 0;
	virtual bool loadTextures()                                          = 0;

protected:
	static void convertToNormal(QImage & im);
	static QStringList search_pathes;
	QMap<QString, GLuint> tex_ids[2];
};

class Map {
public:
	Map() {
		bitmap_id            = 0;
		color_amount         = 1.f;
		color_offset         = 0.f;
		animation_frame_rate = -1.f;
		bitmap_scale         = QPointF(1., 1.);
	}
	QString bitmap_path;
	GLuint bitmap_id;
	QPointF bitmap_offset;
	QPointF bitmap_scale;
	float color_amount;
	float color_offset;
	QString animation;
	float animation_frame_rate;
};

class Material {
public:
	Material();
	void apply(QOpenGLShaderProgram * prog);
	void loadTextures(GLTextureManagerBase * tm);
	QString name;
	QColor color_diffuse;
	QColor color_specular;
	QColor color_self_illumination;
	bool glass;
	float transparency;
	float reflectivity;
	float iof;
	float dispersion;
	Map map_diffuse;
	Map map_normal;
	Map map_relief;
	Map map_self_illumination;
	Map map_specularity;
	Map map_specular;
	/*Map map_diffuse_2;
	Map map_diffuse_3;
	Map map_diffuse_4;*/
	GLCubeTexture map_reflection;
};


inline QDataStream & operator<<(QDataStream & s, const Map & m) {
	ChunkStream cs;
	cs.add(1, m.bitmap_path)
		.add(2, m.color_amount)
		.add(3, m.color_offset)
		.add(4, m.animation)
		.add(5, m.animation_frame_rate)
		.add(6, m.bitmap_scale);
	s << cs.data();
	return s;
}
inline QDataStream & operator>>(QDataStream & s, Map & m) {
	ChunkStream cs(s);
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: m.bitmap_path = cs.getData<QString>(); break;
		case 2: m.color_amount = cs.getData<float>(); break;
		case 3: m.color_offset = cs.getData<float>(); break;
		case 4: m.animation = cs.getData<QString>(); break;
		case 5: m.animation_frame_rate = cs.getData<float>(); break;
		case 6: m.bitmap_scale = cs.getData<QPointF>(); break;
		}
	}
	return s;
}

inline QDataStream & operator<<(QDataStream & s, const Material & m) {
	ChunkStream cs;
	cs.add(1, m.name)
		.add(2, m.color_diffuse)
		.add(3, m.color_specular)
		.add(4, m.color_self_illumination)
		.add(5, m.transparency)
		.add(6, m.reflectivity)
		.add(7, m.glass)
		.add(8, m.map_diffuse)
		.add(9, m.map_normal)
		.add(10, m.map_relief)
		.add(11, m.map_specular)
		.add(12, m.map_specularity)
		.add(13, m.map_self_illumination);
	s << qCompress(cs.data());
	return s;
}
inline QDataStream & operator>>(QDataStream & s, Material & m) {
	QByteArray ba;
	s >> ba;
	ba = qUncompress(ba);
	ChunkStream cs(ba);
	while (!cs.atEnd()) {
		switch (cs.read()) {
		case 1: m.name = cs.getData<QString>(); break;
		case 2: m.color_diffuse = cs.getData<QColor>(); break;
		case 3: m.color_specular = cs.getData<QColor>(); break;
		case 4: m.color_self_illumination = cs.getData<QColor>(); break;
		case 5: m.transparency = cs.getData<float>(); break;
		case 6: m.reflectivity = cs.getData<float>(); break;
		case 7: m.glass = cs.getData<bool>(); break;
		case 8: m.map_diffuse = cs.getData<Map>(); break;
		case 9: m.map_normal = cs.getData<Map>(); break;
		case 10: m.map_relief = cs.getData<Map>(); break;
		case 11: m.map_specular = cs.getData<Map>(); break;
		case 12: m.map_specularity = cs.getData<Map>(); break;
		case 13: m.map_self_illumination = cs.getData<Map>(); break;
		}
	}
	return s;
}

#endif // GLMATERIAL_H
