// This file is part of Blend2D project <https://blend2d.com>
//
// See blend2d.h or LICENSE.md for license and copyright information
// SPDX-License-Identifier: Zlib

#ifndef BLEND2D_RGBA_H_INCLUDED
#define BLEND2D_RGBA_H_INCLUDED

#include "api.h"

//! \addtogroup bl_styling
//! \{

//! 32-bit RGBA color (8-bit per component) stored as `0xAARRGGBB`.
struct BLRgba32 {
  //! Packed 32-bit RGBA value.
  uint32_t value;

#ifdef __cplusplus
  //! \name Construction & Destruction
  //! \{

  BL_INLINE_NODEBUG BLRgba32() noexcept = default;

  BL_INLINE_NODEBUG constexpr BLRgba32(const BLRgba32&) noexcept = default;

  BL_INLINE_NODEBUG constexpr explicit BLRgba32(uint32_t rgba32) noexcept : value(rgba32) {}

  BL_INLINE_NODEBUG explicit BLRgba32(const BLRgba64& rgba64) noexcept { reset(rgba64); }
  BL_INLINE_NODEBUG constexpr BLRgba32(uint32_t r, uint32_t g, uint32_t b, uint32_t a = 0xFFu) noexcept
    : value((r << 16) | (g << 8) | b | (a << 24)) {}

  //! \}

  //! \name Overloaded Operators
  //! \{

  BL_INLINE_NODEBUG constexpr explicit operator bool() const noexcept { return value != 0; }

  BL_INLINE_NODEBUG BLRgba32& operator=(const BLRgba32& other) noexcept = default;

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool operator==(const BLRgba32& other) const noexcept { return  equals(other); }

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool operator!=(const BLRgba32& other) const noexcept { return !equals(other); }

  //! \}

  //! \name Accessors
  //! \{

  BL_INLINE_NODEBUG constexpr uint32_t r() const noexcept { return (value >> 16) & 0xFFu; }
  BL_INLINE_NODEBUG constexpr uint32_t g() const noexcept { return (value >>  8) & 0xFFu; }
  BL_INLINE_NODEBUG constexpr uint32_t b() const noexcept { return (value >>  0) & 0xFFu; }
  BL_INLINE_NODEBUG constexpr uint32_t a() const noexcept { return (value >> 24); }

  BL_INLINE_NODEBUG void setR(uint32_t r) noexcept { value = (value & 0xFF00FFFFu) | (r << 16); }
  BL_INLINE_NODEBUG void setG(uint32_t g) noexcept { value = (value & 0xFFFF00FFu) | (g <<  8); }
  BL_INLINE_NODEBUG void setB(uint32_t b) noexcept { value = (value & 0xFFFFFF00u) | (b <<  0); }
  BL_INLINE_NODEBUG void setA(uint32_t a) noexcept { value = (value & 0x00FFFFFFu) | (a << 24); }

  //! \}

  //! \name Common Functionality
  //! \{

  BL_INLINE_NODEBUG void reset() noexcept { value = 0u; }
  BL_INLINE_NODEBUG void reset(uint32_t rgba32) noexcept { value = rgba32;}

  BL_INLINE_NODEBUG void reset(uint32_t r, uint32_t g, uint32_t b, uint32_t a = 0xFFu) noexcept {
    value = (r << 16) | (g << 8) | b | (a << 24);
  }

  BL_INLINE_NODEBUG void reset(const BLRgba32& rgba32) noexcept {
    value = rgba32.value;
  }

  BL_INLINE_NODEBUG void reset(const BLRgba64& rgba64) noexcept;

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool equals(const BLRgba32& other) const noexcept { return value == other.value; }

  //! \}

  //! \name Utilities
  //! \{

  //! Tests whether the color is fully-opaque (alpha equals 0xFFFF).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isOpaque() const noexcept { return value >= 0xFF000000u; }

  //! Tests whether the color is fully-transparent (alpha equals 0).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isTransparent() const noexcept { return value <= 0x00FFFFFFu; }

  //! \}
#endif
};

//! 64-bit RGBA color (16-bit per component) stored as `0xAAAARRRRGGGGBBBB`.
struct BLRgba64 {
  //! Packed 64-bit RGBA value.
  uint64_t value;

#ifdef __cplusplus
  //! \name Construction & Destruction
  //! \{

