#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>

#include "optread.h"
#include "core.h"
#include "encode.h"
#include "main.h"
#include "random.h"

extern const char *help_text;

typedef enum _cfg_state {
    CURRENT_SLIDE = 0,
    CURRENT_TRANS = 1,
    DEFAULT_SLIDE = 2,
    DEFAULT_TRANS = 3
} cfg_state_t;

static const value_list_t encoders[] = {
    { ENCTYPE_AUTO,         "auto" },
    { ENCTYPE_RAW_YUV,      "raw" },
    { ENCTYPE_RAW_YUV,      "yuv" },
    { ENCTYPE_RAW_YUV,      "rawyuv" },
    { ENCTYPE_YUV4MPEG,     "yuv4mpeg" },
    { ENCTYPE_YUV4MPEG,     "yuv4mpeg2" },
    { ENCTYPE_YUV4MPEG,     "y4m" },
    { ENCTYPE_YUV4MPEG,     "y4m2" },
    { ENCTYPE_X264,         "x264" },
    { ENCTYPE_XVID,         "xvid" },
    { ENCTYPE_FFMPEG_MPEG2, "mp2" },
    { ENCTYPE_FFMPEG_MPEG2, "mpeg" },
    { ENCTYPE_FFMPEG_MPEG2, "mpeg2" },
    { ENCTYPE_FFMPEG_MPEG2, "ffmpeg2" },
    { ENCTYPE_FFMPEG_MPEG4, "mpeg4" },
    { ENCTYPE_FFMPEG_MPEG4, "ffmpeg4" },
    { ENCTYPE_FFMPEG_MPEG4, "fmp4" },
END_OF_VALUE_LIST };

static const value_list_t trans_types[] = {
    { TRANS_CROSSFADE,             "default" },
    { TRANS_CROSSFADE,             "standard" },
    { TRANS_CROSSFADE,             "std" },
    { TRANS_CROSSFADE,             "normal" },
    { TRANS_CROSSFADE,             "simple" },
    { TRANS_CROSSFADE,             "crossfade" },
    { TRANS_CROSSFADE,             "xfade" },
    { TRANS_CROSSFADE,             "fade" },
    { TRANS_CROSSFADE,             "dissolve" },
    { TRANS_HORIZONTAL,            "horizontal" },
    { TRANS_HORIZONTAL,            "horiz" },
    { TRANS_HORIZONTAL,            "hor" },
    { TRANS_VERTICAL,              "vertical" },
    { TRANS_VERTICAL,              "vert" },
    { TRANS_CIRCULAR,              "circular" },
    { TRANS_CIRCULAR,              "circle" },
    { TRANS_CIRCULAR,              "circ" },
    { TRANS_DIAGONAL,              "diagonal" },
    { TRANS_DIAGONAL,              "diag" },
    { TRANS_DIAGONAL,              "diagonal-alt" },
    { TRANS_DIAGONAL,              "diagonal2" },
    { TRANS_DIAGONAL,              "diag-alt" },
    { TRANS_DIAGONAL,              "diag2" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horizontal-center-out" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horizontal-center" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horizontal-co" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horiz-center-out" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horiz-center" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horiz-co" },
    { TRANS_HORIZONTAL_CENTER_OUT, "horiz-out" },
    { TRANS_HORIZONTAL_CENTER_OUT, "hor-center-out" },
    { TRANS_HORIZONTAL_CENTER_OUT, "hor-center" },
    { TRANS_HORIZONTAL_CENTER_OUT, "hor-co" },
    { TRANS_HORIZONTAL_CENTER_OUT, "hor-out" },
    { TRANS_VERTICAL_CENTER_OUT,   "vertical-center-out" },
    { TRANS_VERTICAL_CENTER_OUT,   "vertical-center" },
    { TRANS_VERTICAL_CENTER_OUT,   "vertical-co" },
    { TRANS_VERTICAL_CENTER_OUT,   "vertical-out" },
    { TRANS_VERTICAL_CENTER_OUT,   "vert-center-out" },
    { TRANS_VERTICAL_CENTER_OUT,   "vert-center" },
    { TRANS_VERTICAL_CENTER_OUT,   "vert-co" },
    { TRANS_VERTICAL_CENTER_OUT,   "vert-out" },
END_OF_VALUE_LIST };

