// SPDX-License-Identifier: Unlicense

#include "texture.h"

#include "opengl.h"
#include <string.h>

#ifdef SHR3D_SHRED
#include "png.h"
#endif // SHR3D_SHRED

#ifdef  SHR3D_SDL
#include <SDL_opengl_glext.h>
#else // SHR3D_SDL

#define GL_COMPRESSED_RGBA_S3TC_DXT1_EXT  0x83F1
#define GL_COMPRESSED_RGBA_S3TC_DXT3_EXT  0x83F2
#define GL_COMPRESSED_RGBA_S3TC_DXT5_EXT  0x83F3

//#ifdef PLATFORM_ANDROID_SDL
//#include "common/gfxwrapper_opengl.h"
//#endif // PLATFORM_ANDROID_SDL

#endif // SHR3D_SDL

GLuint Texture::openGlLoadTextureRaw(const u8* data, const i32 width, const i32 height, const GLint format)
{
  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  GL(glBindTexture(GL_TEXTURE_2D, texture));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));

  GL(glTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data));

  GL(glBindTexture(GL_TEXTURE_2D, 0));

  return texture;
}

#ifdef SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE
static GLuint openGlLoadCubeMapTextureRaw(const u8* data[6], const i32 width, const i32 height, const GLint format, const GLsizei compressedImageSize)
{
  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  GL(glBindTexture(GL_TEXTURE_CUBE_MAP, texture));
  GL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE));
  GL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE));
  GL(glTexParameteri(GL_TEXTURE_CUBE_MAP, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE));

  //if (compressedImageSize == 0)
  //{
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[0]));
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[1]));
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[2]));
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[3]));
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[4]));
  //  GL(glTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, width, height, 0, format, GL_UNSIGNED_BYTE, data[5]));
  //}
  //else
  //{
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_X, 0, format, width, height, 0, compressedImageSize, data[0]));
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_X, 0, format, width, height, 0, compressedImageSize, data[1]));
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Y, 0, format, width, height, 0, compressedImageSize, data[2]));
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Y, 0, format, width, height, 0, compressedImageSize, data[3]));
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_POSITIVE_Z, 0, format, width, height, 0, compressedImageSize, data[4]));
  GL(glCompressedTexImage2D(GL_TEXTURE_CUBE_MAP_NEGATIVE_Z, 0, format, width, height, 0, compressedImageSize, data[5]));
  //}

  ASSERT(glGetError() == GL_NO_ERROR);

  GL(glBindTexture(GL_TEXTURE_2D, 0));

  return texture;
}
#endif // SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE

static u32 ddsHeight(const u8* data)
{
  ASSERT(memcmp(data, "DDS ", 4) == 0);
  return (data[12]) | (data[13] << 8) | (data[14] << 16) | (data[15] << 24);
}
static u32 ddsWidth(const u8* data)
{
  ASSERT(memcmp(data, "DDS ", 4) == 0);
  return (data[16]) | (data[17] << 8) | (data[18] << 16) | (data[19] << 24);
}

//static u32 ddsMipMapCount(const u8* data)
//{
//  ASSERT(memcmp(data, "DDS ", 4) == 0);
//  return (data[28]) | (data[29] << 8) | (data[30] << 16) | (data[31] << 24);
//}

static u32 ddsFormat(const u8* data)
{
  ASSERT(memcmp(data, "DDS ", 4) == 0);

  //ASSERT(memcmp(&in_dds[84], "DXT", 3) == 0);
  if (memcmp(&data[84], "DXT", 3) != 0)
    return 0; // invalid texture

  switch (data[87]) {
  case '1': // DXT1
    return GL_COMPRESSED_RGBA_S3TC_DXT1_EXT;
    break;
  case '3': // DXT3
    return GL_COMPRESSED_RGBA_S3TC_DXT3_EXT;
    break;
  case '5': // DXT5
    return GL_COMPRESSED_RGBA_S3TC_DXT5_EXT;
  case '0': // DX10
  default:
    unreachable();
  }
}

// There is no GPU support for .dds textures on most Android devices. Same for mobile webbrowsers.
#if defined(__ANDROID__) || defined(PLATFORM_EMSCRIPTEN)
enum {
  DDSD_CAPS = 0x1,
  DDSD_HEIGHT = 0x2,
  DDSD_WIDTH = 0x4,
  DDSD_PITCH = 0x8,
  DDSD_PIXELFORMAT = 0x1000,
  DDSD_MIPMAPCOUNT = 0x20000,
  DDSD_LINEARSIZE = 0x80000,
  DDSD_DEPTH = 0x800000
};

enum {
  DDSCAPS_COMPLEX = 0x8,
  DDSCAPS_MIPMAP = 0x400000,
  DDSCAPS_TEXTURE = 0x1000
};

