
#define _CRT_SECURE_NO_WARNINGS

#include "save-tiff.h"

#include <cstdio>

#ifdef MSDFGEN_USE_CPP11
    #include <cstdint>
#else
    typedef int int32_t;
    typedef unsigned uint32_t;
    typedef unsigned short uint16_t;
    typedef unsigned char uint8_t;
#endif

namespace msdfgen {

template <typename T>
static bool writeValue(FILE *file, T value) {
    return fwrite(&value, sizeof(T), 1, file) == 1;
}
template <typename T>
static void writeValueRepeated(FILE *file, T value, int times) {
    for (int i = 0; i < times; ++i)
        writeValue(file, value);
}

static bool writeTiffHeader(FILE *file, int width, int height, int channels) {
    #ifdef __BIG_ENDIAN__
        writeValue<uint16_t>(file, 0x4d4du);
    #else
        writeValue<uint16_t>(file, 0x4949u);
    #endif
    writeValue<uint16_t>(file, 42);
    writeValue<uint32_t>(file, 0x0008u); // Offset of first IFD
    // Offset = 0x0008

    writeValue<uint16_t>(file, 15); // Number of IFD entries

    // ImageWidth
    writeValue<uint16_t>(file, 0x0100u);
    writeValue<uint16_t>(file, 0x0004u);
    writeValue<uint32_t>(file, 1);
    writeValue<int32_t>(file, width);
    // ImageLength
    writeValue<uint16_t>(file, 0x0101u);
    writeValue<uint16_t>(file, 0x0004u);
    writeValue<uint32_t>(file, 1);
    writeValue<int32_t>(file, height);
    // BitsPerSample
    writeValue<uint16_t>(file, 0x0102u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, channels);
    if (channels > 1)
        writeValue<uint32_t>(file, 0x00c2u); // Offset of 32, 32, ...
    else {
        writeValue<uint16_t>(file, 32);
        writeValue<uint16_t>(file, 0);
    }
    // Compression
    writeValue<uint16_t>(file, 0x0103u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint16_t>(file, 1);
    writeValue<uint16_t>(file, 0);
    // PhotometricInterpretation
    writeValue<uint16_t>(file, 0x0106u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint16_t>(file, channels >= 3 ? 2 : 1);
    writeValue<uint16_t>(file, 0);
    // StripOffsets
    writeValue<uint16_t>(file, 0x0111u);
    writeValue<uint16_t>(file, 0x0004u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint32_t>(file, 0x00d2u+(channels > 1)*channels*12); // Offset of pixel data
    // SamplesPerPixel
    writeValue<uint16_t>(file, 0x0115u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint16_t>(file, channels);
    writeValue<uint16_t>(file, 0);
    // RowsPerStrip
    writeValue<uint16_t>(file, 0x0116u);
    writeValue<uint16_t>(file, 0x0004u);
    writeValue<uint32_t>(file, 1);
    writeValue<int32_t>(file, height);
    // StripByteCounts
    writeValue<uint16_t>(file, 0x0117u);
    writeValue<uint16_t>(file, 0x0004u);
    writeValue<uint32_t>(file, 1);
    writeValue<int32_t>(file, sizeof(float)*channels*width*height);
    // XResolution
    writeValue<uint16_t>(file, 0x011au);
    writeValue<uint16_t>(file, 0x0005u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint32_t>(file, 0x00c2u+(channels > 1)*channels*2); // Offset of 300, 1
    // YResolution
    writeValue<uint16_t>(file, 0x011bu);
    writeValue<uint16_t>(file, 0x0005u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint32_t>(file, 0x00cau+(channels > 1)*channels*2); // Offset of 300, 1
    // ResolutionUnit
    writeValue<uint16_t>(file, 0x0128u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, 1);
    writeValue<uint16_t>(file, 2);
    writeValue<uint16_t>(file, 0);
    // SampleFormat
    writeValue<uint16_t>(file, 0x0153u);
    writeValue<uint16_t>(file, 0x0003u);
    writeValue<uint32_t>(file, channels);
    if (channels > 1)
        writeValue<uint32_t>(file, 0x00d2u+channels*2); // Offset of 3, 3, ...
    else {
        writeValue<uint16_t>(file, 3);
        writeValue<uint16_t>(file, 0);
    }
    // SMinSampleValue
    writeValue<uint16_t>(file, 0x0154u);
    writeValue<uint16_t>(file, 0x000bu);
    writeValue<uint32_t>(file, channels);
    if (channels > 1)
        writeValue<uint32_t>(file, 0x00d2u+channels*4); // Offset of 0.f, 0.f, ...
    else
        writeValue<float>(file, 0.f);
    // SMaxSampleValue
    writeValue<uint16_t>(file, 0x0155u);
    writeValue<uint16_t>(file, 0x000bu);
    writeValue<uint32_t>(file, channels);
    if (channels > 1)
        writeValue<uint32_t>(file, 0x00d2u+channels*8); // Offset of 1.f, 1.f, ...
    else
        writeValue<float>(file, 1.f);
    // Offset = 0x00be

    writeValue<uint32_t>(file, 0);

    if (channels > 1) {
        // 0x00c2 BitsPerSample data
        writeValueRepeated<uint16_t>(file, 32, channels);
        // 0x00c2 + 2*N XResolution data
        writeValue<uint32_t>(file, 300);
        writeValue<uint32_t>(file, 1);
        // 0x00ca + 2*N YResolution data
        writeValue<uint32_t>(file, 300);
        writeValue<uint32_t>(file, 1);
        // 0x00d2 + 2*N SampleFormat data
        writeValueRepeated<uint16_t>(file, 3, channels);
        // 0x00d2 + 4*N SMinSampleValue data
        writeValueRepeated<float>(file, 0.f, channels);
        // 0x00d2 + 8*N SMaxSampleValue data
        writeValueRepeated<float>(file, 1.f, channels);
        // Offset = 0x00d2 + 12*N
    } else {
        // 0x00c2 XResolution data
        writeValue<uint32_t>(file, 300);
        writeValue<uint32_t>(file, 1);
        // 0x00ca YResolution data
        writeValue<uint32_t>(file, 300);
        writeValue<uint32_t>(file, 1);
        // Offset = 0x00d2
    }

    return true;
}

template <int N>
bool saveTiffFloat(const BitmapConstRef<float, N> &bitmap, const char *filename) {
    FILE *file = fopen(filename, "wb");
    if (!file)
        return false;
    writeTiffHeader(file, bitmap.width, bitmap.height, N);
    for (int y = bitmap.height-1; y >= 0; --y)
        fwrite(bitmap(0, y), sizeof(float), N*bitmap.width, file);
    return !fclose(file);
}

bool saveTiff(const BitmapConstRef<float, 1> &bitmap, const char *filename) {
    return saveTiffFloat(bitmap, filename);
}
bool saveTiff(const BitmapConstRef<float, 3> &bitmap, const char *filename) {
    return saveTiffFloat(bitmap, filename);
}
bool saveTiff(const BitmapConstRef<float, 4> &bitmap, const char *filename) {
    return saveTiffFloat(bitmap, filename);
}

}
