/* main.c
 *
 * Copyright (C) 2005-2006 by Andy Goth <unununium@openverse.com>.
 *
 * 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 2 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, write to the Free Software Foundation, Inc., 59 Temple
 * Place - Suite 330, Boston, MA 02111-1307, USA. */

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include "qbism.h"

/* Allocate memory or die. */
void* xmalloc(size_t bytes)
{
    void* result = malloc(bytes);

    if (result == NULL) {
        perror("malloc");
        exit(EXIT_FAILURE);
    }

    return result;
}

/* Reallocate memory or die. */
void* xrealloc(void* buf, size_t bytes)
{
    void* result = realloc(buf, bytes);

    if (result == NULL) {
        perror("realloc");
        exit(EXIT_FAILURE);
    }

    return result;
}

/* Numeric list of options. */
typedef enum {
    OPT_INQBE,
    OPT_INQBM,
    OPT_OUTQBE,
    OPT_OUTQBM,
    OPT_OUTIMG,
    OPT_IMGFMT,
    OPT_OVERSAMP,
    OPT_SIZE,
    OPT_STEPS,
    OPT_REGS,
    OPT_RENDER,
    OPT_COUNT
} opt_t;

/* Textual list of options. */
static char* opt_names[] = {
    "-inqbe",
    "-inqbm",
    "-outqbe",
    "-outqbm",
    "-outimg",
    "-imgfmt",
    "-oversamp",
    "-size",
    "-steps",
    "-regs",
    "-render"
};

/* Open a file. */
static FILE* my_open(char* name, char* mode)
{
    FILE* chan;

    if (strcmp(name, "-") == 0) {
        if (strcmp(mode, "r") == 0) {
            chan = stdin;
        } else {
            chan = stdout;
        }
    } else {
        chan = fopen(name, mode);
        if (chan == NULL) {
            perror(name);
            exit(EXIT_FAILURE);
        }
    }

    return chan;
}

/* Close an opened file. */
static void my_close(FILE* chan)
{
    if (chan != stdin && chan != stdout) {
        if (fclose(chan) == EOF) {
            perror("fclose");
            exit(EXIT_FAILURE);
        }
    }
}

