/*  This file is part of ksquirrel-libs (http://ksquirrel.sf.net)

    Copyright (c) 2004 Dmitry Baryshev <ksquirrel@tut.by>

    This library is free software; you can redistribute it and/or
    modify it under the terms of the GNU Library General Public
    License as published by the Free Software Foundation;
    either version 2 of the License, or (at your option) any later
    version.

    This library 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
    Library General Public License for more details.

    You should have received a copy of the GNU Library General Public License
    as32 with this library; see the file COPYING.  If not, write to
    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
    Boston, MA 02110-1301, USA.
*/

#include <iostream>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "ksquirrel-libs-png/png.h"

#include "ksquirrel-libs/fmt_types.h"
#include "ksquirrel-libs/fileio.h"
#include "ksquirrel-libs/error.h"
#include "ksquirrel-libs/fmt_utils.h"

#include "fmt_codec_png_defs.h"
#include "fmt_codec_png.h"

#if defined CODEC_SVG || defined CODEC_DICOM
#include <sys/types.h>
#include <unistd.h>
#include <sys/wait.h>
#include <cstdio>
#endif

#ifdef CODEC_SVG
#include "../xpm/codec_svg.xpm"
#elif defined CODEC_DICOM
#include "../xpm/codec_dicom.xpm"
#else
#include "../xpm/codec_png.xpm"
#endif

/*
 *
 * PNG (pronounced "ping") is a bitmap file format used to transmit and
 * store bitmapped images.  PNG supports the capability of storing up to
 * 16 bits (gray-scale) or 48 bits (truecolor) per pixel, and up to 16 bits
 * of alpha data. It handles the progressive display
 * of image data and the storage of gamma,
 * transparency and textual information, and it uses an efficient and
 * lossless form of data compression.
 *
 */

inline bool MALLOC_ROWS(png_bytep **A, const int RB, const int H)
{
    *A = (png_bytep*)malloc(H * sizeof(png_bytep*));

    if(!*A)
        return false;

    for(s32 row = 0; row < H; row++)
        (*A)[row] = 0;

    for(s32 row = 0; row < (s32)H; row++)
    {
        (*A)[row] = (png_bytep)malloc(RB);

        if(!(*A)[row])
            return false;

        memset((*A)[row], 0, RB);
    }

    return true;
}

inline void FREE_ROWS(png_bytep **A, const int H)
{
    if(*A)
    {
        for(s32 i = 0;i < H;i++)
        {
            if((*A)[i])
                free((*A)[i]);
        }

        free(*A);
        *A = 0;
    }
}

fmt_codec::fmt_codec() : fmt_codec_base()
{}

fmt_codec::~fmt_codec()
{}

void fmt_codec::options(codec_options *o)
{
#ifdef CODEC_SVG
    o->version = "0.1.2";
    o->name = "Scalable Vector Graphics";
    o->filter = "*.svg *.svgz ";
    o->config = std::string(SVG_UI); // SVG_UI comes from Makefile.am
    o->mime = "";
    o->mimetype = "image/svg+xml";
    o->pixmap = codec_svg;
    o->readable = true;
    o->canbemultiple = false;
    o->writestatic = false;
    o->writeanimated = false;
    o->needtempfile = true;
#elif defined CODEC_DICOM
    o->version = "1.1.3";
    o->name = "DICOM";
    o->filter = "*.dcm ";
    o->config = "";
    o->mime = "";
    o->mimetype = "image/x-dicom";
    o->pixmap = codec_dicom;
    o->readable = true;
    o->canbemultiple = false;
    o->writestatic = false;
    o->writeanimated = false;
    o->needtempfile = true;
#else
    o->version = "1.1.3";
    o->name = "Portable Network Graphics";
    o->filter = "*.png ";
    o->config = "";
    o->mime = "\x0089\x0050\x004E\x0047\x000D\x000A\x001A\x000A";
    o->mimetype = "image/png";
    o->pixmap = codec_png;
    o->readable = true;
    o->canbemultiple = false;
    o->writestatic = true;
    o->writeanimated = false;
    o->needtempfile = false;
#endif
}

#ifdef CODEC_ANOTHER
void fmt_codec::fill_default_settings()
{
    settings_value val;

    // scale factor in percents
    val.type = settings_value::v_int;
    val.iVal = 1;

    m_settings["scale"] = val;
}
#endif

