// SPDX-License-Identifier: Unlicense

#include "helper.h"

#include "global.h"

#include <math.h>
#include <string.h>
#include <sstream>

vec4 colorVec4(const char8_t* hexColor)
{
  ASSERT(strlen(reinterpret_cast<const char*>(hexColor)) == 9);
  ASSERT(hexColor[0] == u8'#');

  const u32 color = strtoul(reinterpret_cast<const char*>(&hexColor[1]), nullptr, 16);

  const u8 r = u8(color >> 24);
  const u8 g = u8(color >> 16);
  const u8 b = u8(color >> 8);
  const u8 a = u8(color);

  return vec4{
    .r = r / 255.0f,
    .g = g / 255.0f,
    .b = b / 255.0f,
    .a = a / 255.0f
  };
}

std::u8string hexColor(const vec4 color)
{
  const u8 r = u8(color.r * 255.0f);
  const u8 g = u8(color.g * 255.0f);
  const u8 b = u8(color.b * 255.0f);
  const u8 a = u8(color.a * 255.0f);

  char8_t hexString[10];
  sprintf(reinterpret_cast<char*>(hexString), "#%02X%02X%02X%02X", r, g, b, a);
  return hexString;
}

//void setColor(std::vector<u8>& rgbaData, const i32 index, const Color color)
//{
//  reinterpret_cast<std::vector<Color>&>(rgbaData)[index] = color;
//}

//f32 x2GlScreen(const f32 x)
//{
//  return 2.0f * x / f32(Global::resolutionWidth) - 1.0_f32;
//}
//
//f32 y2GlScreen(const f32 y)
//{
//  return -(2.0f * y / f32(Global::resolutionHeight) - 1.0_f32);
//}

//f32 radFromDeg(const f32 deg)
//{
//  return 0.0174533f * deg;
//}

f32 TimeNS_To_Seconds(const TimeNS time)
{
  return f32(time / f64(1_s));
}

TimeNS timeNS_From_String(std::u8string timeInSeconds)
{
  // Find the position of the decimal point
  const u64 decimalPos = timeInSeconds.find('.');
  if (decimalPos != std::string::npos)
    timeInSeconds[decimalPos] = '\0';

  // Convert the string to an integer representing the seconds
  const i64 seconds = atoll(reinterpret_cast<const char*>(timeInSeconds.c_str()));

  // Convert the decimal part to an integer representing nanoseconds
  i32 nanoseconds = 0;
  if (decimalPos != std::string::npos) {
    i32 factor = 1;
    const char8_t* decimal = timeInSeconds.c_str() + decimalPos + 1;
    u8 i = 0;
    while (decimal[i] != u8'\0') {
      nanoseconds = nanoseconds * 10 + decimal[i] - u8'0';
      factor *= 10;
      ++i;
    }
    nanoseconds *= i32(1_s / factor);

    // If the original seconds were negative, make the nanoseconds negative as well
    if (timeInSeconds[0] == '-')
      nanoseconds *= -1;
  }

  return seconds * 1_s + nanoseconds;
}

std::u8string TimeNS_To_String(const TimeNS time)
{
  ASSERT(time >= 0); // Negative TimeNS not implemented

  const TimeNS integer = time / 1_s;
  TimeNS decimal = time % 1_s;

  i8 digits = 0;
  while (digits < 8 && decimal % 10 == 0)
  {
    decimal /= 10;
    ++digits;
  }

  const i8 integerChars = i8(snprintf(nullptr, 0, "%lld", integer));
  const i8 decimalChars = i8(snprintf(nullptr, 0, "%lld", decimal));

  const i8 zerosAfterDot = 9 - decimalChars - digits;
  ASSERT(zerosAfterDot >= 0);

  std::u8string result(integerChars + 1 + zerosAfterDot + decimalChars, u8'0');

  sprintf(reinterpret_cast<char*>(&result[0]), "%lld.", integer);
  result[integerChars + 1] = u8'0';

  sprintf(reinterpret_cast<char*>(&result[integerChars + 1 + zerosAfterDot]), "%lld", decimal);

  return result;
}