static const value_list_t easing_curves[] = {
    { EC_LINEAR,     "default" },
    { EC_LINEAR,     "standard" },
    { EC_LINEAR,     "std" },
    { EC_LINEAR,     "normal" },
    { EC_LINEAR,     "linear" },
    { EC_SMOOTH,     "smooth" },
    { EC_SMOOTH,     "cubic" },
    { EC_ACCELERATE, "accelerating" },
    { EC_ACCELERATE, "accelerate" },
    { EC_ACCELERATE, "acc" },
    { EC_DECELERATE, "decelerating" },
    { EC_DECELERATE, "decelerate" },
    { EC_DECELERATE, "dec" },
END_OF_VALUE_LIST };

static const value_list_t enc_flags[] = {
    { ENC_RD,              "rd" },
    { ENC_RD,              "rdo" },
    { ENC_TRELLIS,         "trellis" },
    { ENC_TRELLIS,         "trell" },
    { ENC_PSYCHO,          "psycho" },
    { ENC_PSYCHO,          "psy" },
    { ENC_PSYCHO,          "hvs" },
    { ENC_HQ_ME,           "hqme" },
    { ENC_CHROMA_ME,       "chromame" },
    { ENC_CHROMA_ME,       "chroma" },
    { ENC_MV0,             "mv0" },
    { ENC_4MV,             "4mv" },
    { ENC_QPEL,            "qpel" },
    { ENC_MPEG_QUANT,      "mpegquant" },
    { ENC_MPEG_QUANT,      "mpeg" },
    { ENC_GMC,             "gmc" },
    { ENC_CABAC,           "cabac" },
    { ENC_B_PYRAMID,       "bpyramid" },
    { ENC_B_PYRAMID,       "pyramid" },
    { ENC_NO_DEBLOCK,      "deblock" },
    { ENC_8X8,             "8x8" },
    { ENC_8X8,             "8x8dct" },
    { ENC_WEIGHTED,        "weighted" },
    { ENC_WEIGHTED,        "weight" },
    { ENC_MIXED_REFS,      "mixedrefs" },
    { ENC_MIXED_REFS,      "mixed" },
    { ENC_MAIN,            "main" },
    { ENC_MAIN,            "mp" },
    { ENC_BASELINE,        "baseline" },
    { ENC_BASELINE,        "base" },
    { ENC_BASELINE,        "bp" },
    { ENC_OPTIMAL_QUALITY, "hq" },
END_OF_VALUE_LIST };

static const value_list_t default_positions[] = {
#define DEFPOS(px, py, str) { ((int) ((px) * 2)) + ((int) ((py) * 8)), str }
    DEFPOS(0.5, 0.5, "default"),
    DEFPOS(0.5, 0.5, "standard"),
    DEFPOS(0.5, 0.5, "std"),
    DEFPOS(0.5, 0.5, "center"),
    DEFPOS(0.5, 0.5, "c"),
    DEFPOS(0.0, 0.5, "left"),
    DEFPOS(0.0, 0.5, "l"),
    DEFPOS(1.0, 0.5, "right"),
    DEFPOS(1.0, 0.5, "r"),
    DEFPOS(0.5, 0.0, "top"),
    DEFPOS(0.5, 0.0, "up"),
    DEFPOS(0.5, 0.0, "upper"),
    DEFPOS(0.5, 0.0, "t"),
    DEFPOS(0.5, 0.0, "u"),
    DEFPOS(0.5, 1.0, "bottom"),
    DEFPOS(0.5, 1.0, "lower"),
    DEFPOS(0.5, 1.0, "low"),
    DEFPOS(0.5, 1.0, "b"),
    DEFPOS(0.0, 0.0, "upper-left"),
    DEFPOS(0.0, 0.0, "upperleft"),
    DEFPOS(0.0, 0.0, "ul"),
    DEFPOS(1.0, 0.0, "upper-right"),
    DEFPOS(1.0, 0.0, "upperright"),
    DEFPOS(1.0, 0.0, "ur"),
    DEFPOS(0.0, 1.0, "lower-left"),
    DEFPOS(0.0, 1.0, "lowerleft"),
    DEFPOS(0.0, 1.0, "ll"),
    DEFPOS(1.0, 1.0, "lower-right"),
    DEFPOS(1.0, 1.0, "lowerright"),
    DEFPOS(1.0, 1.0, "lr"),
    DEFPOS(0.0, 0.0, "top-left"),
    DEFPOS(0.0, 0.0, "topleft"),
    DEFPOS(0.0, 0.0, "tl"),
    DEFPOS(1.0, 0.0, "top-right"),
    DEFPOS(1.0, 0.0, "topright"),
    DEFPOS(1.0, 0.0, "tr"),
    DEFPOS(0.0, 1.0, "bottom-left"),
    DEFPOS(0.0, 1.0, "bottomleft"),
    DEFPOS(0.0, 1.0, "bl"),
    DEFPOS(1.0, 1.0, "bottom-right"),
    DEFPOS(1.0, 1.0, "bottomright"),
    DEFPOS(1.0, 1.0, "br"),
    DEFPOS(0.0, 0.0, "northwest"),
    DEFPOS(0.0, 0.0, "nw"),
    DEFPOS(0.5, 0.0, "north"),
    DEFPOS(0.5, 0.0, "n"),
    DEFPOS(1.0, 0.0, "northeast"),
    DEFPOS(1.0, 0.0, "ne"),
    DEFPOS(1.0, 0.5, "east"),
    DEFPOS(1.0, 0.5, "e"),
    DEFPOS(1.0, 1.0, "southeast"),
    DEFPOS(1.0, 1.0, "se"),
    DEFPOS(0.5, 1.0, "south"),
    DEFPOS(0.5, 1.0, "s"),
    DEFPOS(0.0, 1.0, "southwest"),
    DEFPOS(0.0, 1.0, "sw"),
    DEFPOS(0.0, 0.5, "west"),
    DEFPOS(0.0, 0.5, "w"),
END_OF_VALUE_LIST };

