// Aseprite
// Copyright (C) 2020-2023  Igara Studio S.A.
// Copyright (C) 2001-2018  David Capello
//
// This program is distributed under the terms of
// the End-User License Agreement for Aseprite.

#ifdef HAVE_CONFIG_H
  #include "config.h"
#endif

#include "app/color.h"

#include "app/color_utils.h"
#include "app/modules/palettes.h"
#include "base/debug.h"
#include "doc/image.h"
#include "doc/palette.h"
#include "doc/primitives.h"
#include "doc/tile.h"
#include "gfx/hsl.h"
#include "gfx/hsv.h"
#include "gfx/rgb.h"

#include <cmath>
#include <cstdlib>
#include <iomanip>
#include <sstream>
#include <string>

namespace app {

using namespace doc;
using namespace gfx;

// static
Color Color::fromMask()
{
  return Color(Color::MaskType);
}

// static
Color Color::fromRgb(int r, int g, int b, int a)
{
  Color color(Color::RgbType);
  color.m_value.rgb.r = r;
  color.m_value.rgb.g = g;
  color.m_value.rgb.b = b;
  color.m_value.rgb.a = a;
  return color;
}

// static
Color Color::fromHsv(double h, double s, double v, int a)
{
  Color color(Color::HsvType);
  color.m_value.hsv.h = h;
  color.m_value.hsv.s = s;
  color.m_value.hsv.v = v;
  color.m_value.hsv.a = a;
  return color;
}

// static
Color Color::fromHsl(double h, double s, double l, int a)
{
  Color color(Color::HslType);
  color.m_value.hsl.h = h;
  color.m_value.hsl.s = s;
  color.m_value.hsl.l = l;
  color.m_value.hsl.a = a;
  return color;
}

// static
Color Color::fromGray(int g, int a)
{
  Color color(Color::GrayType);
  color.m_value.gray.g = g;
  color.m_value.gray.a = a;
  return color;
}

// static
Color Color::fromIndex(int index)
{
  ASSERT(index >= 0);

  Color color(Color::IndexType);
  color.m_value.index = index;
  return color;
}

// static
Color Color::fromTile(doc::tile_t tile)
{
  Color color(Color::TileType);
  color.m_value.tile = tile;
  return color;
}

// static
Color Color::fromImage(PixelFormat pixelFormat, color_t c)
{
  Color color = app::Color::fromMask();

  switch (pixelFormat) {
    case IMAGE_RGB:
      if (rgba_geta(c) > 0) {
        color = Color::fromRgb(rgba_getr(c), rgba_getg(c), rgba_getb(c), rgba_geta(c));
      }
      break;

    case IMAGE_GRAYSCALE:
      if (graya_geta(c) > 0) {
        color = Color::fromGray(graya_getv(c), graya_geta(c));
      }
      break;

    case IMAGE_INDEXED: color = Color::fromIndex(c); break;

    case IMAGE_TILEMAP: color = Color::fromTile(c); break;
  }

  return color;
}

// static
Color Color::fromImageGetPixel(Image* image, int x, int y)
{
  if ((x >= 0) && (y >= 0) && (x < image->width()) && (y < image->height()))
    return Color::fromImage(image->pixelFormat(), doc::get_pixel(image, x, y));
  else
    return Color::fromMask();
}

// static
Color Color::fromString(const std::string& str)
{
  Color color = app::Color::fromMask();

  if (str != "mask") {
    if (str.find("rgb{") == 0 || str.find("hsv{") == 0 || str.find("hsl{") == 0 ||
        str.find("gray{") == 0) {
      int c = 0;
      double table[4] = { 0.0, 0.0, 0.0, 255.0 };
      std::string::size_type i = str.find_first_of('{') + 1, j;

      while ((j = str.find_first_of(",}", i)) != std::string::npos) {
        std::string element = str.substr(i, j - i);
        if (c < 4)
          table[c++] = std::strtod(element.c_str(), NULL);
        if (c >= 4)
          break;
        i = j + 1;
      }

      if (str[0] == 'r')
        color = Color::fromRgb(int(table[0]), int(table[1]), int(table[2]), int(table[3]));
      else if (str[0] == 'h' && str[1] == 's' && str[2] == 'v')
        color = Color::fromHsv(table[0], table[1] / 100.0, table[2] / 100.0, int(table[3]));
      else if (str[0] == 'h' && str[1] == 's' && str[2] == 'l')
        color = Color::fromHsl(table[0], table[1] / 100.0, table[2] / 100.0, int(table[3]));
      else if (str[0] == 'g')
        color = Color::fromGray(int(table[0]), (c >= 2 ? int(table[1]) : 255));
    }
    else if (str.find("index{") == 0) {
      color = Color::fromIndex(std::strtol(str.c_str() + 6, NULL, 10));
    }
  }

  return color;
}

Color Color::toRgb() const
{
  return Color::fromRgb(getRed(), getGreen(), getBlue(), getAlpha());
}

std::string Color::toString() const
{
  std::stringstream result;

  switch (getType()) {
    case Color::MaskType: result << "mask"; break;

    case Color::RgbType:
      result << "rgb{" << m_value.rgb.r << "," << m_value.rgb.g << "," << m_value.rgb.b << ","
             << m_value.rgb.a << "}";
      break;

    case Color::HsvType:
      result << "hsv{" << std::setprecision(2) << std::fixed << m_value.hsv.h << ","
             << std::clamp(m_value.hsv.s * 100.0, 0.0, 100.0) << ","
             << std::clamp(m_value.hsv.v * 100.0, 0.0, 100.0) << "," << m_value.hsv.a << "}";
      break;

    case Color::HslType:
      result << "hsl{" << std::setprecision(2) << std::fixed << m_value.hsl.h << ","
             << std::clamp(m_value.hsl.s * 100.0, 0.0, 100.0) << ","
             << std::clamp(m_value.hsl.l * 100.0, 0.0, 100.0) << "," << m_value.hsl.a << "}";
      break;

    case Color::GrayType:
      result << "gray{" << m_value.gray.g << "," << m_value.gray.a << "}";
      break;

    case Color::IndexType: result << "index{" << m_value.index << "}"; break;
  }

  return result.str();
}

std::string Color::toHumanReadableString(PixelFormat pixelFormat,
                                         HumanReadableString humanReadable) const
{
  std::stringstream result;

  if (humanReadable == LongHumanReadableString) {
    switch (getType()) {
      case Color::MaskType: result << "Mask"; break;

      case Color::RgbType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gray " << getGray();
        }
        else {
          result << "RGB " << m_value.rgb.r << " " << m_value.rgb.g << " " << m_value.rgb.b;

          if (pixelFormat == IMAGE_INDEXED)
            result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);
        }
        break;

      case Color::HsvType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gray " << getGray();
        }
        else {
          result << "HSV " << int(m_value.hsv.h) << "\xc2\xb0 "
                 << std::clamp(int(m_value.hsv.s * 100.0), 0, 100) << "% "
                 << std::clamp(int(m_value.hsv.v * 100.0), 0, 100) << "%";

          if (pixelFormat == IMAGE_INDEXED)
            result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);

