// File: crn_ktx_texture.h
#ifndef _KTX_TEXTURE_H_
#define _KTX_TEXTURE_H_
#ifdef _MSC_VER
#pragma once
#endif

#include "crn_data_stream_serializer.h"

#define KTX_ENDIAN 0x04030201
#define KTX_OPPOSITE_ENDIAN 0x01020304

namespace crnlib
{
   extern const uint8 s_ktx_file_id[12];

   struct ktx_header
   {
      uint8 m_identifier[12];
      uint32 m_endianness;
      uint32 m_glType;
      uint32 m_glTypeSize;
      uint32 m_glFormat;
      uint32 m_glInternalFormat;
      uint32 m_glBaseInternalFormat;
      uint32 m_pixelWidth;
      uint32 m_pixelHeight;
      uint32 m_pixelDepth;
      uint32 m_numberOfArrayElements;
      uint32 m_numberOfFaces;
      uint32 m_numberOfMipmapLevels;
      uint32 m_bytesOfKeyValueData;

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

      void endian_swap()
      {
         utils::endian_swap_mem32(&m_endianness, (sizeof(*this) - sizeof(m_identifier)) / sizeof(uint32));
      }
   };

   typedef crnlib::vector<uint8_vec> ktx_key_value_vec;
   typedef crnlib::vector<uint8_vec> ktx_image_data_vec;

   // Compressed pixel data formats: ETC1, DXT1, DXT3, DXT5
   enum 
   { 
      KTX_ETC1_RGB8_OES = 0x8D64, KTX_RGB_S3TC = 0x83A0, KTX_RGB4_S3TC = 0x83A1, KTX_COMPRESSED_RGB_S3TC_DXT1_EXT = 0x83F0,
      KTX_COMPRESSED_RGBA_S3TC_DXT1_EXT = 0x83F1, KTX_COMPRESSED_SRGB_S3TC_DXT1_EXT = 0x8C4C, KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT1_EXT = 0x8C4D,
      KTX_RGBA_S3TC = 0x83A2, KTX_RGBA4_S3TC = 0x83A3, KTX_COMPRESSED_RGBA_S3TC_DXT3_EXT = 0x83F2, KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT3_EXT = 0x8C4E,
      KTX_COMPRESSED_RGBA_S3TC_DXT5_EXT = 0x83F3, KTX_COMPRESSED_SRGB_ALPHA_S3TC_DXT5_EXT = 0x8C4F, KTX_RGBA_DXT5_S3TC = 0x83A4, KTX_RGBA4_DXT5_S3TC = 0x83A5,
      KTX_COMPRESSED_RED_RGTC1_EXT = 0x8DBB, KTX_COMPRESSED_SIGNED_RED_RGTC1_EXT = 0x8DBC, KTX_COMPRESSED_RED_GREEN_RGTC2_EXT = 0x8DBD, KTX_COMPRESSED_SIGNED_RED_GREEN_RGTC2_EXT = 0x8DBE,
      KTX_COMPRESSED_LUMINANCE_LATC1_EXT = 0x8C70, KTX_COMPRESSED_SIGNED_LUMINANCE_LATC1_EXT = 0x8C71, KTX_COMPRESSED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C72, KTX_COMPRESSED_SIGNED_LUMINANCE_ALPHA_LATC2_EXT = 0x8C73
   };

   // Pixel formats (various internal, base, and base internal formats)
   enum 
   { 
      KTX_R8 = 0x8229, KTX_R8UI = 0x8232, KTX_RGB8 = 0x8051, KTX_SRGB8 = 0x8C41, KTX_SRGB = 0x8C40, KTX_SRGB_ALPHA = 0x8C42,
      KTX_SRGB8_ALPHA8 = 0x8C43, KTX_RGBA8 = 0x8058, KTX_STENCIL_INDEX = 0x1901, KTX_DEPTH_COMPONENT = 0x1902, KTX_DEPTH_STENCIL = 0x84F9, KTX_RED = 0x1903,
      KTX_GREEN = 0x1904, KTX_BLUE = 0x1905, KTX_ALPHA = 0x1906, KTX_RG = 0x8227, KTX_RGB = 0x1907, KTX_RGBA = 0x1908, KTX_BGR = 0x80E0, KTX_BGRA = 0x80E1,
      KTX_RED_INTEGER = 0x8D94, KTX_GREEN_INTEGER = 0x8D95, KTX_BLUE_INTEGER = 0x8D96, KTX_ALPHA_INTEGER = 0x8D97, KTX_RGB_INTEGER = 0x8D98, KTX_RGBA_INTEGER = 0x8D99,
      KTX_BGR_INTEGER = 0x8D9A, KTX_BGRA_INTEGER = 0x8D9B, KTX_LUMINANCE = 0x1909, KTX_LUMINANCE_ALPHA = 0x190A, KTX_RG_INTEGER = 0x8228, KTX_RG8 = 0x822B,
      KTX_ALPHA8 = 0x803C, KTX_LUMINANCE8 = 0x8040, KTX_LUMINANCE8_ALPHA8 = 0x8045
   };

