/*
 *  Copyright © 2006-2013 SplinterGU (Fenix/Bennugd)
 *  Copyright © 2002-2006 Fenix Team (Fenix)
 *  Copyright © 1999-2002 José Luis Cebrián Pagüe (Fenix)
 *
 *  This file is part of Bennu - Game Development
 *
 *  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.
 *
 */

/* --------------------------------------------------------------------------- */

#include <png.h>

#include "mod_map.h"

/* --------------------------------------------------------------------------- */

static void user_read_data( png_structp png_ptr, png_bytep data, png_size_t length )
{
    file * png = (file *)png_get_io_ptr( png_ptr );
    file_read( png, data, length ) ;
}

GRAPH * gr_read_png( const char * filename )
{
    GRAPH * bitmap ;
    unsigned int n, x ;
    uint16_t * ptr ;
    uint32_t * ptr32 ;
    uint32_t * orig ;
    uint32_t * row ;
    uint32_t Rshift, Gshift, Bshift ;
    uint32_t Rmask, Gmask, Bmask ;

    png_bytep * rowpointers ;

    png_structp png_ptr ;
    png_infop info_ptr, end_info ;
    png_uint_32 width, height, rowbytes;
    int depth, color ;

    /* Abre el fichero y se asegura de que screen está inicializada */

    file * png = file_open( filename, "rb" ) ;
    if ( !png ) return NULL;

    /* Prepara las estructuras internas */

    png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ) ;
    if ( !png_ptr )
    {
        file_close( png );
        return NULL;
    }

    info_ptr = png_create_info_struct( png_ptr ) ;
    end_info = png_create_info_struct( png_ptr ) ;
    if ( !info_ptr || !end_info )
    {
        png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
        file_close( png ) ;
        return NULL;
    }

    /* Rutina de error */

#if (PNG_LIBPNG_VER>=10500)
    if ( setjmp( png_jmpbuf( png_ptr ) ) )
#else
    if ( setjmp( png_ptr->jmpbuf ) )
#endif
    {
        png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
        file_close( png ) ;
        return NULL;
    }

    /* Recupera información sobre el PNG */

    png_set_read_fn( png_ptr, png, user_read_data ) ;
    png_read_info( png_ptr, info_ptr ) ;
    png_get_IHDR( png_ptr, info_ptr, &width, &height, &depth, &color, 0, 0, 0 ) ;

    row = malloc( sizeof( uint32_t ) * width );
    if ( !row )
    {
        png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
        file_close( png ) ;
        return NULL;
    }

    rowpointers = malloc( sizeof( png_bytep ) * height );
    if ( !rowpointers )
    {
        png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
        free( row ) ;
        file_close( png ) ;
        return NULL;
    }

    /* Configura los distintos modos disponibles */
