/* main.c - Main routine from icotool
 *
 * 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 <unistd.h>		/* POSIX */
#include <errno.h>		/* C89 */
#include <stdio.h>		/* C89 */
#include <getopt.h>		/* Gnulib/GNU Libc */
#include <string.h>		/* C89 */
#include <stdlib.h>		/* C89 */
#ifdef HAVE_LOCALE_H
# include <locale.h>		/* Solaris */
#endif
#include "gettext.h"
#include "configmake.h"
#define _(s) gettext(s)
#define N_(s) gettext_noop(s)
#include "progname.h"		/* Gnulib */
#include "version-etc.h"	/* Gnulib */
#include "xalloc.h"		/* Gnulib */
#include "common/error.h"
#include "common/strbuf.h"
#include "common/string-utils.h"
#include "common/intutil.h"
#include "common/io-utils.h"
#include "icotool.h"

#define PROGRAM "icotool"

static int32_t image_index = -1;
static int32_t width = -1;
static int32_t height = -1;
static int32_t bitdepth = -1;
/*static uint32_t minbitdepth = 1;*/
static int32_t palettesize = -1;
static int32_t hotspot_x = 0;
static int32_t hotspot_y = 0;
static int hotspot_x_set = 0;
static int hotspot_y_set = 0;
static int32_t alpha_threshold = 127;
static bool icon_only = false;	
static bool cursor_only = false;
static char *output = NULL;

const char version_etc_copyright[] = "Copyright (C) 1998 Oskar Liljeblad";

enum {
    VERSION_OPT	= 1000,
    HELP_OPT,
    ICON_OPT,
    CURSOR_OPT,
};

static const char *short_opts = "-xlco:i:w:h:p:b:X:Y:t:r:";
static struct option long_opts[] = {
    { "extract",		no_argument,    	NULL, 'x' },
    { "list",			no_argument,		NULL, 'l' },
    { "create",			no_argument,       	NULL, 'c' },
    { "version",		no_argument, 	    	NULL, VERSION_OPT },
    { "help", 	    	 	no_argument,	    	NULL, HELP_OPT },
    { "output", 		required_argument, 	NULL, 'o' },
    { "index",	    	 	required_argument, 	NULL, 'i' },
    { "width",	    	 	required_argument, 	NULL, 'w' },
    { "height", 		required_argument, 	NULL, 'h' },
    { "palette-size",		required_argument, 	NULL, 'p' },
    { "bit-depth",		required_argument, 	NULL, 'b' },
    /*{ "min-bit-depth",          required_argument,      NULL, 'm' },*/
    { "hotspot-x",       	required_argument, 	NULL, 'X' },
    { "hotspot-y",       	required_argument, 	NULL, 'Y' },
    { "alpha-threshold", 	required_argument, 	NULL, 't' },
    { "icon",       	 	no_argument,       	NULL, ICON_OPT	},
    { "cursor",     	 	no_argument,       	NULL, CURSOR_OPT },
    { "raw", 			required_argument, 	NULL, 'r' },
    { 0, 0, 0, 0 }
};

static bool
filter(int i, int w, int h, int bd, int ps, bool icon, int hx, int hy)
{
    if (image_index != -1 && i != image_index)
        return false;
    if (width != -1 && w != width)
        return false;
    if (height != -1 && h != height)
        return false;
    if (bitdepth != -1 && bd != bitdepth)
        return false;
    /*if (bd < minbitdepth)
        return false;*/
    if (palettesize != -1 && ps != palettesize)
        return false;
    if ((icon_only && !icon) || (cursor_only && icon))
        return false;
    if (hotspot_x_set && hx != hotspot_x)
        return false;
    if (hotspot_y_set && hy != hotspot_y)	
        return false;
    return true;
}

static FILE *
create_outfile_gen(char **out)
{
    if (output != NULL) {
        *out = xstrdup(output);
        return fopen(output, "wb");
    }
    if (isatty(STDOUT_FILENO))
        die(_("refusing to write binary data to terminal"));
    *out = xstrdup(_("(standard out)"));
    return stdout;
}

static FILE *
extract_outfile_gen(const char *inname, char **outname_ptr, int w, int h, int bc, int i)
{
    if (output == NULL || is_directory(output)) {
        StrBuf *outname;
        const char *inbase;

        outname = strbuf_new();
        if (output != NULL) {
            strbuf_append(outname, output);
            if (!ends_with(output, "/"))
                strbuf_append(outname, "/");
        }
        inbase = strrchr(inname, '/');
        inbase = (inbase == NULL ? inname : inbase+1);
        if (ends_with_nocase(inbase, ".ico") || ends_with_nocase(inbase, ".cur")) {
            strbuf_append_substring(outname, inbase, 0, strlen(inbase)-4);
        } else {
            strbuf_append(outname, inbase);
        }
        strbuf_appendf(outname, "_%d_%dx%dx%d.png", i, w, h, bc);
        *outname_ptr = strbuf_free_to_string(outname);
        return fopen(*outname_ptr, "wb");
    }
    else if (strcmp(output, "-") == 0) {
        *outname_ptr = xstrdup(_("(standard out)"));
        return stdout;
    }

    *outname_ptr = xstrdup(output);
    return fopen(output, "wb");
}

