/*
    QGLView Types
    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 <OpenGL/gl.h>
// #    include <OpenGL/glu.h>
// #    include <GLUT/glut.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 <QOpenGLExtraFunctions>
#include <QOpenGLShader>
#include <QOpenGLShaderProgram>
#include <QVector2D>
#include <QVector3D>
#include <cmath>
#include <float.h>
#include <qopenglext.h>
#ifndef QNX
#  include <cmath>
#  include <complex>
#else
#  include <complex.h>
#  include <math.h>
#endif
#include "pimathbase.h"
#include "qglengine_core_export.h"
#include "qglengine_version.h"

#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);
}
inline uint letobe_i(const uint & v) {
	return (v >> 24) | ((v >> 8) & 0xFF00) | ((v << 8) & 0xFF0000) | ((v << 24) & 0xFF000000);
}
#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;
}
QGLENGINE_CORE_EXPORT QString readCharsUntilNull(QDataStream & s);
QGLENGINE_CORE_EXPORT 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));
}


inline void qglColor(const QColor & c) {
	glColor4f(c.redF(), c.greenF(), c.blueF(), c.alphaF());
}
inline void glClearError() {
	int c = 100;
	while (glGetError() != GL_NO_ERROR && --c > 0)
		glGetError();
}
inline void glSetCapEnabled(GLenum cap, bool on = true) {
	if (on)
		glEnable(cap);
	else
		glDisable(cap);
}
inline void glSetPolygonMode(GLenum mode) {
	glPolygonMode(GL_FRONT_AND_BACK, mode);
}
inline void deleteGLTexture(QOpenGLExtraFunctions * f, GLuint & tex) {
	if (tex != 0) f->glDeleteTextures(1, &tex);
	tex = 0;
}
QGLENGINE_CORE_EXPORT void glEnableDepth();
QGLENGINE_CORE_EXPORT void glDisableDepth();
QGLENGINE_CORE_EXPORT void glClearFramebuffer(const QColor & color = Qt::black, bool depth = true);
QGLENGINE_CORE_EXPORT void glDrawQuad(QOpenGLShaderProgram * prog = nullptr,
									  QVector4D * corner_dirs     = nullptr,
									  GLfloat x                   = -1.f,
									  GLfloat y                   = -1.f,
									  GLfloat w                   = 2.f,
									  GLfloat h                   = 2.f);
QGLENGINE_CORE_EXPORT void createGLTexture(QOpenGLExtraFunctions * f,
										   GLuint & tex,
										   int width,
										   int height,
										   const GLenum & format = GL_RGBA,
										   const GLenum & target = GL_TEXTURE_2D);
QGLENGINE_CORE_EXPORT void createGLTexture(QOpenGLExtraFunctions * f,
										   GLuint & tex,
										   const QImage & image,
										   const GLenum & format = GL_RGBA,
										   const GLenum & target = GL_TEXTURE_2D);
QGLENGINE_CORE_EXPORT QMatrix4x4 glMatrixPerspective(float angle, float aspect, float near_);
QGLENGINE_CORE_EXPORT QImage rotateQImageLeft(const QImage & im);
QGLENGINE_CORE_EXPORT QImage rotateQImageRight(const QImage & im);
inline QImage rotateQImage180(const QImage & im) {
	return im.mirrored(true, true);
}

class QGLView;
class MouseController;
class ObjectBase;
class AimedObject;
class Light;
class Camera;
class Texture;
class CubeTexture;
class Map;
class Material;
class TextureManager;
class Texture2DArray;
class Framebuffer;
class FramebufferMipmap;
class FramebufferEffectBase;
class VertexObject;
class Mesh;
class Scene;
class RendererBase;
class Renderer;
class RendererMaterial;
class RendererService;
class RendererSelection;

enum RenderPass {
	rpSolid,
	rpTransparent,
};

typedef QList<ObjectBase *> ObjectBaseList;

struct QGLENGINE_CORE_EXPORT 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;
}


#pragma pack(push, 1)
struct QGLENGINE_CORE_EXPORT Vector2i {
	Vector2i(int p0_ = 0, int p1_ = 0) {
		p0 = p0_;
		p1 = p1_;
	}
	Vector2i(const QString & str);
	Vector2i movedX(const int & o) { return Vector2i(p0 + o, p1); }
	Vector2i movedY(const int & o) { return Vector2i(p0, p1 + o); }
	Vector2i moved(const int & x, const int & y) { return Vector2i(p0 + x, p1 + y); }
	GLint p0;
	GLint p1;
	bool operator==(const Vector2i & o) const { return p0 == o.p0 && p1 == o.p1; }
	bool operator!=(const Vector2i & o) const { return p0 != o.p0 || p1 != o.p1; }
	void operator+=(int v) {
		p0 += v;
		p1 += v;
	}
	QVector2D toQVector2D() const { return QVector2D(p0, p1); }
};
#pragma pack(pop)

inline Vector2i operator+(const Vector2i & f, const Vector2i & s) {
	return Vector2i(f.p0 + s.p0, f.p1 + s.p1);
}
inline Vector2i operator-(const Vector2i & f, const Vector2i & s) {
	return Vector2i(f.p0 - s.p0, f.p1 - s.p1);
}
inline Vector2i operator/(const Vector2i & f, const int & s) {
	return Vector2i(f.p0 / s, f.p1 / s);
}
inline uint qHash(const Vector2i & v) {
	return v.p0 ^ ((v.p1 << 8) | (v.p1 >> 24));
}
inline QDebug operator<<(QDebug d, const Vector2i & v) {
	d.nospace() << "{" << v.p0 << ", " << v.p1 << "}";
	return d.space();
}

inline QDataStream & operator<<(QDataStream & s, const Vector2i & v) {
	s << v.p0 << v.p1;
	return s;
}
inline QDataStream & operator>>(QDataStream & s, Vector2i & v) {
	s >> v.p0 >> v.p1;
	return s;
}


#pragma pack(push, 1)
struct QGLENGINE_CORE_EXPORT 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); }
	GLint p0;
	GLint p1;
	GLint 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; }
	void operator+=(int v) {
		p0 += v;
		p1 += v;
		p2 += v;
	}
	QVector3D toQVector3D() const { return QVector3D(p0, p1, p2); }
};
#pragma pack(pop)

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 << 8) | (v.p1 >> 24)) ^ ((v.p2 << 16) | (v.p2 >> 16));
}
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 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;
}


QGLENGINE_CORE_EXPORT QVector3D vectorFromString(const QString & str);
QGLENGINE_CORE_EXPORT QColor colorFromString(const QString & str);
inline QVector4D QColor2QVector(const QColor & c) {
	return QVector4D(c.redF(), c.greenF(), c.blueF(), c.alphaF());
}
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 void normalizeAngleRad(float & a) {
	while (a < 0.)
		a += M_2PI;
	while (a >= M_2PI)
		a -= M_2PI;
}
inline void normalizeAngleDeg360(float & a) {
	while (a < 0.)
		a += 360.;
	while (a >= 360.)
		a -= 360.;
}
inline QVector3D projection(const QVector3D & v, const QVector3D & to) {
	return to.normalized() * v.length() * cosABV(v, to);
}
QGLENGINE_CORE_EXPORT QVector3D orthToVector(const QVector3D & v, const float & scale = 1.);
QGLENGINE_CORE_EXPORT QVector3D rotateVector(const QVector3D & v, const QVector3D & a);
QGLENGINE_CORE_EXPORT void setVectorLength(QVector3D & v, const float & l);
QGLENGINE_CORE_EXPORT 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;
}
QImage generateNoise(QSize sz);


#endif // GLTYPES_H