/*
    if ( ( color == PNG_COLOR_TYPE_GRAY && depth == 1 ) || color == PNG_COLOR_TYPE_GRAY_ALPHA )
    {
        png_set_gray_to_rgb( png_ptr );
        if ( color == PNG_COLOR_TYPE_GRAY ) png_set_filler( png_ptr, 0xFF, PNG_FILLER_AFTER ) ;
    }
*/
    if ( depth == 16 ) png_set_strip_16( png_ptr ) ;

    if ( color == PNG_COLOR_TYPE_RGB ) png_set_filler( png_ptr, 0xFF, PNG_FILLER_AFTER ) ;

    png_set_bgr( png_ptr ) ;

    /* Recupera el fichero, convirtiendo a 16 bits si es preciso */

    rowbytes = png_get_rowbytes( png_ptr, info_ptr ) ;
    bitmap = bitmap_new( 0, width, height, ( color == PNG_COLOR_TYPE_GRAY ) ? depth : ( color == PNG_COLOR_TYPE_PALETTE ) ? 8 : ( sys_pixel_format->depth == 16 ? 16 : 32 ) ) ;
    if ( !bitmap )
    {
        png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
        free( rowpointers ) ;
        free( row ) ;
        file_close( png ) ;
        return NULL;
    }

    if ( color == PNG_COLOR_TYPE_GRAY )
    {
        for ( n = 0 ; n < height ; n++ ) rowpointers[n] = (( uint8_t* )bitmap->data ) + n * bitmap->pitch ;
        png_read_image( png_ptr, rowpointers ) ;

        if ( depth == 8 )
        {
            uint8_t colors[256 * 3];
            uint8_t * p = colors;

            for ( n = 0; n < 256 ; n++ )
            {
                * p++ = n;
                * p++ = n;
                * p++ = n;
            }

            bitmap->format->palette = pal_new_rgb(( uint8_t * )colors );
            pal_refresh( bitmap->format->palette );
        }

//        png_read_rows( png_ptr, rowpointers, 0, height ) ;
    }
    else if ( color == PNG_COLOR_TYPE_PALETTE )
    {
        /* Read the color palette */

        png_colorp png_palette = ( png_colorp ) png_malloc( png_ptr, 256 * sizeof( png_color ) ) ;
        if ( !png_palette )
        {
            bitmap_destroy( bitmap );
            png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;
            free( rowpointers ) ;
            free( row ) ;
            file_close( png ) ;
            return NULL;
        }
        png_get_PLTE( png_ptr, info_ptr, &png_palette, ( int * ) &n ) ;

        uint8_t colors[256 * 3];
        uint8_t * p = colors;

        for ( n = 0; n < 256 ; n++ )
        {
            * p++ = png_palette[n].red;
            * p++ = png_palette[n].green;
            * p++ = png_palette[n].blue;
        }

        bitmap->format->palette = pal_new_rgb(( uint8_t * )colors );
        pal_refresh( bitmap->format->palette );

        if ( !sys_pixel_format->palette )
        {
            sys_pixel_format->palette = pal_new( bitmap->format->palette );
/*            pal_use( bitmap->format->palette ); */
            palette_changed = 1 ;
        }

        for ( n = 0 ; n < height ; n++ ) rowpointers[n] = (( uint8_t* )bitmap->data ) + n * bitmap->pitch ;
        png_read_image( png_ptr, rowpointers ) ;

        /* If the depth is less than 8, expand the pixel values */

        if ( depth == 4 )
        {
            for ( n =  0; n < height; n++ )
            {
                char * orig, * dest;
                orig = ( char *  ) ( rowpointers[n] );
                dest = orig + width - 1;
                orig += ( width - 1 ) / 2;

                for ( x = width; x--; )
                {
                    *dest-- = ( *orig >> ((( 1 - ( x & 0x01 ) ) << 2 ) ) ) & 0x0F ;
                    if ( !( x & 0x01 ) ) orig--;
                }
            }
        }
        else if ( depth == 2 )
        {
            for ( n = 0; n < height; n++ )
            {
                char * orig, * dest;
                orig = ( char * ) rowpointers[n];
                dest = orig + width - 1;
                orig += ( width - 1 ) / 4;

                for ( x = width; x--; )
                {
                    *dest-- = ( *orig >> ((( 3 - ( x & 0x03 ) ) << 1 ) ) ) & 0x03 ;
                    if ( !( x & 0x03 ) ) orig--;
                }
            }
        }
        else if ( depth == 1 )
        {
            for ( n = 0; n < height; n++ )
            {
                char * orig, * dest;
                orig = ( char * ) rowpointers[n];
                dest = orig + width - 1;
                orig += ( width - 1 ) / 8;

                for ( x = width; x--; )
                {
                    *dest-- = ( *orig >> ( 7 - ( x & 0x07 ) ) ) & 0x01 ;
                    if ( !( x & 0x07 ) ) orig--;
                }
            }
        }
    }
    else if ( depth == 8 && sys_pixel_format->depth != 16 )
    {
#if (PNG_LIBPNG_VER>=10500)
        png_color_16p trans_color = 0;
        png_get_tRNS( png_ptr, info_ptr, 0, 0, &trans_color);
#endif

        for ( n = 0 ; n < height ; n++ )
        {
            rowpointers[0] = ( void * )row ;
            png_read_rows( png_ptr, rowpointers, 0, 1 ) ;

            ptr32 = ( uint32_t* )((( uint8_t * )bitmap->data ) + n * bitmap->pitch );
            orig = row ;
            for ( x = 0 ; x < width ; x++ )
            {
                ARRANGE_DWORD( orig );
                *ptr32 = *orig ;

                /* DCelso */
#if (PNG_LIBPNG_VER>=10500)
                if (( color == PNG_COLOR_TYPE_RGB ) && ( png_get_bit_depth(png_ptr, info_ptr) == 24 ) && ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ))
#else
                if (( color == PNG_COLOR_TYPE_RGB ) && ( info_ptr->pixel_depth == 24 ) && ( info_ptr->valid & PNG_INFO_tRNS ))
#endif
                {
                	uint8_t * ptr8 = (uint8_t *)orig;
        			if (
#if (PNG_LIBPNG_VER>=10500)
        			    ( ptr8[0] == trans_color->red   ) &&
        				( ptr8[1] == trans_color->green ) &&
        				( ptr8[2] == trans_color->blue  )
#else
        			    ( ptr8[0] == info_ptr->trans_values.red   ) &&
        				( ptr8[1] == info_ptr->trans_values.green ) &&
        				( ptr8[2] == info_ptr->trans_values.blue  )
#endif
        			   )
        				*ptr32 = 0;
                }
                ptr32++, orig++ ;
            }
        }
    }
    else
    {
#if (PNG_LIBPNG_VER>=10500)
        png_color_16p trans_color = 0;
        png_get_tRNS( png_ptr, info_ptr, 0, 0, &trans_color);
#endif

        Rshift = 8;
        Gshift = 5;
        Bshift = 3;

        Rmask = 0xF80000 ; // 3
        Gmask = 0x00FC00 ; // 2
        Bmask = 0x0000F8 ; // 3

        for ( n = 0 ; n < height ; n++ )
        {
            rowpointers[0] = ( void * )row ;
            png_read_rows( png_ptr, rowpointers, 0, 1 ) ;

            ptr = ( uint16_t* )((( uint8_t * )bitmap->data ) + n * bitmap->pitch );
            orig = row ;
            for ( x = 0 ; x < width ; x++ )
            {
                ARRANGE_DWORD( orig );

                if (( *orig ) & 0x80000000 )
                {
                    *ptr = (( *orig & Rmask ) >> Rshift ) | (( *orig & Gmask ) >> Gshift ) | (( *orig & Bmask ) >> Bshift )  ;
                    if ( !*ptr )( *ptr )++ ;
                }
                else
                    *ptr = 0 ;

                /* DCelso */
#if (PNG_LIBPNG_VER>=10500)
                if (( color == PNG_COLOR_TYPE_RGB ) && ( png_get_bit_depth(png_ptr, info_ptr) == 24 ) && ( png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS) ))
#else
                if (( color == PNG_COLOR_TYPE_RGB ) && ( info_ptr->pixel_depth == 24 ) && ( info_ptr->valid & PNG_INFO_tRNS ))
#endif
                {
                	uint8_t * ptr8 = (uint8_t *)orig;
        			if (
#if (PNG_LIBPNG_VER>=10500)
        			    ( ptr8[0] == trans_color->red   ) &&
        				( ptr8[1] == trans_color->green ) &&
        				( ptr8[2] == trans_color->blue  )
#else
        			    ( ptr8[0] == info_ptr->trans_values.red   ) &&
        				( ptr8[1] == info_ptr->trans_values.green ) &&
        				( ptr8[2] == info_ptr->trans_values.blue  )
#endif
        			   )
        				*ptr = 0;
                }
                ptr++, orig++ ;
            }
        }
    }

    /* Fin */

