#if defined(_WIN32)

#ifndef NOMINMAX
#define NOMINMAX
#endif
#include <tchar.h>
#include <windows.h>
#endif

#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <vector>

// Uncomment if you want to use system provided zlib.
//#define TINYEXR_USE_MINIZ (0)
//#include <zlib.h>

#define TINYEXR_IMPLEMENTATION
#include "tinyexr.h"

#ifdef __clang__
#if __has_warning("-Wzero-as-null-pointer-constant")
#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant"
#endif
#endif

#define SIMPLE_API_EXAMPLE
//#define TEST_ZFP_COMPRESSION

#ifdef SIMPLE_API_EXAMPLE

#if 0
static void
SaveAsPFM(const char* filename, int width, int height, float* data)
{
#ifdef _WIN32
 FILE* fp = NULL;
 fopen_s(&fp, filename, "wb");
#else
 FILE* fp = fopen(filename, "wb");
#endif
 if (!fp) {
   fprintf(stderr, "failed to write a PFM file.\n");
   return;
 }

 fprintf(fp, "PF\n");
 fprintf(fp, "%d %d\n", width, height);
 fprintf(fp, "-1\n"); // -1: little endian, 1: big endian

 // RGBA -> RGB
 std::vector<float> rgb(static_cast<size_t>(width*height*3));

 for (size_t i = 0; i < static_cast<size_t>(width * height); i++) {
   rgb[3*i+0] = data[4*i+0];
   rgb[3*i+1] = data[4*i+1];
   rgb[3*i+2] = data[4*i+2];
 }

 fwrite(&rgb.at(0), sizeof(float), static_cast<size_t>(width * height * 3), fp);

 fclose(fp);
}
#endif

#else

static const char* GetPixelType(int id) {
 if (id == TINYEXR_PIXELTYPE_HALF) {
   return "HALF";
 } else if (id == TINYEXR_PIXELTYPE_FLOAT) {
   return "FLOAT";
 } else if (id == TINYEXR_PIXELTYPE_UINT) {
   return "UINT";
 }

 return "???";
}

// Simple tile -> scanline converter. Assumes FLOAT pixel type for all channels.
static void TiledImageToScanlineImage(EXRImage* src, const EXRHeader* header) {
 assert(header->data_window.max_x - header->data_window.min_x + 1 >= 0);
 assert(header->data_window.max_y - header->data_window.min_y + 1 >= 0);
 size_t data_width =
     static_cast<size_t>(header->data_window.max_x - header->data_window.min_x + 1);
 size_t data_height =
     static_cast<size_t>(header->data_window.max_y - header->data_window.min_y + 1);

 src->images = static_cast<unsigned char**>(
     malloc(sizeof(float*) * static_cast<size_t>(header->num_channels)));
 for (size_t c = 0; c < static_cast<size_t>(header->num_channels); c++) {
   assert(header->pixel_types[c] == TINYEXR_PIXELTYPE_FLOAT);
   src->images[c] = static_cast<unsigned char*>(
       malloc(sizeof(float) * data_width * data_height));
   memset(src->images[c], 0, sizeof(float) * data_width * data_height);
 }

 for (size_t tile_idx = 0; tile_idx < static_cast<size_t>(src->num_tiles);
      tile_idx++) {
   size_t sx = static_cast<size_t>(src->tiles[tile_idx].offset_x *
                                   header->tile_size_x);
   size_t sy = static_cast<size_t>(src->tiles[tile_idx].offset_y *
                                   header->tile_size_y);
   size_t ex = static_cast<size_t>(src->tiles[tile_idx].offset_x *
                                       header->tile_size_x +
                                   src->tiles[tile_idx].width);
   size_t ey = static_cast<size_t>(src->tiles[tile_idx].offset_y *
                                       header->tile_size_y +
                                   src->tiles[tile_idx].height);

   for (size_t c = 0; c < static_cast<size_t>(header->num_channels); c++) {
     float* dst_image = reinterpret_cast<float*>(src->images[c]);
     const float* src_image =
         reinterpret_cast<const float*>(src->tiles[tile_idx].images[c]);
     for (size_t y = 0; y < static_cast<size_t>(ey - sy); y++) {
       for (size_t x = 0; x < static_cast<size_t>(ex - sx); x++) {
         dst_image[(y + sy) * data_width + (x + sx)] =
             src_image[y * static_cast<size_t>(header->tile_size_x) + x];
       }
     }
   }
 }
}
#endif

#if defined(_WIN32)

