// #pragma once
#ifndef AnimationrigMisc_H
#define AnimationrigMisc_H

#include <vector>
#include <map>
#include <string>
#include <algorithm>
#include <math.h>
#include <functional>

#include <CommonMemoryStream.h>

using namespace CommonMemoryStream;
using namespace std;

namespace Animrig {
	// enum
	enum class ObjectType
	{
		None,
		Spine,
		Null,
		AnimationBMP
	};
	enum class LayerType
	{
		None,
		AnimBlend4, AnimBlend2, AnimBlend1,
		SeekBlend4, SeekBlend2, SeekBlend1,
		Transform, Collision, Event, Priority,
		AnimationBMP
	};
	enum class MixBlend
	{
		Replace, Add
	};
	enum class Easing
	{
		None,
		Linear,
		OutQuad, InQuad,
		OutInQuad, InOutQuad,
		Terminate,
		Custom0 = 0x80,
		Custom1 = 0x81,
		Custom2 = 0x82,
		Custom3 = 0x83,
		Custom4 = 0x84,
		Custom5 = 0x85,
		Custom6 = 0x86,
		Custom7 = 0x87,
		CustomTerminate = 0x88
	};
	struct EasingData
	{
		float value;
		float prevValue;
		float setValue;
		// state
		bool done;
		float duration;
		float beta;
		double playTime;
		Easing mode;
	};
	enum class Spline
	{
		Linear,
		BSpline,
		Hermite,
		BezierAuto,
		Terminate,
		Custom0 = 0x80,
		Custom1 = 0x81,
		Custom2 = 0x82,
		Custom3 = 0x83,
		Custom4 = 0x84,
		Custom5 = 0x85,
		Custom6 = 0x86,
		Custom7 = 0x87,
		CustomTerminate = 0x88
	};
	enum class RotateOrder
	{
		Pass,
		XYZ, XZY, YXZ, YZX, ZXY, ZYX
	};
	struct Matrix4
	{
		float _11; float _12; float _13; float _14;
		float _21; float _22; float _23; float _24;
		float _31; float _32; float _33; float _34;
		float _41; float _42; float _43; float _44;
	};
	struct Vector3
	{
		float x;
		float y;
		float z;
		float _dummy;
	};
	struct Quaternion
	{
		float x, y, z, w;
	};

	class Misc {
	public:
		// const
		static const float FloatZero; // = 0.000001f;
		static const int InputCount = 32;
		static const float InputFloatIndex; // = 0x10000;
		static const std::wstring _noname; // = L"unnamed";
		