  BL_INLINE_NODEBUG BLRgba64() noexcept = default;

  BL_INLINE_NODEBUG constexpr BLRgba64(const BLRgba64&) noexcept = default;

  BL_INLINE_NODEBUG constexpr explicit BLRgba64(uint64_t rgba64) noexcept : value(rgba64) {}

  BL_INLINE_NODEBUG constexpr BLRgba64(uint32_t r, uint32_t g, uint32_t b, uint32_t a = 0xFFFFu) noexcept
    : value((uint64_t(a) << 48) |
            (uint64_t(r) << 32) |
            (uint64_t(g) << 16) |
            (uint64_t(b) <<  0) ) {}

  BL_INLINE_NODEBUG constexpr explicit BLRgba64(const BLRgba32& rgba32) noexcept
    : value(((uint64_t(rgba32.r()) << 32) |
             (uint64_t(rgba32.g()) << 16) |
             (uint64_t(rgba32.b()) <<  0) |
             (uint64_t(rgba32.a()) << 48)) * 0x0101u) {}

  //! \}

  //! \name Overloaded Operators
  //! \{

  BL_INLINE_NODEBUG constexpr explicit operator bool() const noexcept { return value != 0; }

  BL_INLINE_NODEBUG BLRgba64& operator=(const BLRgba64& other) noexcept = default;

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool operator==(const BLRgba64& other) const noexcept { return  equals(other); }

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool operator!=(const BLRgba64& other) const noexcept { return !equals(other); }

  //! \}

  //! \name Accessors
  //! \{

  BL_INLINE_NODEBUG constexpr uint32_t r() const noexcept { return uint32_t((value >> 32) & 0xFFFFu); }
  BL_INLINE_NODEBUG constexpr uint32_t g() const noexcept { return uint32_t((value >> 16) & 0xFFFFu); }
  BL_INLINE_NODEBUG constexpr uint32_t b() const noexcept { return uint32_t((value >>  0) & 0xFFFFu); }
  BL_INLINE_NODEBUG constexpr uint32_t a() const noexcept { return uint32_t((value >> 48)); }

  BL_INLINE_NODEBUG void setR(uint32_t r) noexcept { value = (value & 0xFFFF0000FFFFFFFFu) | (uint64_t(r) << 32); }
  BL_INLINE_NODEBUG void setG(uint32_t g) noexcept { value = (value & 0xFFFFFFFF0000FFFFu) | (uint64_t(g) << 16); }
  BL_INLINE_NODEBUG void setB(uint32_t b) noexcept { value = (value & 0xFFFFFFFFFFFF0000u) | (uint64_t(b) <<  0); }
  BL_INLINE_NODEBUG void setA(uint32_t a) noexcept { value = (value & 0x0000FFFFFFFFFFFFu) | (uint64_t(a) << 48); }

  //! \}

  //! \name Common Functionality
  //! \{

  BL_INLINE_NODEBUG void reset() noexcept { value = 0u; }
  BL_INLINE_NODEBUG void reset(uint64_t rgba64) noexcept { value = rgba64; }

  BL_INLINE_NODEBUG void reset(uint32_t r, uint32_t g, uint32_t b, uint32_t a = 0xFFFFu) noexcept {
    value = (uint64_t(r) << 32) |
            (uint64_t(g) << 16) |
            (uint64_t(b) <<  0) |
            (uint64_t(a) << 48);
  }

  BL_INLINE_NODEBUG void reset(const BLRgba64& rgba64) noexcept {
    value = rgba64.value;
  }

  BL_INLINE_NODEBUG void reset(const BLRgba32& rgba32) noexcept {
    value = ((uint64_t(rgba32.r()) << 32) |
             (uint64_t(rgba32.g()) << 16) |
             (uint64_t(rgba32.b()) <<  0) |
             (uint64_t(rgba32.a()) << 48)) * 0x0101u;
  }

  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool equals(const BLRgba64& other) const noexcept { return value == other.value; }

  //! \}

  //! \name Utilities
  //! \{

  //! Tests whether the color is fully-opaque (alpha equals 0xFFFF).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isOpaque() const noexcept { return value >= 0xFFFF000000000000u; }

  //! Tests whether the color is fully-transparent (alpha equals 0).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isTransparent() const noexcept { return value <= 0x0000FFFFFFFFFFFFu; }

  //! \}
#endif
};