          result << " (RGB " << getRed() << " " << getGreen() << " " << getBlue() << ")";
        }
        break;

      case Color::HslType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gray " << getGray();
        }
        else {
          result << "HSL " << int(m_value.hsl.h) << "\xc2\xb0 "
                 << std::clamp(int(m_value.hsl.s * 100.0), 0, 100) << "% "
                 << std::clamp(int(m_value.hsl.l * 100.0), 0, 100) << "%";

          if (pixelFormat == IMAGE_INDEXED)
            result << " Index " << color_utils::color_for_image(*this, IMAGE_INDEXED);

          result << " (RGB " << getRed() << " " << getGreen() << " " << getBlue() << ")";
        }
        break;

      case Color::GrayType:  result << "Gray " << m_value.gray.g; break;

      case Color::IndexType: {
        int i = m_value.index;
        if (i >= 0 && i < (int)get_current_palette()->size()) {
          uint32_t _c = get_current_palette()->getEntry(i);
          result << "Index " << i << " (RGB " << (int)rgba_getr(_c) << " " << (int)rgba_getg(_c)
                 << " " << (int)rgba_getb(_c) << ")";
        }
        else {
          result << "Index " << i << " (out of range)";
        }
        break;
      }

      default: ASSERT(false); break;
    }

    result << " #" << std::hex << std::setfill('0') << std::setw(2) << getRed() << std::setw(2)
           << getGreen() << std::setw(2) << getBlue();
  }
  else if (humanReadable == ShortHumanReadableString) {
    switch (getType()) {
      case Color::MaskType: result << "Mask"; break;

      case Color::RgbType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gry-" << getGray();
        }
        else {
          result << "#" << std::hex << std::setfill('0') << std::setw(2) << m_value.rgb.r
                 << std::setw(2) << m_value.rgb.g << std::setw(2) << m_value.rgb.b;
        }
        break;

      case Color::HsvType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gry-" << getGray();
        }
        else {
          result << int(m_value.hsv.h) << "\xc2\xb0"
                 << std::clamp(int(m_value.hsv.s * 100.0), 0, 100) << ","
                 << std::clamp(int(m_value.hsv.v * 100.0), 0, 100);
        }
        break;

      case Color::HslType:
        if (pixelFormat == IMAGE_GRAYSCALE) {
          result << "Gry-" << getGray();
        }
        else {
          result << int(m_value.hsl.h) << "\xc2\xb0"
                 << std::clamp(int(m_value.hsl.s * 100.0), 0, 100) << ","
                 << std::clamp(int(m_value.hsl.l * 100.0), 0, 100);
        }
        break;

      case Color::GrayType:  result << "Gry-" << m_value.gray.g; break;

      case Color::IndexType: result << "Idx-" << m_value.index; break;

      default:               ASSERT(false); break;
    }
  }

  return result.str();
}