enum {
  DDPF_ALPHAPIXELS = 0x1,
  DDPF_ALPHA = 0x2,
  DDPF_FOURCC = 0x4,
  DDPF_RGB = 0x40,
  DDPF_YUV = 0x200,
  DDPF_LUMINANCE = 0x20000
};

struct dds_pixelformat {
  u32 size;
  u32 flags;
  u32 four_cc;
  u32 rgb_bit_count;
  u32 r_bit_mask;
  u32 g_bit_mask;
  u32 b_bit_mask;
  u32 a_bit_mask;
};

struct dds_header {
  u32 size;
  u32 flags;
  u32 height;
  u32 width;
  u32 pitch_linear_size;
  u32 depth;
  u32 mipmap_count;
  u32 reserved1[11];
  dds_pixelformat pixel_format;
  u32 caps;
  u32 caps2;
  u32 caps3;
  u32 caps4;
  u32 reserved2;
};

struct dds_header_dxt10
{
  u32 dxgi_format;
  u32 resource_dimension;
  u32 misc_flag;
  u32 array_size;
  u32 misc_flags2;
};

struct dds_image {
  dds_header header;
  dds_header_dxt10 header10;

  u8* pixels; // always RGBA; currently no support for mipmaps
};

#define FOURCC(str) (u8)(((str)[3] << 24U) | ((str)[2] << 16U) | ((str)[1] << 8U) | (str)[0])
#ifndef MAX
#define MAX(X, Y) (((X) > (Y)) ? (X) : (Y))
#endif // MAX
#define IMAGE_PITCH(width, block_size) MAX(1, ((width + 3) / 4)) * block_size

static u32 dds_calculate_left_shift(u32* right_shift, u32 bit_count)
{
  if (bit_count >= 8) {
    right_shift = right_shift + (bit_count - 8);
    return 0;
  }
  return 8 - bit_count;
}

static void dds_parse_uncompressed(dds_image* image, const u8* data, i32 data_length)
{
  u32 img_width = MAX(1, image->header.width);
  u32 img_height = MAX(1, image->header.height);
  u32 img_depth = MAX(1, image->header.depth);

  // will this even work? have I envisioned this solution correctly?
  u32 r_right_shift = 0xFFFFFFFF, g_right_shift = 0xFFFFFFFF, b_right_shift = 0xFFFFFFFF, a_right_shift = 0xFFFFFFFF;
  u32 r_left_shift = 0, g_left_shift = 0, b_left_shift = 0, a_left_shift = 0;
  for (u8 i = 0; i < 32; i++) {
    if ((image->header.pixel_format.r_bit_mask) >> i & 1) {
      if (r_right_shift == 0xFFFFFFFF)
        r_right_shift = i;
      r_left_shift++;
    }
    if ((image->header.pixel_format.g_bit_mask) >> i & 1) {
      if (g_right_shift == 0xFFFFFFFF)
        g_right_shift = i;
      g_left_shift++;
    }
    if ((image->header.pixel_format.b_bit_mask) >> i & 1) {
      if (b_right_shift == 0xFFFFFFFF)
        b_right_shift = i;
      b_left_shift++;
    }
    if ((image->header.pixel_format.a_bit_mask) >> i & 1) {
      if (a_right_shift == 0xFFFFFFFF)
        a_right_shift = i;
      a_left_shift++;
    }
  }

  // to avoid undefined behavior:
  if (r_right_shift == 0xFFFFFFFF) r_right_shift = 0;
  if (g_right_shift == 0xFFFFFFFF) g_right_shift = 0;
  if (b_right_shift == 0xFFFFFFFF) b_right_shift = 0;
  if (a_right_shift == 0xFFFFFFFF) a_right_shift = 0;

  // fix left/right shift based on the bit count (currently stored in X_left_shift)
  r_left_shift = dds_calculate_left_shift(&r_right_shift, r_left_shift);
  g_left_shift = dds_calculate_left_shift(&g_right_shift, g_left_shift);
  b_left_shift = dds_calculate_left_shift(&b_right_shift, b_left_shift);
  a_left_shift = dds_calculate_left_shift(&a_right_shift, a_left_shift);

  u8 bytes_per_pixel = image->header.pixel_format.rgb_bit_count / 8;

  data += 128; // skip the header

  // read the actual data
  for (u32 z = 0; z < img_depth; z++)
    for (u32 x = 0; x < img_width; x++)
      for (u32 y = 0; y < img_height; y++) {
        u32 px_index = (z * img_width * img_height + y * img_width + x) * bytes_per_pixel;
        u32 data_index = (z * img_width * img_height + (img_height - y - 1) * img_width + x) * 4;

        // get the data into uint
        u32 px = 0;
        memcpy(&px, data + px_index, bytes_per_pixel);

        // decode
        image->pixels[data_index + 0] = ((px & image->header.pixel_format.r_bit_mask) >> r_right_shift) << r_left_shift;
        image->pixels[data_index + 1] = ((px & image->header.pixel_format.g_bit_mask) >> g_right_shift) << g_left_shift;
        image->pixels[data_index + 2] = ((px & image->header.pixel_format.b_bit_mask) >> b_right_shift) << b_left_shift;
        if (image->header.pixel_format.a_bit_mask == 0)
          image->pixels[data_index + 3] = 0xFF;
        else
          image->pixels[data_index + 3] = ((px & image->header.pixel_format.a_bit_mask) >> a_right_shift) << a_left_shift;
      }
}