std::u8string F32_To_String(const f32 value)
{
  const i32 len = snprintf(nullptr, 0, "%f", value);
  std::u8string str(len, '\0');
  sprintf(reinterpret_cast<char*>(str.data()), "%f", value);
  return str;
}

i64 musicTimeElapsedToPlaybackPositionNonStretched(const TimeNS musicTimeElapsedNS, const f32 sampleRate)
{
  return i64(TimeNS_To_Seconds(musicTimeElapsedNS) * sampleRate);
}

u16 lfsr()
{
  static u16 lfsr = 0xACE1u;

  u16 bit = ((lfsr >> 0) ^ (lfsr >> 2) ^ (lfsr >> 3) ^ (lfsr >> 5));
  lfsr = (lfsr >> 1) | (bit << 15);

  return lfsr;
}

f32 saturate(const f32 x)
{
  return max_(0.0f, min_(1.0f, x));
}

f32 smoothstep(const f32 a, const f32 b, const f32 x)
{
  f32 t = saturate((x - a) / (b - a));
  return t * t * (3.0f - (2.0f * t));
}

f32 vec::lengthSquared(const f32 x, const f32 y)
{
  return x * x + y * y;
}

f32 vec::lengthSquared(const vec3& x)
{
  return x.x * x.x + x.y * x.y + x.z * x.z;
}

f32 vec::length(const f32 x, const f32 y)
{
  return sqrtf(lengthSquared(x, y));
}

f32 vec::length(const vec3& x)
{
  return sqrtf(lengthSquared(x));
}

void vec::rotate(f32& x, f32& y, const f32 rad)
{
  const f32 oldX = x;
  x = x * cosf(rad) - y * sinf(rad);
  y = oldX * sinf(rad) + y * cosf(rad);
}

void vec::norm(f32& x, f32& y)
{
  const f32 len = length(x, y);
  x /= len;
  y /= len;
}

vec3 vec::norm(const vec3& x)
{
  const f32 len = vec::length(x);

  if (len == 0.0f)
    return {};

  vec3 normX
  {
    .x = x.x / len,
    .y = x.y / len,
    .z = x.z / len
  };
  return normX;
}

vec3 vec::multiply(const vec3& x, const f32 scale)
{
  return {
    .x = x.x * scale,
    .y = x.y * scale,
    .z = x.z * scale
  };
}

mat4 vec::multiply(const mat4& m0, const mat4& m1)
{
  return {
    .m00 = m0.m00 * m1.m00 + m0.m01 * m1.m10 + m0.m02 * m1.m20 + m0.m03 * m1.m30,
    .m10 = m0.m10 * m1.m00 + m0.m11 * m1.m10 + m0.m12 * m1.m20 + m0.m13 * m1.m30,
    .m20 = m0.m20 * m1.m00 + m0.m21 * m1.m10 + m0.m22 * m1.m20 + m0.m23 * m1.m30,
    .m30 = m0.m30 * m1.m00 + m0.m31 * m1.m10 + m0.m32 * m1.m20 + m0.m33 * m1.m30,
    .m01 = m0.m00 * m1.m01 + m0.m01 * m1.m11 + m0.m02 * m1.m21 + m0.m03 * m1.m31,
    .m11 = m0.m10 * m1.m01 + m0.m11 * m1.m11 + m0.m12 * m1.m21 + m0.m13 * m1.m31,
    .m21 = m0.m20 * m1.m01 + m0.m21 * m1.m11 + m0.m22 * m1.m21 + m0.m23 * m1.m31,
    .m31 = m0.m30 * m1.m01 + m0.m31 * m1.m11 + m0.m32 * m1.m21 + m0.m33 * m1.m31,
    .m02 = m0.m00 * m1.m02 + m0.m01 * m1.m12 + m0.m02 * m1.m22 + m0.m03 * m1.m32,
    .m12 = m0.m10 * m1.m02 + m0.m11 * m1.m12 + m0.m12 * m1.m22 + m0.m13 * m1.m32,
    .m22 = m0.m20 * m1.m02 + m0.m21 * m1.m12 + m0.m22 * m1.m22 + m0.m23 * m1.m32,
    .m32 = m0.m30 * m1.m02 + m0.m31 * m1.m12 + m0.m32 * m1.m22 + m0.m33 * m1.m32,
    .m03 = m0.m00 * m1.m03 + m0.m01 * m1.m13 + m0.m02 * m1.m23 + m0.m03 * m1.m33,
    .m13 = m0.m10 * m1.m03 + m0.m11 * m1.m13 + m0.m12 * m1.m23 + m0.m13 * m1.m33,
    .m23 = m0.m20 * m1.m03 + m0.m21 * m1.m13 + m0.m22 * m1.m23 + m0.m23 * m1.m33,
    .m33 = m0.m30 * m1.m03 + m0.m31 * m1.m13 + m0.m32 * m1.m23 + m0.m33 * m1.m33
  };
}