static slide_t defaults;

/******************************************************************************/

#define MAX_NUMS 4
typedef struct _numbers {
    int count;
    float floatval[MAX_NUMS];
    int intval[MAX_NUMS];
    char sep[MAX_NUMS];
    int int_mask;
} numbers_t;
static numbers_t nums;
#define NUM_IS_INT(x) ((nums.int_mask & (1 << (x))) != 0)

#define NP_STRICT      1
#define NP_EXPECT_INT  2
static int numparse(char *str, const char *expect_pattern, int flags) {
    char *pos, *end, *dot;
    int no_int;
    if (!str) {
        nextopt(OPT_NONZERO | OPT_LOWERCASE);
        str = opt.stringval;
    }
    nums.int_mask = 0;
    pos = end = str;
    for (nums.count = 0;  (nums.count < MAX_NUMS) && *end;  ++nums.count) {
        nums.floatval[nums.count] = strtof(pos, &end);
        if (!end || (pos == end)) {
            if (flags & NP_STRICT)
                opterr("invalid numeric value: '%s'", str);
            return 0;
        }
        if (*end == '%') {
            nums.floatval[nums.count] /= 100.0f;
            ++end;
            no_int = 1;
        } else {
            dot = strchr(pos, '.');
            no_int = dot && (dot < end);
        }
        nums.intval[nums.count] = (int) nums.floatval[nums.count];
        if (!no_int && (nums.floatval[nums.count] == ((float) nums.intval[nums.count])))
            nums.int_mask |= 1 << nums.count;
        nums.sep[nums.count] = *end;
        pos = end + 1;
    }
    if (!nums.count) {
        if (flags & NP_STRICT)
            opterr("no number: '%s'", str);
        return 0;
    }
    if (*end)
        opterr("too many numbers: '%s'", str);
    if (expect_pattern && strcmp(expect_pattern, nums.sep)) {
        if (flags & NP_STRICT)
            opterr("invalid numeric pattern: '%s'", str);
        return 0;
    }
    if ((flags & NP_EXPECT_INT) && ((nums.int_mask + 1) != (1 << nums.count)))
        opterr("integer values expected: '%s'", str);
    return 1;
}

/******************************************************************************/