s32 fmt_codec::read_init(const std::string &file)
{
    png_ptr = 0;
    info_ptr = 0;
    fptr = 0;
    frame = 0;
    prev = 0;
    cur = 0;
    zerror = false;

#ifdef CODEC_SVG
    int status;

    fmt_settings::iterator it = m_settings.find("scale");

    // percents / 100
    int scale = (it == m_settings.end() || (*it).second.type != settings_value::v_int)
             ? 1 : (*it).second.iVal;

    if(scale < 1 || scale > 10)
        scale = 1;

    char z[32];
    snprintf(z, 32, "%d", scale);

    pid_t pid = fork();

    if(!pid)
    {
        execlp(SVG2PNG, SVG2PNG, "--binary", RSVG, "--input", file.c_str(), "--output", tmp.c_str(), "-z", z, (char *)0);
        exit(1);
    }
    else if(pid == -1)
        return SQE_R_BADFILE;

    ::waitpid(pid, &status, 0);

    if(WIFEXITED(status))
        if(WEXITSTATUS(status))
            return SQE_R_BADFILE;
        else;
    else
        return SQE_R_BADFILE;

    fptr = fopen(tmp.c_str(), "rb");

#elif defined CODEC_DICOM

    int status;

    pid_t pid = fork();

    if(!pid)
    {
        execlp(DICOM, DICOM, file.c_str(), tmp.c_str(), (char *)0);
        exit(1);
    }
    else if(pid == -1)
        return SQE_R_BADFILE;

    ::waitpid(pid, &status, 0);

    if(WIFEXITED(status))
        if(WEXITSTATUS(status))
            return SQE_R_BADFILE;
        else;
    else
        return SQE_R_BADFILE;

    fptr = fopen(tmp.c_str(), "rb");

#else
    fptr = fopen(file.c_str(), "rb");
#endif

    if(!fptr)
	return SQE_R_NOFILE;

    currentImage = -1;

    if((png_ptr = my_png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0)) == NULL)
    {
	zerror = true;
	return SQE_R_NOMEMORY;
    }

    if((info_ptr = my_png_create_info_struct(png_ptr)) == NULL)
    {
	zerror = true;
	return SQE_R_NOMEMORY;
    }

    if(setjmp(png_jmpbuf(png_ptr)))
    {
        zerror = true;
        return SQE_R_BADFILE;
    }

    my_png_init_io(png_ptr, fptr);
    my_png_read_info(png_ptr, info_ptr);
    my_png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type, &interlace_type, (int*)0, (int*)0);

    img.w = next_frame_width = width;
    img.h = next_frame_height = height;
    img.bpp = bit_depth;

    if(img.bpp == 16)
	my_png_set_strip_16(png_ptr);

    if(img.bpp < 8)
	my_png_set_packing(png_ptr);

    if(color_type == PNG_COLOR_TYPE_GRAY && img.bpp < 8)
	my_png_set_gray_1_2_4_to_8(png_ptr);

    if(color_type == PNG_COLOR_TYPE_PALETTE)
	my_png_set_palette_to_rgb(png_ptr);

    if(color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
	my_png_set_gray_to_rgb(png_ptr);

    if(my_png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
	my_png_set_tRNS_to_alpha(png_ptr);

    my_png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);

    number_passes = my_png_set_interlace_handling(png_ptr);

    my_png_read_update_info(png_ptr, info_ptr);

    finfo.animated = !!my_png_get_valid(png_ptr, info_ptr, PNG_INFO_acTL);

    frames = finfo.animated ? my_png_get_num_frames(png_ptr, info_ptr) : 1;

    if(!frames) return SQE_R_BADFILE;

    img.interlaced = number_passes > 1;
    img.passes = finfo.animated ? 1 : number_passes;

    if(finfo.animated)
    {
        if(!MALLOC_ROWS(&prev, width * sizeof(RGBA), height))
            return SQE_R_NOMEMORY;

        if(!MALLOC_ROWS(&cur, width * sizeof(RGBA), height))
            return SQE_R_NOMEMORY;
    }

    std::string color_;

    img.hasalpha = (color_type & PNG_COLOR_MASK_ALPHA);

    switch((color_type & ~PNG_COLOR_MASK_ALPHA))
    {
	case PNG_COLOR_TYPE_RGB:         color_ = "RGB";                  break;
	case PNG_COLOR_TYPE_PALETTE:     color_ = "Color indexed";        break;
	case PNG_COLOR_TYPE_GRAY:        color_ = "Grayscale";            break;

	default:
	    color_ = "Unknown";
    }

    if(img.hasalpha)
        color_ += " with ALPHA";

    img.compression = "Deflate method 8, 32K window";
    img.colorspace = color_;
    if(!finfo.animated) img.delay = 0;

#ifdef PNG_TEXT_SUPPORTED
    png_textp lines = info_ptr->text;

    if(!lines || !info_ptr->num_text)
        return SQE_OK;

    for(s32 i = 0;i < info_ptr->num_text;i++)
    {
	fmt_metaentry mt;

        mt.group = lines[i].key;
        mt.data = lines[i].text;

        addmeta(mt);
    }
#endif

    return SQE_OK;
}

