/********************************************************************************
* OpenGL-Framework                                                              *
* Copyright (c) 2013 Daniel Chappuis                                            *
*********************************************************************************
*                                                                               *
* This software is provided 'as-is', without any express or implied warranty.   *
* In no event will the authors be held liable for any damages arising from the  *
* use of this software.                                                         *
*                                                                               *
* Permission is granted to anyone to use this software for any purpose,         *
* including commercial applications, and to alter it and redistribute it        *
* freely, subject to the following restrictions:                                *
*                                                                               *
* 1. The origin of this software must not be misrepresented; you must not claim *
*    that you wrote the original software. If you use this software in a        *
*    product, an acknowledgment in the product documentation would be           *
*    appreciated but is not required.                                           *
*                                                                               *
* 2. Altered source versions must be plainly marked as such, and must not be    *
*    misrepresented as being the original software.                             *
*                                                                               *
* 3. This notice may not be removed or altered from any source distribution.    *
*                                                                               *
********************************************************************************/

// Librairies
#include "TextureReaderWriter.h"
#include <string>
#ifdef USE_JPEG_TEXTURE
    #include <jpeglib.h>
    #include <jerror.h>
#endif

using namespace openglframework;
using namespace std;

// Constants
const uint TextureReaderWriter::JPEG_COMPRESSION_QUALITY = 98;

// TGA file Header
#pragma pack(push, 1)
typedef struct {
    unsigned char  identsize;           // size of ID field that follows 18 byte header (0 usually)
    unsigned char  colourmaptype;       // type of colour map 0=none, 1=has palette
    unsigned char  imagetype;           // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed

    short colourmapstart;               // first colour map entry in palette
    short colourmaplength;              // number of colours in palette
    unsigned char  colourmapbits;       // number of bits per palette entry 15,16,24,32

    short xstart;                       // image x origin
    short ystart;                       // image y origin
    short width;                        // image width in pixels
    short height;                       // image height in pixels
    unsigned char  bits;                // image bits per pixel 8,16,24,32
    unsigned char  descriptor;          // image descriptor bits (vh flip bits)

    // pixel data follows header
} TGA_HEADER;
#pragma pack(pop)

// Load a texture from a file
void TextureReaderWriter::loadTextureFromFile(const std::string& filename,
                                              Texture2D& textureToCreate) {

    // Get the extension of the file
    uint startPosExtension = filename.find_last_of(".");
    string extension = filename.substr(startPosExtension+1);

    // Load the file using the correct method
    if (extension == "tga") {
        readTGAPicture(filename, textureToCreate);
    }
#ifdef USE_JPEG_TEXTURE
    else if (extension == "jpg" || extension == "jpeg"){
        readJPEGPicture(filename, textureToCreate);
    }
#endif
    else {

        // Display an error message and throw an exception
        string errorMessage("Error : the TextureLoader class cannot load a file with the extension .");
        errorMessage += extension;
        std::cerr << errorMessage << std::endl;
        throw std::invalid_argument(errorMessage.c_str());
    }
}

// Write a texture to a file
void TextureReaderWriter::writeTextureToFile(const std::string& filename,const Texture2D& texture) {

    // Get the extension of the file
    uint startPosExtension = filename.find_last_of(".");
    string extension = filename.substr(startPosExtension+1);

    // Write the file using the correct method
    if (extension == "tga") {
        writeTGAPicture(filename, texture);
    }
#ifdef USE_JPEG_TEXTURE
    else if (extension == "jpg" || extension == "jpeg"){
        writeJPEGPicture(filename, texture);
    }
#endif
    else {

        // Display an error message and throw an exception
        string errorMessage("Error : the TextureReaderWriter class cannot write a file with the extension .");
        errorMessage += extension;
        std::cerr << errorMessage << std::endl;
        throw std::invalid_argument(errorMessage.c_str());
    }
}

// Load a TGA picture
void TextureReaderWriter::readTGAPicture(const std::string &filename, Texture2D& textureToCreate) {

    // Open the file
    std::ifstream stream(filename.c_str(), std::ios::binary);

    // If we cannot open the file
    if(!stream.is_open()) {

        // Throw an exception and display an error message
        string errorMessage("Error : Cannot open the file " + filename);
        std::cerr << errorMessage << std::endl;
        throw std::runtime_error(errorMessage);
    }

    TGA_HEADER header;
    stream.read((char *)(&header), sizeof(TGA_HEADER));
    assert(header.width <= 4096 && header.width <= 4096 &&
           header.imagetype == 2 && header.bits == 24);

    // Read the file
    uint width = header.width;
    uint height = header.width;
    uint sizeImg = width*height;
    char* data = new char[sizeImg*3];
    assert(data);
    stream.read(data, sizeImg*3);
    for(uint i = 0; i < sizeImg; i++) {
        unsigned pos = i*3;
        unsigned char red = data[pos];
        data[pos] = data[pos + 2];
        data[pos + 2] = red;
    }

    // Close the stream
    stream.close();

    // Create the OpenGL texture using the picture data from the file
    textureToCreate.create(width, height, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, data);

    // Free the data memory
    delete[] data;
}