static void dds_parse_dxt1(dds_image* image, const u8* data, i32 data_length)
{
  u32 img_width = MAX(1, image->header.width);
  u32 img_height = MAX(1, image->header.height);
  u32 img_depth = MAX(1, image->header.depth);

  u32 blocks_x = MAX(1, img_width / 4);
  u32 blocks_y = MAX(1, img_height / 4);

  for (u32 z = 0; z < img_depth; z++) {
    for (u32 x = 0; x < blocks_x; x++)
      for (u32 y = 0; y < blocks_y; y++) {
        unsigned short color0 = 0, color1 = 0;
        u32 codes = 0;

        // read the block data
        u32 block_offset = (y * blocks_x + x) * 8; // * 8 == * 64bits cuz blocks are 64 bits (2x shorts, 1x uint)
        memcpy(&color0, data + block_offset + 0, 2);
        memcpy(&color1, data + block_offset + 2, 2);
        memcpy(&codes, data + block_offset + 4, 4);

        // unpack the color data
        u8 r0 = (color0 & 0b1111100000000000) >> 8;
        u8 g0 = (color0 & 0b0000011111100000) >> 3;
        u8 b0 = (color0 & 0b0000000000011111) << 3;
        u8 r1 = (color1 & 0b1111100000000000) >> 8;
        u8 g1 = (color1 & 0b0000011111100000) >> 3;
        u8 b1 = (color1 & 0b0000000000011111) << 3;

        // process the data
        for (u32 b = 0; b < 16; b++) {
          u32 px_index = ((z * 4) * img_height * img_width + (img_height - ((y * 4) + b / 4) - 1) * img_width + x * 4 + b % 4) * 4;

          u8 code = (codes >> (2 * b)) & 3;
          image->pixels[px_index + 3] = 0xFF;
          if (code == 0) {
            // color0
            image->pixels[px_index + 0] = r0;
            image->pixels[px_index + 1] = g0;
            image->pixels[px_index + 2] = b0;
          }
          else if (code == 1) {
            // color1
            image->pixels[px_index + 0] = r1;
            image->pixels[px_index + 1] = g1;
            image->pixels[px_index + 2] = b1;
          }
          else if (code == 2) {
            // (2*color0 + color1) / 3
            if (color0 > color1) {
              image->pixels[px_index + 0] = (2 * r0 + r1) / 3;
              image->pixels[px_index + 1] = (2 * g0 + g1) / 3;
              image->pixels[px_index + 2] = (2 * b0 + b1) / 3;
            }
            // (color0 + color1) / 2
            else {
              image->pixels[px_index + 0] = (r0 + r1) / 2;
              image->pixels[px_index + 1] = (g0 + g1) / 2;
              image->pixels[px_index + 2] = (b0 + b1) / 2;
            }
          }
          else if (code == 3) {
            // (color0 + 2*color1) / 3
            if (color0 > color1) {
              image->pixels[px_index + 0] = (r0 + 2 * r1) / 3;
              image->pixels[px_index + 1] = (g0 + 2 * g1) / 3;
              image->pixels[px_index + 2] = (b0 + 2 * b1) / 3;
            }
            // black
            else {
              image->pixels[px_index + 0] = 0x00;
              image->pixels[px_index + 1] = 0x00;
              image->pixels[px_index + 2] = 0x00;
            }
          }
        }
      }

    // skip this slice
    data += blocks_x * blocks_y * 16;
  }
}