s32 fmt_codec::read_next()
{
    currentImage++;

    if(currentImage == frames)
	return SQE_NOTOK;

    if(setjmp(png_jmpbuf(png_ptr)))
    {
        zerror = true;
        return SQE_R_BADFILE;
    }

    if(finfo.animated)
    {
        if(currentImage)
        {
            if(next_frame_dispose_op == PNG_DISPOSE_OP_BACKGROUND)
            {
                for(u32 j = next_frame_y_offset,i = 0;i < next_frame_height;j++,i++)
                    memset(cur[j]+next_frame_x_offset*sizeof(RGBA), 0, next_frame_width * sizeof(RGBA));
            }
            else if(next_frame_dispose_op == PNG_DISPOSE_OP_PREVIOUS)
            {
                for(u32 i = 0;i < height;i++)
                    memcpy(cur[i], prev[i], width*sizeof(RGBA));
            }
            else // next_frame_dispose_op == PNG_DISPOSE_OP_NONE
            {
            }

            for(u32 i = 0;i < height;i++)
                memcpy(prev[i], cur[i], width*sizeof(RGBA));
        }
        else if(my_png_get_first_frame_is_hidden(png_ptr, info_ptr))
        {
            if(!MALLOC_ROWS(&frame, width * sizeof(RGBA), height))
                return SQE_R_NOMEMORY;

            my_png_read_frame_head(png_ptr, info_ptr);
            my_png_read_image(png_ptr, frame);

            FREE_ROWS(&frame, height);

            frames--;

            if(frames == 1)
            {
                my_png_read_frame_head(png_ptr, info_ptr);
                finfo.animated = false;
                img.passes = number_passes;
                finfo.image.push_back(img);
                return SQE_OK;
            }
            else if(!frames)
                return SQE_R_BADFILE; // oops? 
        }

        FREE_ROWS(&frame, next_frame_height);

        my_png_read_frame_head(png_ptr, info_ptr);

        if(my_png_get_valid(png_ptr, info_ptr, PNG_INFO_fcTL))
        {
            my_png_get_next_frame_fcTL(png_ptr, info_ptr,
                                    &next_frame_width, &next_frame_height,
                                    &next_frame_x_offset, &next_frame_y_offset,
                                    &next_frame_delay_num, &next_frame_delay_den,
                                    &next_frame_dispose_op, &next_frame_blend_op);
        }
        else
        {
            next_frame_width = width;
            next_frame_height = height;
            next_frame_x_offset = 0;
            next_frame_y_offset = 0;
            next_frame_dispose_op = PNG_DISPOSE_OP_BACKGROUND;
            next_frame_blend_op   = PNG_BLEND_OP_SOURCE;
        }

        if(!next_frame_delay_den) next_frame_delay_den = 100;

        img.delay = (s32)(((double)next_frame_delay_num / next_frame_delay_den) * 1000);

        if(next_frame_width + next_frame_x_offset > width || next_frame_height + next_frame_y_offset > height)
            return SQE_R_BADFILE;

        if(!MALLOC_ROWS(&frame, next_frame_width * sizeof(RGBA), next_frame_height))
            return SQE_R_NOMEMORY;

        my_png_read_image(png_ptr, frame);

        // copy all pixel values including alpha
        if(!currentImage || next_frame_blend_op == PNG_BLEND_OP_SOURCE)
        {
            for(u32 j = next_frame_y_offset,i = 0;i < next_frame_height;j++,i++)
                memcpy(cur[j]+next_frame_x_offset*sizeof(RGBA), frame[i], next_frame_width * sizeof(RGBA));
        }
        else // over
        {
            RGBA *src, *dst;

            for(u32 j = next_frame_y_offset,i = 0;i < next_frame_height;j++,i++)
            {
                src = (RGBA *)frame[i];
                dst = (RGBA *)(cur[j]+next_frame_x_offset*sizeof(RGBA));
                u32 k = next_frame_width;

                while(k--)
                {
                    // fully transparent foreground
                    if(src->a == 0)
                        ;
                    else if(src->a == 255 || dst->a == 0)
                        *dst = *src;
                    else // composite
                    {
                        dst->r = ((src->a * (src->r - dst->r))>>8) + dst->r;
                        dst->g = ((src->a * (src->g - dst->g))>>8) + dst->g;
                        dst->b = ((src->a * (src->b - dst->b))>>8) + dst->b;
                        //dst->a = ((src->a * (src->a - dst->a))>>8) + dst->a;
                    }

                    src++;
                    dst++;
                }
            }
        }
    }

    finfo.image.push_back(img);

    return SQE_OK;
}

