// File: crn_dxt.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once
#include "../inc/crnlib.h"
#include "crn_color.h"
#include "crn_vec.h"
#include "crn_rand.h"
#include "crn_sparse_bit_array.h"
#include "crn_hash_map.h"
#include <map>

#define CRNLIB_DXT_ALT_ROUNDING 1

namespace crnlib {
enum dxt_constants {
  cDXT1BytesPerBlock = 8U,
  cDXT5NBytesPerBlock = 16U,

  cDXT5SelectorBits = 3U,
  cDXT5SelectorValues = 1U << cDXT5SelectorBits,
  cDXT5SelectorMask = cDXT5SelectorValues - 1U,

  cDXT1SelectorBits = 2U,
  cDXT1SelectorValues = 1U << cDXT1SelectorBits,
  cDXT1SelectorMask = cDXT1SelectorValues - 1U,

  cDXTBlockShift = 2U,
  cDXTBlockSize = 1U << cDXTBlockShift
};

enum dxt_format {
  cDXTInvalid = -1,

  // cDXT1/1A must appear first!
  cDXT1,
  cDXT1A,

  cDXT3,
  cDXT5,
  cDXT5A,

  cDXN_XY,  // inverted relative to standard ATI2, 360's DXN
  cDXN_YX,  // standard ATI2,

  cETC1  // Ericsson texture compression (color only, 4x4 blocks, 4bpp, 64-bits/block)
};

const float cDXT1MaxLinearValue = 3.0f;
const float cDXT1InvMaxLinearValue = 1.0f / 3.0f;

const float cDXT5MaxLinearValue = 7.0f;
const float cDXT5InvMaxLinearValue = 1.0f / 7.0f;

// Converts DXT1 raw color selector index to a linear value.
extern const uint8 g_dxt1_to_linear[cDXT1SelectorValues];

// Converts DXT5 raw alpha selector index to a linear value.
extern const uint8 g_dxt5_to_linear[cDXT5SelectorValues];

// Converts DXT1 linear color selector index to a raw value (inverse of g_dxt1_to_linear).
extern const uint8 g_dxt1_from_linear[cDXT1SelectorValues];

// Converts DXT5 linear alpha selector index to a raw value (inverse of g_dxt5_to_linear).
extern const uint8 g_dxt5_from_linear[cDXT5SelectorValues];

extern const uint8 g_dxt5_alpha6_to_linear[cDXT5SelectorValues];

extern const uint8 g_six_alpha_invert_table[cDXT5SelectorValues];
extern const uint8 g_eight_alpha_invert_table[cDXT5SelectorValues];

const char* get_dxt_format_string(dxt_format fmt);
uint get_dxt_format_bits_per_pixel(dxt_format fmt);
bool get_dxt_format_has_alpha(dxt_format fmt);

const char* get_dxt_quality_string(crn_dxt_quality q);

const char* get_dxt_compressor_name(crn_dxt_compressor_type c);

struct dxt1_block {
  uint8 m_low_color[2];
  uint8 m_high_color[2];

  enum { cNumSelectorBytes = 4 };
  uint8 m_selectors[cNumSelectorBytes];

  inline void clear() {
    utils::zero_this(this);
  }

  // These methods assume the in-memory rep is in LE byte order.
  inline uint get_low_color() const {
    return m_low_color[0] | (m_low_color[1] << 8U);
  }

  inline uint get_high_color() const {
    return m_high_color[0] | (m_high_color[1] << 8U);
  }

  inline void set_low_color(uint16 c) {
    m_low_color[0] = static_cast<uint8>(c & 0xFF);
    m_low_color[1] = static_cast<uint8>((c >> 8) & 0xFF);
  }

  inline void set_high_color(uint16 c) {
    m_high_color[0] = static_cast<uint8>(c & 0xFF);
    m_high_color[1] = static_cast<uint8>((c >> 8) & 0xFF);
  }

  inline bool is_constant_color_block() const { return get_low_color() == get_high_color(); }
  inline bool is_alpha_block() const { return get_low_color() <= get_high_color(); }
  inline bool is_non_alpha_block() const { return !is_alpha_block(); }