static void dds_parse_dxt3(dds_image* image, const u8* data, i32 data_length)
{
  u32 img_width = MAX(1, image->header.width);
  u32 img_height = MAX(1, image->header.height);
  u32 img_depth = MAX(1, image->header.depth);

  u32 blocks_x = MAX(1, img_width / 4);
  u32 blocks_y = MAX(1, img_height / 4);

  for (u32 z = 0; z < img_depth; z++) {
    for (u32 x = 0; x < blocks_x; x++)
      for (u32 y = 0; y < blocks_y; y++) {
        unsigned short color0 = 0, color1 = 0;
        u32 codes = 0;
        unsigned long long alpha_data = 0;

        // read the block data
        u32 block_offset = (y * blocks_x + x) * 16; // 16 == 128bits
        memcpy(&alpha_data, data + block_offset, 8);
        memcpy(&color0, data + block_offset + 8, 2);
        memcpy(&color1, data + block_offset + 10, 2);
        memcpy(&codes, data + block_offset + 12, 4);

        // unpack the color data
        u8 r0 = (color0 & 0b1111100000000000) >> 8;
        u8 g0 = (color0 & 0b0000011111100000) >> 3;
        u8 b0 = (color0 & 0b0000000000011111) << 3;
        u8 r1 = (color1 & 0b1111100000000000) >> 8;
        u8 g1 = (color1 & 0b0000011111100000) >> 3;
        u8 b1 = (color1 & 0b0000000000011111) << 3;

        // process the data
        for (u32 b = 0; b < 16; b++) {
          u32 px_index = ((z * 4) * img_height * img_width + (img_height - ((y * 4) + b / 4) - 1) * img_width + x * 4 + b % 4) * 4;
          u8 code = (codes >> (2 * b)) & 0b0011;

          u8 alpha = (alpha_data >> (4 * b)) & 0b1111;
          image->pixels[px_index + 3] = alpha;

          // color0
          if (code == 0) {
            image->pixels[px_index + 0] = r0;
            image->pixels[px_index + 1] = g0;
            image->pixels[px_index + 2] = b0;
          }
          // color1
          else if (code == 1) {
            image->pixels[px_index + 0] = r1;
            image->pixels[px_index + 1] = g1;
            image->pixels[px_index + 2] = b1;
          }
          // (2*color0 + color1) / 3
          else if (code == 2) {
            image->pixels[px_index + 0] = (2 * r0 + r1) / 3;
            image->pixels[px_index + 1] = (2 * g0 + g1) / 3;
            image->pixels[px_index + 2] = (2 * b0 + b1) / 3;
          }
          // (color0 + 2*color1) / 3
          else if (code == 3) {
            image->pixels[px_index + 0] = (r0 + 2 * r1) / 3;
            image->pixels[px_index + 1] = (g0 + 2 * g1) / 3;
            image->pixels[px_index + 2] = (b0 + 2 * b1) / 3;
          }
        }
      }

    // skip this slice
    data += blocks_x * blocks_y * 16;
  }
}
void dds_parse_dxt5(dds_image* image, const u8* data, i32 data_length)
{

  u32 img_width = MAX(1, image->header.width);
  u32 img_height = MAX(1, image->header.height);
  u32 img_depth = MAX(1, image->header.depth);

  u32 blocks_x = MAX(1, img_width / 4);
  u32 blocks_y = MAX(1, img_height / 4);

  for (u32 z = 0; z < img_depth; z++) {
    for (u32 x = 0; x < blocks_x; x++)
      for (u32 y = 0; y < blocks_y; y++) {
        unsigned short color0 = 0, color1 = 0;
        u32 codes = 0;
        unsigned long long alpha_codes = 0;
        u8 alpha0 = 0, alpha1 = 0;

        // read the block data
        u32 block_offset = (y * blocks_x + x) * 16; // 16 == 128bits
        alpha0 = data[block_offset + 0];
        alpha1 = data[block_offset + 1];
        memcpy(&alpha_codes, data + block_offset + 2, 6);
        memcpy(&color0, data + block_offset + 8, 2);
        memcpy(&color1, data + block_offset + 10, 2);
        memcpy(&codes, data + block_offset + 12, 4);

        // unpack the color data
        u8 r0 = (color0 & 0b1111100000000000) >> 8;
        u8 g0 = (color0 & 0b0000011111100000) >> 3;
        u8 b0 = (color0 & 0b0000000000011111) << 3;
        u8 r1 = (color1 & 0b1111100000000000) >> 8;
        u8 g1 = (color1 & 0b0000011111100000) >> 3;
        u8 b1 = (color1 & 0b0000000000011111) << 3;

        // process the data
        for (u32 b = 0; b < 16; b++) {
          u32 px_index = (z * img_height * img_width + (img_height - ((y * 4) + b / 4) - 1) * img_width + x * 4 + b % 4) * 4;
          u8 code = (codes >> (2 * b)) & 0b0011;
          u8 alpha_code = (alpha_codes >> (3 * b)) & 0b0111;

          /* COLOR */
          // color0
          if (code == 0) {
            image->pixels[px_index + 0] = r0;
            image->pixels[px_index + 1] = g0;
            image->pixels[px_index + 2] = b0;
          }
          // color1
          else if (code == 1) {
            image->pixels[px_index + 0] = r1;
            image->pixels[px_index + 1] = g1;
            image->pixels[px_index + 2] = b1;
          }
          // (2*color0 + color1) / 3
          else if (code == 2) {
            image->pixels[px_index + 0] = (2 * r0 + r1) / 3;
            image->pixels[px_index + 1] = (2 * g0 + g1) / 3;
            image->pixels[px_index + 2] = (2 * b0 + b1) / 3;
          }
          // (color0 + 2*color1) / 3
          else if (code == 3) {
            image->pixels[px_index + 0] = (r0 + 2 * r1) / 3;
            image->pixels[px_index + 1] = (g0 + 2 * g1) / 3;
            image->pixels[px_index + 2] = (b0 + 2 * b1) / 3;
          }

          /* ALPHA */
          u8 alpha = 0xFF;
          if (code == 0)
            alpha = alpha0;
          else if (code == 1)
            alpha = alpha1;
          else {
            if (alpha0 > alpha1)
              alpha = ((8 - alpha_code) * alpha0 + (alpha_code - 1) * alpha1) / 7;
            else {
              if (alpha_code == 6)
                alpha = 0;
              else if (alpha_code == 7)
                alpha = 255;
              else
                alpha = ((6 - alpha_code) * alpha0 + (alpha_code - 1) * alpha1) / 5;
            }
          }
          image->pixels[px_index + 3] = alpha;
        }
      }

    // skip this slice
    data += blocks_x * blocks_y * 16;
  }
}

