/*
 * util/Texload.cc:
 *
 * Copyright (C) 2000 John Watson, jwatson@tempusmud.com
 * 
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 * 
 */

#include "Texload.h"

#include <iostream>
#include <algorithm>
#include <fstream>
#include <cstdio>
#include <cstdlib>
#include <png.h>
#include <math.h>


char * Texload::readPNMLine( ifstream & ifs, char * buf, int bufsize ) {
        while ( ifs.getline( buf, bufsize ) ) {
                
                //
                // skip whitespace
                //
                
                while ( *buf == ' ' || *buf == '\t' ) {
                        ++buf;
                }
                
                //
                // completely blank line...
                //

                if ( ! *buf ) {
                        continue;
                }

                //
                // skip comment line
                //

                if ( *buf == '#' ) {
                        continue;
                }

                return buf;
        }
        
        return 0;
                        
}

static void my_memcpy( unsigned char * dest, unsigned char * src, int num ) {
        for ( int i = 0; i < num ; ++i  ) {
                *(dest++) = *(src++);
        }
}

void Texload::applyGamma( unsigned char * pixels, float gamma, int w, int h, int components ) {
        int numpix = w * h * components;
        
        while ( numpix-- ) {
                float x = (float) *pixels / 255;
                int val = (int) ( (float) 255 * pow( x, 1.0 / gamma ) );
                val = min( 255, val );
                *pixels = val;
                ++pixels;
        }
}


void Texload::flipImage( GLubyte *pixels, int width, int height, int components ) {
        const int byte_width = width * components;
        GLubyte * tmprow = new GLubyte[ byte_width ];

        GLubyte * ptr[2] = { pixels, 
                             pixels + byte_width * height - byte_width };

        while ( ptr[0] < ptr[1] ) {
                my_memcpy( tmprow, ptr[0], byte_width );
                my_memcpy( ptr[0], ptr[1], byte_width );
                my_memcpy( ptr[1], tmprow, byte_width );
                ptr[0] += byte_width;
                ptr[1] -= byte_width;
        }

        delete[] tmprow;
}

GLubyte * Texload::loadPNM( const char *filename, int *width, int *height, PNM_t *pnmType, bool flip ) {
        GLubyte *pixels = 0;
        
        *pnmType = PNM_ERR;
        
        int w, h;
        int maxval = 0;

        static const int BUFSIZE = 64;

        char buf[ BUFSIZE ];
        char *ptr = 0;

        ifstream ifs( filename, ios::binary | ios::in );
        
        if ( !ifs ) {
                cout << " :: Texload::loadPNM() " << filename << " cannot be read." << endl;
                return 0;
        }

        //
        // get the 2-byte MAGIC NUMBER
        //
        
        if ( ( ptr = readPNMLine( ifs, buf, BUFSIZE ) ) ) {
                
                if ( ! strcmp( ptr, "P5" ) ) {
                        *pnmType = PNM_PGM;
                }
                else if ( ! strcmp( ptr, "P6" ) ) {
                        *pnmType = PNM_PPM;
                }
                else {
                        cout << " :: TexLoad::loadPNM() " << filename << " is not a PNM file.  magic=" << ptr << endl;
                        ifs.close();
                        return 0;
                }
        }
        else {
                cout << " :: Texload::loadPNM() " << filename << " could not load magic number line." << endl;
                return 0;
        }
        
        
        //
        // get the width/height
        //
        
        if ( ( ptr = readPNMLine( ifs, buf, BUFSIZE ) ) ) {
                char *sep = ptr;
                while ( *sep != ' ' && *sep != '\t' )
                        ++sep;

                while ( *sep == ' ' || *sep == '\t' ) {
                        *sep = 0;
                        ++sep;
                }
                
                w = atoi( ptr );
                h = atoi( sep );
                
        }
        else {
                cout << " :: Texload::loadPNM() " << filename << " could not geometry line." << endl;
                return 0;
        }
        
        
        //
        // get the pixel maxvalue
        //

        if ( *pnmType != PNM_PBM ) {
                if ( ( ptr = readPNMLine( ifs, buf, BUFSIZE ) ) ) {
                        maxval = atoi( ptr );
                }
        }
        
        if ( maxval <= 0 ) {
                cout << " :: Texload::loadPNM() " << filename << " maxval is " + maxval << endl;
                ifs.close();
                return 0;
        }
        
        int numpixels = w * h;
        int numbytes  = numpixels;
        switch ( *pnmType ) {
        case PNM_PPM:
                numbytes *= 3;
                break;
        case PNM_PBM:
                numbytes /= 8;
                break;
        default:
                break;
        }
        
        pixels = new GLubyte[ numbytes ];

        //        pixels = (GLubyte*) malloc( numbytes * sizeof( GLubyte ) );
        
        ifs.read( pixels, numbytes ); 

        int tot_num_read = ifs.gcount();
        
        if ( tot_num_read != numbytes ) {
                cout << " :: Texload::loadPNM() " << filename << 
                        " an error occured while loading pixels." << endl
                     << " ::                    " << tot_num_read << " read, " 
                     << numbytes << " expected." << endl;
                ifs.close();
                delete[] pixels;
                //                free( pixels );
                return 0;
        }

        
        if ( *pnmType != PNM_PBM && maxval < 255 ) {
                for ( int i = 0; i < numpixels; ++i ) {
                        pixels[i] = pixels[i] * 255 / maxval;
                }
        }
        
        cout << " :: Texload::loadPNM() loaded : " << filename << ", " << w << "x" << h << endl;

        *width = w;
        *height = h;
        
        if ( flip )
                flipImage( pixels, w, h, *pnmType == PNM_PPM ? 3 :1 );

        return pixels;       
}