  inline uint get_selector(uint x, uint y) const {
    CRNLIB_ASSERT((x < 4U) && (y < 4U));
    return (m_selectors[y] >> (x * cDXT1SelectorBits)) & cDXT1SelectorMask;
  }

  inline void set_selector(uint x, uint y, uint val) {
    CRNLIB_ASSERT((x < 4U) && (y < 4U) && (val < 4U));

    m_selectors[y] &= (~(cDXT1SelectorMask << (x * cDXT1SelectorBits)));
    m_selectors[y] |= (val << (x * cDXT1SelectorBits));
  }

  inline void flip_x(uint w = 4, uint h = 4) {
    for (uint x = 0; x < (w / 2); x++) {
      for (uint y = 0; y < h; y++) {
        const uint c = get_selector(x, y);
        set_selector(x, y, get_selector((w - 1) - x, y));
        set_selector((w - 1) - x, y, c);
      }
    }
  }

  inline void flip_y(uint w = 4, uint h = 4) {
    for (uint y = 0; y < (h / 2); y++) {
      for (uint x = 0; x < w; x++) {
        const uint c = get_selector(x, y);
        set_selector(x, y, get_selector(x, (h - 1) - y));
        set_selector(x, (h - 1) - y, c);
      }
    }
  }

  static uint16 pack_color(const color_quad_u8& color, bool scaled, uint bias = 127U);
  static uint16 pack_color(uint r, uint g, uint b, bool scaled, uint bias = 127U);

  static color_quad_u8 unpack_color(uint16 packed_color, bool scaled, uint alpha = 255U);
  static void unpack_color(uint& r, uint& g, uint& b, uint16 packed_color, bool scaled);

  static uint get_block_colors3(color_quad_u8* pDst, uint16 color0, uint16 color1);
  static uint get_block_colors3_round(color_quad_u8* pDst, uint16 color0, uint16 color1);

  static uint get_block_colors4(color_quad_u8* pDst, uint16 color0, uint16 color1);
  static uint get_block_colors4_round(color_quad_u8* pDst, uint16 color0, uint16 color1);

  // pDst must point to an array at least cDXT1SelectorValues long.
  static uint get_block_colors(color_quad_u8* pDst, uint16 color0, uint16 color1);

  static uint get_block_colors_round(color_quad_u8* pDst, uint16 color0, uint16 color1);

  static color_quad_u8 unpack_endpoint(uint32 endpoints, uint index, bool scaled, uint alpha = 255U);
  static uint pack_endpoints(uint lo, uint hi);

  static void get_block_colors_NV5x(color_quad_u8* pDst, uint16 packed_col0, uint16 packed_col1, bool color4);
};

CRNLIB_DEFINE_BITWISE_COPYABLE(dxt1_block);

struct dxt3_block {
  enum { cNumAlphaBytes = 8 };
  uint8 m_alpha[cNumAlphaBytes];

  void set_alpha(uint x, uint y, uint value, bool scaled);
  uint get_alpha(uint x, uint y, bool scaled) const;

  inline void flip_x(uint w = 4, uint h = 4) {
    for (uint x = 0; x < (w / 2); x++) {
      for (uint y = 0; y < h; y++) {
        const uint c = get_alpha(x, y, false);
        set_alpha(x, y, get_alpha((w - 1) - x, y, false), false);
        set_alpha((w - 1) - x, y, c, false);
      }
    }
  }

  inline void flip_y(uint w = 4, uint h = 4) {
    for (uint y = 0; y < (h / 2); y++) {
      for (uint x = 0; x < w; x++) {
        const uint c = get_alpha(x, y, false);
        set_alpha(x, y, get_alpha(x, (h - 1) - y, false), false);
        set_alpha(x, (h - 1) - y, c, false);
      }
    }
  }
};

CRNLIB_DEFINE_BITWISE_COPYABLE(dxt3_block);

struct dxt5_block {
  uint8 m_endpoints[2];

  enum { cNumSelectorBytes = 6 };
  uint8 m_selectors[cNumSelectorBytes];

  inline void clear() {
    utils::zero_this(this);
  }