/* Repeal gravity. */
int main(int argc, char** argv)
{
    FILE* chan;
    int i, j;
    algo_t algo = {NULL, 36, 12};
    image_t img;

    char* opts[OPT_COUNT] = {NULL};

    /* Defaults. */
    opts[OPT_OVERSAMP] = "1";
    opts[OPT_SIZE]     = "640x480";
    opts[OPT_IMGFMT]   = "miff8";
    opts[OPT_REGS]     = "6";
    opts[OPT_STEPS]    = "36";
    opts[OPT_RENDER]   = "yes";

    /* Check if help is requested. */
    if (argc > 1 && ((strcmp(argv[1], "-h") == 0) ||
            (strcmp(argv[1], "-help") == 0) ||
            (strcmp(argv[1], "--help") == 0))) {
        printf("Usage: qbism [OPTION]...\n"
        "  -inqbe    FILE         run the algorithm in the QBE FILE\n"
        "  -inqbm    FILE         run the algorithm in the QBM FILE\n"
        "  -outqbe   FILE         save the algorithm in QBE format to FILE\n"
        "  -outqbm   FILE         save the algorithm in QBM format to FILE\n"
        "  -outimg   FILE         render the image in MIFF format to FILE\n"
        "  -imgfmt   FORMAT_NAME  can be 'miff8', 'miff16', or 'bmp'\n"
        "  -oversamp NUM          sample NUMxNUM points per pixel\n"
        "  -size     WIDTHxHEIGHT render at WIDTH by HEIGHT pixels\n"
        "  -regs     NUM          for random algorithms, use NUM registers\n"
        "  -steps    NUM          for random algorithms, use NUM steps\n"
        "  -render   'yes'/'no'   if 'yes', render image\n"
        "  -h, -help              display this message\n"
        "  -v, -version           print the version information\n"
        "If neither -inqbe nor -inqbm are specified, a random algorithm is\n"
        "generated and used to render the image.  Otherwise the algorithm\n"
        "stored in the QBE or QBM file is used.\n"
        "\n"
        "Specify - as FILE to use stdin or stdout.  By default, the output\n"
        "image is written to stdout; pipe through display or convert (from\n"
        "ImageMagick) for the desired effect.\n"
        "\n"
        "Report features to Andy Goth <unununium@openverse.com>.\n");
        return EXIT_SUCCESS;
    }

    /* Check if the version number is requested. */
    if (argc > 1 && ((strcmp(argv[1], "-v") == 0) ||
            (strcmp(argv[1], "-version") == 0) ||
            (strcmp(argv[1], "--version") == 0))) {
        printf("qbism 0.4\n");
        return EXIT_SUCCESS;
    }

    /* Check that every option has a parameter. */
    if ((argc - 1) % 2 != 0) {
        fprintf(stderr, "Option \"%s\" has no parameter\n", argv[argc - 1]);
        return EXIT_FAILURE;
    }

    /* Get all parameters. */
    for (i = 1; i < argc; i += 2) {
        for (j = 0; j < OPT_COUNT; ++j) {
            if (strcmp(argv[i], opt_names[j]) == 0) {
                opts[j] = argv[i + 1];
                break;
            }
        }
        if (j == OPT_COUNT) {
            fprintf(stderr, "Unknown option \"%s\"\n", argv[i]);
            return EXIT_FAILURE;
        }
    }

    /* Check for disallowed combinations. */
    if (opts[OPT_INQBE] != NULL && opts[OPT_INQBM] != NULL) {
        fprintf(stderr, "Cannot specify both -inqbe and -inqbm\n");
        return EXIT_FAILURE;
    }

    /* Interpret options. */
    if (strcmp(opts[OPT_IMGFMT], "miff8") == 0) {
        img.format = IF_MIFF8;
    } else if (strcmp(opts[OPT_IMGFMT], "miff16") == 0) {
        img.format = IF_MIFF16;
    } else if (strcmp(opts[OPT_IMGFMT], "bmp") == 0) {
        img.format = IF_BMP;
    } else {
        fprintf(stderr, "Unsupported image format \"%s\"\n", opts[OPT_IMGFMT]);
        return EXIT_FAILURE;
    }
    if (sscanf(opts[OPT_OVERSAMP], "%d", &img.oversample) != 1 ||
            img.oversample <= 0) {
        fprintf(stderr, "Bad oversample value \"%s\"\n", opts[OPT_OVERSAMP]);
        return EXIT_FAILURE;
    }
    if (sscanf(opts[OPT_SIZE], "%dx%d", &img.width, &img.height) != 2 ||
            img.width <= 0 || img.height <= 0) {
        fprintf(stderr, "Bad size \"%s\"\n", opts[OPT_SIZE]);
        return EXIT_FAILURE;
    }
    if (sscanf(opts[OPT_STEPS], "%d", &algo.seq_len) != 1 ||
            algo.seq_len < 0) {
        fprintf(stderr, "Bad step count \"%s\"\n", opts[OPT_STEPS]);
        return EXIT_FAILURE;
    }
    if (sscanf(opts[OPT_REGS], "%d", &algo.num_regs) != 1 ||
            algo.num_regs < 1) {
        fprintf(stderr, "Bad register count \"%s\"\n", opts[OPT_REGS]);
        return EXIT_FAILURE;
    }
    if (strcmp(opts[OPT_RENDER], "yes") != 0 &&
            strcmp(opts[OPT_RENDER], "no") != 0 &&
            strcmp(opts[OPT_RENDER], "force") != 0) {
        fprintf(stderr, "Bad render flag \"%s\"\n", opts[OPT_RENDER]);
        return EXIT_FAILURE;
    }

    /* Read/generate an algorithm. */
    if (opts[OPT_INQBE] != NULL) {
        chan = my_open(opts[OPT_INQBE], "r");
        qbe_read(&algo, chan);
        my_close(chan);
    } else if (opts[OPT_INQBM] != NULL) {
        chan = my_open(opts[OPT_INQBM], "r");
        qbm_read(&algo, chan);
        my_close(chan);
    } else {
        /* Generate a random transformation sequence. */
        srandom(time(NULL));
        algo.seq = xmalloc(sizeof(*algo.seq) * algo.seq_len);
        for (i = 0; i < algo.seq_len; ++i) {
            algo.seq[i].opcode  = random() % XOP_COUNT;
            algo.seq[i].source  = random() % algo.num_regs;
            algo.seq[i].control = random() % algo.num_regs;
            algo.seq[i].dest    = random() % algo.num_regs;
        }
    }

    /* If so requested, write out the algorithm. */
    if (opts[OPT_OUTQBE] != NULL) {
        chan = my_open(opts[OPT_OUTQBE], "w");
        /* Don't write binary QBE data to a terminal. */
        if (ttyname(fileno(chan)) != NULL) {
            fprintf(stderr, "Cowardly refusing to write QBE to a terminal.\n");
            my_close(chan);
            return EXIT_FAILURE;
        } else {
            qbe_write(&algo, chan);
            my_close(chan);
        }
    }
    if (opts[OPT_OUTQBM] != NULL) {
        chan = my_open(opts[OPT_OUTQBM], "w");
        qbm_write(&algo, chan);
        my_close(chan);
    }

    /* If writing QBE/QBM files to stdout and -outimg is left at the default,
     * then don't render an image. */
    if (opts[OPT_OUTIMG] == NULL &&
        ((opts[OPT_OUTQBE] != NULL && strcmp(opts[OPT_OUTQBE], "-") == 0) ||
         (opts[OPT_OUTQBM] != NULL && strcmp(opts[OPT_OUTQBM], "-") == 0))) {
        opts[OPT_RENDER] = "no";
    }

    /* If -outimg wasn't given, default to writing to stdout. */
    if (opts[OPT_OUTIMG] == NULL) {
        opts[OPT_OUTIMG] = "-";
    }

    /* Render! */
    if (strcmp(opts[OPT_RENDER], "no") != 0) {
        chan = my_open(opts[OPT_OUTIMG], "w");

        /* Don't write binary image data to a terminal. */
        if (strcmp(opts[OPT_RENDER], "yes") == 0 &&
                ttyname(fileno(chan)) != NULL) {
            /* Only warn if not also writing QBE/QBM files. */
            if (opts[OPT_OUTQBE] == NULL && opts[OPT_OUTQBM] == NULL) {
                fprintf(stderr, "Cowardly refusing to render to a terminal; "
                        "use \"-render force\" to override.\n");
                my_close(chan);
                return EXIT_FAILURE;
            }
        } else {
            /* Do it. */
            img.algo = &algo;
            img.target = chan;
            qbism_render_image(&img);
            my_close(chan);
        }
    }

    return EXIT_SUCCESS;
}

/* vim: set ts=4 sts=4 sw=4 tw=80 et: */