// Write a TGA picture
void TextureReaderWriter::writeTGAPicture(const std::string& filename, const Texture2D& texture) {
    assert(texture.getID() != 0);

    // Bind the corresponding texture
    glBindTexture(GL_TEXTURE_2D, texture.getID());

    uint sizeImg = texture.getWidth() * texture.getHeight();

    // Get the bytes form the OpenGL texture
    char* data = new char[sizeImg * 3];
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

    // Open the file
    std::ofstream stream(filename.c_str(), std::ios::binary);

    // If the file cannot be opened
    if(!stream.is_open()) {

        // Throw an exception and display an error message
        string errorMessage("Error : Cannot create/access the file " + filename);
        std::cerr << errorMessage << std::endl;
        delete[] data;
        throw std::runtime_error(errorMessage);
    }

    // Fill in the TGA header
    TGA_HEADER header;
    header.identsize = 0;           // size of ID field that follows 18 byte header (0 usually)
    header.colourmaptype = 0;       // type of colour map 0=none, 1=has palette
    header.imagetype = 2;           // type of image 0=none,1=indexed,2=rgb,3=grey,+8=rle packed
    header.colourmapstart = 0;                  // first colour map entry in palette
    header.colourmaplength = 0;                 // number of colours in palette
    header.colourmapbits=0;                     // number of bits per palette entry 15,16,24,32
    header.xstart = 0;                          // image x origin
    header.ystart = 0;                          // image y origin
    header.width = (short)texture.getWidth();   // image width in pixels
    header.height = (short)texture.getHeight(); // image height in pixels
    header.bits = 24;                           // image bits per pixel 8,16,24,32
    header.descriptor = 0;                      // image descriptor bits (vh flip bits)

    // Write the header to the file
    stream.write((char*)(&header), sizeof(TGA_HEADER));

    // Write the bytes to the file
    for(uint i = 0; i < sizeImg; i++) {
        unsigned pos = i*3;
        unsigned char red = data[pos];
        data[pos] = data[pos + 2];
        data[pos + 2] = red;
    }
    stream.write(data, sizeImg*3);

    // Close the file
    stream.close();

    // Delete the data
    delete[] data;

    // Unbind the corresponding texture
    glBindTexture(GL_TEXTURE_2D, 0);
}

#ifdef USE_JPEG_TEXTURE

// Read a JPEG picture
void TextureReaderWriter::readJPEGPicture(const std::string& filename, Texture2D& textureToCreate) {

    struct jpeg_decompress_struct info;
    struct jpeg_error_mgr error;

    info.err = jpeg_std_error(&error);
    jpeg_create_decompress(&info);

    // Open the file
    FILE* file = fopen(filename.c_str(), "rb");

    // If we cannot open the file
    if (!file) {

        // Throw an exception and display an error message
        string errorMessage("Error : Cannot open the file " + filename);
        std::cerr << errorMessage << std::endl;
        throw std::runtime_error(errorMessage);
    }

    jpeg_stdio_src(&info, file);
    jpeg_read_header(&info, true);
    jpeg_start_decompress(&info);

    unsigned long x = info.output_width;
    unsigned long y = info.output_height;
    int channels = info.num_components;
    assert(channels == 3);

    unsigned long size = x * y * 3;

    BYTE* data = new BYTE[size];

    BYTE* p1 = data;
    BYTE** p2 = &p1;
    int numlines = 0;

    while(info.output_scanline < info.output_height) {
        numlines = jpeg_read_scanlines(&info, p2, 1);
        *p2 += numlines * 3 * info.output_width;
    }

    jpeg_finish_decompress(&info);   //finish decompressing this file

    // Close the file
    fclose(file);

    // Create the OpenGL texture
    textureToCreate.create(x, y, GL_RGB, GL_RGB, GL_UNSIGNED_BYTE, data);

    // Free allocated memory
    delete[] data;
}

// Write a JPEG picture
void TextureReaderWriter::writeJPEGPicture(const std::string& filename, const Texture2D& texture) {

    struct jpeg_compress_struct info;
    struct jpeg_error_mgr error;

    info.err = jpeg_std_error(&error);
    jpeg_create_compress(&info);

    // Open the file
    FILE* file = fopen(filename.c_str(), "wb");

    if (!file) {
        // Throw an exception and display an error message
        string errorMessage("Error : Cannot write JPEG picture into the file " + filename);
        std::cerr << errorMessage << std::endl;
        throw std::runtime_error(errorMessage);
    }

    // Get the bytes form the OpenGL texture
    char* data = new char[texture.getWidth() * texture.getHeight() * 3];
    glGetTexImage(GL_TEXTURE_2D, 0, GL_RGB, GL_UNSIGNED_BYTE, data);

    jpeg_stdio_dest(&info, file);

    info.image_width = texture.getWidth();
    info.image_height = texture.getHeight();
    info.input_components = 3;
    info.in_color_space = JCS_RGB;
    jpeg_set_defaults(&info);
    jpeg_set_quality(&info, JPEG_COMPRESSION_QUALITY, true);

    jpeg_start_compress(&info, true);

    // Write the data into the file
    JSAMPROW rowPointer;
    int rowStride = texture.getWidth() * 3;
    while (info.next_scanline < info.image_height) {
        rowPointer = (JSAMPROW) &data[info.next_scanline * rowStride];
        jpeg_write_scanlines(&info, &rowPointer, 1);
    }

    jpeg_finish_compress(&info);
    jpeg_destroy_compress(&info);

    // Close the file
    fclose(file);

    // Free allocated memory
    delete[] data;
}

#endif