/*
QuickCG 20191227

Copyright (c) 2004-2019, Lode Vandevenne

All rights reserved.

Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:

    * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

/*
QuickCG is an SDL codebase that wraps some of the SDL functionality.
It' used by Lode's Computer Graphics Tutorial to work with simple C++ calls
to demonstrate graphical programs.

QuickCG can handle some things that standard C++ doesn't but that are commonly useful, such as:
-drawing graphics
-a bitmap font
-simplified saving and loading of files
-reading keyboard and mouse input
-playing sound
-color models
-loading images
*/

#include "quickcg.h"

#include <cstdlib>
#include <cmath>
#include <vector>
#include <map>
#include <iostream>
#include <fstream>

namespace QuickCG
{
  ////////////////////////////////////////////////////////////////////////////////
  // COLOR STRUCTS/////////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////
  ColorRGB::ColorRGB(Uint8 r, Uint8 g, Uint8 b)
  {
    this->r = r;
    this->g = g;
    this->b = b;
  }
  ColorRGB::ColorRGB(const ColorRGB8bit &color)
  {
    this->r = color.r;
    this->g = color.g;
    this->b = color.b;
  }
  ColorRGB::ColorRGB()
  {
    this->r = 0;
    this->g = 0;
    this->b = 0;
  }
  ColorRGB8bit::ColorRGB8bit(Uint8 r, Uint8 g, Uint8 b)
  {
    this->r = r;
    this->g = g;
    this->b = b;
  }
  ColorRGB8bit::ColorRGB8bit(const ColorRGB &color)
  {
    this->r = color.r;
    this->g = color.g;
    this->b = color.b;
  }
  ColorRGB8bit::ColorRGB8bit()
  {
    this->r = 0;
    this->g = 0;
    this->b = 0;
  }

  // Add two colors
  ColorRGB operator+(const ColorRGB &color, const ColorRGB &color2)
  {
    ColorRGB c;
    c.r = color.r + color2.r;
    c.g = color.g + color2.g;
    c.b = color.b + color2.b;
    return c;
  }

  // Subtract two colors
  ColorRGB operator-(const ColorRGB &color, const ColorRGB &color2)
  {
    ColorRGB c;
    c.r = color.r - color2.r;
    c.g = color.g - color2.g;
    c.b = color.b - color2.b;
    return c;
  }

  // Multiplies a color with an integer
  ColorRGB operator*(const ColorRGB &color, int a)
  {
    ColorRGB c;
    c.r = color.r * a;
    c.g = color.g * a;
    c.b = color.b * a;
    return c;
  }

  // Multiplies a color with an integer
  ColorRGB operator*(int a, const ColorRGB &color)
  {
    ColorRGB c;
    c.r = color.r * a;
    c.g = color.g * a;
    c.b = color.b * a;
    return c;
  }

  // Divides a color through an integer
  ColorRGB operator/(const ColorRGB &color, int a)
  {
    if (a == 0)
      return color;
    ColorRGB c;
    c.r = color.r / a;
    c.g = color.g / a;
    c.b = color.b / a;
    return c;
  }

  // Are both colors equal?
  bool operator==(const ColorRGB &color, const ColorRGB &color2)
  {
    return (color.r == color2.r && color.g == color2.g && color.b == color2.b);
  }

  // Are both colors not equal?
  bool operator!=(const ColorRGB &color, const ColorRGB &color2)
  {
    return (!(color.r == color2.r && color.g == color2.g && color.b == color2.b));
  }

  ColorHSL::ColorHSL(Uint8 h, Uint8 s, Uint8 l)
  {
    this->h = h;
    this->s = s;
    this->l = l;
  }
  ColorHSL::ColorHSL()
  {
    this->h = 0;
    this->s = 0;
    this->l = 0;
  }
  ColorHSV::ColorHSV(Uint8 h, Uint8 s, Uint8 v)
  {
    this->h = h;
    this->s = s;
    this->v = v;
  }
  ColorHSV::ColorHSV()
  {
    this->h = 0;
    this->s = 0;
    this->v = 0;
  }

  ////////////////////////////////////////////////////////////////////////////////
  // COLOR CONVERSIONS/////////////////////////////////////////////////////////////
  ////////////////////////////////////////////////////////////////////////////////

  /*
  Convert colors from one type to another
  r=red  g=green  b=blue  h=hue  s=saturation  l=lightness  v=value
  Color components from the color structs are Uint8's between 0 and 255
  color components used in the calculations are normalized between 0.0-1.0
  */

  // Converts an RGB color to HSL color
  ColorHSL RGBtoHSL(const ColorRGB &colorRGB)
  {
    float r, g, b, h = 0, s = 0, l; // this function works with floats between 0 and 1
    r = colorRGB.r / 256.0;
    g = colorRGB.g / 256.0;
    b = colorRGB.b / 256.0;

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

    if (minColor == maxColor) // R = G = B, so it's a shade of grey
    {
      h = 0; // it doesn't matter what value it has
      s = 0;
      l = r; // doesn't matter if you pick r, g, or b
    }
    else
    {
      l = (minColor + maxColor) / 2;

      if (l < 0.5)
        s = (maxColor - minColor) / (maxColor + minColor);
      if (l >= 0.5)
        s = (maxColor - minColor) / (2.0 - maxColor - minColor);

      if (r == maxColor)
        h = (g - b) / (maxColor - minColor);
      if (g == maxColor)
        h = 2.0 + (b - r) / (maxColor - minColor);
      if (b == maxColor)
        h = 4.0 + (r - g) / (maxColor - minColor);

      h /= 6; // to bring it to a number between 0 and 1
      if (h < 0)
        h += 1;
    }

    ColorHSL colorHSL;
    colorHSL.h = int(h * 255.0);
    colorHSL.s = int(s * 255.0);
    colorHSL.l = int(l * 255.0);
    return colorHSL;
  }