		static bool isInputValue(float f) {
			if (f < InputFloatIndex) return false;
			// range check
			if (!(floor(f - InputFloatIndex + 0.5) < InputCount))
			{
				throw std::exception("IsInputValue : out of range.");
			}
			return true;
		}
		// read write
		static void writeString(MemoryStream *ms, const wstring &s)
		{
			CommonMS::writeString(ms, s);
		}
		static wstring readString(MemoryStream *ms, int max_ = 4096)
		{
			return CommonMS::readString(ms, max_);
		}
		static void writeFloat(MemoryStream *ms, float f)
		{
			CommonMS::writeFloat(ms, f);
		}
		static float readFloat(MemoryStream *ms, float min_ = -0xffffff, float max_ = 0xffffff, bool round_ = false, bool checkspecial = false)
		{
			auto f = CommonMS::readFloat(ms);

			if (round_)
			{
				if (!checkspecial)
				{
					f = min(max(f, min_), max_);
				}
				else
				{
					f = min(max(f, min_), InputFloatIndex + InputCount - 1.0f);
				}
			}
			else
			{
				if ((f < min_) || (f > max_))
				{
					throw exception("ReadFloat : out of range.");
				}
			}

			return f;
		}
		static void writeInteger(MemoryStream *ms, int v)
		{
			CommonMS::writeInt32(ms, v);
		}
		static int readInteger(MemoryStream *ms, int min_ = -0xffffff, int max_ = 0xffffff, bool round_ = false, bool checkspecial = false)
		{
			auto v = CommonMS::readInt32(ms);

			if (round_)
			{
				if (!checkspecial)
				{
					v = min(max(v, min_), max_);
				}
				else
				{
					// unsupport
					// v = Math.Min(Math.Max(v, min_), InputIntIndex + InputCount - 1.0f);
					v = min(max(v, min_), max_);
				}
			}
			else
			{
				if ((v < min_) || (v > max_))
				{
					throw exception("ReadInteger : out of range.");
				}
			}

			return v;
		}
		static void writeBool(MemoryStream *ms, bool b)
		{
			CommonMS::writeBool(ms, b);
		}
		static bool readBool(MemoryStream *ms)
		{
			return CommonMS::readBool(ms);
		}
		static void writeHead(MemoryStream *ms, const wstring &s)
		{
			CommonMS::writeHead(ms, s);
		}
		static void readHead(MemoryStream *ms, const wstring &s)
		{
			CommonMS::readHead(ms, s);
		}
		static void writeStringList(MemoryStream *ms, vector<wstring> &sl)
		{
			writeInteger(ms, (int)sl.size());
			for (auto s : sl) {
				writeString(ms,s);
			}
		}
		static void readStringList(MemoryStream *ms, vector<wstring> &sl)
		{
			sl.clear();

			auto count = readInteger(ms, 0, 0xffffff);
			sl.reserve(count);
			for (auto i = 0; i < count; i++)
			{
				sl.emplace_back(readString(ms));
			}
		}
		// data skip function
		static int64_t writeSkipBegin(MemoryStream *ms)
		{
			auto p = ms->position();
			int diff = 0;
			writeInteger(ms, diff);
			return p;
		}
		static void writeSkipEnd(MemoryStream *ms, int64_t pos)
		{
			auto p = ms->position();
			auto diff = p - pos;
			ms->position(pos);
			writeInteger(ms, (int)diff);
			ms->position(p);
		}
		static int64_t readSkipBegin(MemoryStream *ms)
		{
			auto p = ms->position();
		    auto diff = readInteger(ms, 0, 0xffffff);
			return p + diff;
		}
		static void jumpSkipEnd(MemoryStream *ms, int64_t pos)
		{
			// optimize
			if (ms->position() == pos) return;

			ms->position(pos);
		}
		// type <--> string
		static wstring objectTypeToString(ObjectType t)
		{
			switch (t)
			{
				case ObjectType::None: return L"none";
				case ObjectType::Null: return L"null";
				case ObjectType::Spine: return L"spine";
				case ObjectType::AnimationBMP: return L"abmp";
			}

			throw exception("ObjectTypeToString : detect undefined type.");
		}
		static ObjectType stringToObjectType(const wstring &s)
		{
			if (s == L"none") return ObjectType::None;
			else if (s == L"null") return ObjectType::Null;
			else if (s == L"spine") return ObjectType::Spine;
			else if (s == L"abmp") return ObjectType::AnimationBMP;

			throw exception("StringToObjectType : detect undefined type");
		}
		static wstring layerTypeToString(LayerType t)
		{
			switch (t)
			{
			case LayerType::None: return L"none";
			case LayerType::AnimBlend4: return L"anim4";
			case LayerType::AnimBlend1: return L"anim1";
			case LayerType::SeekBlend4: return L"seek4";
			case LayerType::SeekBlend1: return L"seek1";
			case LayerType::Transform: return L"transform";
			case LayerType::Collision: return L"collision";
			case LayerType::Event: return L"event";
			case LayerType::Priority: return L"priority";
			case LayerType::AnimationBMP: return L"abmp";
			}
			throw exception("LayerTypeToString : detect undefined type.");
		}
		static LayerType stringToLayerType(const wstring &s)
		{
			if (s == L"none") return LayerType::None;
			else if (s == L"spine4") return LayerType::AnimBlend4;    // compati
			else if (s == L"spine1") return LayerType::AnimBlend1;
			else if (s == L"anim4") return LayerType::AnimBlend4;
			else if (s == L"anim1") return LayerType::AnimBlend1;
			else if (s == L"seek4") return LayerType::SeekBlend4;
			else if (s == L"seek1") return LayerType::SeekBlend1;
			else if (s == L"transform") return LayerType::Transform;
			else if (s == L"collision") return LayerType::Collision;
			else if (s == L"event") return LayerType::Event;
			else if (s == L"priority") return LayerType::Priority;
			else if (s == L"abmp") return LayerType::AnimationBMP;

			throw exception("StringToLayerType : detect undefined type");
		}
		// easing
		static Easing readEasing(MemoryStream *ms)
		{
			auto v = readInteger(ms, 0, 0xff, true);
			if (v < (int)Easing::Custom0)
			{
				v = min(v, (int)Easing::Terminate - 1);
				return (Easing)v;
			}
			else
			{
				v = min(v, (int)Easing::CustomTerminate - 1);
				return (Easing)v;
			}
		}