static void
display_help(void)
{
    printf(_("Usage: %s [OPTION]... [FILE]...\n"), program_name);
    printf(_("Convert and create Win32 icon (.ico) and cursor (.cur) files.\n"));
    printf(_("\nCommands:\n"));
    printf(_("  -x, --extract                extract images from files\n"));
    printf(_("  -l, --list                   print a list of images in files\n"));
    printf(_("  -c, --create                 create an icon file from specified files\n"));
    printf(_("      --help                   display this help and exit\n"));
    printf(_("      --version                output version information and exit\n"));
    printf(_("\nOptions:\n"));
    printf(_("  -i, --index=NUMBER           match index of image (first is 1)\n"));
    printf(_("  -w, --width=PIXELS           match width of image\n"));
    printf(_("  -h, --height=PIXELS          match height of image\n"));
    printf(_("  -p, --palette-size=COUNT     match number of colors in palette (or 0)\n"));
    printf(_("  -b, --bit-depth=COUNT        match or set number of bits per pixel\n"));
    /*printf(_("  -m, --min-bit-depth=COUNT    match or set minimum number of bits per pixel\n"));*/
    printf(_("  -X, --hotspot-x=COORD        match or set cursor hotspot x-coordinate\n"));
    printf(_("  -Y, --hotspot-y=COORD        match or set cursor hotspot y-coordinate\n"));
    printf(_("  -t, --alpha-threshold=LEVEL  highest level in alpha channel indicating\n"
             "                               transparent image portions (default is 127)\n"));
    printf(_("  -r, --raw=FILENAME           store input file as raw PNG (\"Vista icons\")\n"));
    printf(_("      --icon                   match icons only\n"));
    printf(_("      --cursor                 match cursors only\n"));
    printf(_("  -o, --output=PATH            where to place extracted files\n"));
    printf(_("\n"));
    printf(_("Report bugs to <%s>.\n"), PACKAGE_BUGREPORT);
}

static bool
open_file_or_stdin(char *name, FILE **outfile, const char **outname)
{
    if (strcmp(name, "-") == 0) {
        *outfile = stdin;
        *outname = "(standard in)";
    } else {
        *outfile = fopen(name, "rb");
        *outname = name;
        if (*outfile == NULL) {
            warn("%s: cannot open file", name);
            return false;
        }
    }
    return true;
}

static void
input_files_init (InputFiles *files)
{
    memset (files, 0, sizeof (InputFiles));
}

static void
input_files_add (InputFiles *files, char* name, int32_t bit_count, int32_t file_hotspot_x, int32_t file_hotspot_y)
{
    files->files = realloc (files->files, (files->count+1)*sizeof (InputFile));
    files->files[files->count].name = name;
    files->files[files->count].is_raw = false;
    files->files[files->count].bit_count = bitdepth;
    files->files[files->count].hotspot_x = file_hotspot_x;
    files->files[files->count].hotspot_y = file_hotspot_y;
    files->count++;
}

static void
input_files_add_raw (InputFiles *files, char* name, int32_t file_hotspot_x, int32_t file_hotspot_y)
{
    files->files = realloc (files->files, (files->count+1)*sizeof (InputFile));
    files->files[files->count].name = name;
    files->files[files->count].is_raw = true;
    files->files[files->count].hotspot_x = file_hotspot_x;
    files->files[files->count].hotspot_y = file_hotspot_y;
    files->count++;
}

