/*
The following code is based on:
http://www.iwr.uni-heidelberg.de/groups/ngg/CG200708/Txt/png.c

The original copyright notice:

 * png.c -- png texture loader
 * last modification: aug. 14, 2007
 *
 * Copyright (c) 2005-2007 David HENRY
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or
 * sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
 * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF
 * CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 * gcc -Wall -ansi -lGL -lGLU -lglut
 *    `libpng12-config --cflags --libs` png.c -o png

*/

#include <stdio.h>
#include <stdlib.h>
#include <png.h>
#include "readpng.h"

// Returns NULL if loading failed, or a pointer to
// a malloc()ed pixel buffer otherwise.
// The output parameters width, height and has_alpha_channel will be
// set apropriately in the latter case
GLubyte*
readPNGFile(const char *filename, int *width, int *height, int *has_alpha_channel)
{
    png_byte    magic[8];
    png_structp png_ptr;
    png_infop   info_ptr;
    png_uint_32 w, h;
    png_bytep   *row_pointers = NULL;
    GLubyte     *texels;
    int         bytes_per_pixel;

    int         bit_depth, color_type;
    FILE        *f;

    int i;

    f = fopen (filename, "rb");
    if (!f)
    {
        fprintf(stderr, "Could not open '%s'!\n", filename);
        return NULL;
    }

    // Read and check magic number
    fread(magic, 1, sizeof (magic), f);

    if (!png_check_sig(magic, sizeof(magic)))
    {
        fprintf(stderr, "Not a valid PNG file: '%s'\n", filename);
        fclose(f);
        return NULL;
    }

    // Create png read struct
    png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr)
    {
        fclose(f);
        return NULL;
    }

    // Create png info struct
    info_ptr = png_create_info_struct (png_ptr);
    if (!info_ptr)
    {
        fclose(f);
        png_destroy_read_struct (&png_ptr, NULL, NULL);
        return NULL;
    }

    if (setjmp (png_jmpbuf (png_ptr)))
    {
        fclose(f);
        png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

        if (row_pointers)
            free(row_pointers);

        // XXX free texels

        return NULL;
    }

    png_init_io(png_ptr, f);
    png_set_sig_bytes(png_ptr, sizeof (magic));

    // Read png info
    png_read_info(png_ptr, info_ptr);

    // Get some data from the header
    bit_depth = png_get_bit_depth(png_ptr, info_ptr);
    color_type = png_get_color_type(png_ptr, info_ptr);

    // Check if we can handle this image type

    if (bit_depth != 8)
    {
        // XXX proper cleanup
        return NULL;
    }

    if (color_type == PNG_COLOR_TYPE_RGB)
    {
        bytes_per_pixel = 3;
        *has_alpha_channel = 0;
    }
    else if (color_type == PNG_COLOR_TYPE_RGBA)
    {
        bytes_per_pixel = 4;
        *has_alpha_channel = 1;
    }
    else
    {
        return NULL;
    }

    png_get_IHDR (png_ptr, info_ptr, &w, &h, &bit_depth, &color_type, NULL, NULL, NULL);

    *width = w;
    *height = h;

    // Allocate memory for pixel data
    texels = (GLubyte*) malloc(w * h * bytes_per_pixel);

    // Allocate memory for the row pointers array
    row_pointers = (png_bytep*) malloc(sizeof(png_bytep) * h);

    // Set up the row pointers
    for (i = 0; i < h; ++i)
        row_pointers[i] = (png_bytep) (texels + (h - (i + 1)) * w * bytes_per_pixel);

    // Read pixel data using the row pointers
    png_read_image(png_ptr, row_pointers);

    // Finish decompression and release memory
    png_read_end(png_ptr, NULL);
    png_destroy_read_struct(&png_ptr, &info_ptr, NULL);

    // Cleanup
    free(row_pointers);
    fclose(f);

    return texels;
}