		using EasingModeFunction = std::function<float(float, float, float, float)>;
		static float easingFloat(float x1, float x2, float a, float b, Easing mode)
		{
			if (x1 == x2) return x1;

			switch (mode)
			{
			case Easing::None: return x1;
			case Easing::Linear: return easingLinear(x1, x2, a);
			case Easing::OutQuad: return easingOutQuad(x1, x2, a, b);
			case Easing::InQuad: return easingOutQuad(x1, x2, a, b);
			case Easing::OutInQuad: return easingSplit(x1, x2, a, b, easingOutQuad, easingInQuad);
			case Easing::InOutQuad: return easingSplit(x1, x2, a, b, easingInQuad, easingOutQuad);
			}

			return x1;
		}
		static float easingSplit(float x1, float x2, float a, float b, EasingModeFunction f1, EasingModeFunction f2)
		{
			float x3 = (x1 + x2) * 0.5f;
			if (a < 0.5)
				return f1(x1, x3, a * 2.0f, b);
			else
				return f2(x3, x2, (a - 0.5f) * 2.0f, b);
		}
		static float easingLinear(float x1, float x2, float a)
		{
			return x1 + (x2 - x1) * a;
		}
		static float easingOutQuad(float x1, float x2, float a, float b)
		{
			float ia = 1.0f - a;
			if (b < 1.0)
			{
				float v1 = 1.0f - ia;
				float v2 = 1.0f - (ia * ia);
				a = v1 + (v2 - v1) * b;
			}
			else
			{
				float v1 = 1.0f - (ia * ia);
				float v2 = 1.0f - (ia * ia * ia * ia);
				a = v1 + (v2 - v1) * (b - 1.0f);
			}
			return x1 + (x2 - x1) * a;
		}
		static float easingInQuad(float x1, float x2, float a, float b)
		{
			return x1 + easingOutQuad(x2 - x1, 0.0f, 1.0f - a, b);
		}
		static float easingOutExpo(float x1, float x2, float a, float b)
		{
			float d = x2 - x1;
			return d * (-1.0f * (float)pow(2.0f, -10.0f * a) + 1.0f) + x1;
		}
		static float easingInExpo(float x1, float x2, float a, float b)
		{
			float d = x2 - x1;
			return d * (float)pow(2.0f, 10.0f * (a - 1.0f)) + x1;
		}
		// easing data
		static const int EasingSaveVersion = 1;
		static void easingValueClear(EasingData &d)
		{
			d.done = true;
			d.mode = Easing::None;
			d.duration = 0.0f;
			d.beta = 1.0f;
			d.playTime = 0.0f;

			d.value = 0.0f;
			d.setValue = 0.0f;
			d.prevValue = 0.0f;
		}
		static void easingValueUpdate(EasingData &d, double deltatime)
		{
			if (d.done) return;

			if (d.duration < 0.0001f)
			{
				d.value = d.setValue;
				d.playTime = d.duration;
				d.done = true;
				return;
			}

			// update time
			d.playTime = d.playTime + deltatime;
			if (d.playTime >= d.duration)
			{
				d.value = d.setValue;
				d.playTime = d.duration;
				d.done = true;
				return;
			}

			float a = (float)d.playTime / d.duration;
			d.value = easingFloat(d.prevValue, d.setValue, a, d.beta, d.mode);
		}
		static void easingValueEase(EasingData &d, float v, float duration = 0.0f, Easing mode = Easing::Linear, float beta = 1.0f)
		{
			// no time
			if (duration < 0.0001f)
			{
				d.value = v;
				d.setValue = v;
				d.prevValue = v;
				d.done = true;
				return;
			}

			// optimize same value
			if (d.setValue == v)
				if (d.duration == duration)
					if (d.beta == beta)
						if (d.mode == mode)
							return;

			d.prevValue = d.value;
			d.setValue = v;
			d.playTime = 0.0f;
			d.duration = duration;
			d.beta = beta;
			d.mode = mode;

			d.done = false;
		}
		static void easingValueSaveToBinary(EasingData &d, MemoryStream *ms)
		{
			writeHead(ms, L"easing");
			writeInteger(ms, EasingSaveVersion);
			if (d.done)
			{
				// value only
				// done
				writeBool(ms, true);
				// value
				writeFloat(ms, d.value);
				return;
			}
			else
			{
				// value + ease param
				// done
				writeBool(ms, false);
				// value
				writeFloat(ms, d.value);
				// ease state
				writeFloat(ms, d.prevValue);
				writeFloat(ms, d.setValue);
				writeFloat(ms, d.duration);
				writeFloat(ms, d.beta);
				writeFloat(ms, (float)d.playTime);
				writeInteger(ms, (int)d.mode);
			}
		}
		static bool easingValueLoadFromBinary(EasingData &d, MemoryStream *ms)
		{
			try
			{
				readHead(ms, L"easing");
				auto ver = readInteger(ms, 0, EasingSaveVersion);
				auto done = readBool(ms);
				if (done)
				{
					// value only
					d.done = true;
					auto f = readFloat(ms, -0xffffff, 0xffffff);
					easingValueEase(d, f);
				}
				else
				{
					// value + ease param
					d.done = true;
					d.value = readFloat(ms, -0xffffff, 0xffffff);
					//
					d.prevValue = readFloat(ms, -0xffffff, 0xffffff);
					d.setValue = readFloat(ms, -0xffffff, 0xffffff);
					d.duration = readFloat(ms);
					d.beta = readFloat(ms);
					d.playTime = readFloat(ms);
					d.mode = readEasing(ms);
				}
			}
			catch (...)
			{
				return false;
			}
			return true;

		}
		// spline
		static Spline readSpline(MemoryStream *ms)
		{
			auto v = readInteger(ms, 0, 0xff, true);

			if (v < (int)Spline::Custom0)
			{
				v = min(v, (int)Spline::Terminate - 1);
				return (Spline)v;
			}
			else
			{
				v = min(v, (int)Spline::CustomTerminate - 1);
				return (Spline)v;
			}
		}
		static float splineFloat(float x0, float x1, float x2, float x3, float a, float b, Spline mode)
		{
			if ((x1 == x2) && (x0 == x3) && (x1 == x2))
			{
				return x1;
			}

			switch (mode)
			{
			case Spline::Linear: return x1 + (x2 - x1) * a;
			case Spline::BSpline:
			{
				float ia = 1.0f - a;
				float t1 = a * a;
				float t2 = t1 * a;
				return ((x0 * ia * ia * ia) + x1 * (3.0f * t2 - 6.0f * t1 + 4.0f) + x2 * (-3.0f * (t2 - t1 - a) + 1.0f) + x3 * t2) / 6.0f;
			}
			case Spline::Hermite:
			{
				float ia = 1.0f - a;
				float id = a - 1.0f;
				// fake class line
				float t1 = (x2 - x0) * b;
				float t2 = (x3 - x1) * b;
				// slow down
				//if x1 = x2 then
				//begin
				//  t1 := t1 * 0.25;
				//  t2 := t2 * 0.25;
				//end;
				return
					x1 * (id * id) * (a * 2.0f + 1.0f) +
					x2 * (a * a) * (3.0f - a * 2.0f) +
					t1 * (ia * ia) * (a)+
					t2 * (id) * (a * a);
			}
			case Spline::BezierAuto:
			{
				// auto anchor point
				float ia = 1.0f - a;
				float t1 = ((x1 - x0) + (x2 - x1)) * 0.25f * b;
				float t2 = ((x2 - x3) + (x1 - x2)) * 0.25f * b;
				t1 = x1 + t1;
				t2 = x2 + t2;
				// bezier
				return ia * ia * ia * x1 + 3.0f * ia * ia * a * t1 + 3.0f * ia * a * a * t2 + a * a * a * x2;
			}
			}

			return x1 + (x2 - x1) * a;
		}
		// color
		static int linearColor(int c1, int c2, float a)
		{
			int aa = (int)floor(a * 256 + 0.5);

			int rr1 = (c1 >> 16) & 0xff;
			int gg1 = (c1 >> 8) & 0xff;
			int bb1 = c1 & 0xff;

			int rr2 = (c2 >> 16) & 0xff;
			int gg2 = (c2 >> 8) & 0xff;
			int bb2 = c2 & 0xff;

			rr1 = rr1 + (rr2 - rr1) * aa / 256;
			gg1 = gg1 + (gg2 - gg1) * aa / 256;
			bb1 = bb1 + (bb2 - bb1) * aa / 256;

			return (rr1 << 16) | (gg1 << 8) | bb1;
		}
		static int multiplyColor(int c1, int c2, float a)
		{
			int aa = (int)floor(a * 256 + 0.5);

			int rr1 = (c1 >> 16) & 0xff;
			int gg1 = (c1 >> 8) & 0xff;
			int bb1 = c1 & 0xff;

			int rr2 = (c2 >> 16) & 0xff;
			int gg2 = (c2 >> 8) & 0xff;
			int bb2 = c2 & 0xff;

			rr2 = rr2 + (0xff - rr2) * aa / 256;
			gg2 = gg2 + (0xff - gg2) * aa / 256;
			bb2 = bb2 + (0xff - bb2) * aa / 256;

			rr1 = rr1 * rr2 / 256;
			gg1 = gg1 * gg2 / 256;
			bb1 = bb1 * bb2 / 256;

			return (rr1 << 16) | (gg1 << 8) | bb1;
		}
		// matrix
		static const float PI; // = 3.14159265358979323846f;
		static const Matrix4 IdentityMatrix4;
		static const Vector3 ZeroVector3;
		static RotateOrder readRotateOrder(MemoryStream *ms)
		{
			return (RotateOrder)readInteger(ms, 0, 6 + 1, true);
		}
		static void v3SetVector(Vector3 &v, float x, float y, float z)
		{
			v.x = x;
			v.y = y;
			v.z = z;
			v._dummy = 0;
		}
		static float v3DotProduct(const Vector3 &v1, const Vector3 &v2)
		{
			return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
		}
		static Vector3 v3CrossProduct(const Vector3 &v1, const Vector3 &v2)
		{
			Vector3 r;
			r.x = v1.y * v2.z - v1.z * v2.y;
			r.y = v1.z * v2.x - v1.x * v2.z;
			r.z = v1.x * v2.y - v1.y * v2.x;
			r._dummy = 0.0f;

			return r;
		}
		static void v3Normalize(Vector3 &v)
		{
			double len = sqrt(v.x * v.x + v.y * v.y + v.z * v.z);

			if (abs(len) < 0.000001f)
			{
				return;
			}
			else
			{
				len = 1 / len;
				v.x = (float)(v.x * len);
				v.y = (float)(v.y * len);
				v.z = (float)(v.z * len);
				//v.x = (float)(v.x / len);
				//v.y = (float)(v.y / len);
				//v.z = (float)(v.z / len);
			}
		}
		static Vector3 m4Transform(Vector3 &v, Matrix4 &m)
		{
			Vector3 o;
			o.x = v.x * m._11 + v.y * m._21 + v.z * m._31 + m._41;
			o.y = v.x * m._12 + v.y * m._22 + v.z * m._32 + m._42;
			o.z = v.x * m._13 + v.y * m._23 + v.z * m._33 + m._43;
			o._dummy = 1.0f;
			return o;
		}
		static void m4SetIdentity(Matrix4 &m)
		{
			m = IdentityMatrix4;
		}
		static void m4PushMove(Matrix4 &m, float x, float y, float z)
		{
			if ((x == 0.0f) && (y == 0.0f) && (z == 0.0f)) return;
			//      1         2         3        4
			// 1
			// 2
			// 3
			// 4    x         y         z        1.0
			float v4 = m._14;
			m._11 = m._11 + v4 * x;
			m._12 = m._12 + v4 * y;
			m._13 = m._13 + v4 * z;

			v4 = m._24;
			m._21 = m._21 + v4 * x;
			m._22 = m._22 + v4 * y;
			m._23 = m._23 + v4 * z;

			v4 = m._34;
			m._31 = m._31 + v4 * x;
			m._32 = m._32 + v4 * y;
			m._33 = m._33 + v4 * z;

			v4 = m._44;
			m._41 = m._41 + v4 * x;
			m._42 = m._42 + v4 * y;
			m._43 = m._43 + v4 * z;
		}
		static void m4PushRotateX(Matrix4 &m, float r)
		{
			if (r == 0.0f) return;

			//      1         2         3        4
			// 1
			// 2              c         s
			// 3              -s        c
			// 4
			float s = (float)sin(r * 2.0f * PI);
			float c = (float)cos(r * 2.0f * PI);

			float v2 = m._12;
			float v3 = m._13;
			m._12 = v2 * c + v3 * -s;
			m._13 = v2 * s + v3 * c;

			v2 = m._22;
			v3 = m._23;
			m._22 = v2 * c + v3 * -s;
			m._23 = v2 * s + v3 * c;

			v2 = m._32;
			v3 = m._33;
			m._32 = v2 * c + v3 * -s;
			m._33 = v2 * s + v3 * c;

			v2 = m._42;
			v3 = m._43;
			m._42 = v2 * c + v3 * -s;
			m._43 = v2 * s + v3 * c;
		}
		static void m4PushRotateY(Matrix4 &m, float r)
		{
			if (r == 0.0f) return;

			//      1         2         3        4
			// 1    c                   -s
			// 2
			// 3    s                   c
			// 4
			float s = (float)sin(r * 2.0f * PI);
			float c = (float)cos(r * 2.0f * PI);

			float v1 = m._11;
			float v3 = m._13;
			m._11 = v1 * c + v3 * s;
			m._13 = v1 * -s + v3 * c;

			v1 = m._21;
			v3 = m._23;
			m._21 = v1 * c + v3 * s;
			m._23 = v1 * -s + v3 * c;

			v1 = m._31;
			v3 = m._33;
			m._31 = v1 * c + v3 * s;
			m._33 = v1 * -s + v3 * c;

			v1 = m._41;
			v3 = m._43;
			m._41 = v1 * c + v3 * s;
			m._43 = v1 * -s + v3 * c;
		}
		static void m4PushRotateZ(Matrix4 &m, float r)
		{
			if (r == 0.0f) return;

			//      1         2         3        4
			// 1    c         s
			// 2    -s        c
			// 3
			// 4
			float s = (float)sin(r * 2.0f * PI);
			float c = (float)cos(r * 2.0f * PI);

			float v1 = m._11;
			float v2 = m._12;
			m._11 = v1 * c + v2 * -s;
			m._12 = v1 * s + v2 * c;

			v1 = m._21;
			v2 = m._22;
			m._21 = v1 * c + v2 * -s;
			m._22 = v1 * s + v2 * c;

			v1 = m._31;
			v2 = m._32;
			m._31 = v1 * c + v2 * -s;
			m._32 = v1 * s + v2 * c;

			v1 = m._41;
			v2 = m._42;
			m._41 = v1 * c + v2 * -s;
			m._42 = v1 * s + v2 * c;
		}
		static void m4PushRotate(Matrix4 &m, float x, float y, float z, RotateOrder mode)
		{
			switch (mode)
			{
				case RotateOrder::XYZ:
				{
					m4PushRotateX(m, x);
					m4PushRotateY(m, y);
					m4PushRotateZ(m, z);
					break;
				}
				case RotateOrder::XZY:
				{
					m4PushRotateX(m, x);
					m4PushRotateZ(m, z);
					m4PushRotateY(m, y);
					break;
				}
				case RotateOrder::YXZ:
				{
					m4PushRotateY(m, y);
					m4PushRotateX(m, x);
					m4PushRotateZ(m, z);
					break;
				}
				case RotateOrder::YZX:
				{
					m4PushRotateY(m, y);
					m4PushRotateZ(m, z);
					m4PushRotateX(m, x);
					break;
				}
				case RotateOrder::ZXY:
				{
					m4PushRotateZ(m, z);
					m4PushRotateX(m, x);
					m4PushRotateY(m, y);
					break;
				}
				case RotateOrder::ZYX:
				case RotateOrder::Pass:
				{
					m4PushRotateZ(m, z);
					m4PushRotateY(m, y);
					m4PushRotateX(m, x);
					break;
				}
			}
		}
		static void m4PushScale(Matrix4 &m, float x, float y, float z)
		{
			if ((x == 1.0f) && (y == 1.0f) && (z == 1.0f)) return;

			//      1         2         3        4
			// 1    x
			// 2              y
			// 3                        z
			// 4                                 1.0
			m._11 = m._11 * x;
			m._12 = m._12 * y;
			m._13 = m._13 * z;

			m._21 = m._21 * x;
			m._22 = m._22 * y;
			m._23 = m._23 * z;

			m._31 = m._31 * x;
			m._32 = m._32 * y;
			m._33 = m._33 * z;

			m._41 = m._41 * x;
			m._42 = m._42 * y;
			m._43 = m._43 * z;
		}
		static void m4PushMatrix(Matrix4 &m1, const Matrix4 &m2)
		{
			float v1 = m1._11;
			float v2 = m1._12;
			float v3 = m1._13;
			float v4 = m1._14;
			m1._11 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
			m1._12 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
			m1._13 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
			m1._14 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

			v1 = m1._21;
			v2 = m1._22;
			v3 = m1._23;
			v4 = m1._24;
			m1._21 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
			m1._22 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
			m1._23 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
			m1._24 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

			v1 = m1._31;
			v2 = m1._32;
			v3 = m1._33;
			v4 = m1._34;
			m1._31 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
			m1._32 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
			m1._33 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
			m1._34 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;

			v1 = m1._41;
			v2 = m1._42;
			v3 = m1._43;
			v4 = m1._44;
			m1._41 = m2._11 * v1 + m2._21 * v2 + m2._31 * v3 + m2._41 * v4;
			m1._42 = m2._12 * v1 + m2._22 * v2 + m2._32 * v3 + m2._42 * v4;
			m1._43 = m2._13 * v1 + m2._23 * v2 + m2._33 * v3 + m2._43 * v4;
			m1._44 = m2._14 * v1 + m2._24 * v2 + m2._34 * v3 + m2._44 * v4;
		}
		// matrix to TRS
		static Vector3 m4ExtractMove(const Matrix4 &m)
		{
			Vector3 v;
			v.x = m._41;
			v.y = m._42;
			v.z = m._43;
			v._dummy = 0.0f;

			return v;
		}
		static Vector3 m4ExtractScale(const Matrix4 &m)
		{
			Vector3 v;
			v.x = (float)sqrt(m._11 * m._11 + m._12 * m._12 + m._13 * m._13 + m._14 * m._14);
			v.y = (float)sqrt(m._21 * m._21 + m._22 * m._22 + m._23 * m._23 + m._24 * m._24);
			v.z = (float)sqrt(m._31 * m._31 + m._32 * m._32 + m._33 * m._33 + m._34 * m._34);
			v._dummy = 0.0f;
			return v;
		}
		static Quaternion q4CreateFromAxisAngle(const Vector3 &axis, float angle)
		{
			float halfangle = angle * 0.5f;
			float s = sin(halfangle);
			float c = cos(halfangle);

			Quaternion q;
			q.x = axis.x * s;
			q.y = axis.y * s;
			q.z = axis.z * s;
			q.w = c;
			return q;
		}
		static Quaternion q4LookAt(const Vector3 &facedir, const Vector3 &forward, const Vector3 &up)
		{
			auto nfacedir = facedir;
			v3Normalize(nfacedir);

			auto nforward = forward;
			v3Normalize(nforward);

			auto nup = up;
			v3Normalize(nup);

			return q4LookAtNormalized(nfacedir, nforward, nup);
		}
		static Quaternion q4LookAtNormalized(const Vector3 &facedir, const Vector3 &forward, const Vector3 &up)
		{
			float dot = v3DotProduct(facedir, forward);
			if (abs(dot - (-1.0f)) < 0.000001f)
			{
				Quaternion q;
				q.x = up.x;
				q.y = up.y;
				q.z = up.z;
				q.w = (float)Misc::PI;
				return q;
			}
			if (abs(dot - (1.0f)) < 0.000001f)
			{
				Quaternion q;
				q.x = 0.0f;
				q.y = 0.0f;
				q.z = 0.0f;
				q.w = 1.0f;
				return q;
			}
			auto angle = (float)acos(dot);
			Vector3 axis = v3CrossProduct(facedir, forward);
			v3Normalize(axis);
			return q4CreateFromAxisAngle(axis, angle);
		}