   // Pixel data types
   enum 
   {
      KTX_UNSIGNED_BYTE = 0x1401, KTX_BYTE = 0x1400, KTX_UNSIGNED_SHORT = 0x1403, KTX_SHORT = 0x1402,
      KTX_UNSIGNED_INT = 0x1405, KTX_INT = 0x1404, KTX_HALF_FLOAT = 0x140B, KTX_FLOAT = 0x1406,
      KTX_UNSIGNED_BYTE_3_3_2 = 0x8032, KTX_UNSIGNED_BYTE_2_3_3_REV = 0x8362, KTX_UNSIGNED_SHORT_5_6_5 = 0x8363,
      KTX_UNSIGNED_SHORT_5_6_5_REV = 0x8364, KTX_UNSIGNED_SHORT_4_4_4_4 = 0x8033, KTX_UNSIGNED_SHORT_4_4_4_4_REV = 0x8365,
      KTX_UNSIGNED_SHORT_5_5_5_1 = 0x8034, KTX_UNSIGNED_SHORT_1_5_5_5_REV = 0x8366, KTX_UNSIGNED_INT_8_8_8_8 = 0x8035,
      KTX_UNSIGNED_INT_8_8_8_8_REV = 0x8367, KTX_UNSIGNED_INT_10_10_10_2 = 0x8036, KTX_UNSIGNED_INT_2_10_10_10_REV = 0x8368,
      KTX_UNSIGNED_INT_24_8 = 0x84FA, KTX_UNSIGNED_INT_10F_11F_11F_REV = 0x8C3B, KTX_UNSIGNED_INT_5_9_9_9_REV = 0x8C3E, 
      KTX_FLOAT_32_UNSIGNED_INT_24_8_REV = 0x8DAD
   };
   
   bool is_packed_pixel_ogl_type(uint32 ogl_type);
   uint get_ogl_type_size(uint32 ogl_type);
   bool get_ogl_fmt_desc(uint32 ogl_fmt, uint32 ogl_type, uint& block_dim, uint& bytes_per_block);
   uint get_ogl_type_size(uint32 ogl_type);
   uint32 get_ogl_base_internal_fmt(uint32 ogl_fmt);

   class ktx_texture
   {
   public:
      ktx_texture()
      {
         clear();
      }

      ktx_texture(const ktx_texture& other)
      {
         *this = other;
      }

      ktx_texture& operator= (const ktx_texture& rhs)
      {
         if (this == &rhs)
            return *this;

         clear();

         m_header = rhs.m_header;
         m_key_values = rhs.m_key_values;
         m_image_data = rhs.m_image_data;
         m_block_dim = rhs.m_block_dim;
         m_bytes_per_block = rhs.m_bytes_per_block;
         m_opposite_endianness = rhs.m_opposite_endianness;

         return *this;
      }

      void clear()
      {
         m_header.clear();
         m_key_values.clear();
         m_image_data.clear();
         
         m_block_dim = 0;
         m_bytes_per_block = 0;

         m_opposite_endianness = false;
      }

      // High level methods
      bool read_from_stream(data_stream_serializer& serializer);
      bool write_to_stream(data_stream_serializer& serializer, bool no_keyvalue_data = false);
      