#if (PNG_LIBPNG_VER>=10500)
    if ( !setjmp( png_jmpbuf( png_ptr ) ) )
#else
    if ( !setjmp( png_ptr->jmpbuf ) )
#endif
        png_read_end( png_ptr, 0 ) ;

    bitmap->modified = 1 ;

    png_destroy_read_struct( &png_ptr, &info_ptr, &end_info ) ;

    free( rowpointers ) ;
    free( row ) ;
    file_close( png ) ;

    return bitmap ;
}

/*
 *  FUNCTION : gr_save_png
 *
 *  Save a GRAPH into a PNG file
 *
 *  PARAMS :
 *      gr              GRAPH with the image to save
 *      filename        name for the file
 *
 *  RETURN VALUE :
 *      0 - FAILURE
 *      1 - SUCCESS
 *
 */

int gr_save_png( GRAPH * gr, const char * filename )
{
    if ( !gr ) return( 0 ) ;

    FILE * file = fopen( filename, "wb" ) ;
    png_structp png_ptr ;
    png_infop info_ptr ;
    int k, i ;
    png_bytep * rowpointers ;
    png_colorp pal ;
    uint32_t * data, * ptr ;
    uint16_t * orig ;
    uint32_t * orig32 ;
    rgb_component * gpal = NULL;

    if ( !file ) return( 0 ) ;

    rowpointers = malloc( sizeof( png_bytep ) * gr->height );
    if ( !rowpointers )
    {
        fclose( file ) ;
        return 0 ;
    }

    png_ptr  = png_create_write_struct( PNG_LIBPNG_VER_STRING, 0, 0, 0 ) ;
    if ( !png_ptr )
    {
        free( rowpointers ) ;
        fclose( file ) ;
        return( 0 ) ;
    }

    info_ptr = png_create_info_struct( png_ptr ) ;
    if ( !info_ptr )
    {
        png_destroy_write_struct( &png_ptr, NULL ) ;
        free( rowpointers ) ;
        fclose( file ) ;
        return( 0 ) ;
    }

    /* Error handling... */

#if (PNG_LIBPNG_VER>=10500)
    if ( setjmp( png_jmpbuf( png_ptr ) ) )
#else
    if ( setjmp( png_ptr->jmpbuf ) )
#endif
    {
        png_destroy_write_struct( &png_ptr, NULL ) ;
        free( rowpointers ) ;
        fclose( file ) ;
        return( 0 ) ;
    }

    png_init_io( png_ptr, file ) ;

    if ( gr->format->depth == 1 )
    {
        png_set_IHDR( png_ptr, info_ptr, gr->width,
                gr->height, 1, PNG_COLOR_TYPE_GRAY,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_BASE ) ;

        png_write_info( png_ptr, info_ptr ) ;

        for ( k = 0 ; k < ( unsigned )gr->height ; k++ )
            rowpointers[k] = ( uint8_t * )gr->data + gr->pitch * k ;

        png_write_image( png_ptr, rowpointers ) ;
    }
    else
    if ( gr->format->depth == 8 )
    {
        /* 8 bits PNG file */
        png_set_IHDR( png_ptr, info_ptr, gr->width,
                gr->height, 8, PNG_COLOR_TYPE_PALETTE,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_BASE ) ;


#if (PNG_LIBPNG_VER>=10500)
        if (!( gr->info_flags & GI_NOCOLORKEY ))
        {
            /* this need test */
            png_color_16 trans_color;
            png_byte trans = 1;

            trans_color.red = 0;
            trans_color.green = 0;
            trans_color.blue = 0;
            trans_color.gray = 0;

            png_set_tRNS( png_ptr, info_ptr, &trans, 1, &trans_color );
        }
#else
        /* DCelso */
        if (!( gr->info_flags & GI_NOCOLORKEY ))
        {
            uint8_t trans = 1;
			info_ptr->num_trans = 1;
			info_ptr->trans = &trans;
			info_ptr->valid = info_ptr->valid | PNG_INFO_tRNS;
        }
        /* DCelso */
#endif
        pal = ( png_colorp ) png_malloc( png_ptr, 256 * sizeof( png_color ) ) ;
        if ( !pal )
        {
            png_destroy_write_struct( &png_ptr, NULL ) ;
            free( rowpointers ) ;
            fclose( file ) ;
            return( 0 ) ;
        }

        if ( gr->format->palette )
            gpal = gr->format->palette->rgb;
        else if ( sys_pixel_format->palette )
            gpal = sys_pixel_format->palette->rgb;
        else
            gpal = ( rgb_component * )default_palette;

        /* Generate palette info */
        for ( k = 0 ; k < 256 ; k++ )
        {
            pal[k].red   = gpal[k].r ;
            pal[k].green = gpal[k].g ;
            pal[k].blue  = gpal[k].b ;
        }
        png_set_PLTE( png_ptr, info_ptr, pal, 256 ) ;
        png_write_info( png_ptr, info_ptr ) ;

        /* no need to rearrange data */
        for ( k = 0 ; k < ( unsigned )gr->height ; k++ )
            rowpointers[k] = ( uint8_t * )gr->data + gr->pitch * k ;
        png_write_image( png_ptr, rowpointers ) ;

        /* Free allocated palette... */
        png_free( png_ptr, ( png_voidp ) pal ) ;
//        info_ptr->palette = NULL ;
    }
    else
    {
        png_set_IHDR( png_ptr, info_ptr, gr->width,
                gr->height, 8, PNG_COLOR_TYPE_RGB_ALPHA,
                PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_BASE,
                PNG_FILTER_TYPE_BASE ) ;

        png_write_info( png_ptr, info_ptr ) ;

        data = malloc( gr->width * gr->height * 4 ) ;
        if ( !data )
        {
            png_destroy_write_struct( &png_ptr, NULL ) ;
            free( rowpointers ) ;
            fclose( file ) ;
            return( 0 ) ;
        }

        if ( gr->format->depth == 16 )
        {
            for ( k = 0; k < ( unsigned )gr->height; k++ )
            {
                ptr  = data + gr->width * k ; /* uses dword for each pixel! */
                orig = ( uint16_t * )( gr->data + gr->pitch * k ) ;
                rowpointers[k] = ( uint8_t * )ptr ;
                for ( i = 0 ; i < ( unsigned )gr->width ; i++ )
                {
                    if ( *orig == 0 && !( gr->info_flags & GI_NOCOLORKEY ) )
                        *ptr = 0x00000000 ;
                    else
                    {
                        *ptr =
                            (( *orig & 0xf800 ) >> 8 ) |
                            (( *orig & 0x07e0 ) << 5 ) |
                            (( *orig & 0x001f ) << 19 ) |
                            0xFF000000 ;
                        /* Rearrange data */
                        ARRANGE_DWORD( ptr ) ;
                    }
                    orig++ ;
                    ptr++  ;
                }
            }
        }
        else if ( gr->format->depth == 32 )
        {
            for ( k = 0; k < ( unsigned )gr->height; k++ )
            {
                ptr  = data + gr->width * k ; /* uses dword for each pixel! */
                orig32 = ( uint32_t * )( gr->data + gr->pitch * k ) ;
                rowpointers[k] = ( uint8_t * )ptr ;
                for ( i = 0 ; i < ( unsigned )gr->width ; i++ )
                {
                    *ptr =
                        (( *orig32 & 0xff000000 ) ) |
                        (( *orig32 & 0x00ff0000 ) >> 16 ) |
                        (( *orig32 & 0x0000ff00 ) ) |
                        (( *orig32 & 0x000000ff ) << 16 ) ;

                    /* Rearrange data */
                    ARRANGE_DWORD( ptr ) ;

                    orig32++ ;
                    ptr++  ;
                }
            }
        }
        png_write_image( png_ptr, rowpointers ) ;
        free( data ) ;
    }

    png_write_end( png_ptr, info_ptr ) ;
    png_destroy_write_struct( &png_ptr, NULL ) ;
    free( rowpointers ) ;
    fclose( file ) ;
    return ( 1 ) ;
}

/* --------------------------------------------------------------------------- */

int gr_load_png( const char * mapname )
{
    GRAPH * gr = gr_read_png( mapname ) ;
    if ( !gr ) return 0 ;
    gr->code = bitmap_next_code() ;
    grlib_add_map( 0, gr ) ;
    return gr->code ;
}

/* --------------------------------------------------------------------------- */