		// misc
		static wstring stringToLower(const wstring &s) {
			wstring buf = s;
			transform(s.cbegin(), s.cend(), buf.begin(), tolower);
			return buf;
		}
		static int toIntDef(const wstring &s, int b)
		{
			try {
				return stoi(s);
			}
			catch (...) {
				return b;
			}
		}
		static float toFloatDef(const wstring &s, float b)
		{
			try
			{
				return (float)stod(s);
			}
			catch (...)
			{
				return b;
			}
		}
		static wstring slice(const wstring &s, int offset, int count)
		{
			auto length = (int)s.length();
			if (length < 1) return L"";
			if (count < 1) return L"";
			// backward
			if (offset < 0) offset = length - offset;
			// check
			if (offset < 0) return L"";
			if (offset >= length) return L"";
			if (offset + count > length) count = length - offset;

			return s.substr(offset, count);
		}
		static vector<wstring>* divideString(char delimiter, const wstring &src)
		{
			auto sl = new vector<wstring>();

			auto len = (int)src.length();

			wstring s = L"";
			for (auto i = 0; i < len; i++)
			{
				if (src[i] == delimiter)
				{
					sl->emplace_back(s);
					s = L"";
					continue;
				}
				s = s + src[i];
			}
			if (s != L"") sl->emplace_back(s);

			return sl;
		}
		static void convertKeyValue(map<wstring, wstring> &dic, const vector<wstring> &sl)
		{
			dic.clear();

			for(auto &s : sl)
			{
				auto p = (int)s.find('=');
				if (p < 0) continue;
				auto s1 = slice(s, 0, p);
				auto s2 = slice(s, p + 1, 4096);
				dic.emplace(std::make_pair(s1, s2));
			}
		}
		static void getSortedIndexes(int *tbl, const double *data, int count, bool ascending = true)
		{
			if (count == 0) return;

			for (auto i = 0; i < count; i++)
			{
				tbl[i] = i;
			}
			if (count == 1) return;

			if (ascending)
			{
				for (auto i = count - 1 - 1; i >= 0; i--)
				{
					for (auto j = i; j < count - 1; j++)
					{
						if (data[tbl[j]] > data[tbl[j + 1]])
						{
							int t = tbl[j];
							tbl[j] = tbl[j + 1];
							tbl[j + 1] = t;
						}
					}
				}
			}
			else
			{
				for (auto i = count - 1 - 1; i >= 0; i--)
				{
					for (auto j = i; j < count - 1; j++)
					{
						if (data[tbl[j]] < data[tbl[j + 1]])
						{
							int t = tbl[j];
							tbl[j] = tbl[j + 1];
							tbl[j + 1] = t;
						}
					}
				}
			}
		}
	};
}

#endif