//! 128-bit RGBA color stored as 4 32-bit floating point values in [RGBA] order.
struct BLRgba {
  //! Red component.
  float r;
  //! Green component.
  float g;
  //! Blur component.
  float b;
  //! Alpha component.
  float a;

#ifdef __cplusplus
  //! \name Construction & Destruction
  //! \{

  BL_INLINE_NODEBUG BLRgba() noexcept = default;

  BL_INLINE_NODEBUG constexpr BLRgba(const BLRgba&) noexcept = default;

  BL_INLINE_NODEBUG constexpr BLRgba(float rValue, float gValue, float bValue, float aValue = 1.0f) noexcept
    : r(rValue),
      g(gValue),
      b(bValue),
      a(aValue) {}

  BL_INLINE_NODEBUG constexpr BLRgba(const BLRgba32& rgba32) noexcept
    : r(float(int32_t(rgba32.r())) * (1.0f / 255.0f)),
      g(float(int32_t(rgba32.g())) * (1.0f / 255.0f)),
      b(float(int32_t(rgba32.b())) * (1.0f / 255.0f)),
      a(float(int32_t(rgba32.a())) * (1.0f / 255.0f)) {}

  BL_INLINE_NODEBUG constexpr BLRgba(const BLRgba64& rgba64) noexcept
    : r(float(int32_t(rgba64.r())) * (1.0f / 65535.0f)),
      g(float(int32_t(rgba64.g())) * (1.0f / 65535.0f)),
      b(float(int32_t(rgba64.b())) * (1.0f / 65535.0f)),
      a(float(int32_t(rgba64.a())) * (1.0f / 65535.0f)) {}

  //! \}

  //! \name Overloaded Operators
  //! \{

  BL_INLINE_NODEBUG constexpr explicit operator bool() const noexcept {
    return !((r == 0.0f) & (g == 0.0f) & (b == 0.0f) & (a == 0.0f));
  }

  BL_INLINE_NODEBUG BLRgba& operator=(const BLRgba& other) noexcept = default;

  BL_NODISCARD
  BL_INLINE_NODEBUG bool operator==(const BLRgba& other) const noexcept { return  equals(other); }

  BL_NODISCARD
  BL_INLINE_NODEBUG bool operator!=(const BLRgba& other) const noexcept { return !equals(other); }

  //! \}

  //! \name Common Functionality
  //! \{

  BL_INLINE_NODEBUG void reset() noexcept {
    reset(0.0f, 0.0f, 0.0f, 0.0f);
  }

  BL_INLINE_NODEBUG void reset(const BLRgba32& rgba32) noexcept {
    *this = BLRgba(rgba32);
  }

  BL_INLINE_NODEBUG void reset(const BLRgba64& rgba64) noexcept {
    *this = BLRgba(rgba64);
  }

  BL_INLINE_NODEBUG void reset(const BLRgba& other) noexcept {
    reset(other.r, other.g, other.b, other.a);
  }

  BL_INLINE_NODEBUG void reset(float rValue, float gValue, float bValue, float aValue = 1.0f) noexcept {
    this->r = rValue;
    this->g = gValue;
    this->b = bValue;
    this->a = aValue;
  }

  BL_NODISCARD
  BL_INLINE_NODEBUG bool equals(const BLRgba32& rgba32) const noexcept {
    return equals(BLRgba(rgba32));
  }

  BL_NODISCARD
  BL_INLINE_NODEBUG bool equals(const BLRgba64& rgba64) const noexcept {
    return equals(BLRgba(rgba64));
  }

  BL_NODISCARD
  BL_INLINE_NODEBUG bool equals(const BLRgba& other) const noexcept {
    return bool(unsigned(blEquals(this->r, other.r)) &
                unsigned(blEquals(this->g, other.g)) &
                unsigned(blEquals(this->b, other.b)) &
                unsigned(blEquals(this->a, other.a)));
  }

  BL_NODISCARD
  BL_INLINE_NODEBUG bool equals(float rValue, float gValue, float bValue, float aValue = 1.0f) const noexcept {
    return bool(unsigned(blEquals(this->r, rValue)) &
                unsigned(blEquals(this->g, gValue)) &
                unsigned(blEquals(this->b, bValue)) &
                unsigned(blEquals(this->a, aValue)));
  }

  //! \}

  //! \name Conversion
  //! \{