static void dds_image_free(dds_image* image)
{
  free(image->pixels);
  free(image);
}

static dds_image* dds_load_from_memory(const u8* data, i32 data_length)
{
  const u8* data_loc = data;

  u32 magic = 0x00;
  memcpy(&magic, data_loc, sizeof(magic));
  data_loc += 4;

  if (magic != 0x20534444) // 'DDS '
    return NULL;

  dds_image* ret = (dds_image*)malloc(sizeof(struct dds_image));


  // read the header
  memcpy(&ret->header, data_loc, sizeof(struct dds_header));
  data_loc += sizeof(struct dds_header);

  // check if the dds_header::dwSize (must be equal to 124)
  if (ret->header.size != 124) {
    dds_image_free(ret);
    return NULL;
  }

  // check the dds_header::flags (DDSD_CAPS, DDSD_HEIGHT, DDSD_WIDTH, DDSD_PIXELFORMAT must be set)
  if (!((ret->header.flags & DDSD_CAPS) && (ret->header.flags & DDSD_HEIGHT) && (ret->header.flags & DDSD_WIDTH) && (ret->header.flags & DDSD_PIXELFORMAT))) {
    dds_image_free(ret);
    return NULL;
  }

  // check the dds_header::caps
  if ((ret->header.caps & DDSCAPS_TEXTURE) == 0) {
    dds_image_free(ret);
    return NULL;
  }

  // check if we need to load dds_header_dxt10
  if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DX10")) {
    // read the header10
    memcpy(&ret->header10, data_loc, sizeof(struct dds_header_dxt10));
    data_loc += sizeof(struct dds_header_dxt10);
  }

  // allocate pixel data
  u32 img_width = MAX(1, ret->header.width);
  u32 img_height = MAX(1, ret->header.height);
  u32 img_depth = MAX(1, ret->header.depth);
  ret->pixels = (u8*)malloc(img_width * img_height * img_depth * 4);


  //dds_parse_dxt5(ret, data_loc, data_length - (data_loc - data));

  switch (data[87]) {
  case '1': // DXT1
    dds_parse_dxt1(ret, data_loc, data_length - (data_loc - data));
    break;
  case '3': // DXT3
    dds_parse_dxt3(ret, data_loc, data_length - (data_loc - data));
    break;
  case '5': // DXT5
    dds_parse_dxt5(ret, data_loc, data_length - (data_loc - data));
    break;
  case '0': // DX10
  default:
    ASSERT(false);
    return 0; // invalid texture
  }


  // parse/decompress the pixel data
  //if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DXT1"))
  //	dds_parse_dxt1(ret, data_loc, data_length - (data_loc - data));
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DXT2"))
  //	dds_parse_dxt3(ret, data_loc, data_length - (data_loc - data));
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DXT3"))
  //	dds_parse_dxt3(ret, data_loc, data_length - (data_loc - data));
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DXT4"))
  //	dds_parse_dxt5(ret, data_loc, data_length - (data_loc - data));
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DXT5"))
  //	dds_parse_dxt5(ret, data_loc, data_length - (data_loc - data));
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("DX10")) {
  //	// dds_parse_dxt10
  //}
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("ATI1")) {
  //	// dds_parse_ati1
  //}
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("ATI2")) {
  //	// dds_parse_ati2
  //}
  //else if ((ret->header.pixel_format.flags & DDPF_FOURCC) && ret->header.pixel_format.four_cc == FOURCC("A2XY")) {
  //	// dds_parse_a2xy
  //}
  //else
  //	dds_parse_uncompressed(ret, data, data_length - (data_loc - data));

  return ret;
}