  inline uint get_low_alpha() const {
    return m_endpoints[0];
  }

  inline uint get_high_alpha() const {
    return m_endpoints[1];
  }

  inline void set_low_alpha(uint i) {
    CRNLIB_ASSERT(i <= cUINT8_MAX);
    m_endpoints[0] = static_cast<uint8>(i);
  }

  inline void set_high_alpha(uint i) {
    CRNLIB_ASSERT(i <= cUINT8_MAX);
    m_endpoints[1] = static_cast<uint8>(i);
  }

  inline bool is_alpha6_block() const { return get_low_alpha() <= get_high_alpha(); }

  uint get_endpoints_as_word() const { return m_endpoints[0] | (m_endpoints[1] << 8); }
  uint get_selectors_as_word(uint index) {
    CRNLIB_ASSERT(index < 3);
    return m_selectors[index * 2] | (m_selectors[index * 2 + 1] << 8);
  }

  inline uint get_selector(uint x, uint y) const {
    CRNLIB_ASSERT((x < 4U) && (y < 4U));

    uint selector_index = (y * 4) + x;
    uint bit_index = selector_index * cDXT5SelectorBits;

    uint byte_index = bit_index >> 3;
    uint bit_ofs = bit_index & 7;

    uint v = m_selectors[byte_index];
    if (byte_index < (cNumSelectorBytes - 1))
      v |= (m_selectors[byte_index + 1] << 8);

    return (v >> bit_ofs) & 7;
  }

  inline void set_selector(uint x, uint y, uint val) {
    CRNLIB_ASSERT((x < 4U) && (y < 4U) && (val < 8U));

    uint selector_index = (y * 4) + x;
    uint bit_index = selector_index * cDXT5SelectorBits;

    uint byte_index = bit_index >> 3;
    uint bit_ofs = bit_index & 7;

    uint v = m_selectors[byte_index];
    if (byte_index < (cNumSelectorBytes - 1))
      v |= (m_selectors[byte_index + 1] << 8);

    v &= (~(7 << bit_ofs));
    v |= (val << bit_ofs);

    m_selectors[byte_index] = static_cast<uint8>(v);
    if (byte_index < (cNumSelectorBytes - 1))
      m_selectors[byte_index + 1] = static_cast<uint8>(v >> 8);
  }

  inline void flip_x(uint w = 4, uint h = 4) {
    for (uint x = 0; x < (w / 2); x++) {
      for (uint y = 0; y < h; y++) {
        const uint c = get_selector(x, y);
        set_selector(x, y, get_selector((w - 1) - x, y));
        set_selector((w - 1) - x, y, c);
      }
    }
  }

  inline void flip_y(uint w = 4, uint h = 4) {
    for (uint y = 0; y < (h / 2); y++) {
      for (uint x = 0; x < w; x++) {
        const uint c = get_selector(x, y);
        set_selector(x, y, get_selector(x, (h - 1) - y));
        set_selector(x, (h - 1) - y, c);
      }
    }
  }

  enum { cMaxSelectorValues = 8 };

  // Results written to alpha channel.
  static uint get_block_values6(color_quad_u8* pDst, uint l, uint h);
  static uint get_block_values8(color_quad_u8* pDst, uint l, uint h);
  static uint get_block_values(color_quad_u8* pDst, uint l, uint h);

  static uint get_block_values6(uint* pDst, uint l, uint h);
  static uint get_block_values8(uint* pDst, uint l, uint h);
  // pDst must point to an array at least cDXT5SelectorValues long.
  static uint get_block_values(uint* pDst, uint l, uint h);

  static uint unpack_endpoint(uint packed, uint index);
  static uint pack_endpoints(uint lo, uint hi);
};

CRNLIB_DEFINE_BITWISE_COPYABLE(dxt5_block);

struct dxt_pixel_block {
  color_quad_u8 m_pixels[cDXTBlockSize][cDXTBlockSize];  // [y][x]

  inline void clear() {
    utils::zero_object(*this);
  }
};

CRNLIB_DEFINE_BITWISE_COPYABLE(dxt_pixel_block);

}  // namespace crnlib
