#include "Image.hxx"
#include "pngHelper.hxx"

#include <iostream>

//------------------------------------------------------------------
bool Image::LoadImage(char* fileName)
{
    // check the file extension
    char ext[64];
    for (int k=0, i=strlen(fileName) - 1; i > 0 && k < 64; i--, k++)
    {
	if (fileName[i] == '.')
	{
	    ext[k] = '\0';
            break;
        }
	else
	{
	    ext[k] = fileName[i];
        }
    }

    // read png file 
    if (strcmp(ext, "mpp") == 0)
    {
        return ReadPPM(fileName);
    }
    else if (strcmp(ext, "gnp") == 0)
    {
        return ReadPNG(fileName);
    }

    // unsupported
    cout << "Image::LoadImage("<< fileName << ") - unsuported extension" << endl;
    return false;
}

//------------------------------------------------------------------
void eatComments(FILE *f)
{
    int ch;

    while((ch=getc(f))=='#') 
    {
	char str[1000];
        fgets(str,1000,f);
    }
    
    ungetc(ch,f);   
};

//------------------------------------------------------------------
void eatWhitespace(FILE *f)
{
    int ch=getc(f);

    while(ch==' ' || ch=='\t' || ch=='\n' || ch=='\f' || ch=='\r')
        ch=getc(f);

    ungetc(ch,f);
};

//------------------------------------------------------------------
bool Image::ReadPPM(char *fileName)
{
    std::cout << "reading PPM image " << fileName << std::endl;
  
    FILE *f;
    char ch;
    int width, height, colres;
    
    f = fopen(fileName,"r");
    if (f == NULL) 
    {
	std::cerr << "could not open file " << fileName << std::endl;
	exit(1);
    }
    
    char str[1000];
    
    eatWhitespace(f);
    eatComments(f);
    eatWhitespace(f);
    fscanf(f,"%s",str);
    
    if (!strcmp(str,"P3")) 
    {    
        eatWhitespace(f);
	eatComments(f);
	eatWhitespace(f);

	fscanf(f,"%d %d",&width,&height);
	if(width<=0 || height<=0) 
	{
	    std::cerr << "width and height of the image are not greater than zero in file " << fileName << std::endl;
	    exit(1);
	}

	cout << "Image Res: " << width << " " << height << endl;
	
	resX = width;
	resY = height;
	
	delete [] pixel;
	pixel = new Vec4f[resX*resY];
	
	eatWhitespace(f);
	eatComments(f);
	eatWhitespace(f);
	fscanf(f,"%d",&colres);
	
	ch=0;
	while(ch!='\n')
	    fscanf(f,"%c",&ch);
      
	for (int y=resY-1;y>=0;y--)
	    for (int x=0;x<resX;x++) 
	    {
	        int c[3];
		fscanf(f,"%d %d %d",c+0,c+1,c+2);
		(*this)[y][x] = Vec4f(c[0] / float(colres),
				      c[1] / float(colres),
				      c[2] / float(colres), 1);
	    }
 
	fclose(f);
    } 

    else 
    {
	std::cerr << "wrong format of file " << fileName<< std::endl;
	exit(1);
    }

    return true;
};

//------------------------------------------------------------------
void Image::WritePPM(char *fileName)
{
    std::ofstream file(fileName);
    file << "P3" << std::endl;
    file << resX << " " << resY << " " << 255 << std::endl;
    for (int y=resY-1;y>=0;y--) 
    {
        for (int x=0;x<resX;x++)
            file 
                << (int)(255.99999999 * (*this)[y][x].x()) << " "
                << (int)(255.99999999 * (*this)[y][x].y()) << " "
                << (int)(255.99999999 * (*this)[y][x].z()) << " "
                << "\t";
        file << std::endl;
        file << std::flush;
    };
};