GLuint Texture::openGlLoadTextureDds(const u8* data, i32 size)
{
  dds_image* image = dds_load_from_memory(data, size);

  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  GL(glBindTexture(GL_TEXTURE_2D, texture));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));

  Color* flippedYPixels = reinterpret_cast<Color*>(image->pixels);
  for (i32 y = 0; y < image->header.height / 2; ++y) {
    for (i32 x = 0; x < image->header.width; ++x) {
      const Color tempColor = flippedYPixels[y * image->header.width + x];
      flippedYPixels[y * image->header.width + x] = flippedYPixels[(image->header.height - y - 1) * image->header.width + x];
      flippedYPixels[(image->header.height - y - 1) * image->header.width + x] = tempColor;
    }
  }

  GL(glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, image->header.width, image->header.height, 0, GL_RGBA, GL_UNSIGNED_BYTE, image->pixels));
  //ASSERT(glGetError() == GL_NO_ERROR);

  GL(glBindTexture(GL_TEXTURE_2D, 0));

  dds_image_free(image);

  return texture;
}

#else // __ANDROID__ || PLATFORM_EMSCRIPTEN

GLuint Texture::openGlLoadTextureDds(const u8* data, const i32 size)
{
  ASSERT(memcmp(data, "DDS ", 4) == 0);

  const u32 height = ddsHeight(data);
  const u32 width = ddsWidth(data);
  const u32 format = ddsFormat(data);
  //const u32 mipMapCount = ddsMipMapCount(data);

  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  GL(glBindTexture(GL_TEXTURE_2D, texture));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));

  GL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, format, width, height, 0, size - 128, &data[128]));

  GL(glBindTexture(GL_TEXTURE_2D, 0));

  return texture;
}

#endif // __ANDROID__ || PLATFORM_EMSCRIPTEN

#ifdef __ANDROID__

#ifndef GL_COMPRESSED_RGBA_ASTC_8x6_KHR
#define GL_COMPRESSED_RGBA_ASTC_8x6_KHR 0x93B6 // this should be defined. No idea why it is missing.
#endif