static void parse_pos(char *str, key_t *key) {
    int poscode;
    char *scale, *pos, nullbyte = '\0';
    key->flags = 0;

    /* check for auto-animation */
    if (!str[0] || !strcmp(str, "auto")) {
        key->flags = KF_AUTOANIM;
        return;
    }

    /* check for x0,y0/x1,y1 form */
    if (numparse(str, ",/,", NP_EXPECT_INT)) {
        key->flags = KF_VALID | KF_ABS_SCALE | KF_ABS_POS;
        key->px = nums.floatval[0];
        key->py = nums.floatval[1];
        key->sx = nums.floatval[2] - key->px;
        key->sy = nums.floatval[3] - key->py;
        return;
    }

    /* try to split into scale and position */
    pos = strchr(str, '@');
    if (!pos) {
        /* single item -> check if it's a scale value */
        if (numparse(str, "", 0)) {
            key->flags = KF_VALID;
            key->sx = key->sy = nums.floatval[0];
            key->px = key->py = 0.5f;
            return;
        }
        /* single item, no scale -> assume position code */
        scale = &nullbyte;
        pos = str;
    } else {
        scale = str;
        *pos++ = '\0';
    }

    /* parse scale */
    if (numparse(scale, "", 0)) {
        key->sx = key->sy = nums.floatval[0];
    } else if (numparse(scale, "x", 0)) {
        key->sx = nums.floatval[0];
        key->sy = nums.floatval[1];
        if (nums.int_mask == 3)
            key->flags |= KF_ABS_SCALE;
    } else if (!scale[0]) {
        key->sx = key->sy = 1.0f;
    } else
        opterr("invalid scale '%s'", scale);

    /* parse position */
    if (numparse(pos, ",", 0)) {
        key->px = nums.floatval[0];
        key->py = nums.floatval[1];
        if (nums.int_mask == 3)
            key->flags |= KF_ABS_POS;
    } else {
        poscode = optenum_list_ex(pos, default_positions, OPTENUM_MUST_MATCH);
        key->px = 0.5f * (poscode & 3);
        key->py = 0.5f * (poscode >> 2);
    }

    /* done. */
    key->flags |= KF_VALID;
}

/******************************************************************************/

