#include "image_texture.hpp"

#include <png.h>
#include <cmath>

namespace endofthejedi {

    bool ImageTexture::loadPng()
    {
        m_valid = false;

        //header for testing if it is a png
        png_byte header[8];

        //open file as binary
        FILE *fp = fopen(m_path.c_str(), "rb");
        if (!fp) {
            printf("[image_texture] warning: could not open png file to load: %s\n", m_path.c_str());
            return false;
        }

        //read the header
        fread(header, 1, 8, fp);

        //test if png
        int is_png = !png_sig_cmp(header, 0, 8);
        if (!is_png) {
            puts("warning: image loader: file is not PNG!");
            fclose(fp);
            return false;
        }

        //create png struct
        png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
        if (!png_ptr) {
            fclose(fp);
            return false;
        }

        //create png info struct
        png_infop info_ptr = png_create_info_struct(png_ptr);
        if (!info_ptr) {
            png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
            fclose(fp);
            return false;
        }

        //create png info struct
        png_infop end_info = png_create_info_struct(png_ptr);
        if (!end_info) {
            png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
            fclose(fp);
            return false;
        }

        //png error stuff, not sure libpng man suggests this.
        if (setjmp(png_jmpbuf(png_ptr))) {
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            fclose(fp);
            return false;
        }

        //init png reading
        png_init_io(png_ptr, fp);

        //let libpng know you already read the first 8 bytes
        png_set_sig_bytes(png_ptr, 8);

        // read all the info up to the image data
        png_read_info(png_ptr, info_ptr);

        //variables to pass to get info
        int bit_depth, color_type;
        png_uint_32 twidth, theight;

        // get info about png
        png_get_IHDR(png_ptr, info_ptr, &twidth, &theight, &bit_depth, &color_type,
                NULL, NULL, NULL);


        //if (color_type == PNG_COLOR_TYPE_RGB) {
        //    puts("PNG_COLOR_TYPE_RGB");
        //} else if (color_type == PNG_COLOR_TYPE_RGBA) {
        //    puts("PNG_COLOR_TYPE_RGBA");
        //}

        //update width and height based on png info

        int pot_width  = (int) pow(2, ceilf(log2f(twidth)));
        int pot_height = (int) pow(2, ceilf(log2f(theight)));

        m_uvScale = glm::vec2(
                twidth  / (float) pot_width,
                theight / (float) pot_height);

        m_size = glm::vec2();

        //puts("#########################################");
        //printf("# PNG: width=%d, height=%d\n", twidth, theight);
        //printf("# powers of two: w=%d, h=%d\n", pot_width, pot_height);
        //printf("# uv scale two: x=%f, y=%f\n", m_uvScale.x, m_uvScale.y);
        //puts("#########################################");

        // Update the png info struct.
        png_read_update_info(png_ptr, info_ptr);

        // Row size in bytes.
        int rowbytes = png_get_rowbytes(png_ptr, info_ptr);

        int valuesPerColor;
        switch(color_type) {
            case PNG_COLOR_TYPE_RGB:  valuesPerColor = 3; break;
            case PNG_COLOR_TYPE_RGBA: valuesPerColor = 4; break;
            default:
                printf("error: could not parse png color that is not RGB or RGBA\n");
                png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
                fclose(fp);
                return false;
        }

        int pot_rowbytes = valuesPerColor * (int) pow(2, ceilf(log2f(rowbytes/valuesPerColor)));
        /*printf("rowbytes: %d, pot_rowbytes=%d\n", rowbytes, pot_rowbytes);*/

        // Allocate the image_data as a big block, to be given to opengl
        png_byte *image_data = (png_byte *) calloc(pot_rowbytes * pot_height, sizeof(png_byte));
        if (!image_data) {
            //clean up memory and close stuff
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            fclose(fp);
            return false;
        }

        //row_pointers is for pointing to image_data for reading the png with libpng
        png_bytep *row_pointers = (png_bytep *) malloc(sizeof(png_bytep) * theight);
        if (!row_pointers) {
            //clean up memory and close stuff
            png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
            free(image_data);
            fclose(fp);
            return false;
        }

        // set the individual row_pointers to point at the correct offsets of image_data
        for (uint32_t i = 0; i < theight; ++i) {
            row_pointers[theight- 1 - i] = image_data + i * pot_rowbytes;
        }

        //read the png into image_data through row_pointers
        png_read_image(png_ptr, row_pointers);

        //Now generate the OpenGL texture object
        glGenTextures(1, &m_texture_id);
        glBindTexture(GL_TEXTURE_2D, m_texture_id);
        glTexImage2D(GL_TEXTURE_2D,
                0,
                GL_RGB,
                pot_width,
                pot_height,
                0,
                GL_RGB,
                GL_UNSIGNED_BYTE,
                (GLvoid*) image_data);

        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);

        // TODO: make selectable via switch!
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
        glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

        //clean up memory and close stuff
        png_destroy_read_struct(&png_ptr, &info_ptr, &end_info);
        free(image_data);
        free(row_pointers);
        fclose(fp);

        m_valid = true;

        return true;
    }
}