GLuint Texture::openGlLoadTextureAstc(const u8* in_astc, const i32 in_size)
{
  struct astc_header
  {
    u8 magic[4];
    u8 blockdim_x;
    u8 blockdim_y;
    u8 blockdim_z;
    u8 xsize[3];			// x-size = xsize[0] + xsize[1] + xsize[2]
    u8 ysize[3];			// x-size, y-size and z-size are given in texels;
    u8 zsize[3];			// block count is inferred
  };

  const astc_header* hdr = reinterpret_cast<const astc_header*>(in_astc);

  const u32 magicval = hdr->magic[0] + 256 * (uint32_t)(hdr->magic[1]) + 65536 * (uint32_t)(hdr->magic[2]) + 16777216 * (uint32_t)(hdr->magic[3]);
  ASSERT(0x5CA1AB13 == magicval);

  const i32 xdim = hdr->blockdim_x;
  const i32 ydim = hdr->blockdim_y;
  const i32 zdim = hdr->blockdim_z;

  if (xdim < 3 || xdim > 12 || ydim < 3 || ydim > 12 || (zdim < 3 && zdim != 1) || zdim > 12)
  {
    ASSERT(false);
  }

  const i32 xsize = hdr->xsize[0] + 256 * hdr->xsize[1] + 65536 * hdr->xsize[2];
  const i32 ysize = hdr->ysize[0] + 256 * hdr->ysize[1] + 65536 * hdr->ysize[2];
  const i32 zsize = hdr->zsize[0] + 256 * hdr->zsize[1] + 65536 * hdr->zsize[2];

  const i32 xblocks = (xsize + xdim - 1) / xdim;
  const i32 yblocks = (ysize + ydim - 1) / ydim;
  const i32 zblocks = (zsize + zdim - 1) / zdim;

  const u64 bytes_to_read = xblocks * yblocks * zblocks * 16;
  ASSERT(bytes_to_read == in_size - sizeof(astc_header));

  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  GL(glBindTexture(GL_TEXTURE_2D, texture));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT));
  GL(glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT));

  GL(glCompressedTexImage2D(GL_TEXTURE_2D, 0, GL_COMPRESSED_RGBA_ASTC_8x6_KHR, xsize, ysize, 0, bytes_to_read, &in_astc[sizeof(astc_header)]));

  if (glGetError() != GL_NO_ERROR)
  {
    ASSERT(false);
    while (glGetError() != GL_NO_ERROR)
      ; // pull all the glErrors.
    return 0; // invalid texture
  }

  //GL(glBindTexture(GL_TEXTURE_2D, 0));

  return texture;
}
#ifdef SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE
GLuint Texture::openGlLoadCubeTextureAstc6(const u8* in_astc, const i32 in_size)
{
  const i32 singleImage_size = in_size / 6;

  struct astc_header
  {
    u8 magic[4];
    u8 blockdim_x;
    u8 blockdim_y;
    u8 blockdim_z;
    u8 xsize[3];			// x-size = xsize[0] + xsize[1] + xsize[2]
    u8 ysize[3];			// x-size, y-size and z-size are given in texels;
    u8 zsize[3];			// block count is inferred
  };

  const astc_header* hdr = reinterpret_cast<const astc_header*>(in_astc);

  const u32 magicval = hdr->magic[0] + 256 * (uint32_t)(hdr->magic[1]) + 65536 * (uint32_t)(hdr->magic[2]) + 16777216 * (uint32_t)(hdr->magic[3]);
  ASSERT(0x5CA1AB13 == magicval);

  const i32 xdim = hdr->blockdim_x;
  const i32 ydim = hdr->blockdim_y;
  const i32 zdim = hdr->blockdim_z;

  if (xdim < 3 || xdim > 12 || ydim < 3 || ydim > 12 || (zdim < 3 && zdim != 1) || zdim > 12)
  {
    ASSERT(false);
  }

  const i32 xsize = hdr->xsize[0] + 256 * hdr->xsize[1] + 65536 * hdr->xsize[2];
  const i32 ysize = hdr->ysize[0] + 256 * hdr->ysize[1] + 65536 * hdr->ysize[2];
  const i32 zsize = hdr->zsize[0] + 256 * hdr->zsize[1] + 65536 * hdr->zsize[2];

  const i32 xblocks = (xsize + xdim - 1) / xdim;
  const i32 yblocks = (ysize + ydim - 1) / ydim;
  const i32 zblocks = (zsize + zdim - 1) / zdim;

  const u64 bytes_to_read = xblocks * yblocks * zblocks * 16;
  ASSERT(bytes_to_read == singleImage_size - sizeof(astc_header));

  GLuint texture = 0;
  GL(glGenTextures(1, &texture));
  ASSERT(texture != 0);

  const u8* pixelDataPtrs[] =
  {
          &in_astc[sizeof(astc_header)],
          &in_astc[singleImage_size + sizeof(astc_header)],
          &in_astc[singleImage_size * 2 + sizeof(astc_header)],
          &in_astc[singleImage_size * 3 + sizeof(astc_header)],
          &in_astc[singleImage_size * 4 + sizeof(astc_header)],
          &in_astc[singleImage_size * 5 + sizeof(astc_header)]
  };

  return openGlLoadCubeMapTextureRaw(pixelDataPtrs, xsize, ysize, GL_COMPRESSED_RGBA_ASTC_8x6_KHR, bytes_to_read);
}
#endif // SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE
#endif // __ANDROID__

#ifdef SHR3D_PNG_DECODER
GLuint Texture::openGlLoadTexturePng(const u8* data, const i32 dataSize)
{
  ASSERT(dataSize > 0);

  u32 width;
  u32 height;
  u32 channels;
  std::vector<u8> pixelData;
  Png::fpng_decode_memory(data, u32(dataSize), pixelData, width, height, channels, 3);

  if (pixelData.size() == 0)
    Png::decode(pixelData, reinterpret_cast<unsigned long&>(width), reinterpret_cast<unsigned long&>(height), data, u64(dataSize), false);

  return openGlLoadTextureRaw(pixelData.data(), width, height, GL_RGB);
}
#endif // SHR3D_PNG_DECODER

GLuint Texture::openGlLoadTexture(const u8* data, const i32 dataSize)
{
  if (memcmp(data, "DDS ", 4) == 0)
    return openGlLoadTextureDds(data, dataSize);
#ifdef SHR3D_PNG_DECODER
  if (memcmp(&data[1], "PNG", 3) == 0)
    return openGlLoadTexturePng(data, dataSize);
#endif // SHR3D_PNG_DECODER
#ifdef __ANDROID__
  return Texture::openGlLoadTextureAstc(data, dataSize);
#endif // __ANDROID__

  unreachable();
}