      bool init_2D(uint width, uint height, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
      bool init_2D_array(uint width, uint height, uint num_mips, uint array_size, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
      bool init_3D(uint width, uint height, uint depth, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);
      bool init_cubemap(uint dim, uint num_mips, uint32 ogl_internal_fmt, uint32 ogl_fmt, uint32 ogl_type);

      bool check_header() const;
      bool consistency_check() const;

      // General info

      bool is_valid() const { return (m_header.m_pixelWidth > 0) && (m_image_data.size() > 0); }

      uint get_width() const { return m_header.m_pixelWidth; }
      uint get_height() const { return CRNLIB_MAX(m_header.m_pixelHeight, 1); }
      uint get_depth() const { return CRNLIB_MAX(m_header.m_pixelDepth, 1); }
      uint get_num_mips() const { return CRNLIB_MAX(m_header.m_numberOfMipmapLevels, 1); }
      uint get_array_size() const { return CRNLIB_MAX(m_header.m_numberOfArrayElements, 1); }
      uint get_num_faces() const { return m_header.m_numberOfFaces; }

      uint32 get_ogl_type() const { return m_header.m_glType; }
      uint32 get_ogl_fmt() const { return m_header.m_glFormat; }
      uint32 get_ogl_base_fmt() const { return m_header.m_glBaseInternalFormat; }
      uint32 get_ogl_internal_fmt() const { return m_header.m_glInternalFormat; }
            
      uint get_total_images() const { return get_num_mips() * (get_depth() * get_num_faces() * get_array_size()); }

      bool is_compressed() const { return m_block_dim > 1; }
      bool is_uncompressed() const { return !is_compressed(); }
     
      bool get_opposite_endianness() const { return m_opposite_endianness; }
      void set_opposite_endianness(bool flag) { m_opposite_endianness = flag; }

      uint32 get_block_dim() const { return m_block_dim; }
      uint32 get_bytes_per_block() const { return m_bytes_per_block; }

      const ktx_header& get_header() const { return m_header; }

      // Key values
      const ktx_key_value_vec& get_key_value_vec() const { return m_key_values; }
            ktx_key_value_vec& get_key_value_vec()       { return m_key_values; }

      const uint8_vec* find_key(const char* pKey) const;
      bool get_key_value_as_string(const char* pKey, dynamic_string& str) const;

      uint add_key_value(const char* pKey, const void* pVal, uint val_size);
      uint add_key_value(const char* pKey, const char* pVal) { return add_key_value(pKey, pVal, static_cast<uint>(strlen(pVal)) + 1); }

      // Image data
      uint get_num_images() const { return m_image_data.size(); }

      const uint8_vec& get_image_data(uint image_index) const  { return m_image_data[image_index]; }
            uint8_vec& get_image_data(uint image_index)        { return m_image_data[image_index]; }

      const uint8_vec& get_image_data(uint mip_index, uint array_index, uint face_index, uint zslice_index) const  { return get_image_data(get_image_index(mip_index, array_index, face_index, zslice_index)); }
            uint8_vec& get_image_data(uint mip_index, uint array_index, uint face_index, uint zslice_index)        { return get_image_data(get_image_index(mip_index, array_index, face_index, zslice_index)); }
      
      const ktx_image_data_vec& get_image_data_vec() const  { return m_image_data; }
            ktx_image_data_vec& get_image_data_vec()        { return m_image_data; }

      void add_image(uint face_index, uint mip_index, const void* pImage, uint image_size) 
      { 
         const uint image_index = get_image_index(mip_index, 0, face_index, 0);
         if (image_index >= m_image_data.size())
            m_image_data.resize(image_index + 1);
         if (image_size)
         {  
            uint8_vec& v = m_image_data[image_index];
            v.resize(image_size);
            memcpy(&v[0], pImage, image_size);
         }
      }

      uint get_image_index(uint mip_index, uint array_index, uint face_index, uint zslice_index) const
      {
         CRNLIB_ASSERT((mip_index < get_num_mips()) && (array_index < get_array_size()) && (face_index < get_num_faces()) && (zslice_index < get_depth()));
         return zslice_index + (face_index * get_depth()) + (array_index * (get_depth() * get_num_faces())) + (mip_index * (get_depth() * get_num_faces() * get_array_size()));
      }
      
      void get_mip_dim(uint mip_index, uint& mip_width, uint& mip_height) const
      {
         CRNLIB_ASSERT(mip_index < get_num_mips());
         mip_width = CRNLIB_MAX(get_width() >> mip_index, 1);
         mip_height = CRNLIB_MAX(get_height() >> mip_index, 1);
      }

      void get_mip_dim(uint mip_index, uint& mip_width, uint& mip_height, uint& mip_depth) const
      {
         CRNLIB_ASSERT(mip_index < get_num_mips());
         mip_width = CRNLIB_MAX(get_width() >> mip_index, 1);
         mip_height = CRNLIB_MAX(get_height() >> mip_index, 1);
         mip_depth = CRNLIB_MAX(get_depth() >> mip_index, 1);
      }
         
   private:
      ktx_header                 m_header;
      
      ktx_key_value_vec          m_key_values;
      ktx_image_data_vec         m_image_data;

      uint32                     m_block_dim;
      uint32                     m_bytes_per_block;
            
      bool                       m_opposite_endianness;
      
      bool compute_pixel_info();
   };
   
} // namespace crnlib

#endif // #ifndef _KTX_TEXTURE_H_