  BL_INLINE_NODEBUG BLRgba32 toRgba32() const noexcept {
    return BLRgba32(uint32_t(int(blClamp(r, 0.0f, 1.0f) * 255.0f + 0.5f)),
                    uint32_t(int(blClamp(g, 0.0f, 1.0f) * 255.0f + 0.5f)),
                    uint32_t(int(blClamp(b, 0.0f, 1.0f) * 255.0f + 0.5f)),
                    uint32_t(int(blClamp(a, 0.0f, 1.0f) * 255.0f + 0.5f)));
  }

  BL_INLINE_NODEBUG BLRgba64 toRgba64() const noexcept {
    return BLRgba64(uint32_t(int(blClamp(r, 0.0f, 1.0f) * 65535.0f + 0.5f)),
                    uint32_t(int(blClamp(g, 0.0f, 1.0f) * 65535.0f + 0.5f)),
                    uint32_t(int(blClamp(b, 0.0f, 1.0f) * 65535.0f + 0.5f)),
                    uint32_t(int(blClamp(a, 0.0f, 1.0f) * 65535.0f + 0.5f)));
  }

  //! \}

  //! \name Utilities
  //! \{

  //! Tests whether the color is fully-opaque (alpha equals 1.0).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isOpaque() const noexcept { return a >= 1.0; }

  //! Tests whether the color is fully-transparent (alpha equals 0.0).
  BL_NODISCARD
  BL_INLINE_NODEBUG constexpr bool isTransparent() const noexcept { return a <= 0.0; }

  //! \}
#endif
};

#ifdef __cplusplus
BL_INLINE_NODEBUG void BLRgba32::reset(const BLRgba64& rgba64) noexcept {
  reset(uint32_t((rgba64.value >> 40) & 0xFF),
        uint32_t((rgba64.value >> 24) & 0xFF),
        uint32_t((rgba64.value >>  8) & 0xFF),
        uint32_t((rgba64.value >> 56)       ));
}

static BL_INLINE_NODEBUG constexpr BLRgba32 blMin(const BLRgba32& a, const BLRgba32& b) noexcept {
  return BLRgba32(blMin(a.r(), b.r()), blMin(a.g(), b.g()), blMin(a.b(), b.b()), blMin(a.a(), b.a()));
}

static BL_INLINE_NODEBUG constexpr BLRgba32 blMax(const BLRgba32& a, const BLRgba32& b) noexcept {
  return BLRgba32(blMax(a.r(), b.r()), blMax(a.g(), b.g()), blMax(a.b(), b.b()), blMax(a.a(), b.a()));
}

BL_NODISCARD
static BL_INLINE_NODEBUG constexpr BLRgba64 blMin(const BLRgba64& a, const BLRgba64& b) noexcept {
  return BLRgba64(blMin(a.r(), b.r()), blMin(a.g(), b.g()), blMin(a.b(), b.b()), blMin(a.a(), b.a()));
}

BL_NODISCARD
static BL_INLINE_NODEBUG constexpr BLRgba64 blMax(const BLRgba64& a, const BLRgba64& b) noexcept {
  return BLRgba64(blMax(a.r(), b.r()), blMax(a.g(), b.g()), blMax(a.b(), b.b()), blMax(a.a(), b.a()));
}

BL_NODISCARD
static BL_INLINE_NODEBUG constexpr BLRgba blMin(const BLRgba& a, const BLRgba& b) noexcept {
  return BLRgba(blMin(a.r, b.r), blMin(a.g, b.g), blMin(a.b, b.b), blMin(a.a, b.a));
}

BL_NODISCARD
static BL_INLINE_NODEBUG constexpr BLRgba blMax(const BLRgba& a, const BLRgba& b) noexcept {
  return BLRgba(blMax(a.r, b.r), blMax(a.g, b.g), blMax(a.b, b.b), blMax(a.a, b.a));
}
#endif

#ifdef __cplusplus
static_assert(sizeof(BLRgba) == 16, "'BLRgba' struct must be exactly 16 bytes long");
static_assert(sizeof(BLRgba32) == 4, "'BLRgba32' struct must be exactly 4 bytes long");
static_assert(sizeof(BLRgba64) == 8, "'BLRgba64' struct must be exactly 8 bytes long");
#endif

//! \}

#endif // BLEND2D_RGBA_H_INCLUDED