#ifdef SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE
#ifndef __ANDROID__
//GLuint Texture::openGlLoadCubeTextureDds(const u8* data[6], const i32 size)
//{
//  ASSERT(memcmp(data[0], "DDS ", 4) == 0);
//
//  const u32 height = ddsHeight(data[0]);
//  const u32 width = ddsWidth(data[0]);
//  const u32 format = ddsFormat(data[0]);
//  constexpr u32 ddsDataOffset = 128;
//
//  const u8* pixelDataPtrs[] =
//  {
//    &data[0][ddsDataOffset],
//    &data[1][ddsDataOffset],
//    &data[2][ddsDataOffset],
//    &data[3][ddsDataOffset],
//    &data[4][ddsDataOffset],
//    &data[5][ddsDataOffset]
//  };
//
//  return openGlLoadCubeMapTextureRaw(pixelDataPtrs, width, height, format, size - ddsDataOffset);
//}

GLuint Texture::openGlLoadCubeTextureDds(const u8* data, const i32 size)
{
  ASSERT(memcmp(data, "DDS ", 4) == 0);

  const u32 height = ddsHeight(data);
  const u32 width = ddsWidth(data);
  const u32 format = ddsFormat(data);
  constexpr u32 ddsDataOffset = 128;

  ASSERT((size - ddsDataOffset) % 6 == 0);
  const u32 sizePerFace = (size - ddsDataOffset) / 6;

  const u8* pixelDataPtrs[] =
  {
    &data[ddsDataOffset],
    &data[ddsDataOffset + sizePerFace],
    &data[ddsDataOffset + sizePerFace * 2],
    &data[ddsDataOffset + sizePerFace * 3],
    &data[ddsDataOffset + sizePerFace * 4],
    &data[ddsDataOffset + sizePerFace * 5]
  };

  return openGlLoadCubeMapTextureRaw(pixelDataPtrs, width, height, format, sizePerFace);
}
#endif // __ANDROID__

#ifdef SHR3D_PNG_DECODER
//GLuint Texture::openGlLoadCubeTexturePng(const u8* data[6], const i32 dataSize[6])
//{
//  u32 width = 0;
//  u32 height = 0;
//  std::vector<u8> pixelData[6];
//
//  for (i8 i = 0; i < 6; ++i)
//  {
//    u32 channels;
//    Png::fpng_decode_memory(data[i], u32(dataSize[i]), pixelData[i], width, height, channels, 3);
//
//    if (pixelData[i].size() == 0)
//      Png::decode(pixelData[i], reinterpret_cast<unsigned long&>(width), reinterpret_cast<unsigned long&>(height), data[i], u64(dataSize[i]), false);
//  }
//
//  const u8* pixelDataPtrs[] =
//  {
//    &pixelData[0][0],
//    &pixelData[1][0],
//    &pixelData[2][0],
//    &pixelData[3][0],
//    &pixelData[4][0],
//    &pixelData[5][0]
//  };
//
//  return openGlLoadCubeMapTextureRaw(pixelDataPtrs, width, height, GL_RGB);
//}
//GLuint Texture::openGlLoadCubeTexturePng(const u8* data, const i32 dataSize)
//{
//  u32 width;
//  u32 height;
//  std::vector<u8> pixelData;
//
//  u32 channels;
//  Png::fpng_decode_memory(data, u32(dataSize), pixelData, width, height, channels, 3);
//
//  if (pixelData.size() == 0)
//    Png::decode(pixelData, reinterpret_cast<unsigned long&>(width), reinterpret_cast<unsigned long&>(height), data, u64(dataSize), false);
//
//  const u32 offset = u32(pixelData.size()) / 6;
//
//  const u8* pixelDataPtrs[] =
//  {
//    &pixelData[0],
//    &pixelData[offset],
//    &pixelData[offset * 2],
//    &pixelData[offset * 3],
//    &pixelData[offset * 4],
//    &pixelData[offset * 5]
//  };
//
//  return openGlLoadCubeMapTextureRaw(pixelDataPtrs, width, width, GL_RGB);
//}
#endif // SHR3D_PNG_DECODER

//GLuint Texture::openGlLoadCubeTexture(const u8* data[6], const i32 dataSize[6])
//{
//#ifndef __ANDROID__
//  if (memcmp(data[0], "DDS ", 4) == 0)
//    return openGlLoadCubeTextureDds(data, dataSize[0]);
//#endif // __ANDROID__
//
//#ifdef SHR3D_PNG_DECODER
//  if (memcmp(&data[0][1], "PNG", 3) == 0)
//    return openGlLoadCubeTexturePng(data, dataSize);
//#endif // SHR3D_PNG_DECODER
//
//  unreachable();
//}
#endif // SHR3D_ENVIRONMENT_SKYBOX_CUBE_TEXTURE