GLubyte * Texload::loadPPM( const char *filename, int *width, int *height, bool flip ) {
        PNM_t pnmType;
        GLubyte * pixels = loadPNM( filename, width, height, & pnmType, flip );
        if ( pixels && pnmType != PNM_PPM ) {
                cout << " :: Texload::loadPPM() " << filename 
                     << " resulted in PNM type " << (int)pnmType << endl;
                delete[] pixels;
                //                free( pixels );
                return 0;
        }
        return pixels;
}

GLubyte * Texload::loadPGM( const char *filename, int *width, int *height, bool flip ) {
        PNM_t pnmType;
        GLubyte * pixels = loadPNM( filename, width, height, & pnmType, flip );
        if ( pixels && pnmType != PNM_PGM ) {
                cout << " :: Texload::loadPGM() " << filename 
                     << " resulted in PNM type " << (int)pnmType << endl;
                delete[] pixels;
                //                free( pixels );
                return 0;
        }
        return pixels;
}

/**
 * win32 hack from gltron
 */

static FILE * f = 0;

void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length ) {
        fread ( data, 1, length, f );
}

/**
 * code adapted from gltron 0.59
 */

GLubyte * Texload::loadPNG( const char *filename, int *width, int *height, int *components, bool flip ) {
        
        f = fopen( filename, "rb" );
        
        if( ! f ) {
                cout << " :: loadPNG can't open file " << filename << endl;
                return 0;
        }

        png_structp png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 );

        if ( !png_ptr ) {
                cout << " :: loadPNG upable to create png ptr." << endl;
                fclose( f );
                return 0;
        }

        png_infop info_ptr = png_create_info_struct(png_ptr);
        
        if ( !info_ptr ) {
                cout << " :: loadPNG upable to create info struct" << endl;
                png_destroy_read_struct( &png_ptr, (png_infopp) 0, (png_infopp) 0 );
                fclose( f );
                return 0;
        }
        
        //
        // gltron: I need to do this, otherwise it crashes on win32
        //
        
        png_set_read_fn( png_ptr, 0, user_read_data );

        png_read_info( png_ptr, info_ptr );

        int color_type;
        int bpc;                // bits per channel
        png_uint_32 x, y;       // image size

        png_get_IHDR( png_ptr, info_ptr, &x, &y, &bpc, &color_type, 0, 0, 0 );

        if ( color_type != PNG_COLOR_TYPE_RGB &&
             color_type != PNG_COLOR_TYPE_PALETTE &&
             color_type != PNG_COLOR_TYPE_RGB_ALPHA &&
             color_type == PNG_COLOR_TYPE_GRAY &&
             color_type == PNG_COLOR_TYPE_GRAY_ALPHA ) {
                png_destroy_read_struct(&png_ptr, &info_ptr, 0);
                cout << "wrong png_color_type: " << color_type << endl;
                fclose(f);
                return 0;
        }

        if ( color_type == PNG_COLOR_TYPE_GRAY ||
             color_type == PNG_COLOR_TYPE_GRAY_ALPHA ) {
                png_set_gray_to_rgb(png_ptr);
                if ( bpc < 8 )
                        png_set_expand( png_ptr );
                color_type = PNG_COLOR_TYPE_RGB;
        }

        else if ( color_type == PNG_COLOR_TYPE_PALETTE && bpc <= 8 ) 
                png_set_expand( png_ptr );
        else if ( color_type == PNG_COLOR_TYPE_RGB && bpc == 16 )
                png_set_strip_16( png_ptr );
        else if ( bpc != 8 ) {
                png_destroy_read_struct(&png_ptr, &info_ptr, 0);
                cout << "wrong bitdepth: " << bpc << endl;
                fclose(f);
                return 0;
        }

        int zsize = 0;            // num channels
        
        if ( color_type == PNG_COLOR_TYPE_RGB || color_type == PNG_COLOR_TYPE_PALETTE )
                zsize = 3;
        if ( color_type == PNG_COLOR_TYPE_RGB_ALPHA ) 
                zsize = 4;
        
        unsigned char * data = new unsigned char[ x * y * zsize ];
        //        unsigned char * data = (unsigned char *) malloc(  x * y * zsize );
        
        *width = x;
        *height = y;
  
        /* get pointers */
        png_byte **row_pointers = new png_byte * [ y ];
        //        png_byte *row_pointers[y];
        
        for ( unsigned int i = 0; i < y; i++ ) {
                if ( flip )
                        row_pointers[i] = data + (y - i - 1) * zsize * x;
                else
                        row_pointers[i] = data + (i * zsize * x);
        }
        
        png_read_image( png_ptr, row_pointers );
        png_destroy_read_struct( &png_ptr, &info_ptr, 0 );
        
        delete[] row_pointers;
        
        fclose(f);
        
        *components = zsize;
        return data;
        
}