s32 fmt_codec::read_next_pass()
{
    line = -1;

    return SQE_OK;
}

s32 fmt_codec::read_scanline(RGBA *scan)
{
    fmt_image *im = image(currentImage);

    line++;

    if(zerror || setjmp(png_jmpbuf(png_ptr)))
    {
        zerror = true;
	return SQE_R_BADFILE;
    }

    if(finfo.animated)
        memcpy(scan, cur[line], im->w * sizeof(RGBA));
    else
        my_png_read_row(png_ptr, (png_bytep)scan, png_bytep_NULL);

    return SQE_OK;
}

void fmt_codec::read_close()
{
    if(png_ptr) my_png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);

    if(fptr) fclose(fptr);

    FREE_ROWS(&frame, next_frame_height);
    FREE_ROWS(&prev, height);
    FREE_ROWS(&cur, height);

    finfo.meta.clear();
    finfo.image.clear();
}

#ifdef CODEC_PNG

void fmt_codec::getwriteoptions(fmt_writeoptionsabs *opt)
{
    opt->interlaced = true;
    opt->compression_scheme = CompressionInternal;
    opt->compression_min = 1;
    opt->compression_max = 9;
    opt->compression_def = 7;
    opt->passes = 8;
    opt->needflip = false;
    opt->palette_flags = 0 | fmt_image::pure32;
}

s32 fmt_codec::write_init(const std::string &file, const fmt_image &image, const fmt_writeoptions &opt)
{
    m_png_ptr = 0;
    m_info_ptr = 0;
    m_fptr = 0;
    m_zerror = false;

    if(!image.w || !image.h || file.empty())
	return SQE_W_WRONGPARAMS;

    writeimage = image;
    writeopt = opt;

    m_fptr = fopen(file.c_str(), "wb");

    if(!m_fptr)
	return SQE_W_NOFILE;

    m_png_ptr = my_png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);

    if(!m_png_ptr)
    {
	m_zerror = true;
	return SQE_W_NOMEMORY;
    }

    m_info_ptr = my_png_create_info_struct(m_png_ptr);

    if(!m_info_ptr)
    {
	m_zerror = true;
	return SQE_W_NOMEMORY;
    }

    if(setjmp(png_jmpbuf(m_png_ptr)))
    {
        m_zerror = true;
	return SQE_W_ERROR;
    }

    my_png_init_io(m_png_ptr, m_fptr);

    my_png_set_IHDR(m_png_ptr, m_info_ptr, writeimage.w, writeimage.h, 8, PNG_COLOR_TYPE_RGB_ALPHA,
	((writeopt.interlaced) ? PNG_INTERLACE_ADAM7 : PNG_INTERLACE_NONE), 
	PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

    png_color_8 sig_bit;

    sig_bit.red = 8;
    sig_bit.green = 8;
    sig_bit.blue = 8;
    sig_bit.alpha = 8;
    
    my_png_set_sBIT(m_png_ptr, m_info_ptr, &sig_bit);

    s32 factor = (writeopt.compression_level < 1 || writeopt.compression_level > 9) ? 1 : writeopt.compression_level;

    my_png_set_compression_level(m_png_ptr, factor);

    my_png_write_info(m_png_ptr, m_info_ptr);

    my_png_set_shift(m_png_ptr, &sig_bit);

    return SQE_OK;
}

s32 fmt_codec::write_next()
{
    my_png_set_swap(m_png_ptr);

    my_png_set_packswap(m_png_ptr);

    my_png_set_interlace_handling(m_png_ptr);

    return SQE_OK;
}

s32 fmt_codec::write_next_pass()
{
    return SQE_OK;
}

s32 fmt_codec::write_scanline(RGBA *scan)
{
    if(m_zerror || setjmp(png_jmpbuf(m_png_ptr)))
    {
        m_zerror = true;
	return SQE_W_ERROR;
    }

    m_row_pointer = (png_bytep)scan;

    my_png_write_rows(m_png_ptr, &m_row_pointer, 1);

    return SQE_OK;
}

void fmt_codec::write_close()
{
    if(m_png_ptr && !m_zerror) my_png_write_end(m_png_ptr, m_info_ptr);
    if(m_png_ptr) my_png_destroy_write_struct(&m_png_ptr, &m_info_ptr);
    if(m_fptr)    fclose(m_fptr);
}

std::string fmt_codec::extension(const s32 /*bpp*/)
{
    return std::string("png");
}

#endif

#include "fmt_codec_cd_func.h"