bool Color::operator==(const Color& other) const
{
  if (getType() != other.getType())
    return false;

  switch (getType()) {
    case Color::MaskType: return true;

    case Color::RgbType:
      return m_value.rgb.r == other.m_value.rgb.r && m_value.rgb.g == other.m_value.rgb.g &&
             m_value.rgb.b == other.m_value.rgb.b && m_value.rgb.a == other.m_value.rgb.a;

    case Color::HsvType:
      return (std::fabs(m_value.hsv.h - other.m_value.hsv.h) < 0.001) &&
             (std::fabs(m_value.hsv.s - other.m_value.hsv.s) < 0.00001) &&
             (std::fabs(m_value.hsv.v - other.m_value.hsv.v) < 0.00001) &&
             (m_value.hsv.a == other.m_value.hsv.a);

    case Color::HslType:
      return (std::fabs(m_value.hsl.h - other.m_value.hsl.h) < 0.001) &&
             (std::fabs(m_value.hsl.s - other.m_value.hsl.s) < 0.00001) &&
             (std::fabs(m_value.hsl.l - other.m_value.hsl.l) < 0.00001) &&
             (m_value.hsl.a == other.m_value.hsl.a);

    case Color::GrayType:
      return m_value.gray.g == other.m_value.gray.g && m_value.gray.a == other.m_value.gray.a;

    case Color::IndexType: return m_value.index == other.m_value.index;

    default:               ASSERT(false); return false;
  }
}

// Returns false only if the color is a index and it is outside the
// valid range (outside the maximum number of colors in the current
// palette)
bool Color::isValid() const
{
  switch (getType()) {
    case Color::IndexType: {
      int i = m_value.index;
      return (i >= 0 && i < get_current_palette()->size());
    }
  }
  return true;
}

int Color::getRed() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return m_value.rgb.r;

    case Color::HsvType:   return Rgb(Hsv(m_value.hsv.h, m_value.hsv.s, m_value.hsv.v)).red();

    case Color::HslType:   return Rgb(Hsl(m_value.hsl.h, m_value.hsl.s, m_value.hsl.l)).red();

    case Color::GrayType:  return m_value.gray.g;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size())
        return rgba_getr(get_current_palette()->getEntry(i));
      else
        return 0;
    }
  }

  ASSERT(false);
  return -1;
}

int Color::getGreen() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return m_value.rgb.g;

    case Color::HsvType:   return Rgb(Hsv(m_value.hsv.h, m_value.hsv.s, m_value.hsv.v)).green();

    case Color::HslType:   return Rgb(Hsl(m_value.hsl.h, m_value.hsl.s, m_value.hsl.l)).green();

    case Color::GrayType:  return m_value.gray.g;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size())
        return rgba_getg(get_current_palette()->getEntry(i));
      else
        return 0;
    }
  }

  ASSERT(false);
  return -1;
}

int Color::getBlue() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return m_value.rgb.b;

    case Color::HsvType:   return Rgb(Hsv(m_value.hsv.h, m_value.hsv.s, m_value.hsv.v)).blue();

    case Color::HslType:   return Rgb(Hsl(m_value.hsl.h, m_value.hsl.s, m_value.hsl.l)).blue();

    case Color::GrayType:  return m_value.gray.g;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size())
        return rgba_getb(get_current_palette()->getEntry(i));
      else
        return 0;
    }
  }

  ASSERT(false);
  return -1;
}

