#include <cstdio>   // for fread
#include "files.h"
#include "image_read.h"

// The file type is determined from the file extension.
enum FileType FileRead::get_file_type(const char *filename){
    // find the last '.' in the file name
    int dotposition=-1;
    for(int i=strlen(filename)-1;i>=0;--i){
        if(filename[i]=='.'){
            dotposition=i;
            break;
        }
    }
    // if '.' was not found, the whole string is considered extension
    char *ext=(char*)filename+dotposition+1; // file extension
    if((ext[0]=='j'||ext[0]=='J')&&
       (ext[1]=='p'||ext[1]=='P')&&
       ((strlen(ext)==3&&(ext[2]=='g'||ext[2]=='G'))||
        (strlen(ext)==4&&(ext[2]=='e'||ext[2]=='E')&&
                         (ext[3]=='g'||ext[3]=='G')))){
        return JPEG;
    }
    if((ext[0]=='p'||ext[0]=='P')&&
       (ext[1]=='n'||ext[1]=='N')&&
       (ext[2]=='g'||ext[2]=='G')){
        return PNG;
    }
    if((ext[0]=='p'||ext[0]=='P')&&
       (ext[1]=='n'||ext[1]=='b'||ext[1]=='g'||ext[1]=='p'||
        ext[1]=='N'||ext[1]=='B'||ext[1]=='G'||ext[1]=='P')&&
       (ext[2]=='m'||ext[2]=='M')){
        return PNM;
    }
    return UNKNOWN;
}

unsigned char *FileRead::read_image(const char *new_image,int *width,int *height){
#ifndef _MSC_VER
    // the progname is only used for reading the PNM format
    const char * const progname=(char*)(PROGNAME);
#endif
    switch(get_file_type(new_image)){
    case JPEG:
        fprintf(stderr,"input image has JPEG format\n");
        return jpgRead(new_image,width,height);
        break;
    case PNG:
        fprintf(stderr,"input image has PNG format\n");
        return pngRead(new_image,width,height);
        break;
#ifndef _MSC_VER
    case PNM:
        fprintf(stderr,"input image has PNM format\n");
        return pnmRead(progname,new_image,width,height);
        break;
#endif
    default: // UNKNOWN
        fprintf(stderr,"%s: unknown file format\n",new_image);
        exit(-1);
    }
    return NULL;
}

const char *FileRead::image_types="All images (*.png *.jpg *.jpeg *.pnm *.pbm *.pgm *.ppm);;PNG images (*.png);;JPG images (*.jpg *.jpeg);;PBM images (*.pnm *.pbm *.pgm *.ppm);;All files (*)";

unsigned char *FileRead::jpgRead(const char *texturePath,
                                 int *outImageWidth,
                                 int *outImageHeight){
    FILE *in_file;
    FOPEN_RO(in_file,texturePath)
    struct jpeg_decompress_struct cinfo;
    struct jpeg_error_mgr jerr;
    cinfo.err=jpeg_std_error(&jerr);
    JSAMPROW row_pointer[1];
    jpeg_create_decompress(&cinfo);
    jpeg_stdio_src(&cinfo,in_file);
    jpeg_read_header(&cinfo,0);
    jpeg_start_decompress(&cinfo);
    *outImageWidth=cinfo.output_width;
    *outImageHeight=cinfo.output_height;
    int depth=cinfo.num_components;
    int textureSize=(*outImageWidth)*(*outImageHeight)*depth;
    unsigned char *textureBytes=
            (unsigned char*)malloc(textureSize*sizeof(unsigned char));
    unsigned long scanline=*outImageHeight;
    row_pointer[0]=(unsigned char*)malloc((*outImageWidth)*
                                          depth*
                                          sizeof(unsigned char));
    while(cinfo.output_scanline<(unsigned)*outImageHeight){
        --scanline;
        jpeg_read_scanlines(&cinfo,row_pointer,1);
        for(int i=0;i<(*outImageWidth)*depth;++i){
            textureBytes[scanline*(*outImageWidth)*depth+i]=row_pointer[0][i];
        }
    }
    fclose(in_file);
    jpeg_finish_decompress(&cinfo);
    jpeg_destroy_decompress(&cinfo);
    return textureBytes;
}

