#pragma once

#include <SDL_assert.h>
#include <algorithm>
#include <cmath>
#include <stdint.h>
#include <stdlib.h>
#include <math.h>
#include "common/Compiler.h"
#define GLM_FORCE_RADIANS
GCC_DIAG_OFF(shadow)
GCC_DIAG_OFF(cast-qual)
GCC_DIAG_OFF(cast-align)
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
GCC_DIAG_ON(cast-align)
GCC_DIAG_ON(cast-qual)
GCC_DIAG_ON(shadow)
#include "common/vec2.h"

#define lengthof(x) (sizeof(x) / sizeof(*(x)))
#define lengthofi(x) static_cast<int>(sizeof(x) / sizeof(*(x)))

#ifndef M_PI
#define M_PI 3.14159265358979323846  /* matches value in gcc v2 math.h */
#endif
#ifndef M_PI_2
#define M_PI_2 1.57079632679489661923
#endif

static const double DEG2RAD = M_PI / 180.0;
static const double RAD2DEG = 180.0 / M_PI;

#define FLOAT_CHECK(p) SDL_assert(!isinf(p));SDL_assert(!isnan(p))

// gives random number in the set of {lower, ..., upper}
inline int randBetween (int lower, int upper)
{
	return lower + (int) ((rand() / (RAND_MAX + 1.0)) * (upper - lower + 1));
}

inline float randBetweenf (float lower, float upper)
{
	const float random = ((float) rand()) / (float) RAND_MAX;
	const float diff = upper - lower;
	const float r = random * diff;
	return lower + r;
}

template<typename T>
inline T clamp (T a, T low, T high)
{
	return std::max(low, std::min(a, high));
}

inline double RadiansToDegrees (double radians)
{
	return radians * RAD2DEG;
}

inline double DegreesToRadians (double degrees)
{
	return degrees * DEG2RAD;
}

inline float Round (float r)
{
	return (r > 0.0f) ? floor(r + 0.5f) : ceil(r - 0.5f);
}

template<class T>
inline bool Between (T value, T min, T max)
{
	return value >= min && value <= max;
}

struct Color
{
	float rgba[4];
	float operator[](size_t i) const
	{
		return rgba[i];
	}
	float& operator[](size_t i)
	{
		return rgba[i];
	}
};

static const Color colorGrayAlpha40 = { 0.976f, 0.976f, 0.976f, 0.4f };
static const Color colorGrayAlpha = { 0.976f, 0.976f, 0.976f, 0.8f };
static const Color colorGray = { 0.6f, 0.6f, 0.6f, 1.0f };
static const Color colorWhiteAlpha80 = { 1.0f, 1.0f, 1.0f, 0.8f };
static const Color colorWhite = { 1.0f, 1.0f, 1.0f, 1.0f };
static const Color colorGreen = { 0.0f, 1.0f, 0.0f, 1.0f };
static const Color colorBrightGreen = { 0.0f, 0.6f, 0.0f, 1.0f };
static const Color colorBlue = { 0.0f, 0.0f, 1.0f, 1.0f };
static const Color colorBrightBlue = { 0.0f, 0.0f, 0.6f, 1.0f };
static const Color colorRed = { 1.0f, 0.0f, 0.0f, 1.0f };
static const Color colorBrightRed = { 0.6f, 0.0f, 0.0f, 1.0f };
static const Color colorBlack = { 0.0f, 0.0f, 0.0f, 1.0f };
static const Color colorDark = { 0.2f, 0.2f, 0.2f, 1.0f };
static const Color colorNull = { 0.0f, 0.0f, 0.0f, 0.0f };
static const Color backgroundColor = { 1.0f, 1.0f, 1.0f, 0.2f };
static const Color highlightColor = { 1.0f, 1.0f, 1.0f, 0.5f };
static const Color colorYellow = { 1.0f, 1.0f, 0.0f, 1.0f };
static const Color colorCyan = { 0.2313f, 0.7372f, 0.8274f, 1.0f };

//  water color
// const uint8_t WATER_ALPHA = 80, WATERCOLOR[] = { 26, 45, 71, WATER_ALPHA };  // dark blue
// const uint8_t WATER_ALPHA = 120, WATERCOLOR[] = { 56, 85, 121, WATER_ALPHA };  // dark cyan
// const uint8_t WATER_ALPHA = 130, WATERCOLOR[] = { 56, 95, 151, WATER_ALPHA };  // cyan
static const uint8_t WATER_ALPHA = 150, WATERCOLOR[] = { 58, 118, 181, WATER_ALPHA };  // skyblue

static const Color waterLineColor = 
	{ (float)WATERCOLOR[0] / 255.0f * 1.2f,
	  (float)WATERCOLOR[1] / 255.0f * 1.2f,
	  (float)WATERCOLOR[2] / 255.0f * 1.2f, 1.f };
static const Color waterColor =
	{ (float)WATERCOLOR[0] / 255.0f,
	  (float)WATERCOLOR[1] / 255.0f,
	  (float)WATERCOLOR[2] / 255.0f, WATER_ALPHA / 255.0f };

inline void FadeIn (float& value, float frac)
{
	value *= frac;
}

inline void FadeOut (float& value, float frac)
{
	value *= (1.0f - frac);
}

inline void FadeSin (float& value, float frac)
{
	value *= sin(frac * M_PI);
}

inline void FadeSaw (float& value, float frac)
{
	if (frac < 0.5f)
		frac *= 2.0f;
	else
		frac = (1.0f - frac) * 2.0f;
	FadeIn(value, frac);
}

inline void Vector4Set (const Color& in, Color& out)
{
	out[0] = in[0];
	out[1] = in[1];
	out[2] = in[2];
	out[3] = in[3];
}

// returns the angle between the given two points and the horizontal axis in degree
inline double getAngleBetweenPoints (double x1, double y1, double x2, double y2)
{
	const double dX = x2 - x1;
	const double dY = y2 - y1;
	const double angleInDegrees = ::atan2(dY, dX) * RAD2DEG;
	return angleInDegrees;
}