vec4 vec::multiply(const mat4& m0, const vec4& v0)
{
  return {
    .x = m0.m00 * v0.x + m0.m01 * v0.y + m0.m02 * v0.z + m0.m03 * v0.w,
    .y = m0.m10 * v0.x + m0.m11 * v0.y + m0.m12 * v0.z + m0.m13 * v0.w,
    .z = m0.m20 * v0.x + m0.m21 * v0.y + m0.m22 * v0.z + m0.m23 * v0.w,
    .w = m0.m30 * v0.x + m0.m31 * v0.y + m0.m32 * v0.z + m0.m33 * v0.w
  };
}

vec3 vec::truncate(const vec3& x, const f32 max)
{
  if (vec::length(x) > max)
    return multiply(vec::norm(x), max);

  return x;
}

mat4 vec::createTranslationMat(const f32 x, const f32 y, const f32 z)
{
  // don't use this anymore
  return {
    .m00 = 1.0f, .m10 = 0.0f, .m20 = 0.0f, .m30 = 0.0f,
    .m01 = 0.0f, .m11 = 1.0f, .m21 = 0.0f, .m31 = 0.0f,
    .m02 = 0.0f, .m12 = 0.0f, .m22 = 1.0f, .m32 = 0.0f,
    .m03 = x,    .m13 = y,    .m23 = z,    .m33 = 1.0f
  };
}

mat4 vec::createScaleMat(const f32 x, const f32 y, const f32 z)
{
  return {
    .m00 = x,    .m10 = 0.0f, .m20 = 0.0f, .m30 = 0.0f,
    .m01 = 0.0f, .m11 = y,    .m21 = 0.0f, .m31 = 0.0f,
    .m02 = 0.0f, .m12 = 0.0f, .m22 = z,    .m32 = 0.0f,
    .m03 = 0.0f, .m13 = 0.0f, .m23 = 0.0f, .m33 = 1.0f
  };
}

mat4 vec::createMatFromQuaternion(const vec4& quaternion)
{
  const f32 x2 = quaternion.x + quaternion.x;
  const f32 y2 = quaternion.y + quaternion.y;
  const f32 z2 = quaternion.z + quaternion.z;

  const f32 xx2 = quaternion.x * x2;
  const f32 yy2 = quaternion.y * y2;
  const f32 zz2 = quaternion.z * z2;

  const f32 yz2 = quaternion.y * z2;
  const f32 wx2 = quaternion.w * x2;
  const f32 xy2 = quaternion.x * y2;
  const f32 wz2 = quaternion.w * z2;
  const f32 xz2 = quaternion.x * z2;
  const f32 wy2 = quaternion.w * y2;

  const mat4 result = {
    .m00 = 1.0f - yy2 - zz2,
    .m10 = xy2 + wz2,
    .m20 = xz2 - wy2,
    .m30 = 0.0f,

    .m01 = xy2 - wz2,
    .m11 = 1.0f - xx2 - zz2,
    .m21 = yz2 + wx2,
    .m31 = 0.0f,

    .m02 = xz2 + wy2,
    .m12 = yz2 - wx2,
    .m22 = 1.0f - xx2 - yy2,
    .m32 = 0.0f,

    .m03 = 0.0f,
    .m13 = 0.0f,
    .m23 = 0.0f,
    .m33 = 1.0f
  };

  return result;
}
