/*
    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 GLTYPES_H
#define GLTYPES_H

#if WIN32 || WIN64 || _WIN32 || _WIN64 || __WIN32__ || __WIN64__
#  define WINDOWS
#endif
#if __QNX__ || __QNXNTO__
#  define QNX
#endif
#ifdef __APPLE__
#  define MAC
#endif
#ifndef WINDOWS
#  ifndef QNX
#	ifndef MAC
#	  define LINUX
#	endif
#  endif
#endif
#if __GNUC__
#  define CC_GCC
#elif _MSC_VER
#  define CC_VC
#endif


#include <QObject>
#ifndef WINDOWS
#  ifdef MAC
#	include <GLUT/glut.h>
#	include <OpenGL/gl.h>
#	include <OpenGL/glu.h>
#  else
#	include <GL/gl.h>
#	include <GL/glext.h>
#	include <GL/glu.h>
#  endif
#endif
#include <QColor>
#include <QDataStream>
#include <QDebug>
#include <QDir>
#include <QFile>
#include <QImage>
#include <QMatrix4x4>
#include <QMutex>
#include <QOpenGLFunctions>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QOpenGLWidget>
#include <QVector2D>
#include <QVector3D>
#include <cmath>
#include <float.h>
#ifndef QNX
#  include <cmath>
#  include <complex>
#else
#  include <complex.h>
#  include <math.h>
#endif
#include <iostream>


#ifdef WINDOWS
#  define GL_MAX_TEXTURE_MAX_ANISOTROPY_EXT 0x84FF
#  define GL_TEXTURE_MAX_ANISOTROPY_EXT     0x84FE
#endif

#ifndef M_PI
#  define M_PI 3.14159265358979323846
#endif
#ifndef M_2PI
#  define M_2PI 6.28318530717958647692
#endif
#ifndef M_PI_3
#  define M_PI_3 1.04719755119659774615
#endif

#ifndef GL_RGBA16F
#  define GL_RGBA16F GL_RGBA16F_ARB
#endif

using std::complex;

#ifndef PIP_VERSION
typedef long long llong;
typedef unsigned char uchar;
typedef unsigned short int ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned long long ullong;
typedef long double ldouble;

const float deg2rad = atanf(1.f) / 45.f;
const float rad2deg = 45.f / atanf(1.f);

#  ifdef WINDOWS
inline int random() {
	return rand();
}
#  endif
#else
#  define random randomi
#endif

#ifdef CC_VC
inline float round(const float & v) {
	return floor(v + 0.5);
}
#endif
inline float randomu() {
	return float(random()) / RAND_MAX;
}

inline const QSizeF operator*(const QSizeF & f, const QSizeF & s) {
	return QSizeF(f.width() * s.width(), f.height() * s.height());
}
#ifndef PIP_VERSION
template<typename T>
inline void piSwap(T & f, T & s) {
	T t(f);
	f = s;
	s = t;
}
template<typename Type>
inline Type piMin(const Type & f, const Type & s) {
	return (f > s) ? s : f;
}
template<typename Type>
inline Type piMin(const Type & f, const Type & s, const Type & t) {
	return (f < s && f < t) ? f : ((s < t) ? s : t);
}
template<typename Type>
inline Type piMax(const Type & f, const Type & s) {
	return (f < s) ? s : f;
}
template<typename Type>
inline Type piMax(const Type & f, const Type & s, const Type & t) {
	return (f > s && f > t) ? f : ((s > t) ? s : t);
}
template<typename Type>
inline Type piClamp(const Type & v, const Type & min, const Type & max) {
	return (v > max ? max : (v < min ? min : v));
}
inline ushort letobe_s(ushort v) {
	return (v << 8) | (v >> 8);
}
#endif
// return [-1, 1]
inline float urand(const float & scale = 1.) {
	return ((float)rand() / RAND_MAX - .5f) * (scale + scale);
}
// return [0, 1]
inline float uprand(const float & scale = 1.) {
	return ((float)rand() / RAND_MAX) * scale;
}
QString readCharsUntilNull(QDataStream & s);
QString findFile(const QString & file, const QStringList & pathes);
inline QColor operator*(const QColor & c, float v) {
	return QColor(piClamp<int>(c.red() * v, 0, 255),
	              piClamp<int>(c.green() * v, 0, 255),
	              piClamp<int>(c.blue() * v, 0, 255),
	              piClamp<int>(c.alpha() * v, 0, 255));
}
inline QColor operator/(const QColor & c, float v) {
	return QColor(piClamp<int>(c.red() / v, 0, 255),
	              piClamp<int>(c.green() / v, 0, 255),
	              piClamp<int>(c.blue() / v, 0, 255),
	              piClamp<int>(c.alpha() / v, 0, 255));
}

// extern __GLWidget__ * currentQGLView;

inline void qglColor(const QColor & c) {
	glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF());
}
void qglMultMatrix(const QMatrix4x4 & m);
void glEnableDepth();
void glDisableDepth();
inline void glResetAllTransforms() {
	glMatrixMode(GL_TEXTURE);
	glLoadIdentity();
	glMatrixMode(GL_PROJECTION);
	glLoadIdentity();
	glMatrixMode(GL_MODELVIEW);
	glLoadIdentity();
}
inline void glClearError() {
	int c = 100;
	while (glGetError() != GL_NO_ERROR && --c > 0)
		glGetError();
}
inline void glClearAccumulation(const QColor & color = Qt::black) {
	glClearAccum(color.redF(), color.greenF(), color.blueF(), color.alphaF());
	glClear(GL_ACCUM_BUFFER_BIT);
}
void glClearFramebuffer(const QColor & color = Qt::black, bool depth = true);
inline void glSetCapEnabled(GLenum cap, bool on = true) {
	if (on)
		glEnable(cap);
	else
		glDisable(cap);
}
inline void glSetLightEnabled(bool on) {
	if (on)
		glEnable(GL_LIGHTING);
	else
		glDisable(GL_LIGHTING);
}
inline void glSetFogEnabled(bool on) {
	if (on)
		glEnable(GL_FOG);
	else
		glDisable(GL_FOG);
}
inline void glSetPolygonMode(GLenum mode) {
	glPolygonMode(GL_FRONT_AND_BACK, mode);
}
void glDrawQuad(QOpenGLShaderProgram * prog = 0,
                QVector4D * corner_dirs     = 0,
                GLfloat x                   = -1.f,
                GLfloat y                   = -1.f,
                GLfloat w                   = 2.f,
                GLfloat h                   = 2.f);
QMatrix4x4 getGLMatrix(GLenum matrix);
void setGLMatrix(QMatrix4x4 matrix);
inline void deleteGLTexture(GLuint & tex) {
	if (tex != 0) glDeleteTextures(1, &tex);
	tex = 0;
}
// #  define QGLCI if (!QOpenGLContext::currentContext()) return; QOpenGLFunctions gf(QOpenGLContext::currentContext());
// #  define QGLC gf.
// inline void glActiveTextureChannel(int channel) {QGLCI gf.glActiveTexture(GL_TEXTURE0 + channel);}
// inline void glDisableTextures(int channels = 8) {QGLCI for (int i = channels - 1; i >= 0; --i) {glActiveTextureChannel(i);
// glDisable(GL_TEXTURE_2D); glDisable(GL_TEXTURE_CUBE_MAP);}} inline void glReleaseTextures(int channels = 8) {QGLCI for (int i = channels
// - 1; i >= 0; --i) {glActiveTextureChannel(i); glBindTexture(GL_TEXTURE_2D, 0); glBindTexture(GL_TEXTURE_CUBE_MAP, 0);}} inline void
// glReleaseFramebuffer() {QGLCI gf.glBindFramebuffer(GL_FRAMEBUFFER, 0);} inline void glReleaseShaders() {QGLCI gf.glUseProgram(0);} inline
// void deleteGLFramebuffer(GLuint & fbo) {QGLCI if (fbo != 0) gf.glDeleteFramebuffers(1, &fbo); fbo = 0;} inline void
// deleteGLRenderbuffer(GLuint & drbo) {QGLCI if (drbo != 0) gf.glDeleteRenderbuffers(1, &drbo); drbo = 0;} inline void
// deleteGLBuffer(GLuint & bo) {QGLCI if (bo != 0) gf.glDeleteBuffers(1, &bo); bo = 0;} inline void deleteGLVertexArray(GLuint & va) {QGLCI
// if (va != 0) gf.glDeleteVertexArrays(1, &va); va = 0;}
void createGLTexture(GLuint & tex, int width, int height, const GLenum & format = GL_RGBA8, const GLenum & target = GL_TEXTURE_2D);
void createGLTexture(GLuint & tex, const QImage & image, const GLenum & format = GL_RGBA8, const GLenum & target = GL_TEXTURE_2D);
inline void qglTranslate(const QVector3D & v) {
	glTranslatef(v.x(), v.y(), v.z());
}
inline void qglScale(const QVector3D & v) {
	glScalef(v.x(), v.y(), v.z());
}
QMatrix4x4 glMatrixPerspective(float angle, float aspect, float near_, float far_);
QImage rotateQImageLeft(const QImage & im);
QImage rotateQImageRight(const QImage & im);
inline QImage rotateQImage180(const QImage & im) {
	return im.mirrored(true, true);
}
// const double deg2rad = atan(1.) / 45.;
// const double rad2deg = 45. / atan(1.);

struct Box3D {
	GLfloat x;
	GLfloat y;
	GLfloat z;
	GLfloat width;
	GLfloat length;
	GLfloat height;
	GLfloat angle_z;
	GLfloat angle_xy;
	GLfloat angle_roll;
	Box3D() { x = y = z = width = length = height = angle_z = angle_xy = angle_roll = 0.f; }
	Box3D(const QVector3D & center, GLfloat hwid, GLfloat hlen, GLfloat hhei) {
		x       = center.x() - hwid;
		y       = center.y() - hlen;
		z       = center.z() - hhei;
		width   = 2 * hwid;
		length  = 2 * hlen;
		height  = 2 * hhei;
		angle_z = angle_xy = angle_roll = 0.f;
	}
	Box3D(const QVector<QVector3D> & points);
	bool isEmpty() const { return (qAbs(width) < 1E-6f) || (qAbs(length) < 1E-6f) || (qAbs(height) < 1E-6f); }
	QVector3D randomPoint() const { return QVector3D(uprand(length) + x, uprand(width) + y, uprand(height) + z); }
	QVector3D pos() const { return QVector3D(x, y, z); }
	QVector3D size() const { return QVector3D(length, width, height); }
	QVector3D center() const { return QVector3D(length / 2.f + x, width / 2.f + y, height / 2.f + z); }
	QVector3D angles() const { return QVector3D(angle_xy, angle_roll, angle_z); }
	QVector<QVector3D> corners() const;
	void setPos(const QVector3D & p) {
		x = p.x();
		y = p.y();
		z = p.z();
	}
	void setAngles(const QVector3D & a) {
		angle_xy   = a.x();
		angle_roll = a.y();
		angle_z    = a.z();
	}
	void setSize(const QVector3D & s) {
		length = s.x();
		width  = s.y();
		height = s.z();
	}
	Box3D & moveTo(const QVector3D & v) {
		x = v.x();
		y = v.y();
		z = v.z();
		return *this;
	}
	Box3D & move(const QVector3D & v) {
		x += v.x();
		y += v.y();
		z += v.z();
		return *this;
	}
	Box3D movedTo(const QVector3D & v) const {
		Box3D t(*this);
		t.x = v.x();
		t.y = v.y();
		t.z = v.z();
		return t;
	}
	Box3D moved(const QVector3D & v) const {
		Box3D t(*this);
		t.x += v.x();
		t.y += v.y();
		t.z += v.z();
		return t;
	}
	Box3D & operator|=(const Box3D & o);
};

inline QDebug operator<<(QDebug d, const Box3D & v) {
	d << "Box3D {start (" << v.x << "," << v.y << "," << v.z << "), size (" << v.length << "," << v.width << "," << v.height << ")}";
	return d;
}

struct Vector3d;

GLfloat dot(const Vector3d & v0, const Vector3d & v1);

struct Vector3d {
	GLfloat x;
	GLfloat y;
	GLfloat z;
	Vector3d(GLfloat x_ = 0., GLfloat y_ = 0., GLfloat z_ = 0.) {
		x = x_;
		y = y_;
		z = z_;
	}
	Vector3d(const QVector3D & v) {
		x = v.x();
		y = v.y();
		z = v.z();
	}
	Vector3d(const QString & str);
	inline void clear() { x = y = z = 0.; }
	inline GLfloat length() const { return sqrtf(x * x + y * y + z * z); }
	inline GLfloat lengthSquared() const { return x * x + y * y + z * z; }
	Vector3d & normalize() {
		GLfloat l = length();
		if (l == 0.f) return *this;
		x /= l;
		y /= l;
		z /= l;
		return *this;
	}
	Vector3d normalized() { return Vector3d(*this).normalize(); }
	Vector3d projectTo(Vector3d dir) {
		dir.normalize();
		return dir * dot(dir, *this);
	}
	Vector3d operator*(const GLfloat v) { return Vector3d(x * v, y * v, z * v); }
	Vector3d operator/(const GLfloat v) { return Vector3d(x / v, y / v, z / v); }
	Vector3d operator+(const GLfloat v) { return Vector3d(x + v, y + v, z + v); }
	Vector3d operator-(const GLfloat v) { return Vector3d(x - v, y - v, z - v); }
	Vector3d operator+(const Vector3d & v) { return Vector3d(x + v.x, y + v.y, z + v.z); }
	Vector3d operator-(const Vector3d & v) { return Vector3d(x - v.x, y - v.y, z - v.z); }
	Vector3d operator-() { return Vector3d(-x, -y, -z); }
	Vector3d & operator*=(const GLfloat & v) {
		x *= v;
		y *= v;
		z *= v;
		return *this;
	}
	Vector3d & operator/=(const GLfloat & v) {
		x /= v;
		y /= v;
		z /= v;
		return *this;
	}
	Vector3d & operator+=(const GLfloat & v) {
		x += v;
		y += v;
		z += v;
		return *this;
	}
	Vector3d & operator-=(const GLfloat & v) {
		x -= v;
		y -= v;
		z -= v;
		return *this;
	}
	Vector3d & operator+=(const Vector3d & v) {
		x += v.x;
		y += v.y;
		z += v.z;
		return *this;
	}
	Vector3d & operator-=(const Vector3d & v) {
		x -= v.x;
		y -= v.y;
		z -= v.z;
		return *this;
	}
	bool operator==(const Vector3d & v) { return x == v.x && y == v.y && z == v.z; }
	QVector3D toQVector3D() const { return QVector3D(x, y, z); }
};

inline Vector3d operator*(const Vector3d & v0, const Vector3d & v1) {
	return Vector3d(v0.y * v1.z - v1.y * v0.z, v1.x * v0.z - v0.x * v1.z, v0.x * v1.y - v1.x * v0.y);
}
inline GLfloat dot(const Vector3d & v0, const Vector3d & v1) {
	return v0.x * v1.x + v0.y * v1.y + v0.z * v1.z;
}

struct Vector2d {
	GLfloat x;
	GLfloat y;
	Vector2d(GLfloat x_ = 0., GLfloat y_ = 0.) {
		x = x_;
		y = y_;
	}
	Vector2d(const Vector3d & v3) {
		x = v3.x;
		y = v3.y;
	}
	Vector2d operator*(const GLfloat v) { return Vector2d(x * v, y * v); }
	Vector2d operator/(const GLfloat v) { return Vector2d(x / v, y / v); }
	Vector2d operator+(const GLfloat v) { return Vector2d(x + v, y + v); }
	Vector2d operator-(const GLfloat v) { return Vector2d(x - v, y - v); }
	Vector2d operator+(const Vector3d & v) { return Vector3d(x + v.x, y + v.y); }
	Vector2d operator-(const Vector3d & v) { return Vector3d(x - v.x, y - v.y); }
	Vector2d operator+(const Vector2d & v) { return Vector2d(x + v.x, y + v.y); }
	Vector2d operator-(const Vector2d & v) { return Vector2d(x - v.x, y - v.y); }
	Vector2d & operator*=(const GLfloat & v) {
		x *= v;
		y *= v;
		return *this;
	}
	Vector2d & operator/=(const GLfloat & v) {
		x /= v;
		y /= v;
		return *this;
	}
	Vector2d & operator+=(const GLfloat & v) {
		x += v;
		y += v;
		return *this;
	}
	Vector2d & operator-=(const GLfloat & v) {
		x -= v;
		y -= v;
		return *this;
	}
	Vector2d & operator+=(const Vector3d & v) {
		x += v.x;
		y += v.y;
		;
		return *this;
	}
	Vector2d & operator-=(const Vector3d & v) {
		x -= v.x;
		y -= v.y;
		;
		return *this;
	}
};

struct Vector3i {
	Vector3i(int p0_ = 0, int p1_ = 0, int p2_ = 0) {
		p0 = p0_;
		p1 = p1_;
		p2 = p2_;
	}
	Vector3i(const QString & str);
	Vector3i movedX(const int & o) { return Vector3i(p0 + o, p1, p2); }
	Vector3i movedY(const int & o) { return Vector3i(p0, p1 + o, p2); }
	Vector3i movedZ(const int & o) { return Vector3i(p0, p1, p2 + o); }
	Vector3i moved(const int & x, const int & y, const int & z) { return Vector3i(p0 + x, p1 + y, p2 + z); }
	int p0;
	int p1;
	int p2;
	bool operator==(const Vector3i & o) const { return p0 == o.p0 && p1 == o.p1 && p2 == o.p2; }
	bool operator!=(const Vector3i & o) const { return p0 != o.p0 || p1 != o.p1 || p2 != o.p2; }
	QVector3D toQVector3D() const { return QVector3D(p0, p1, p2); }
};

inline Vector3i operator+(const Vector3i & f, const Vector3i & s) {
	return Vector3i(f.p0 + s.p0, f.p1 + s.p1, f.p2 + s.p2);
}
inline Vector3i operator-(const Vector3i & f, const Vector3i & s) {
	return Vector3i(f.p0 - s.p0, f.p1 - s.p1, f.p2 - s.p2);
}
inline Vector3i operator/(const Vector3i & f, const int & s) {
	return Vector3i(f.p0 / s, f.p1 / s, f.p2 / s);
}
inline uint qHash(const Vector3i & v) {
	return v.p0 + v.p1 * 1024 + v.p2 * 1024 * 1024;
}
inline QDebug operator<<(QDebug d, const Vector3d & v) {
	d.nospace() << "{" << v.x << ", " << v.y << ", " << v.z << "}";
	return d.space();
}
inline QDebug operator<<(QDebug d, const Vector3i & v) {
	d.nospace() << "{" << v.p0 << ", " << v.p1 << ", " << v.p2 << "}";
	return d.space();
}

inline QDataStream & operator<<(QDataStream & s, const Vector3d & v) {
	s << v.x << v.y << v.z;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, Vector3d & v) {
	s >> v.x >> v.y >> v.z;
	return s;
}
inline QDataStream & operator<<(QDataStream & s, const Vector3i & v) {
	s << v.p0 << v.p1 << v.p2;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, Vector3i & v) {
	s >> v.p0 >> v.p1 >> v.p2;
	return s;
}

QColor colorFromString(const QString & str);
inline float cosABV(const QVector3D & v0, const QVector3D & v1) {
	float l = v0.length() * v1.length();
	if (l == 0.f) return 0.;
	return (QVector3D::dotProduct(v0, v1)) / l;
}
inline QVector3D projection(const QVector3D & v, const QVector3D & to) {
	return to.normalized() * v.length() * cosABV(v, to);
}
QVector3D orthToVector(const QVector3D & v, const float & scale = 1.);
QVector3D rotateVector(const QVector3D & v, const QVector3D & a);
void setVectorLength(QVector3D & v, const float & l);
void lengthenVector(QVector3D & v, const float & l);
inline float squareLength(const QVector3D & from, const QVector3D & to) {
	return (to.x() - from.x()) * (to.x() - from.x()) + (to.y() - from.y()) * (to.y() - from.y()) +
	       (to.z() - from.z()) * (to.z() - from.z());
}
inline QVector3D directionFromAngles(const QVector3D & a) {
	return rotateVector(QVector3D(1., 0., 0.), a);
}
inline float frac(const float & x, const float & b) {
	return x - int(x / b) * b;
}

class GLObjectBase;
class QGLView;
class Light;
class Camera;
class GLTextureManagerBase;

class QGLViewBase {
	friend class GLObjectBase;

public:
	QGLViewBase();
	virtual ~QGLViewBase();
	Camera * camera();
	const Camera * camera() const;
	void setCamera(Camera * camera);
	GLTextureManagerBase * textureManager();

protected:
	virtual void collectLights() = 0;
	Camera * camera_;
	GLTextureManagerBase * textures_manager;
};

#endif // GLTYPES_H