#if defined(__MINGW32__)
// __wgetmainargs is not defined in windows.h
extern "C" int __wgetmainargs(int*, wchar_t***, wchar_t***, int, int*);
#endif

// https://gist.github.com/trueroad/fb4d0c3f67285bf66804
namespace {
std::vector<char> utf16_to_utf8(const wchar_t* wc) {
 int size = WideCharToMultiByte(CP_UTF8, 0, wc, -1, NULL, 0, NULL, NULL);
 std::vector<char> retval(size);
 if (size) {
   WideCharToMultiByte(CP_UTF8, 0, wc, -1, retval.data(), retval.size(), NULL,
                       NULL);
 } else
   retval.push_back('\0');
 return retval;
}
}  // namespace
#endif

static int test_main(int argc, char** argv);

#if defined(_WIN32)
#if defined(__MINGW32__)
int main() {
 wchar_t** wargv;
 wchar_t** wenpv;
 int argc = 0, si = 0;
 __wgetmainargs(&argc, &wargv, &wenpv, 1, &si);

 std::vector<std::vector<char> > argv_vvc(argc);
 std::vector<char*> argv_vc(argc);

 for (int i = 0; i < argc; i++) {
   argv_vvc.at(i) = utf16_to_utf8(wargv[i]);
   argv_vc.at(i) = argv_vvc.at(i).data();
 }

 // TODO(syoyo): envp

 return test_main(argc, argv_vc.data());
}
#else  // Assume MSVC
int _tmain(int argc, _TCHAR** wargv) {
 std::vector<std::vector<char> > argv_vvc(argc);
 std::vector<char*> argv_vc(argc);

 for (int i = 0; i < argc; i++) {
#if defined(UNICODE) || defined(_UNICODE)
   argv_vvc.at(i) = utf16_to_utf8(wargv[i]);
#else
   size_t slen = _tcslen(wargv[i]);
   std::vector<char> buf(slen + 1);
   memcpy(buf.data(), wargv[i], slen);
   buf[slen] = '\0';
   argv_vvc.at(i) = buf;
#endif

   argv_vc.at(i) = argv_vvc.at(i).data();
 }

 return test_main(argc, argv_vc.data());
}
#endif
#else
int main(int argc, char** argv) { return test_main(argc, argv); }
#endif

