/* create.c - Create icon and cursor files from PNG images
 *
 * Copyright (C) 1998 Oskar Liljeblad
 *
 * 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 3 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, see <http://www.gnu.org/licenses/>.
 */

#include <config.h>
#include <assert.h>		/* C89 */
#if HAVE_PNG_H
# include <png.h>
#else
# if HAVE_LIBPNG_PNG_H
#  include <libpng/png.h>
# else
#  if HAVE_LIBPNG10_PNG_H
#   include <libpng10/png.h>
#  else
#   if HAVE_LIBPNG12_PNG_H
#    include <libpng12/png.h>
#   endif
#  endif
# endif
#endif
#include <stdint.h>		/* Gnulib/POSIX */
#include <stdio.h>		/* C89 */
#include <stdbool.h>		/* Gnulib/POSIX */
#include <stdlib.h>		/* C89 */
#include "gettext.h"		/* Gnulib */
#include "xalloc.h"		/* Gnulib */
#include "minmax.h"		/* Gnulib */
#define _(s) gettext(s)
#define N_(s) gettext_noop(s)
#include "common/io-utils.h"
#include "common/error.h"
#include "icotool.h"
#include "win32.h"
#include "win32-endian.h"

#define ROW_BYTES(bits) ((((bits) + 31) >> 5) << 2)

static void simple_setvec(uint8_t *data, uint32_t ofs, uint8_t size, uint32_t value);

static bool
xfread(void *ptr, size_t size, FILE *stream)
{
    if (fread(ptr, size, 1, stream) < 1) {
        if (ferror(stream))
            warn_errno(_("cannot read file"));
        else
            warn(_("premature end"));
        return false;
    }
    return true;
}