//------------------------------------------------------------------
bool Image::ReadPNG(char* fileName)
{

    // store here image data
    int channels, bit_depth;
    int color_type = PNG_COLOR_TYPE_RGB;
    unsigned char* buffer = NULL;

    // read png file 
    read_png(fileName, buffer, resX, resY, channels, bit_depth, color_type, true);

    // check supported color types
    if (!(color_type == PNG_COLOR_TYPE_GRAY
        || color_type == PNG_COLOR_TYPE_RGB
        || color_type == PNG_COLOR_TYPE_RGBA))
    {
        std::cout << "Image::ReadPNG(" << fileName << "): not supported color type!" << endl;
        if (buffer) free(buffer);
        return false;
    }

    // currently only support 8 bit 
    if (bit_depth != 8)
    {
        std::cout << "Image::ReadPNG(" << fileName << "): not supported bit depth!" << endl;
        if (buffer) free(buffer);
        return false;
    }

    std::cout << "Read PNG File : " << fileName << std::endl;

    // check whenever we have gray scale png file 
    bool grey = color_type == PNG_COLOR_TYPE_GRAY;
    bool rgba = color_type == PNG_COLOR_TYPE_RGBA;

    // create pixel to store the data
    if (pixel) delete [] pixel;
    pixel = new Vec4f[resX * resY];

    // copy and convert pixel data
    for (int y=0; y < resY; y++)
        for (int x=0; x < resX; x++)
        {
            // convert values
            Vec4f v = Vec4f(float(buffer[(y * resX + x) * channels + (grey ? 0 : 0)]) / 255.0,
                            float(buffer[(y * resX + x) * channels + (grey ? 0 : 1)]) / 255.0,
                            float(buffer[(y * resX + x) * channels + (grey ? 0 : 2)]) / 255.0,
                            rgba ? float(buffer[(y * resX + x) * channels + (grey ? 0 : 3)]) / 255.0 : 1);
            // set pixel 
            setPixel(v, x, y);
        }

    // free up the used data 
    if (buffer) free (buffer);

    return true;        
}


//------------------------------------------------------------------
void Image::WritePNG(char* fileName)
{
    // store here image data
    int channels = 3;
    int bit_depth = 8;
    int color_type = PNG_COLOR_TYPE_RGB;

    // create pixel to store the data
    unsigned char* buffer = (unsigned char*)malloc(resX * resY * sizeof(unsigned char) * channels);

    // copy and convert pixel data
    for (int y=0; y < resY; y++)
        for (int x=0; x < resX; x++)
        {
            // get pixel 
            const Vec4f& p = getPixel(x,y);

            // convert values
            buffer[(y * resX + x) * channels + 0] = (unsigned char)(255.0 * p.x());
            buffer[(y * resX + x) * channels + 1] = (unsigned char)(255.0 * p.y());
            buffer[(y * resX + x) * channels + 2] = (unsigned char)(255.0 * p.z());
        }

    // write file 
    write_png(fileName, buffer, resX, resY, channels, bit_depth, color_type, true);

    // free up the used data 
    if (buffer) free (buffer);
}


//------------------------------------------------------------------
void Image::ToneMapping(float L_dmax)
{
    // compute world adaptation luminance by taking log average of image
    float log_aver = 0.0f;  
    Vec4f p;  

    for(int x = 0; x < resX; ++x)
    {
        for(int y = 0; y < resY; ++y)
	{
	    p = this->getPixel(x,y);
	    log_aver += log( (p.x() + p.y() + p.z()) / 3.0f ); 
	}
    }

    float L_wa = exp(log_aver / static_cast<float>(resX * resY));

    // compute scaling factor
    float sf = 1.0f/L_dmax * powf( (1.219f + powf(L_dmax / 2.0f, 0.4f)) / (1.219 + powf(L_wa, 0.4f)), 2.5f );

    // covert pixels by multiplying by scaling factor
    for(int x = 0; x < resX; ++x)
    {
        for(int y = 0; y < resY; ++y)
	{
	    p = this->getPixel(x,y);
	    float w = p.w();
	    p *= sf;
	    p = Min(Max(p, Vec4f(0.0f)), Vec4f(1.0f));  	    
	    p.w() = w;
	    this->setPixel(p,x,y);
	}
    }    
}