int test_main(int argc, char** argv) {
 const char* outfilename = "output_test.exr";
 const char* err = NULL;

 if (argc < 2) {
   fprintf(stderr, "Needs input.exr.\n");
   exit(-1);
 }

 if (argc > 2) {
   outfilename = argv[2];
 }

 const char* input_filename = argv[1];

#ifdef SIMPLE_API_EXAMPLE
 (void)outfilename;
 int width, height;
 float* image;

 int ret = IsEXR(input_filename);
 if (ret != TINYEXR_SUCCESS) {
   fprintf(stderr, "File not found or given file is not a EXR format. code %d\n", ret);
   exit(-1);
 }

 ret = LoadEXR(&image, &width, &height, input_filename, &err);
 if (ret != TINYEXR_SUCCESS) {
   if (err) {
     fprintf(stderr, "Load EXR err: %s(code %d)\n", err, ret);
   } else {
     fprintf(stderr, "Load EXR err: code = %d\n", ret);
   }
   FreeEXRErrorMessage(err);
   return ret;
 }

 // SaveAsPFM("output.pfm", width, height, image);
 ret = SaveEXR(image, width, height, 4 /* =RGBA*/,
               1 /* = save as fp16 format */, "output.exr", &err);
 if (ret != TINYEXR_SUCCESS) {
   if (err) {
     fprintf(stderr, "Save EXR err: %s(code %d)\n", err, ret);
   } else {
     fprintf(stderr, "Failed to save EXR image. code = %d\n", ret);
   }
 }
 free(image);

 std::cout << "Wrote output.exr." << std::endl;
#else

 EXRVersion exr_version;

 int ret = ParseEXRVersionFromFile(&exr_version, input_filename);
 if (ret != 0) {
   fprintf(stderr, "Invalid EXR file: %s\n", input_filename);
   return -1;
 }

 printf(
     "version: tiled = %d, long_name = %d, non_image = %d, multipart = %d\n",
     exr_version.tiled, exr_version.long_name, exr_version.non_image,
     exr_version.multipart);

 if (exr_version.multipart) {
   EXRHeader** exr_headers;  // list of EXRHeader pointers.
   int num_exr_headers;

   ret = ParseEXRMultipartHeaderFromFile(&exr_headers, &num_exr_headers,
                                         &exr_version, argv[1], &err);
   if (ret != 0) {
     fprintf(stderr, "Parse EXR err: %s\n", err);
     return ret;
   }

   printf("num parts = %d\n", num_exr_headers);

   for (size_t i = 0; i < static_cast<size_t>(num_exr_headers); i++) {
     const EXRHeader& exr_header = *(exr_headers[i]);

     printf("Part: %lu\n", static_cast<unsigned long>(i));

     printf("dataWindow = %d, %d, %d, %d\n", exr_header.data_window.min_x,
            exr_header.data_window.min_y, exr_header.data_window.max_x,
            exr_header.data_window.max_y);
     printf("displayWindow = %d, %d, %d, %d\n", exr_header.display_window.min_x,
            exr_header.display_window.min_y, exr_header.display_window.max_x,
            exr_header.display_window.max_y);
     printf("screenWindowCenter = %f, %f\n",
            static_cast<double>(exr_header.screen_window_center[0]),
            static_cast<double>(exr_header.screen_window_center[1]));
     printf("screenWindowWidth = %f\n",
            static_cast<double>(exr_header.screen_window_width));
     printf("pixelAspectRatio = %f\n",
            static_cast<double>(exr_header.pixel_aspect_ratio));
     printf("lineOrder = %d\n", exr_header.line_order);

     if (exr_header.num_custom_attributes > 0) {
       printf("# of custom attributes = %d\n",
              exr_header.num_custom_attributes);
       for (int a = 0; a < exr_header.num_custom_attributes; a++) {
         printf("  [%d] name = %s, type = %s, size = %d\n", a,
                exr_header.custom_attributes[a].name,
                exr_header.custom_attributes[a].type,
                exr_header.custom_attributes[a].size);
         // if (strcmp(exr_header.custom_attributes[i].type, "float") == 0) {
         //  printf("    value = %f\n", *reinterpret_cast<float
         //  *>(exr_header.custom_attributes[i].value));
         //}
       }
     }
   }

   std::vector<EXRImage> images(static_cast<size_t>(num_exr_headers));
   for (size_t i = 0; i < static_cast<size_t>(num_exr_headers); i++) {
     InitEXRImage(&images[i]);
   }

   ret = LoadEXRMultipartImageFromFile(
       &images.at(0), const_cast<const EXRHeader**>(exr_headers),
       static_cast<unsigned int>(num_exr_headers), input_filename, &err);
   if (ret != 0) {
     fprintf(stderr, "Load EXR err: %s\n", err);
     FreeEXRErrorMessage(err);
     return ret;
   }

   printf("Loaded %d part images\n", num_exr_headers);
   printf(
       "There is no saving feature for multi-part images, thus just exit an "
       "application...\n");

   for (size_t i = 0; i < static_cast<size_t>(num_exr_headers); i++) {
     FreeEXRImage(&images.at(i));
   }

   for (size_t i = 0; i < static_cast<size_t>(num_exr_headers); i++) {
     FreeEXRHeader(exr_headers[i]);
     free(exr_headers[i]);
   }
   free(exr_headers);

 } else {  // single-part EXR

   EXRHeader exr_header;
   InitEXRHeader(&exr_header);

   ret =
       ParseEXRHeaderFromFile(&exr_header, &exr_version, input_filename, &err);
   if (ret != 0) {
     fprintf(stderr, "Parse single-part EXR err: %s\n", err);
     FreeEXRErrorMessage(err);
     return ret;
   }

   printf("dataWindow = %d, %d, %d, %d\n", exr_header.data_window.min_x,
          exr_header.data_window.min_y, exr_header.data_window.max_x,
          exr_header.data_window.max_y);
   printf("displayWindow = %d, %d, %d, %d\n", exr_header.display_window.min_x,
          exr_header.display_window.min_y, exr_header.display_window.max_x,
          exr_header.display_window.max_y);
   printf("screenWindowCenter = %f, %f\n",
          static_cast<double>(exr_header.screen_window_center[0]),
          static_cast<double>(exr_header.screen_window_center[1]));
   printf("screenWindowWidth = %f\n",
          static_cast<double>(exr_header.screen_window_width));
   printf("pixelAspectRatio = %f\n",
          static_cast<double>(exr_header.pixel_aspect_ratio));
   printf("lineOrder = %d\n", exr_header.line_order);

   if (exr_header.num_custom_attributes > 0) {
     printf("# of custom attributes = %d\n", exr_header.num_custom_attributes);
     for (int i = 0; i < exr_header.num_custom_attributes; i++) {
       printf("  [%d] name = %s, type = %s, size = %d\n", i,
              exr_header.custom_attributes[i].name,
              exr_header.custom_attributes[i].type,
              exr_header.custom_attributes[i].size);
       // if (strcmp(exr_header.custom_attributes[i].type, "float") == 0) {
       //  printf("    value = %f\n", *reinterpret_cast<float
       //  *>(exr_header.custom_attributes[i].value));
       //}
     }
   }

   // Read HALF channel as FLOAT.
   for (int i = 0; i < exr_header.num_channels; i++) {
     if (exr_header.pixel_types[i] == TINYEXR_PIXELTYPE_HALF) {
       exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
     }
   }

   EXRImage exr_image;
   InitEXRImage(&exr_image);

   ret = LoadEXRImageFromFile(&exr_image, &exr_header, input_filename, &err);
   if (ret != 0) {
     fprintf(stderr, "Load EXR err: %s\n", err);
     FreeEXRHeader(&exr_header);
     FreeEXRErrorMessage(err);
     return ret;
   }

   printf("EXR: %d x %d\n", exr_image.width, exr_image.height);

   for (int i = 0; i < exr_header.num_channels; i++) {
     printf("pixelType[%d]: %s\n", i, GetPixelType(exr_header.pixel_types[i]));
     printf("chan[%d] = %s\n", i, exr_header.channels[i].name);
     printf("requestedPixelType[%d]: %s\n", i,
            GetPixelType(exr_header.requested_pixel_types[i]));
   }

#if 0  // example to write custom attribute
   int version_minor = 3;
   exr_header.num_custom_attributes = 1;
   exr_header.custom_attributes = reinterpret_cast<EXRAttribute *>(malloc(sizeof(EXRAttribute) * exr_header.custom_attributes));
   strcpy(exr_header.custom_attributes[0].name, "tinyexr_version_minor");
   exr_header.custom_attributes[0].name[strlen("tinyexr_version_minor")] = '\0';
   strcpy(exr_header.custom_attributes[0].type, "int");
   exr_header.custom_attributes[0].type[strlen("int")] = '\0';
   exr_header.custom_attributes[0].size = sizeof(int);
   exr_header.custom_attributes[0].value = (unsigned char*)malloc(sizeof(int));
   memcpy(exr_header.custom_attributes[0].value, &version_minor, sizeof(int));
#endif

   if (exr_header.tiled) {
     TiledImageToScanlineImage(&exr_image, &exr_header);
   }

   exr_header.compression_type = TINYEXR_COMPRESSIONTYPE_NONE;

#ifdef TEST_ZFP_COMPRESSION
   // Assume input image is FLOAT pixel type.
   for (int i = 0; i < exr_header.num_channels; i++) {
     exr_header.channels[i].pixel_type = TINYEXR_PIXELTYPE_FLOAT;
     exr_header.requested_pixel_types[i] = TINYEXR_PIXELTYPE_FLOAT;
   }

   unsigned char zfp_compression_type = TINYEXR_ZFP_COMPRESSIONTYPE_RATE;
   double zfp_compression_rate = 4;
   exr_header.num_custom_attributes = 2;
   strcpy(exr_header.custom_attributes[0].name, "zfpCompressionType");
   exr_header.custom_attributes[0].name[strlen("zfpCompressionType")] = '\0';
   exr_header.custom_attributes[0].size = 1;
   exr_header.custom_attributes[0].value =
       (unsigned char*)malloc(sizeof(unsigned char));
   exr_header.custom_attributes[0].value[0] = zfp_compression_type;

   strcpy(exr_header.custom_attributes[1].name, "zfpCompressionRate");
   exr_header.custom_attributes[1].name[strlen("zfpCompressionRate")] = '\0';
   exr_header.custom_attributes[1].size = sizeof(double);
   exr_header.custom_attributes[1].value =
       (unsigned char*)malloc(sizeof(double));
   memcpy(exr_header.custom_attributes[1].value, &zfp_compression_rate,
          sizeof(double));
   exr_header.compression_type = TINYEXR_COMPRESSIONTYPE_ZFP;
#endif

   ret = SaveEXRImageToFile(&exr_image, &exr_header, outfilename, &err);
   if (ret != 0) {
     fprintf(stderr, "Save EXR err: %s\n", err);
     FreeEXRHeader(&exr_header);
     FreeEXRErrorMessage(err);
     return ret;
   }
   printf("Saved exr file. [ %s ] \n", outfilename);

   FreeEXRHeader(&exr_header);
   FreeEXRImage(&exr_image);
 }
#endif

 return ret;
}