double Color::getHsvHue() const
{
  switch (getType()) {
    case Color::MaskType:  return 0.0;

    case Color::RgbType:   return Hsv(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).hue();

    case Color::HsvType:   return m_value.hsv.h;

    case Color::HslType:   return m_value.hsl.h;

    case Color::GrayType:  return 0.0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsv(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).hue();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

double Color::getHsvSaturation() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return Hsv(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).saturation();

    case Color::HsvType:   return m_value.hsv.s;

    case Color::HslType:   return Hsv(Rgb(getRed(), getGreen(), getBlue())).saturation();

    case Color::GrayType:  return 0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsv(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).saturation();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

double Color::getHsvValue() const
{
  switch (getType()) {
    case Color::MaskType:  return 0.0;

    case Color::RgbType:   return Hsv(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).value();

    case Color::HsvType:   return m_value.hsv.v;

    case Color::HslType:   return Hsv(Rgb(getRed(), getGreen(), getBlue())).value();

    case Color::GrayType:  return m_value.gray.g / 255.0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsv(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).value();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

double Color::getHslHue() const
{
  switch (getType()) {
    case Color::MaskType:  return 0.0;

    case Color::RgbType:   return Hsl(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).hue();

    case Color::HsvType:   return m_value.hsv.h;

    case Color::HslType:   return m_value.hsl.h;

    case Color::GrayType:  return 0.0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsl(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).hue();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

double Color::getHslSaturation() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return Hsl(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).saturation();

    case Color::HsvType:   return Hsl(Rgb(getRed(), getGreen(), getBlue())).saturation();

    case Color::HslType:   return m_value.hsl.s;

    case Color::GrayType:  return 0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsl(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).saturation();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

double Color::getHslLightness() const
{
  switch (getType()) {
    case Color::MaskType:  return 0.0;

    case Color::RgbType:   return Hsl(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).lightness();

    case Color::HsvType:   return Hsl(Rgb(getRed(), getGreen(), getBlue())).lightness();

    case Color::HslType:   return m_value.hsl.l;

    case Color::GrayType:  return m_value.gray.g / 255.0;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return Hsl(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).lightness();
      }
      else
        return 0.0;
    }
  }

  ASSERT(false);
  return -1.0;
}

int Color::getGray() const
{
  switch (getType()) {
    case Color::MaskType: return 0;

    case Color::RgbType:
      return int(255.0 * Hsl(Rgb(m_value.rgb.r, m_value.rgb.g, m_value.rgb.b)).lightness());

    case Color::HsvType:   return int(255.0 * Hsl(Rgb(getRed(), getGreen(), getBlue())).lightness());

    case Color::HslType:   return int(255.0 * m_value.hsl.l);

    case Color::GrayType:  return m_value.gray.g;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size()) {
        uint32_t c = get_current_palette()->getEntry(i);
        return int(255.0 * Hsl(Rgb(rgba_getr(c), rgba_getg(c), rgba_getb(c))).lightness());
      }
      else
        return 0;
    }
  }

  ASSERT(false);
  return -1;
}

int Color::getIndex() const
{
  switch (getType()) {
    case Color::MaskType: return 0;

    case Color::RgbType:
    case Color::HsvType:
    case Color::HslType:
    case Color::GrayType: {
      int i =
        get_current_palette()->findExactMatch(getRed(), getGreen(), getBlue(), getAlpha(), -1);
      if (i >= 0)
        return i;
      else
        return get_current_palette()->findBestfit(getRed(), getGreen(), getBlue(), getAlpha(), 0);
    }

    case Color::IndexType: return m_value.index;

    case Color::TileType:  return doc::tile_geti(m_value.tile);
  }

  ASSERT(false);
  return -1;
}

doc::tile_t Color::getTile() const
{
  switch (getType()) {
    case Color::IndexType: return m_value.index;

    case Color::TileType:  return m_value.tile;
  }

  ASSERT(false);
  return doc::notile;
}

int Color::getAlpha() const
{
  switch (getType()) {
    case Color::MaskType:  return 0;

    case Color::RgbType:   return m_value.rgb.a;

    case Color::HsvType:   return m_value.hsv.a;

    case Color::HslType:   return m_value.hsl.a;

    case Color::GrayType:  return m_value.gray.a;

    case Color::IndexType: {
      int i = m_value.index;
      if (i >= 0 && i < get_current_palette()->size())
        return rgba_geta(get_current_palette()->getEntry(i));
      else
        return 0;
    }

    case Color::TileType: return 255;
  }

  ASSERT(false);
  return -1;
}

void Color::setAlpha(int alpha)
{
  alpha = std::clamp(alpha, 0, 255);

  switch (getType()) {
    case Color::MaskType:  break;

    case Color::RgbType:   m_value.rgb.a = alpha; break;

    case Color::HsvType:   m_value.hsv.a = alpha; break;

    case Color::HslType:   m_value.hsl.a = alpha; break;

    case Color::GrayType:  m_value.gray.a = alpha; break;

    case Color::IndexType: *this = Color::fromRgb(getRed(), getGreen(), getBlue(), alpha); break;
  }
}

} // namespace app