int configure(void) {
    cfg_state_t state = DEFAULT_SLIDE;
    slide_t *slide = &defaults;
    int default_trans = 0;
    int no_trans = 0;
    int has_trans = 0;

    /* set up defaults */
    memset(&defaults, 0, sizeof(slide_t));
    defaults.core_duration = 5.0f;
    defaults.trans.duration = 1.0f;
    defaults.trans.map_band_size = 1.0f;
    defaults.trans.map_curve = EC_SMOOTH;

    #define IS_OPT(x) (!strcmp(opt.stringval, x))
    while (nextopt(OPT_CHECK | OPT_STRIP_DASHES | OPT_LOWERCASE_IF_DASH | OPT_LOWERCASE_IF_DOUBLE_DASH)) {
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        if (!(opt.flags & OPT_DASH) || IS_OPT("empty")) {
            if (!has_trans && default_trans && !no_trans) {
                slide = new_trans(&defaults);
                if (!slide)
                    opterr("failed to add transition");
            }
            slide = new_slide((opt.flags & OPT_DASH) ? NULL : opt.stringval, &defaults);
            if (!slide)
                opterr("failed to add slide");
            state = CURRENT_SLIDE;
            has_trans = 0;
            no_trans = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("c") || IS_OPT("cfg") || IS_OPT("config") || IS_OPT("include")) {
            nextopt(OPT_NONZERO | OPT_NO_DASH);
            optfile(opt.stringval, 0);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("h") || IS_OPT("help")) {
            puts(help_text);
            exit(0);
        }
        else if (IS_OPT("version")) {
            puts(PRODUCT_NAME " " PRODUCT_VERSION "\n");
            exit(0);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("v") || IS_OPT("verbose")) {
            cfg.verbosity = (cfg.verbosity == V_VERBOSE) ? V_DEBUG : V_VERBOSE;
        }
        else if (IS_OPT("q") || IS_OPT("quiet")) {
            cfg.verbosity = (cfg.verbosity == V_QUIET) ? V_SILENT : V_QUIET;
        }
        else if (IS_OPT("really-quiet") || IS_OPT("silent")) {
            cfg.verbosity = V_SILENT;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("o") || IS_OPT("output")) {
            nextopt(OPT_NONZERO);
            cfg.enc.filename = opt.stringval;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("rt") || IS_OPT("realtime")) {
            cfg.realtime = 1;
        }
        else if (IS_OPT("no-rt") || IS_OPT("no-realtime") || IS_OPT("nort") || IS_OPT("norealtime")) {
            cfg.realtime = 0;
        }
        else if (IS_OPT("rt-preview")) {
            cfg.realtime = 1;
            cfg.preview = 1;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("preview")) {
            cfg.preview = 1;
        }
        else if (IS_OPT("nopreview") || IS_OPT("no-preview")) {
            cfg.preview = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("lq") || IS_OPT("draft")) {
            low_quality = 1;
        }
        else if (IS_OPT("hq") || IS_OPT("no-lq") || IS_OPT("no-draft")) {
            low_quality = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("half-size")) {
            cfg.preview_size = PREVIEW_SIZE_HALF;
        }
        else if (IS_OPT("full-size")) {
            cfg.preview_size = PREVIEW_SIZE_FULL;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("size") || IS_OPT("res")) {
            numparse(NULL, "x", NP_STRICT | NP_EXPECT_INT);
            cfg.enc.width = nums.intval[0];
            cfg.enc.height = nums.intval[1];
            if ((cfg.enc.width | cfg.enc.height) & 1)
                opterr("output width and height must be even");
            cfg.size_configured = 1;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("aspect")) {
            numparse(NULL, NULL, NP_STRICT | NP_EXPECT_INT);
            if (!strcmp("/", nums.sep)) {
                cfg.enc.flags &= ~ENC_DAR;
            } else if (!strcmp(":", nums.sep)) {
                cfg.enc.flags |= ENC_DAR;
            } else
                opterr("invalid numeric pattern: '%s'", opt.stringval);
            cfg.enc.aspect_num = nums.intval[0];
            cfg.enc.aspect_den = nums.intval[1];
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("fps")) {
            nextopt(OPT_NONZERO | OPT_NO_DASH);
            if (opt.flags & OPT_INT) {
                cfg.enc.fps_num = opt.intval;
                cfg.enc.fps_den = 1;
            } else if (opt.flags & OPT_FLOAT) {
                cfg.enc.fps_num = (int) floorf(1001.0f * opt.floatval + 0.5f);
                cfg.enc.fps_den = 1001;
            } else {
                numparse(opt.stringval, "/", NP_STRICT | NP_EXPECT_INT);
                cfg.enc.fps_num = nums.intval[0];
                cfg.enc.fps_den = nums.intval[1];
            }
            if ((cfg.enc.fps_num <= 0) || (cfg.enc.fps_den <= 0))
                opterr("frame rate must be greater than zero");
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("cbr") || IS_OPT("abr") || IS_OPT("bitrate")) {
            nextopt(OPT_FLOAT | OPT_NONZERO | OPT_POSITIVE | OPT_SI_SUFFIX | OPT_NO_DASH);
            cfg.enc.quality = (int) floorf(opt.floatval / 1000.0f + 0.5f);
            if (cfg.enc.quality < 1)
                opterr("bitrate too small");
            cfg.enc.flags |= ENC_BITRATE;
            if (IS_OPT("abr"))
                cfg.enc.flags &= ~ENC_FIXED_TARGET;
            else
                cfg.enc.flags |= ENC_FIXED_TARGET;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("cqp") || IS_OPT("quantizer")) {
            nextopt(OPT_INT | OPT_NONZERO | OPT_POSITIVE | OPT_NO_DASH);
            cfg.enc.quality = opt.intval;
            cfg.enc.flags = (cfg.enc.flags | ENC_FIXED_TARGET) & ~ENC_BITRATE;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("crf")) {
            nextopt(OPT_INT | OPT_NONZERO | OPT_POSITIVE | OPT_NO_DASH);
            cfg.enc.quality = opt.intval;
            cfg.enc.flags &= ~(ENC_BITRATE | ENC_FIXED_TARGET);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("keyint")) {
            nextopt(OPT_INT | OPT_NONZERO | OPT_POSITIVE | OPT_NO_DASH);
            cfg.enc.keyint = opt.intval;
        }
        else if (IS_OPT("bframes")) {
            nextopt(OPT_INT | OPT_POSITIVE | OPT_NO_DASH);
            cfg.enc.bframes = opt.intval ? opt.intval : -1;
        }
        else if (IS_OPT("refs")) {
            nextopt(OPT_INT | OPT_NONZERO | OPT_POSITIVE | OPT_NO_DASH);
            cfg.enc.refs = opt.intval;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("bt709")) {
            cfg.enc.flags |= ENC_BT709;
        }
        else if (IS_OPT("bt601") || IS_OPT("bt470") || IS_OPT("no-bt709")) {
            cfg.enc.flags &= ~ENC_BT709;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("interlace")) {
            cfg.enc.flags |= ENC_INTERLACE;
        }
        else if (IS_OPT("nointerlace") || IS_OPT("no-interlace") || IS_OPT("progressive")) {
            cfg.enc.flags &= ~ENC_INTERLACE;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("tff")) {
            cfg.tff = 1;
        }
        else if (IS_OPT("bff")) {
            cfg.tff = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("enc") || IS_OPT("encoder")) {
            nextopt(OPT_NONZERO | OPT_LOWERCASE | OPT_NO_DASH);
            cfg.enc.encoder_type = (encoder_type_t) optenum_list(encoders);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("enc-flags") || IS_OPT("encflags") || IS_OPT("flags")) {
            char *pos, *end, pm = '+', next_pm;
            const value_list_t *vl;
            int flag;
            nextopt(OPT_LOWERCASE);
            pos = opt.stringval;
            if ((*pos == '+') || (*pos == '-'))
                pm = *pos++;
            while (pm) {
                for (end = pos;  *end && (*end != '+') && (*end != '-') && (*end != ',');  ++end);
                next_pm = *end;
                *end = '\0';
                flag = 0;
                if (!strcmp(pos, "cavlc")) {
                    pos = (char*) "cabac";
                    pm = (pm == '-') ? '+' : '-';
                }
                for (vl = enc_flags;  vl && vl->name;  ++vl)
                    if (!strcmp(pos, vl->name)) {
                        flag = vl->value;
                        break;
                    }
                if (flag == ENC_NO_DEBLOCK)
                    pm = (pm == '-') ? '+' : '-';
                if (!flag)
                    opterr("unknown sub-option '%s'", pos);
                else if (pm == '-')
                    cfg.enc.flags &= ~flag;
                else
                    cfg.enc.flags |= flag;
                pos = end + 1;
                pm = next_pm;
            }
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("set-defaults") || IS_OPT("defaults") || IS_OPT("default")) {
            state = DEFAULT_SLIDE;
            slide = &defaults;
        }
        else if (IS_OPT("set-trans-defaults") || IS_OPT("trans-defaults") || IS_OPT("trans-default")) {
            state = DEFAULT_TRANS;
            slide = &defaults;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("t") || IS_OPT("duration")) {
            nextopt(OPT_POSITIVE | OPT_FLOAT | OPT_NO_DASH);
            if (state & CURRENT_TRANS)
                slide->trans.duration = opt.floatval;
            else
                slide->core_duration = opt.floatval;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("color") || IS_OPT("background")) {
            char *end;
            unsigned long color;
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            nextopt(OPT_LOWERCASE | OPT_NO_DASH);
            if (opt.stringval[0] == '#')
                opt.stringval++;
            color = strtoul(opt.stringval, &end, 16);
            if (!end || *end)
                opterr("invalid color '%s'", opt.stringval);
            switch (strlen(opt.stringval)) {
                case 3:
                    color = (((color & 0xF00) * 0x11) << 8)
                          |  ((color & 0x0F0) * 0x110)
                          |  ((color & 0x00F) * 0x110000);
                    break;
                case 6:
                    color = ((color & 0xFF0000) >> 8)
                          |  (color & 0x00FF00)
                          | ((color & 0x0000FF) << 8);
                    break;
                default:
                    opterr("invalid color '%s'", opt.stringval);
            }
            slide->background = color;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("pos") || IS_OPT("anim") || IS_OPT("position") || IS_OPT("animate") || IS_OPT("animation")) {
            char *semicolon;
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            nextopt(OPT_NONZERO | OPT_LOWERCASE);
            semicolon = strchr(opt.stringval, ';');
            if (semicolon) {
                *semicolon = '\0';
                parse_pos(opt.stringval, &slide->start);
                parse_pos(semicolon + 1, &slide->end);
            } else {
                parse_pos(opt.stringval, &slide->start);
                slide->end = slide->start;
            }
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("start-pos") || IS_OPT("startpos")) {
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            nextopt(OPT_NONZERO | OPT_LOWERCASE);
            parse_pos(opt.stringval, &slide->start);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("end-pos") || IS_OPT("endpos")) {
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            nextopt(OPT_NONZERO | OPT_LOWERCASE);
            parse_pos(opt.stringval, &slide->end);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("auto-anim") || IS_OPT("autoanim")) {
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            slide->start.flags = KF_AUTOANIM;
            slide->end.flags = KF_AUTOANIM;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("scroll")) {
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            slide->start.flags = KF_VALID;
            slide->start.sx = slide->start.sy = 1.0f;
            slide->start.px = slide->start.py = 0.0f;
            slide->end.flags = KF_VALID;
            slide->end.sx = slide->end.sy = 1.0f;
            slide->end.px = slide->end.py = 1.0f;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("reverse") || IS_OPT("rev") || IS_OPT("swap")) {
            key_t tmp;
            if (state & CURRENT_TRANS)
                opterr("-%s option is not allowed while specifying a transition", opt.stringval);
            tmp = slide->start;
            slide->start = slide->end;
            slide->end = tmp;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("curve")) {
            nextopt(OPT_NONZERO | OPT_LOWERCASE | OPT_NO_DASH);
            if (state & CURRENT_TRANS)
                slide->trans.time_curve = (easing_curve_t) optenum_list(easing_curves);
            else
                slide->curve = (easing_curve_t) optenum_list(easing_curves);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("linear") || IS_OPT("no-smooth") || IS_OPT("nosmooth")) {
            if (state & CURRENT_TRANS)
                slide->trans.time_curve = EC_LINEAR;
            else
                slide->curve = EC_LINEAR;
        }
        else if (IS_OPT("smooth")) {
            if (state & CURRENT_TRANS)
                slide->trans.time_curve = EC_SMOOTH;
            else
                slide->curve = EC_SMOOTH;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("default-trans")) {
            default_trans = 1;
            state = DEFAULT_TRANS;
            slide = &defaults;
        }
        else if (IS_OPT("no-default-trans")) {
            default_trans = 1;
        }
        else if (IS_OPT("notrans") || IS_OPT("no-trans")) {
            no_trans = 1;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("trans") || IS_OPT("trans-type") || IS_OPT("type")) {
            int may_alloc = !IS_OPT("type");
            int force_alloc = IS_OPT("trans");
            if (!has_trans && (force_alloc || (may_alloc && !(state & CURRENT_TRANS)))) {
                slide = new_trans(&defaults);
                if (!slide)
                    opterr("failed to add transition");
                state = CURRENT_TRANS;
                has_trans = 1;
            }
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            if (!IS_OPT("trans")) {
                nextopt(OPT_NONZERO | OPT_LOWERCASE | OPT_NO_DASH);
                slide->trans.type = (trans_type_t) optenum_list(trans_types);
            }
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("map")) {
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            nextopt(OPT_NONZERO | OPT_NO_DASH);
            if (!trans_set_map(slide, opt.stringval))
                opterr("failed to set transition map");
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("band") || IS_OPT("bandsize") || IS_OPT("band-size")) {
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            nextopt(OPT_POSITIVE | OPT_FLOAT | OPT_FLOAT_PERCENT | OPT_NO_DASH);
            slide->trans.map_band_size = opt.floatval;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("map-curve") || IS_OPT("mapcurve")) {
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            nextopt(OPT_NONZERO | OPT_LOWERCASE | OPT_NO_DASH);
            slide->trans.map_curve = (easing_curve_t) optenum_list(easing_curves);
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("inv") || IS_OPT("invert")) {
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            slide->trans.map_invert = 1;
        }
        else if (IS_OPT("no-inv") || IS_OPT("noinv") || IS_OPT("no-invert") || IS_OPT("noinvert")) {
            if (!(state & CURRENT_TRANS))
                opterr("-%s option is only allowed while specifying a transition", opt.stringval);
            slide->trans.map_invert = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("seed")) {
            nextopt(OPT_NO_DASH);
            if (opt.flags & OPT_INT)
                rand_seed((unsigned int) opt.intval);
            else {
                unsigned int seed = 0xDEADBEEF;
                char *c;
                for (c = opt.stringval;  *c;  ++c)
                    seed = ((seed << 5) | (seed >> 27)) ^ (*c);
                rand_seed(seed);
            }
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("iso") || IS_OPT("no-aniso")) {
            autoanim_isotropic = 1;
        }
        else if (IS_OPT("aniso") || IS_OPT("no-iso")) {
            autoanim_isotropic = 0;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else if (IS_OPT("scale-limit")) {
            nextopt(OPT_NONZERO | OPT_POSITIVE | OPT_FLOAT | OPT_FLOAT_PERCENT | OPT_NO_DASH);
            autoanim_scale_limit = opt.floatval;
        }
        /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
        else
            opterr("unrecognized option '-%s'", opt.stringval);
    }
    return 0;
}
