/**
 * @file Image.cpp
 * @author Petr Egert (egertpet@fel.cvut.cz)
 * @brief Contains Image class implementation
 */

#include <stdio.h>
#include <iostream>
#include <string.h>

#include "Error.h"
#include "Image.h"

using namespace std;
using namespace RD;
/**
 * @brief Helper function used to return a lowecase version of a string
 * @param text String to be converted
 * @return Lowercase version of the string. User must take care of deallocation
 */
char *ToLower(const char *text)
{
    if(!text) return NULL;
    char* ret = new char[strlen(text) + 1];
    ret[strlen(text)] = 0;
    for(unsigned int i = 0; i < strlen(text); i++) ret[i] = tolower(text[i]);
    return ret;
}

/**
 * @brief Performs class initialization. Tries to load an image from the given filename
 * @param fileName Path to the image file to be loaded
 * @return RES_OK on success, RES_MISSING_PARAM if fileName is not specified, RES_NOT_FOUND if the specified file can't be opened, RES_ERROR otherwise
 */
Result Image::Init(const char *fileName)
{
    if(!fileName)
    {
        return ErrorHandler::Error(string("Image::LoadTga() - no filename specified"), RES_MISSING_PARAM);
    }

    Result result = RES_ERROR;
    const char *extension = strrchr(fileName, '.');
    extension = ToLower(extension);

    if(extension)
    {
        if(!strcmp(extension, ".tga"))
        {
            result = LoadTga(fileName);
        }
        delete [] extension;
    }
    else
    {
        return ErrorHandler::Error(string("Image::Init(") + string(fileName) + string(") - failed to get file extension"), RES_ERROR);
    }

    return result;
}

/**
 * @brief Performs class deinitialization
 * @return RES_OK on success
 */
Result Image::Release()
{
    delete [] data;
    data = NULL;

    return RES_OK;
}

/**
 * @brief Loads an image from a TGA file
 *
 * Indexed and greyscale images are not supported
 * Non power of 8 BPP is not supported
 * @param fileName Path to the image file to be loaded
 * @return RES_OK on success, RES_MISSING_PARAM if fileName is not specified, RES_NOT_FOUND if the specified file can't be opened, RES_ERROR otherwise
 */
Result Image::LoadTga(const char *fileName)
{
    if(!fileName)
    {
        return ErrorHandler::Error(string("Image::LoadTga() - no filename specified"), RES_MISSING_PARAM);
    }

    FILE *inFile = NULL;
    inFile = fopen(fileName, "rb");
    if(!inFile)
    {
        return ErrorHandler::Error(string("Image::Init(") + string(fileName) + string(") - file not found"), RES_NOT_FOUND);
    }

    TGAHeader header;

    char headerData[18];
    fread(headerData, 18, 1, inFile);
    header.Set(headerData);
    if(header.indexed || !(header.type & 2))
    {
    	fclose(inFile);
    	return ErrorHandler::Error(string("Image::Init(") + string(fileName) + string(") - indexed and greyscale images not supported"), RES_ERROR);
    }

    if(header.bpp == 15 || header.bpp > 32)
    {
    	fclose(inFile);
    	return ErrorHandler::Error(string("Image::Init(") + string(fileName) + string(") - image color depth not supported"), RES_ERROR);
    }

    bool rle = false;

    if(header.type & 8) rle = true;

    unsigned char bytesPerPixel = header.bpp / 8;

    dataSize = header.width * header.height * bytesPerPixel;

    data = new char[dataSize];
    if(header.offset) fseek(inFile, SEEK_CUR, header.offset);

    // handle RLE decoding
    if(rle)
    {
        unsigned char rleByte, rleCount = 0;
        char *rleColor = new char[bytesPerPixel];
        unsigned int offset = 0;
        while(offset < dataSize)
        {
            rleByte = fgetc(inFile);
            if(rleByte & 0x80)
            {
                rleCount = rleByte - 0x80 + 1;
                fread(rleColor, bytesPerPixel, 1, inFile);
                for(int i = 0; i < rleCount; i++)
                {
                    memcpy(&data[offset], rleColor, bytesPerPixel);
                    offset += bytesPerPixel;
                }
            }
            else
            {
                rleCount = rleByte + 1;
                fread(&data[offset], bytesPerPixel, rleCount, inFile);
                offset += rleCount * bytesPerPixel;
            }
        }
		delete [] rleColor;
    }
    else fread(data, dataSize, 1, inFile);

    // TGA files are BGR, so the colors need to be reversed
    if(!(header.flags & 32))
    {
        char * oldData = data;
        data = new char[dataSize];
        int lineBytes = bytesPerPixel * header.width;
        for(int i = 0; i < header.height; i++)
        {
            memcpy(&data[i * lineBytes], &oldData[(header.height - i - 1) * lineBytes], lineBytes);
        }
        delete [] oldData;
    }

    if(header.bpp >= 24)
    {
        unsigned int offset = 0;
        while(offset < dataSize)
        {
            unsigned char tmpChar = data[offset];
            data[offset] = data[offset + 2];
            data[offset + 2] = tmpChar;
            offset += bytesPerPixel;
        }
    }
    fclose(inFile);
    width = header.width;
    height = header.height;
    bpp = header.bpp;


    return RES_OK;
}