int
main(int argc, char **argv)
{
    int c;
    bool list_mode = false;
    bool extract_mode = false;
    bool create_mode = false;
    FILE *in;
    const char *inname;
    size_t i;
    InputFiles files;
    int num_bitdepth = 0;

    set_program_name(argv[0]);
    input_files_init (&files);

#ifdef ENABLE_NLS
    if (setlocale(LC_ALL, "") == NULL)
        warn(_("%s: cannot set locale: %s"), program_name, errstr);
    if (bindtextdomain(PACKAGE, LOCALEDIR) == NULL)
        warn(_("%s: bindtextdomain failed: %s"), program_name, errstr);
    if (textdomain(PACKAGE) == NULL)
        warn(_("%s: cannot set message domain: %s"), program_name, errstr);
#endif

    while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL)) != -1) {
        switch (c) {
        case 1:
            input_files_add (&files, optarg, bitdepth, hotspot_x, hotspot_y);
            break;
        case 'x':
            extract_mode = true;
            break;
        case 'l':
            list_mode = true;
            break;
        case 'c':
            create_mode = true;
            break;
        case VERSION_OPT:
            version_etc(stdout, PROGRAM, PACKAGE, VERSION, "Oskar Liljeblad", NULL);
            exit(0);
        case HELP_OPT:
            display_help();
            exit(0);
        case 'o':
            output = optarg;
            break;
        case 'i':
            if (!parse_int32(optarg, &image_index) || image_index < 0)
                die(_("invalid index value: %s"), optarg);
            break;
        case 'w':
            if (!parse_int32(optarg, &width) || width < 0)
                die(_("invalid width value: %s"), optarg);
            break;
        case 'h':
            if (!parse_int32(optarg, &height) || height < 0)
                die(_("invalid height value: %s"), optarg);
            break;
        case 'p':
            if (!parse_int32(optarg, &palettesize) || palettesize < 0)
                die(_("invalid palette-size value: %s"), optarg);
            break;
        case 'b':
            if (!parse_int32(optarg, &bitdepth) || bitdepth < 0)
                die(_("invalid bit-depth value: %s"), optarg);
            ++num_bitdepth;
            break;
        /*case 'm':
            if (!parse_uint32(optarg, &minbitdepth))
                die(_("invalid minimum bit-depth value: %s"), optarg);
            break;*/
        case 'X':
            if (!parse_int32(optarg, &hotspot_x) || hotspot_x < 0)
                die(_("invalid hotspot-x value: %s"), optarg);
            ++hotspot_x_set;
            break;
        case 'Y':
            if (!parse_int32(optarg, &hotspot_y) || hotspot_y < 0)
                die(_("invalid hotspot-y value: %s"), optarg);
            ++hotspot_y_set;
            break;
        case 't':
            if (!parse_int32(optarg, &alpha_threshold) || alpha_threshold < 0)
                die(_("invalid alpha-threshold value: %s"), optarg);
            break;
        case 'r':
            input_files_add_raw (&files, optarg, hotspot_x, hotspot_y);
            break;
        case ICON_OPT:
            icon_only = true;
            break;
        case CURSOR_OPT:
            cursor_only = true;
            break;
        case '?':
            exit(1);
        }
    }

    /* Handle options after '--' */
    for (c = optind ; c < argc ; c++) {
        input_files_add (&files, argv[c], bitdepth, hotspot_x, hotspot_y);
    }

    if (extract_mode + create_mode + list_mode > 1)
        die(_("multiple commands specified"));
    if (extract_mode + create_mode + list_mode == 0) {
        warn(_("missing argument"));
        display_help();
        exit (1);
    }
    if (icon_only && cursor_only)
        die(_("only one of --icon and --cursor may be specified"));

    if (list_mode) {
        if (files.count <= 0)
            die(_("missing file argument"));
        for (i = 0 ; i < files.count ; i++) {
            if (open_file_or_stdin(files.files[i].name, &in, &inname)) {
                if (!extract_icons(in, inname, true, NULL, filter))
                    exit(1);
                if (in != stdin)
                    fclose(in);
            }
        }
    }

    if (extract_mode) {
        if (files.count <= 0)
            die(_("missing arguments"));

        for (i = 0 ; i < files.count ; i++) {
            int matched;

            if (open_file_or_stdin(files.files[i].name, &in, &inname)) {
                matched = extract_icons(in, inname, false, extract_outfile_gen, filter);
                if (matched == -1)
                    exit(1);
                if (matched == 0)
                    fprintf(stderr, _("%s: no images matched\n"), inname);
                if (in != stdin)
                    fclose(in);
            }
        }
    }

    if (create_mode) {
        if (files.count <= 0)
           die(_("missing arguments"));
        if (num_bitdepth == 1) {
            /* Change bitdepth to the single value provided (for compatibility) */
            for (i = 0 ; i < files.count ; i++) {
                files.files[i].bit_count = bitdepth;
            }
        } else if (num_bitdepth > 1) {
            /* Warn if bitdepth is unset */
            for (i = 0 ; i < files.count ; i++) {
                if (files.files[i].is_raw) continue;
                if (files.files[i].bit_count < 0) {
                    fprintf(stderr, _("%s: No bit-depth given\n"), files.files[i].name);
                }
            }
        }
        if (hotspot_x_set == 1) {
            /* Change hotspot to the single value provided (for compatibility) */
            for (i = 0 ; i < files.count ; i++) {
                files.files[i].hotspot_x = hotspot_x;
            }
        }
        if (hotspot_y_set == 1) {
            /* Change hotspot to the single value provided (for compatibility) */
            for (i = 0 ; i < files.count ; i++) {
                files.files[i].hotspot_y = hotspot_y;
            }
        }
        if (!create_icon(&files, create_outfile_gen, (icon_only ? true : !cursor_only), alpha_threshold))
            exit(1);
    }

    exit(0);
}
