// File: crn_dxt_image.h
// See Copyright Notice and license at the end of inc/crnlib.h
#pragma once
#include "crn_dxt1.h"
#include "crn_dxt5a.h"
#include "crn_etc.h"
#if CRNLIB_SUPPORT_ETC_A1
#include "crn_etc_a1.h"
#endif
#include "crn_image.h"

#define CRNLIB_SUPPORT_ATI_COMPRESS 0

namespace crnlib {
class task_pool;

class dxt_image {
 public:
  dxt_image();
  dxt_image(const dxt_image& other);
  dxt_image& operator=(const dxt_image& rhs);

  void clear();

  inline bool is_valid() const { return m_blocks_x > 0; }

  uint get_width() const { return m_width; }
  uint get_height() const { return m_height; }

  uint get_blocks_x() const { return m_blocks_x; }
  uint get_blocks_y() const { return m_blocks_y; }
  uint get_total_blocks() const { return m_blocks_x * m_blocks_y; }

  uint get_elements_per_block() const { return m_num_elements_per_block; }
  uint get_bytes_per_block() const { return m_bytes_per_block; }

  dxt_format get_format() const { return m_format; }

  bool has_color() const { return (m_format == cDXT1) || (m_format == cDXT1A) || (m_format == cDXT3) || (m_format == cDXT5) || (m_format == cETC1) || (m_format == cETC2) || (m_format == cETC2A) || (m_format == cETC1S) || (m_format == cETC2AS); }

  // Will be pretty slow if the image is DXT1, as this method scans for alpha blocks/selectors.
  bool has_alpha() const;

  enum element_type {
    cUnused = 0,

    cColorDXT1,  // DXT1 color block

    cAlphaDXT3,  // DXT3 alpha block (only)
    cAlphaDXT5,  // DXT5 alpha block (only)

    cColorETC1,  // ETC1 color block
    cColorETC2,  // ETC2 color block

    cAlphaETC2,  // ETC2 alpha block (only)
  };

  element_type get_element_type(uint element_index) const {
    CRNLIB_ASSERT(element_index < m_num_elements_per_block);
    return m_element_type[element_index];
  }

  //Returns -1 for RGB, or [0,3]
  int8 get_element_component_index(uint element_index) const {
    CRNLIB_ASSERT(element_index < m_num_elements_per_block);
    return m_element_component_index[element_index];
  }

  struct element {
    uint8 m_bytes[8];

    uint get_le_word(uint index) const {
      CRNLIB_ASSERT(index < 4);
      return m_bytes[index * 2] | (m_bytes[index * 2 + 1] << 8);
    }
    uint get_be_word(uint index) const {
      CRNLIB_ASSERT(index < 4);
      return m_bytes[index * 2 + 1] | (m_bytes[index * 2] << 8);
    }

    void set_le_word(uint index, uint val) {
      CRNLIB_ASSERT((index < 4) && (val <= cUINT16_MAX));
      m_bytes[index * 2] = static_cast<uint8>(val & 0xFF);
      m_bytes[index * 2 + 1] = static_cast<uint8>((val >> 8) & 0xFF);
    }
    void set_be_word(uint index, uint val) {
      CRNLIB_ASSERT((index < 4) && (val <= cUINT16_MAX));
      m_bytes[index * 2 + 1] = static_cast<uint8>(val & 0xFF);
      m_bytes[index * 2] = static_cast<uint8>((val >> 8) & 0xFF);
    }

    void clear() {
      memset(this, 0, sizeof(*this));
    }
  };

  typedef crnlib::vector<element> element_vec;

  bool init(dxt_format fmt, uint width, uint height, bool clear_elements);
  bool init(dxt_format fmt, uint width, uint height, uint num_elements, element* pElements, bool create_copy);

  struct pack_params {
    pack_params() {
      clear();
    }

    void clear() {
      m_quality = cCRNDXTQualityUber;
      m_perceptual = true;
      m_dithering = false;
      m_grayscale_sampling = false;
      m_use_both_block_types = true;
      m_endpoint_caching = true;
      m_compressor = cCRNDXTCompressorCRN;
      m_pProgress_callback = NULL;
      m_pProgress_callback_user_data_ptr = NULL;
      m_dxt1a_alpha_threshold = 128;
      m_num_helper_threads = 0;
      m_progress_start = 0;
      m_progress_range = 100;
      m_use_transparent_indices_for_black = false;
      m_pTask_pool = NULL;
    }

    void init(const crn_comp_params& params) {
      m_perceptual = (params.m_flags & cCRNCompFlagPerceptual) != 0;
      m_num_helper_threads = params.m_num_helper_threads;
      m_use_both_block_types = (params.m_flags & cCRNCompFlagUseBothBlockTypes) != 0;
      m_use_transparent_indices_for_black = (params.m_flags & cCRNCompFlagUseTransparentIndicesForBlack) != 0;
      m_dxt1a_alpha_threshold = params.m_dxt1a_alpha_threshold;
      m_quality = params.m_dxt_quality;
      m_endpoint_caching = (params.m_flags & cCRNCompFlagDisableEndpointCaching) == 0;
      m_grayscale_sampling = (params.m_flags & cCRNCompFlagGrayscaleSampling) != 0;
      m_compressor = params.m_dxt_compressor_type;
    }