  // Converts an HSL color to RGB color
  ColorRGB HSLtoRGB(const ColorHSL &colorHSL)
  {
    float r, g, b, h, s, l; // this function works with floats between 0 and 1
    float temp1, temp2, tempr, tempg, tempb;
    h = colorHSL.h / 256.0;
    s = colorHSL.s / 256.0;
    l = colorHSL.l / 256.0;

    // If saturation is 0, the color is a shade of grey
    if (s == 0)
      r = g = b = l;
    // If saturation > 0, more complex calculations are needed
    else
    {
      // set the temporary values
      if (l < 0.5)
        temp2 = l * (1 + s);
      else
        temp2 = (l + s) - (l * s);
      temp1 = 2 * l - temp2;
      tempr = h + 1.0 / 3.0;
      if (tempr > 1.0)
        tempr--;
      tempg = h;
      tempb = h - 1.0 / 3.0;
      if (tempb < 0.0)
        tempb++;

      // red
      if (tempr < 1.0 / 6.0)
        r = temp1 + (temp2 - temp1) * 6.0 * tempr;
      else if (tempr < 0.5)
        r = temp2;
      else if (tempr < 2.0 / 3.0)
        r = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempr) * 6.0;
      else
        r = temp1;

      // green
      if (tempg < 1.0 / 6.0)
        g = temp1 + (temp2 - temp1) * 6.0 * tempg;
      else if (tempg < 0.5)
        g = temp2;
      else if (tempg < 2.0 / 3.0)
        g = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempg) * 6.0;
      else
        g = temp1;

      // blue
      if (tempb < 1.0 / 6.0)
        b = temp1 + (temp2 - temp1) * 6.0 * tempb;
      else if (tempb < 0.5)
        b = temp2;
      else if (tempb < 2.0 / 3.0)
        b = temp1 + (temp2 - temp1) * ((2.0 / 3.0) - tempb) * 6.0;
      else
        b = temp1;
    }

    ColorRGB colorRGB;
    colorRGB.r = int(r * 255.0);
    colorRGB.g = int(g * 255.0);
    colorRGB.b = int(b * 255.0);
    return colorRGB;
  }

  // Converts an RGB color to HSV color
  ColorHSV RGBtoHSV(const ColorRGB &colorRGB)
  {
    float r, g, b, h = 0.0, s = 0.0, v; // this function works with floats between 0 and 1
    r = colorRGB.r / 256.0;
    g = colorRGB.g / 256.0;
    b = colorRGB.b / 256.0;

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

    v = maxColor;

    if (maxColor != 0.0) // avoid division by zero when the color is black
    {
      s = (maxColor - minColor) / maxColor;
    }

    if (s == 0.0)
    {
      h = 0.0; // it doesn't matter what value it has
    }
    else
    {
      if (r == maxColor)
        h = (g - b) / (maxColor - minColor);
      if (g == maxColor)
        h = 2.0 + (b - r) / (maxColor - minColor);
      if (b == maxColor)
        h = 4.0 + (r - g) / (maxColor - minColor);

      h /= 6.0; // to bring it to a number between 0 and 1
      if (h < 0.0)
        h++;
    }

    ColorHSV colorHSV;
    colorHSV.h = int(h * 255.0);
    colorHSV.s = int(s * 255.0);
    colorHSV.v = int(v * 255.0);
    return colorHSV;
  }

  // Converts an HSV color to RGB color
  ColorRGB HSVtoRGB(const ColorHSV &colorHSV)
  {
    float r, g, b, h, s, v; // this function works with floats between 0 and 1
    h = colorHSV.h / 256.0;
    s = colorHSV.s / 256.0;
    v = colorHSV.v / 256.0;

    // if saturation is 0, the color is a shade of grey
    if (s == 0.0)
      r = g = b = v;
    // if saturation > 0, more complex calculations are needed
    else
    {
      float f, p, q, t;
      int i;
      h *= 6.0;          // to bring hue to a number between 0 and 6, better for the calculations
      i = int(floor(h)); // e.g. 2.7 becomes 2 and 3.01 becomes 3 or 4.9999 becomes 4
      f = h - i;         // the fractional part of h

      p = v * (1.0 - s);
      q = v * (1.0 - (s * f));
      t = v * (1.0 - (s * (1.0 - f)));

      switch (i)
      {
      case 0:
        r = v;
        g = t;
        b = p;
        break;
      case 1:
        r = q;
        g = v;
        b = p;
        break;
      case 2:
        r = p;
        g = v;
        b = t;
        break;
      case 3:
        r = p;
        g = q;
        b = v;
        break;
      case 4:
        r = t;
        g = p;
        b = v;
        break;
      case 5:
        r = v;
        g = p;
        b = q;
        break;
      default:
        r = g = b = 0;
        break;
      }
    }
    ColorRGB colorRGB;
    colorRGB.r = int(r * 255.0);
    colorRGB.g = int(g * 255.0);
    colorRGB.b = int(b * 255.0);
    return colorRGB;
  }

  Uint32 RGBtoINT(const ColorRGB &colorRGB)
  {
    return 65536 * colorRGB.r + 256 * colorRGB.g + colorRGB.b;
  }

  ColorRGB INTtoRGB(Uint32 colorINT)
  {
    ColorRGB colorRGB;
    colorRGB.r = (colorINT / 65536) % 256;
    colorRGB.g = (colorINT / 256) % 256;
    colorRGB.b = colorINT % 256;
    return colorRGB;
  }
}