unsigned char *FileRead::pngRead(const char *texturePath,
                                 int *outImageWidth,
                                 int *outImageHeight){
    FILE *in_file;
    FOPEN_RO(in_file,texturePath)
    // read the header of the file
    unsigned char header[8];
    if(fread(header,1,8,in_file)!=(unsigned long)ftell(in_file)){
        fprintf(stderr,"error reading %s\n",texturePath);
        exit(-1);
    }
    // check the file is valid
    if(png_sig_cmp(header,0,8)){
        fprintf(stderr,"%s is not a valid PNG file!\n",texturePath);
        exit(-1);
    }
    // initialize
    png_structp png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,
                                               NULL,
                                               NULL,
                                               NULL);
    if(!png_ptr){
        fprintf(stderr,"error reading %s\n",texturePath);
        exit(-1);
    }
    png_infop info_ptr=png_create_info_struct(png_ptr);
    if (!info_ptr){
        fprintf(stderr,"error reading %s\n",texturePath);
        png_destroy_read_struct(&png_ptr,(png_infopp)NULL,(png_infopp)NULL);
        exit(-1);
    }
    if(setjmp(png_jmpbuf(png_ptr))){
        fprintf(stderr,"error reading %s\n",texturePath);
        exit(-1);
    }
    png_init_io(png_ptr,in_file);
    png_set_sig_bytes(png_ptr,8);
    png_read_info(png_ptr,info_ptr);
    *outImageWidth=png_get_image_width(png_ptr,info_ptr);
    *outImageHeight=png_get_image_height(png_ptr,info_ptr);
    png_byte color_type=png_get_color_type(png_ptr,info_ptr);
    png_byte bit_depth=png_get_bit_depth(png_ptr,info_ptr);
    if(bit_depth==16)
        png_set_strip_16(png_ptr);
    if(bit_depth<8)
        png_set_expand_gray_1_2_4_to_8(png_ptr);
    if(color_type==PNG_COLOR_TYPE_GRAY||color_type==PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb(png_ptr);
    if(color_type==PNG_COLOR_TYPE_PALETTE){
        png_set_palette_to_rgb(png_ptr);
        // TODO: Read the transparency values for indexed images;
        // currently, they are ignored by stripping the alpha information.
        png_set_strip_alpha(png_ptr);
    }
    png_read_update_info(png_ptr,info_ptr);
    // allocate memory to store the texture
    unsigned char *textureBytes=
            (unsigned char*)malloc((*outImageWidth)*
                                   (*outImageHeight)*
                                   3* // RGB
                                   sizeof(unsigned char));
    // read the contents of the file
    if(setjmp(png_jmpbuf(png_ptr))){
        fprintf(stderr,"error reading %s\n",texturePath);
        exit(-1);
    }
    // TODO: we can avoid writing in row_pointers[] and later copying to
    // the texture.
    png_bytep *row_pointers=
            (png_bytep*)malloc(sizeof(png_bytep)*(*outImageHeight));
    for(int y=0;y<(*outImageHeight);++y)
        row_pointers[y]=(png_byte*)malloc(png_get_rowbytes(png_ptr,info_ptr));
    png_read_image(png_ptr,row_pointers);
    switch(color_type){
    case PNG_COLOR_TYPE_GRAY:
    case PNG_COLOR_TYPE_PALETTE:
    case PNG_COLOR_TYPE_RGB:
        // RGB: copy directly RGB bits to the texture.
        for(int row=0;row<(*outImageHeight);++row)
            for(int j=0;j<(*outImageWidth);++j)
                for(int k=0;k<3;++k)
                        textureBytes[row*(*outImageWidth)*3+j*3+k]=
                            row_pointers[(*outImageHeight)-row-1][j*3+k];
        break;
    case PNG_COLOR_TYPE_GRAY_ALPHA:
    case PNG_COLOR_TYPE_RGB_ALPHA:
        // RGBA: multiply each one of the three first bytes (channels R, G
        // and B), by the alpha (the fourth byte).
        for(int row=0;row<(*outImageHeight);++row)
            for(int j=0;j<(*outImageWidth);++j)
                for(int k=0;k<3;++k)
                        textureBytes[row*(*outImageWidth)*3+j*3+k]=
                            (double)
                            row_pointers[(*outImageHeight)-row-1][j*4+k]*
                            row_pointers[(*outImageHeight)-row-1][j*4+3]/
                            255;
        break;
    default:
        fprintf(stderr,"%s: unrecognized PNG type\n",texturePath);
        exit(-1);
    }
    png_read_end(png_ptr,NULL);
    png_destroy_read_struct(&png_ptr,&info_ptr,NULL);
    for(int row=0;row<(*outImageHeight);++row)
            free(row_pointers[row]);
    free(row_pointers);
    fclose(in_file);
    return textureBytes;
}

#ifndef _MSC_VER
unsigned char *FileRead::pnmRead(const char * const progname,
                                 const char *texturePath,
                                 int *outImageWidth,
                                 int *outImageHeight){
    struct pam inpam;
    pm_init(progname, 0);
    FILE *in_file;
    FOPEN_RO(in_file,texturePath)
#ifdef PAM_STRUCT_SIZE
    pnm_readpaminit(in_file,&inpam,PAM_STRUCT_SIZE(tuple_type));
#else
    pnm_readpaminit(in_file,&inpam,sizeof(struct pam));
#endif
    *outImageWidth=inpam.width;
    *outImageHeight=inpam.height;
    int textureSize=
            (*outImageWidth)*
            (*outImageHeight)*
            inpam.depth*
            inpam.bytes_per_sample;
    unsigned char *textureBytes=
            (unsigned char*)malloc(textureSize*sizeof(unsigned char));
    tuple *tuplerow=pnm_allocpamrow(&inpam);
    for(int row=0;row<*outImageHeight;row++) {
        int column;
        pnm_readpamrow(&inpam,tuplerow);
        for (column=0;column<*outImageWidth;++column) {
            unsigned int plane;
            for(plane=0;plane<inpam.depth;++plane) {
                textureBytes[((*outImageHeight)-row-1)*3*(*outImageWidth)+3*column+plane]=
                        tuplerow[column][plane];
            }
        }
    }
    pnm_freepamrow(tuplerow);
    pm_close(in_file);
    return textureBytes;
}
#endif