bool
create_icon(InputFiles *files, CreateNameGen outfile_gen, bool icon_mode, int32_t alpha_threshold)
{
    struct {
        FILE *in;
        png_structp png_ptr;
        png_infop info_ptr;
        uint32_t bit_count;
        uint32_t palette_count;
        uint32_t image_size;
        uint32_t mask_size;
        uint32_t width;
        uint32_t height;
        uint8_t *image_data;
        uint8_t **row_datas;
        Palette *palette;
        bool store_raw;
        int32_t hotspot_x;
        int32_t hotspot_y;
    } *img;

    Win32CursorIconFileDir dir;
    FILE *out;
    char *outname = NULL;
    size_t c;
    uint32_t d, x;
    uint32_t dib_start;
    png_byte ct;

    img = xzalloc(files->count * sizeof(*img));

    for (c = 0; c < files->count; c++) {
        char header[8];
        uint32_t row_bytes;
        uint8_t transparency[256];
        uint16_t transparency_count;
        bool need_transparency;
        const char* real_filev;
        int32_t bit_count = -1;

        real_filev = files->files[c].name;
        if(!files->files[c].is_raw) bit_count = files->files[c].bit_count;
        img[c].hotspot_x = files->files[c].hotspot_x;
        img[c].hotspot_y = files->files[c].hotspot_y;

        img[c].store_raw = files->files[c].is_raw;
        set_message_header(real_filev);

        img[c].in = fopen(real_filev, "rb");
        if (img[c].in == NULL) {
            warn_errno(_("cannot open file"));
            goto cleanup;
        }
        if (!xfread(header, 8, img[c].in))
            goto cleanup;
        if (png_sig_cmp((png_bytep)header, 0, 8)) {
        
            warn(_("not a png file"));
            goto cleanup;
        }

        img[c].png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL /*user_error_fn, user_warning_fn*/);
        if (img[c].png_ptr == NULL) {
            warn(_("cannot initialize PNG library"));
            goto cleanup;
        }
        img[c].info_ptr = png_create_info_struct(img[c].png_ptr);
        if (img[c].info_ptr == NULL) {
            warn(_("cannot create PNG info structure - out of memory"));
            goto cleanup;
        }

        png_init_io(img[c].png_ptr, img[c].in);
        png_set_sig_bytes(img[c].png_ptr, 8);
        png_set_strip_16(img[c].png_ptr);
        png_set_expand(img[c].png_ptr);
        png_set_gray_to_rgb(img[c].png_ptr);
        png_set_interlace_handling(img[c].png_ptr);
        png_set_filler(img[c].png_ptr, 0xFF, PNG_FILLER_AFTER);
        png_read_info(img[c].png_ptr, img[c].info_ptr);
        png_read_update_info(img[c].png_ptr, img[c].info_ptr);

        img[c].width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
        img[c].height = png_get_image_height(img[c].png_ptr, img[c].info_ptr);
        
        if (img[c].store_raw)
        {
            ct = png_get_color_type(img[c].png_ptr, img[c].info_ptr);
            if (ct & PNG_COLOR_MASK_PALETTE)
            {
                img[c].bit_count = png_get_bit_depth(img[c].png_ptr, img[c].info_ptr);
            }
            else
                img[c].bit_count = png_get_bit_depth(img[c].png_ptr, img[c].info_ptr)
                    * png_get_channels(img[c].png_ptr, img[c].info_ptr);
            png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
            
            fseek(img[c].in, 0, SEEK_END);
            img[c].image_size = ftell(img[c].in);
            fseek(img[c].in, 0, SEEK_SET);
            img[c].image_data = xmalloc(img[c].image_size);
                if (!xfread(img[c].image_data, img[c].image_size, img[c].in))
                goto cleanup;
        }
        else
        {
            row_bytes = png_get_rowbytes(img[c].png_ptr, img[c].info_ptr);
            img[c].row_datas = xmalloc(img[c].height * sizeof(png_bytep *));
            img[c].row_datas[0] = xmalloc(img[c].height * row_bytes);
            for (d = 1; d < img[c].height; d++)
                img[c].row_datas[d] = img[c].row_datas[d-1] + row_bytes;
            png_read_rows(img[c].png_ptr, img[c].row_datas, NULL, img[c].height);

            ct = png_get_color_type(img[c].png_ptr, img[c].info_ptr);
            img[c].palette = palette_new();


            /* Count number of necessary colors in palette and number of transparencies */
            memset(transparency, 0, 256);
            for (d = 0; d < img[c].height; d++) {
                png_bytep row = img[c].row_datas[d];
                for (x = 0; x < img[c].width; x++) {
                    /* Set color of fully transparent pixels to black.
                        On Windows Mobile, and possibly on regular Windows OSes as well,
                        it seems that Windows does not completely ignore RGB-values of 
                        entirely transparent pixels as expected.
                     */
                    if ((ct & PNG_COLOR_MASK_ALPHA) && (row[4*x+3] == 0))
                        row[4*x+0] = row[4*x+1] = row[4*x+2] = 0;
                    if (palette_count(img[c].palette) <= (1 << 8))
                        palette_add(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
                    if (ct & PNG_COLOR_MASK_ALPHA)
                        transparency[row[4*x+3]] = 1;
                }
            }
            transparency_count = 0;
            for (d = 0; d < 256; d++)
                transparency_count += transparency[d];

            /* If there are more than two steps of transparency, or if the
             * two steps are NOT either entirely off (0) and entirely on (255),
             * then we will lose transparency information if bit_count is not 32.
             */
            need_transparency =
                transparency_count > 2
                ||
                (transparency_count == 2 && (transparency[0] == 0 || transparency[255] == 0));

            /* Can we keep all colors in a palette? */
            if (need_transparency) {
                if (bit_count != -1) {
                    if (bit_count != 32)
                        warn("decreasing bit depth will discard variable transparency", transparency_count);
                    /* Why 24 and not bit_count? Otherwise we might decrease below what's possible
                     * due to number of colors in image. The real decrease happens below. */
                    img[c].bit_count = 24;
                } else {
                    img[c].bit_count = 32;
                }
                img[c].palette_count = 0;
            }
            else if (palette_count(img[c].palette) <= 256) {
                for (d = 1; palette_count(img[c].palette) > (uint32_t)(1 << d); d <<= 1);
                if (d == 2)	/* four colors (two bits) are not supported */
                    d = 4;
                img[c].bit_count = d;
                img[c].palette_count = 1 << d;
            }
            else {
                img[c].bit_count = 24;
                img[c].palette_count = 0;
            }

            /* Does the user want to change number of bits per pixel? */
            if (bit_count != -1) {
                if (img[c].bit_count == (uint32_t) bit_count) {
                    /* No operation */
                } else if (img[c].bit_count < (uint32_t) bit_count) {
                    img[c].bit_count = bit_count;
                    img[c].palette_count = (bit_count > 16 ? 0 : 1 << bit_count);
                } else {
                    warn(_("cannot decrease bit depth from %d to %d, bit depth not changed"), img[c].bit_count, bit_count);
                }
            }
        
            img[c].image_size = img[c].height * ROW_BYTES(img[c].width * img[c].bit_count);
            img[c].mask_size = img[c].height * ROW_BYTES(img[c].width);
        }

        restore_message_header();
    }

    out = outfile_gen(&outname);
    restore_message_header();
    set_message_header(outname);
    if (out == NULL) {
        warn_errno(_("cannot create file"));
        goto cleanup;
    }

    dir.reserved = 0;
    dir.type = (icon_mode ? 1 : 2);
    dir.count = files->count;
    fix_win32_cursor_icon_file_dir_endian(&dir);
    if (fwrite(&dir, sizeof(Win32CursorIconFileDir), 1, out) != 1) {
        warn_errno(_("cannot write to file"));
        goto cleanup;
    }

    dib_start = sizeof(Win32CursorIconFileDir) + files->count * sizeof(Win32CursorIconFileDirEntry);
    for (c = 0; c < files->count; c++) {
        Win32CursorIconFileDirEntry entry;

        /* If one of the dimensions is larger or equal to 256, both icon dimensions have
           to be stored as 0 in the entry. */
        if ((img[c].width >= 256) || (img[c].height >= 256))
        {
            entry.width = 0;
            entry.height = 0;
        }
        else
        {
            entry.width = img[c].width;
            entry.height = img[c].height;
        }
        entry.reserved = 0;
        if (icon_mode) {
            entry.hotspot_x = 1;	            /* color planes for icons */
            entry.hotspot_y = img[c].bit_count; /* bit_count for icons */
        } else {
            entry.hotspot_x = img[c].hotspot_x;
            entry.hotspot_y = img[c].hotspot_y;
        }
        entry.dib_offset = dib_start;
        entry.color_count = (img[c].bit_count >= 8 ? 0 : 1 << img[c].bit_count);
        if (img[c].store_raw)
            entry.dib_size = img[c].image_size;
        else
            entry.dib_size = img[c].palette_count * sizeof(Win32RGBQuad)
                    + sizeof(Win32BitmapInfoHeader)
                    + img[c].image_size
                    + img[c].mask_size;

        dib_start += entry.dib_size;

        fix_win32_cursor_icon_file_dir_entry_endian(&entry);
        if (fwrite(&entry, sizeof(Win32CursorIconFileDirEntry), 1, out) != 1) {
            warn_errno(_("cannot write to file"));
            goto cleanup;
        }

    }

    for (c = 0; c < files->count; c++) {
        if (img[c].store_raw)
        {
            if (fwrite(img[c].image_data, img[c].image_size, 1, out) != 1) {
                warn_errno(_("cannot write to file"));
                goto cleanup;
            }
        }
        else
        {
            Win32BitmapInfoHeader bitmap;

            bitmap.size = sizeof(Win32BitmapInfoHeader);
            bitmap.width = png_get_image_width(img[c].png_ptr, img[c].info_ptr);
            bitmap.height = png_get_image_height(img[c].png_ptr, img[c].info_ptr) * 2;
            bitmap.planes = 1;							// appears to be 1 always (XXX)
            bitmap.bit_count = img[c].bit_count;
            bitmap.compression = 0;
            bitmap.x_pels_per_meter = 0;				// should be 0 always
            bitmap.y_pels_per_meter = 0;				// should be 0 always
            bitmap.clr_important = 0;					// should be 0 always
            bitmap.clr_used = img[c].palette_count;
            bitmap.size_image = img[c].image_size;		// appears to be ok here (may be image_size+mask_size or 0, XXX)

            fix_win32_bitmap_info_header_endian(&bitmap);
            if (fwrite(&bitmap, sizeof(Win32BitmapInfoHeader), 1, out) != 1) {
                warn_errno("cannot write to file");
                goto cleanup;
            }

            if (img[c].bit_count <= 16) {
                Win32RGBQuad color;

                palette_assign_indices(img[c].palette);
                color.reserved = 0;
                while (palette_next(img[c].palette, &color.red, &color.green, &color.blue))
                    fwrite(&color, sizeof(Win32RGBQuad), 1, out);

                /* Pad with empty colors. The reason we do this is because we
                 * specify bitmap.clr_used as a base of 2. The latter is probably
                 * not necessary according to the original specs, but many
                 * programs that read icons assume it. Especially gdk-pixbuf.
                 */
                    memset(&color, 0, sizeof(Win32RGBQuad));
                for (d = palette_count(img[c].palette); d < (uint32_t) (1 << img[c].bit_count); d++)
                    fwrite(&color, sizeof(Win32RGBQuad), 1, out);
            }

            img[c].image_data = xzalloc(img[c].image_size);

            for (d = 0; d < img[c].height; d++) {
                png_bytep row = img[c].row_datas[img[c].height - d - 1];
                if (img[c].bit_count < 24) {
                    uint32_t imod = d * (img[c].image_size/img[c].height) * 8 / img[c].bit_count;
                    for (x = 0; x < img[c].width; x++) {
                        uint32_t color;
                        color = palette_lookup(img[c].palette, row[4*x+0], row[4*x+1], row[4*x+2]);
                        simple_setvec(img[c].image_data, x+imod, img[c].bit_count, color);
                    }
                } else if (img[c].bit_count == 24) {
                    uint32_t irow = d * (img[c].image_size/img[c].height);
                    for (x = 0; x < img[c].width; x++) {
                        img[c].image_data[3*x+0 + irow] = row[4*x+2];
                        img[c].image_data[3*x+1 + irow] = row[4*x+1];
                        img[c].image_data[3*x+2 + irow] = row[4*x+0];
                    }
                } else if (img[c].bit_count == 32) {
                    uint32_t irow = d * (img[c].image_size/img[c].height);
                    for (x = 0; x < img[c].width; x++) {
                        img[c].image_data[4*x+0 + irow] = row[4*x+2];
                        img[c].image_data[4*x+1 + irow] = row[4*x+1];
                        img[c].image_data[4*x+2 + irow] = row[4*x+0];
                        img[c].image_data[4*x+3 + irow] = row[4*x+3];
                    }
                }
            }

            if (fwrite(img[c].image_data, img[c].image_size, 1, out) != 1) {
                warn_errno(_("cannot write to file"));
                goto cleanup;
            }

            for (d = 0; d < img[c].height; d++) {
                png_bytep row = img[c].row_datas[img[c].height - d - 1];

                for (x = 0; x < img[c].width; x += 8) {
                    uint8_t mask = 0;
                    mask |= (row[4*(x+0)+3] <= alpha_threshold ? 1 << 7 : 0);
                    mask |= (row[4*(x+1)+3] <= alpha_threshold ? 1 << 6 : 0);
                    mask |= (row[4*(x+2)+3] <= alpha_threshold ? 1 << 5 : 0);
                    mask |= (row[4*(x+3)+3] <= alpha_threshold ? 1 << 4 : 0);
                    mask |= (row[4*(x+4)+3] <= alpha_threshold ? 1 << 3 : 0);
                    mask |= (row[4*(x+5)+3] <= alpha_threshold ? 1 << 2 : 0);
                    mask |= (row[4*(x+6)+3] <= alpha_threshold ? 1 << 1 : 0);
                    mask |= (row[4*(x+7)+3] <= alpha_threshold ? 1 << 0 : 0);
                    fputc(mask, out);
                }

                fpad(out, 0, img[c].mask_size/img[c].height - x/8);
            }
        }

        free(img[c].image_data);
        if (img[c].palette) palette_free(img[c].palette);
        if (img[c].row_datas)
        {
            free(img[c].row_datas[0]);
            free(img[c].row_datas);
        }
        if (!img[c].store_raw)
        {
            png_read_end(img[c].png_ptr, img[c].info_ptr);
        }
        if (img[c].png_ptr)
            png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
        fclose(img[c].in);
        memset(&img[c], 0, sizeof(*img));
    }

    free(outname);
    free(img);
    return true;

cleanup:

    restore_message_header();
    for (c = 0; c < files->count; c++) {
        if (img[c].image_data != NULL)
            free(img[c].image_data);
        if (img[c].palette != NULL)
            palette_free(img[c].palette);
        if (img[c].row_datas != NULL && img[c].row_datas[0] != NULL) {
            free(img[c].row_datas[0]);
            free(img[c].row_datas);
        }
        if (img[c].png_ptr != NULL)
            png_destroy_read_struct(&img[c].png_ptr, &img[c].info_ptr, NULL);
        if (img[c].in != NULL)
            fclose(img[c].in);
    }
    if (outname != NULL)
        free(outname);

    free(img);
    return false;
}

static void
simple_setvec(uint8_t *data, uint32_t ofs, uint8_t size, uint32_t value)
{
    switch (size) {
    case 1:
        data[ofs/8] |= (value & 1) << (7 - ofs%8);
        break;
    case 2:
        data[ofs/4] |= (value & 3) << ((3 - ofs%4) << 1);
        break;
    case 4:
        data[ofs/2] |= (value & 15) << ((1 - ofs%2) << 2);
        break;
    case 8:
        data[ofs] = value;
        break;
    case 16:
        data[2*ofs] = value;
        data[2*ofs+1] = (value >> 8);
        break;
    case 24:
        data[3*ofs] = value;
        data[3*ofs+1] = (value >> 8);
        data[3*ofs+2] = (value >> 16);
        break;
    case 32:
        data[4*ofs] = value;
        data[4*ofs+1] = (value >> 8);
        data[4*ofs+2] = (value >> 16);
        data[4*ofs+3] = (value >> 24);
        break;
    }
}
