// File: example2.cpp - This example uses the crn_decomp.h stand-alone header file library
// to transcode .CRN files directly to .DDS, with no intermediate recompression step to DXTn.
// This tool does NOT depend on the crnlib library at all. It only needs the low-level
// decompression/transcoding functionality defined in inc/crn_decomp.h.
// This is the basic functionality a game engine would need to employ at runtime to utilize
// .CRN textures (excluding writing the output DDS file - instead you would provide the DXTn
// bits directly to OpenGL/D3D).
// See Copyright Notice and license at the end of inc/crnlib.h
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>
#include <algorithm>

#if !defined(_WIN32)
#include <libgen.h>
#endif

// CRN transcoder library.
#include "crn_decomp.h"
// .DDS file format definitions.
#include "dds_defs.h"

// A simple high-precision, platform independent timer class.
#include "timer.h"

#include "crn_platform.h"

using namespace crnlib;

#if defined(_WIN32)
#define example2_strcpy_safe(d, l, s) strcpy_s(d, l, s)
#else
void example2_strcpy_safe(char *d, size_t l, const char* s) {l = strnlen(s, l - 1); memcpy(d, s, l); d[l] = '\0';}
#endif

static int print_usage() {
  printf("Description: Transcodes .CRN to .DDS files using crn_decomp.h.\n");
  printf("Copyright (c) 2010-2016 Richard Geldreich, Jr. and Binomial LLC\n");
  printf("Usage: example2 [source_file] [options]\n");
  printf("\nOptions:\n");
  printf("-out filename - Force output filename.\n");
  return EXIT_FAILURE;
}

static int error(const char* pMsg, ...) {
  va_list args;
  va_start(args, pMsg);
  char buf[512];
  crnlib_vsnprintf(buf, sizeof(buf), pMsg, args);
  va_end(args);
  printf("%s", buf);
  return EXIT_FAILURE;
}

// Loads an entire file into an allocated memory block.
static crn_uint8* read_file_into_buffer(const char* pFilename, crn_uint32& size) {
  size = 0;

  FILE* pFile = NULL;
  crn_fopen(&pFile, pFilename, "rb");
  if (!pFile)
    return NULL;

  fseek(pFile, 0, SEEK_END);
  size = ftell(pFile);
  fseek(pFile, 0, SEEK_SET);

  crn_uint8* pSrc_file_data = static_cast<crn_uint8*>(malloc(std::max(1U, size)));
  if ((!pSrc_file_data) || (fread(pSrc_file_data, size, 1, pFile) != 1)) {
    fclose(pFile);
    free(pSrc_file_data);
    size = 0;
    return NULL;
  }

  fclose(pFile);
  return pSrc_file_data;
}