    uint m_dxt1a_alpha_threshold;

    uint m_num_helper_threads;

    crn_dxt_quality m_quality;

    crn_dxt_compressor_type m_compressor;

    bool m_perceptual;
    bool m_dithering;
    bool m_grayscale_sampling;
    bool m_use_both_block_types;
    bool m_endpoint_caching;
    bool m_use_transparent_indices_for_black;

    typedef bool (*progress_callback_func)(uint percentage_complete, void* pUser_data_ptr);
    progress_callback_func m_pProgress_callback;
    void* m_pProgress_callback_user_data_ptr;

    uint m_progress_start;
    uint m_progress_range;

    task_pool* m_pTask_pool;
  };

  bool init(dxt_format fmt, const image_u8& img, const pack_params& p = dxt_image::pack_params());

  bool unpack(image_u8& img) const;

  void endian_swap();

  uint get_total_elements() const { return m_elements.size(); }

  const element_vec& get_element_vec() const { return m_elements; }
  element_vec& get_element_vec() { return m_elements; }

  const element& get_element(uint block_x, uint block_y, uint element_index) const;
  element& get_element(uint block_x, uint block_y, uint element_index);

  const element* get_element_ptr() const { return m_pElements; }
  element* get_element_ptr() { return m_pElements; }

  uint get_size_in_bytes() const { return m_elements.size() * sizeof(element); }
  uint get_row_pitch_in_bytes() const { return m_blocks_x * m_bytes_per_block; }

  color_quad_u8 get_pixel(uint x, uint y) const;
  uint get_pixel_alpha(uint x, uint y, uint element_index) const;

  void set_pixel(uint x, uint y, const color_quad_u8& c, bool perceptual = true);

  // get_block_pixels() only sets those components stored in the image!
  bool get_block_pixels(uint block_x, uint block_y, color_quad_u8* pPixels) const;

  struct set_block_pixels_context {
    dxt1_endpoint_optimizer m_dxt1_optimizer;
    dxt5_endpoint_optimizer m_dxt5_optimizer;
    pack_etc1_block_context m_etc1_optimizer;
#if CRNLIB_SUPPORT_ETC_A1
    etc_a1::pack_etc1_block_context m_etc1_a1_optimizer;
#endif
  };

  void set_block_pixels(uint block_x, uint block_y, const color_quad_u8* pPixels, const pack_params& p, set_block_pixels_context& context);
  void set_block_pixels(uint block_x, uint block_y, const color_quad_u8* pPixels, const pack_params& p);

  void get_block_endpoints(uint block_x, uint block_y, uint element_index, uint& packed_low_endpoint, uint& packed_high_endpoint) const;

  // Returns a value representing the component(s) that where actually set, where -1 = RGB.
  // This method does not always set every component!
  int get_block_endpoints(uint block_x, uint block_y, uint element_index, color_quad_u8& low_endpoint, color_quad_u8& high_endpoint, bool scaled = true) const;

  // pColors should point to a 16 entry array, to handle DXT3.
  // Returns the number of block colors: 3, 4, 6, 8, or 16.
  uint get_block_colors(uint block_x, uint block_y, uint element_index, color_quad_u8* pColors, uint subblock_index = 0);

  uint get_subblock_index(uint x, uint y, uint element_index) const;
  uint get_total_subblocks(uint element_index) const;

  uint get_selector(uint x, uint y, uint element_index) const;

  void change_dxt1_to_dxt1a();

  bool can_flip(uint axis_index);

  // Returns true if the texture can actually be flipped.
  bool flip_x();
  bool flip_y();

 private:
  element_vec m_elements;
  element* m_pElements;

  uint m_width;
  uint m_height;

  uint m_blocks_x;
  uint m_blocks_y;
  uint m_total_blocks;
  uint m_total_elements;

  uint m_num_elements_per_block;  // 1 or 2
  uint m_bytes_per_block;         // 8 or 16

  int8 m_element_component_index[2];
  element_type m_element_type[2];

  dxt_format m_format;  // DXT1, 1A, 3, 5, N/3DC, or 5A

  bool init_internal(dxt_format fmt, uint width, uint height);
  void init_task(uint64 data, void* pData_ptr);

#if CRNLIB_SUPPORT_ATI_COMPRESS
  bool init_ati_compress(dxt_format fmt, const image_u8& img, const pack_params& p);
#endif

  void flip_col(uint x);
  void flip_row(uint y);
};

}  // namespace crnlib
