#pragma once

#include "helpers/ColorHSV.h"
#include "helpers/ColorRGBW.h"

#include <cstdint>
#include <cmath>
#include <algorithm>

// https://stackoverflow.com/questions/3018313/algorithm-to-convert-rgb-to-hsv-and-hsv-to-rgb-in-range-0-255-for-both
inline ColorHSV rgb2hsv(const ColorRGBW &in)
{
  ColorHSV out;

  const float r = (float)(in.r) / 255.0f;
  const float g = (float)(in.g) / 255.0f;
  const float b = (float)(in.b) / 255.0f;

  const float min = std::min(r, std::min(g, b));
  const float max = std::max(r, std::max(g, b));

  out.v = max; // v
  const float delta = max - min;
  if (delta < 0.00001f)
  {
    out.s = 0;
    out.h = 0; // undefined, maybe nan?
    return out;
  }
  if (max > 0.0f)
  {                        // NOTE: if Max is == 0, this divide would cause a crash
    out.s = (delta / max); // s
  }
  else
  {
    // if max is 0, then r = g = b = 0
    // s = 0, h is undefined
    out.s = 0.0f;
    out.h = 0.0f; //NAN; // its now undefined
    return out;
  }
  if (r >= max)              // > is bogus, just keeps compiler happy
    out.h = (g - b) / delta; // between yellow & magenta
  else if (g >= max)
    out.h = 2.0f + (b - r) / delta; // between cyan & yellow
  else
    out.h = 4.0f + (r - g) / delta; // between magenta & cyan

  out.h *= 60.0f; // degrees

  if (out.h < 0.0f)
    out.h += 360.0f;

  return out;
}

ColorRGBW hsv2rgb(const ColorHSV &in)
{
  int i;
  ColorRGBW out;
  out.w = 0;

  if (in.s <= 0.0f)
  { // < is bogus, just shuts up warnings
    out.r = (uint8_t)(in.v * 255.0f);
    out.g = (uint8_t)(in.v * 255.0f);
    out.b = (uint8_t)(in.v * 255.0f);
    return out;
  }
  float hh = in.h;
  if (hh >= 360.0f)
    hh = 0.0f;
  hh /= 60.0f;
  i = (long)hh;
  auto ff = hh - i;
  float p = in.v * (1.0f - in.s);
  float q = in.v * (1.0f - (in.s * ff));
  float t = in.v * (1.0f - (in.s * (1.0f - ff)));

  switch (i)
  {
  case 0:
    out.r = (uint8_t)(in.v * 255.0f);
    out.g = (uint8_t)(t * 255.0f);
    out.b = (uint8_t)(p * 255.0f);
    break;
  case 1:
    out.r = (uint8_t)(q * 255.0f);
    out.g = (uint8_t)(in.v * 255.0f);
    out.b = (uint8_t)(p * 255.0f);
    break;
  case 2:
    out.r = (uint8_t)(p * 255.0f);
    out.g = (uint8_t)(in.v * 255.0f);
    out.b = (uint8_t)(t * 255.0f);
    break;

  case 3:
    out.r = (uint8_t)(p * 255.0f);
    out.g = (uint8_t)(q * 255.0f);
    out.b = (uint8_t)(in.v * 255.0f);
    break;
  case 4:
    out.r = (uint8_t)(t * 255.0f);
    out.g = (uint8_t)(p * 255.0f);
    out.b = (uint8_t)(in.v * 255.0f);
    break;
  case 5:
  default:
    out.r = (uint8_t)(in.v * 255.0f);
    out.g = (uint8_t)(p * 255.0f);
    out.b = (uint8_t)(q * 255.0f);
    break;
  }
  return out;
}