int main(int argc, char* argv[]) {
  printf("example2 - Version v%u.%02u Built " __DATE__ ", " __TIME__ "\n", CRNLIB_VERSION / 100, CRNLIB_VERSION % 100);

  if (argc < 2)
    return print_usage();

  // Parse command line options
  const char* pSrc_filename = argv[1];
  char out_filename[FILENAME_MAX] = {'\0'};

  for (int i = 2; i < argc; i++) {
    if (argv[i][0] == '/')
      argv[i][0] = '-';

    if (!crnlib_stricmp(argv[i], "-out")) {
      if (++i >= argc)
        return error("Expected output filename!");

      example2_strcpy_safe(out_filename, sizeof(out_filename), argv[i]);
    } else
      return error("Invalid option: %s\n", argv[i]);
  }

  // Load the source file into memory.
  printf("Loading source file: %s\n", pSrc_filename);
  crn_uint32 src_file_size;
  crn_uint8* pSrc_file_data = read_file_into_buffer(pSrc_filename, src_file_size);
  if (!pSrc_file_data)
    return error("Unable to read source file\n");

  // Decompress/transcode CRN to DDS.
  // DDS files are organized in face-major order, like this:
  //  Face0: Mip0, Mip1, Mip2, etc.
  //  Face1: Mip0, Mip1, Mip2, etc.
  //  etc.
  // While CRN files are organized in mip-major order, like this:
  //  Mip0: Face0, Face1, Face2, Face3, Face4, Face5
  //  Mip1: Face0, Face1, Face2, Face3, Face4, Face5
  //  etc.
  printf("Transcoding CRN to DDS\n");

  crnd::crn_texture_info tex_info;
  if (!crnd::crnd_get_texture_info(pSrc_file_data, src_file_size, &tex_info)) {
    free(pSrc_file_data);
    return error("crnd_get_texture_info() failed!\n");
  }

  timer tm;

  tm.start();
  crnd::crnd_unpack_context pContext = crnd::crnd_unpack_begin(pSrc_file_data, src_file_size);
  double total_unpack_begin_time = tm.get_elapsed_ms();

  if (!pContext) {
    free(pSrc_file_data);
    return error("crnd_unpack_begin() failed!\n");
  }

  // Now create the DDS file.
  char dst_filename[FILENAME_MAX];
  if (out_filename[0]) {
    example2_strcpy_safe(dst_filename, sizeof(dst_filename), out_filename);
  } else {
    unsigned int stripped_length = UINT32_MAX;
    const char* ext_begin = strrchr(pSrc_filename, '.');
    if (ext_begin) {
#ifdef _WIN32
      const char* sep = strpbrk(ext_begin, "/\\");
#else
      const char* sep = strpbrk(ext_begin, "/");
#endif
      if (!sep)
        stripped_length = ext_begin - pSrc_filename;
    }
    crnlib_snprintf(dst_filename, sizeof(dst_filename), "%-*s.dds", stripped_length, pSrc_filename);
  }

  printf("Writing DDS file: %s\n", dst_filename);

  FILE* pDDS_file = NULL;
  crn_fopen(&pDDS_file, dst_filename, "wb");
  if (!pDDS_file) {
    crnd::crnd_unpack_end(pContext);
    free(pSrc_file_data);
    return error("Failed creating destination file!\n");
  }

  // Write the 4-byte DDS signature (not endian safe, but whatever this is a sample).
  fwrite(&crnlib::cDDSFileSignature, sizeof(crnlib::cDDSFileSignature), 1, pDDS_file);

  // Prepare the DDS header.
  crnlib::DDSURFACEDESC2 dds_desc;
  memset(&dds_desc, 0, sizeof(dds_desc));
  dds_desc.dwSize = sizeof(dds_desc);
  dds_desc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT | ((tex_info.m_levels > 1) ? DDSD_MIPMAPCOUNT : 0);
  dds_desc.dwWidth = tex_info.m_width;
  dds_desc.dwHeight = tex_info.m_height;
  dds_desc.dwMipMapCount = (tex_info.m_levels > 1) ? tex_info.m_levels : 0;

  dds_desc.ddpfPixelFormat.dwSize = sizeof(crnlib::DDPIXELFORMAT);
  dds_desc.ddpfPixelFormat.dwFlags = DDPF_FOURCC;
  crn_format fundamental_fmt = crnd::crnd_get_fundamental_dxt_format(tex_info.m_format);
  dds_desc.ddpfPixelFormat.dwFourCC = crnd::crnd_crn_format_to_fourcc(fundamental_fmt);
  if (fundamental_fmt != tex_info.m_format) {
    // It's a funky swizzled DXTn format - write its FOURCC to dwRGBBitCount.
    dds_desc.ddpfPixelFormat.dwRGBBitCount = crnd::crnd_crn_format_to_fourcc(tex_info.m_format);
  }

  dds_desc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;
  if (tex_info.m_levels > 1) {
    dds_desc.ddsCaps.dwCaps |= (DDSCAPS_COMPLEX | DDSCAPS_MIPMAP);
  }

  if (tex_info.m_faces == 6) {
    dds_desc.ddsCaps.dwCaps2 = DDSCAPS2_CUBEMAP |
                               DDSCAPS2_CUBEMAP_POSITIVEX | DDSCAPS2_CUBEMAP_NEGATIVEX | DDSCAPS2_CUBEMAP_POSITIVEY |
                               DDSCAPS2_CUBEMAP_NEGATIVEY | DDSCAPS2_CUBEMAP_POSITIVEZ | DDSCAPS2_CUBEMAP_NEGATIVEZ;
  }

  // Set pitch/linearsize field (some DDS readers require this field to be non-zero).
  int bits_per_pixel = crnd::crnd_get_crn_format_bits_per_texel(tex_info.m_format);
  dds_desc.lPitch = (((dds_desc.dwWidth + 3) & ~3) * ((dds_desc.dwHeight + 3) & ~3) * bits_per_pixel) >> 3;
  dds_desc.dwFlags |= DDSD_LINEARSIZE;

  // Write the DDS header to the output file.
  fwrite(&dds_desc, sizeof(dds_desc), 1, pDDS_file);

  // Now transcode all face and mipmap levels into memory, one mip level at a time.
  void* pImages[cCRNMaxFaces][cCRNMaxLevels];
  crn_uint32 image_size_in_bytes[cCRNMaxLevels];
  memset(pImages, 0, sizeof(pImages));
  memset(image_size_in_bytes, 0, sizeof(image_size_in_bytes));

  crn_uint32 total_unpacked_texels = 0;

  double total_unpack_time = 0.0f;
  for (crn_uint32 level_index = 0; level_index < tex_info.m_levels; level_index++) {
    // Compute the face's width, height, number of DXT blocks per row/col, etc.
    const crn_uint32 width = std::max(1U, tex_info.m_width >> level_index);
    const crn_uint32 height = std::max(1U, tex_info.m_height >> level_index);
    const crn_uint32 blocks_x = std::max(1U, (width + 3) >> 2);
    const crn_uint32 blocks_y = std::max(1U, (height + 3) >> 2);
    const crn_uint32 row_pitch = blocks_x * crnd::crnd_get_bytes_per_dxt_block(tex_info.m_format);
    const crn_uint32 total_face_size = row_pitch * blocks_y;

    image_size_in_bytes[level_index] = total_face_size;

    for (crn_uint32 face_index = 0; face_index < tex_info.m_faces; face_index++) {
      void* p = malloc(total_face_size);
      if (!p) {
        for (crn_uint32 f = 0; f < cCRNMaxFaces; f++)
          for (crn_uint32 l = 0; l < cCRNMaxLevels; l++)
            free(pImages[f][l]);
        crnd::crnd_unpack_end(pContext);
        free(pSrc_file_data);
        return error("Out of memory!");
      }

      pImages[face_index][level_index] = p;
    }

    // Prepare the face pointer array needed by crnd_unpack_level().
    void* pDecomp_images[cCRNMaxFaces];
    for (crn_uint32 face_index = 0; face_index < tex_info.m_faces; face_index++)
      pDecomp_images[face_index] = pImages[face_index][level_index];

    // Now transcode the level to raw DXTn
    tm.start();
    if (!crnd::crnd_unpack_level(pContext, pDecomp_images, total_face_size, row_pitch, level_index)) {
      for (crn_uint32 f = 0; f < cCRNMaxFaces; f++)
        for (crn_uint32 l = 0; l < cCRNMaxLevels; l++)
          free(pImages[f][l]);

      crnd::crnd_unpack_end(pContext);
      free(pSrc_file_data);

      return error("Failed transcoding texture!");
    }

    total_unpack_time += tm.get_elapsed_ms();
    total_unpacked_texels += (blocks_x * blocks_y * 16);
  }

  printf("crnd_unpack_begin time: %3.3fms\n", total_unpack_begin_time);
  printf("Total crnd_unpack_level time: %3.3fms\n", total_unpack_time);
  double total_time = total_unpack_begin_time + total_unpack_time;
  printf("Total transcode time: %3.3fms\n", total_time);
  printf("Total texels transcoded: %u\n", total_unpacked_texels);
  printf("Overall transcode throughput: %3.3f million texels/sec\n", (total_unpacked_texels / (total_time / 1000.0f)) / 1000000.0f);

  // Now write the DXTn data to the DDS file in face-major order.
  for (crn_uint32 face_index = 0; face_index < tex_info.m_faces; face_index++)
    for (crn_uint32 level_index = 0; level_index < tex_info.m_levels; level_index++)
      fwrite(pImages[face_index][level_index], image_size_in_bytes[level_index], 1, pDDS_file);

  for (crn_uint32 f = 0; f < cCRNMaxFaces; f++)
    for (crn_uint32 l = 0; l < cCRNMaxLevels; l++)
      free(pImages[f][l]);

  crnd::crnd_unpack_end(pContext);
  free(pSrc_file_data);

  if (fclose(pDDS_file) == EOF) {
    return error("Failed writing to DDS file!\n");
  }

  return EXIT_SUCCESS;
}
