/*
* Copyright(c) 2019 Intel Corporation
*
* This source code is subject to the terms of the BSD 3-Clause Clear License and
* the Alliance for Open Media Patent License 1.0. If the BSD 3-Clause Clear License
* was not distributed with this source code in the LICENSE file, you can
* obtain it at https://www.aomedia.org/license. If the Alliance for Open
* Media Patent License 1.0 was not distributed with this source code in the
* PATENTS file, you can obtain it at https://www.aomedia.org/license/patent-license.
*/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <ctype.h>
#include <sys/stat.h>

#include "EbSvtAv1Metadata.h"
#include "EbAppConfig.h"
#include "EbAppContext.h"
#include "EbAppInputy4m.h"
#ifdef _WIN32
#include <windows.h>
#include <io.h>
#else
#include <unistd.h>
#include <sys/file.h>
#endif

#include "EbAppOutputivf.h"

#if !defined(_WIN32) || !defined(HAVE_STRNLEN_S)
#include "third_party/safestringlib/safe_str_lib.h"
#endif

/**********************************
 * Defines
 **********************************/
#define HELP_TOKEN "--help"
#define VERSION_TOKEN "--version"
#define CHANNEL_NUMBER_TOKEN "--nch"
#define COMMAND_LINE_MAX_SIZE 2048
#define CONFIG_FILE_TOKEN "-c"
#define CONFIG_FILE_LONG_TOKEN "--config"
#define INPUT_FILE_TOKEN "-i"
#define OUTPUT_BITSTREAM_TOKEN "-b"
#define OUTPUT_RECON_TOKEN "-o"
#define ERROR_FILE_TOKEN "--errlog"

/* two pass token */
#define PASS_TOKEN "--pass"
#define TWO_PASS_STATS_TOKEN "--stats"
#define PASSES_TOKEN "--passes"
#define STAT_FILE_TOKEN "--stat-file"
#define WIDTH_TOKEN "-w"
#define HEIGHT_TOKEN "-h"
#define NUMBER_OF_PICTURES_TOKEN "-n"
#define BUFFERED_INPUT_TOKEN "--nb"
#define NO_PROGRESS_TOKEN "--no-progress" // tbd if it should be removed
#define PROGRESS_TOKEN "--progress"
#define QP_TOKEN "-q"
#define USE_QP_FILE_TOKEN "--use-q-file"
#define FORCE_KEY_FRAMES_TOKEN "--force-key-frames"

#define USE_FIXED_QINDEX_OFFSETS_TOKEN "--use-fixed-qindex-offsets"
#define QINDEX_OFFSETS_TOKEN "--qindex-offsets"
#define KEY_FRAME_QINDEX_OFFSET_TOKEN "--key-frame-qindex-offset"
#define KEY_FRAME_CHROMA_QINDEX_OFFSET_TOKEN "--key-frame-chroma-qindex-offset"
#define CHROMA_QINDEX_OFFSETS_TOKEN "--chroma-qindex-offsets"
#define LUMA_Y_DC_QINDEX_OFFSET_TOKEN "--luma-y-dc-qindex-offset"
#define CHROMA_U_DC_QINDEX_OFFSET_TOKEN "--chroma-u-dc-qindex-offset"
#define CHROMA_U_AC_QINDEX_OFFSET_TOKEN "--chroma-u-ac-qindex-offset"
#define CHROMA_V_DC_QINDEX_OFFSET_TOKEN "--chroma-v-dc-qindex-offset"
#define CHROMA_V_AC_QINDEX_OFFSET_TOKEN "--chroma-v-ac-qindex-offset"

// scale factors for lambda value for different frame types
#define LAMBDA_SCALE_FACTORS_TOKEN "--lambda-scale-factors"

#define FRAME_RATE_TOKEN "--fps"
#define FRAME_RATE_NUMERATOR_TOKEN "--fps-num"
#define FRAME_RATE_DENOMINATOR_TOKEN "--fps-denom"
#define ENCODER_COLOR_FORMAT "--color-format"
#define HIERARCHICAL_LEVELS_TOKEN "--hierarchical-levels" // no Eval
#define PRED_STRUCT_TOKEN "--pred-struct"
#define PROFILE_TOKEN "--profile"
#define INTRA_PERIOD_TOKEN "--intra-period"
#define TIER_TOKEN "--tier"
#define LEVEL_TOKEN "--level"
#define FILM_GRAIN_TOKEN "--film-grain"
#define FILM_GRAIN_DENOISE_APPLY_TOKEN "--film-grain-denoise"
#define INTRA_REFRESH_TYPE_TOKEN "--irefresh-type" // no Eval
#define CDEF_ENABLE_TOKEN "--enable-cdef"
#define SCREEN_CONTENT_TOKEN "--scm"
// --- start: ALTREF_FILTERING_SUPPORT
#define ENABLE_TF_TOKEN "--enable-tf"
#define ENABLE_OVERLAYS "--enable-overlays"
#define TUNE_TOKEN "--tune"
// --- end: ALTREF_FILTERING_SUPPORT
// --- start: SUPER-RESOLUTION SUPPORT
#define SUPERRES_MODE_INPUT "--superres-mode"
#define SUPERRES_DENOM "--superres-denom"
#define SUPERRES_KF_DENOM "--superres-kf-denom"
#define SUPERRES_QTHRES "--superres-qthres"
#define SUPERRES_KF_QTHRES "--superres-kf-qthres"
// --- end: SUPER-RESOLUTION SUPPORT
// --- start: REFERENCE SCALING SUPPORT
#define RESIZE_MODE_INPUT "--resize-mode"
#define RESIZE_DENOM "--resize-denom"
#define RESIZE_KF_DENOM "--resize-kf-denom"
#define RESIZE_FRAME_EVTS "--frame-resz-events"
#define RESIZE_FRAME_KF_DENOMS "--frame-resz-kf-denoms"
#define RESIZE_FRAME_DENOMS "--frame-resz-denoms"
// --- end: REFERENCE SCALING SUPPORT
#define RATE_CONTROL_ENABLE_TOKEN "--rc"
#define TARGET_BIT_RATE_TOKEN "--tbr"
#define MAX_BIT_RATE_TOKEN "--mbr"
#define MAX_QP_TOKEN "--max-qp"
#define MIN_QP_TOKEN "--min-qp"
#define VBR_BIAS_PCT_TOKEN "--bias-pct"
#define VBR_MIN_SECTION_PCT_TOKEN "--minsection-pct"
#define VBR_MAX_SECTION_PCT_TOKEN "--maxsection-pct"
#define UNDER_SHOOT_PCT_TOKEN "--undershoot-pct"
#define OVER_SHOOT_PCT_TOKEN "--overshoot-pct"
#define MBR_OVER_SHOOT_PCT_TOKEN "--mbr-overshoot-pct"
#define GOP_CONSTRAINT_RC_TOKEN "--gop-constraint-rc"
#define BUFFER_SIZE_TOKEN "--buf-sz"
#define BUFFER_INITIAL_SIZE_TOKEN "--buf-initial-sz"
#define BUFFER_OPTIMAL_SIZE_TOKEN "--buf-optimal-sz"
#define RECODE_LOOP_TOKEN "--recode-loop"
#define ENABLE_TPL_LA_TOKEN "--enable-tpl-la"
#define TILE_ROW_TOKEN "--tile-rows"
#define TILE_COL_TOKEN "--tile-columns"

#define SCENE_CHANGE_DETECTION_TOKEN "--scd"
#define INJECTOR_TOKEN "--inj" // no Eval
#define INJECTOR_FRAMERATE_TOKEN "--inj-frm-rt" // no Eval
#define ASM_TYPE_TOKEN "--asm"
#define THREAD_MGMNT "--lp"
#define PIN_TOKEN "--pin"
#define TARGET_SOCKET "--ss"
#define RESTRICTED_MOTION_VECTOR "--rmv"

//double dash
#define PRESET_TOKEN "--preset"
#define QP_FILE_NEW_TOKEN "--qpfile"
#define INPUT_DEPTH_TOKEN "--input-depth"
#define KEYINT_TOKEN "--keyint"
#define LOOKAHEAD_NEW_TOKEN "--lookahead"
#define SVTAV1_PARAMS "--svtav1-params"

#define STAT_REPORT_NEW_TOKEN "--enable-stat-report"
#define ENABLE_RESTORATION_TOKEN "--enable-restoration"
#define MFMV_ENABLE_NEW_TOKEN "--enable-mfmv"
#define DG_ENABLE_NEW_TOKEN "--enable-dg"
#define FAST_DECODE_TOKEN "--fast-decode"
#define HDR_INPUT_NEW_TOKEN "--enable-hdr"
#define ADAPTIVE_QP_ENABLE_NEW_TOKEN "--aq-mode"
#define INPUT_FILE_LONG_TOKEN "--input"
#define OUTPUT_BITSTREAM_LONG_TOKEN "--output"
#define OUTPUT_RECON_LONG_TOKEN "--recon"
#define WIDTH_LONG_TOKEN "--width"
#define HEIGHT_LONG_TOKEN "--height"
#define NUMBER_OF_PICTURES_LONG_TOKEN "--frames"
#define NUMBER_OF_PICTURES_TO_SKIP "--skip"

#define QP_LONG_TOKEN "--qp"
#define CRF_LONG_TOKEN "--crf"
#define LOOP_FILTER_ENABLE "--enable-dlf"
#define FORCED_MAX_FRAME_WIDTH_TOKEN "--forced-max-frame-width"
#define FORCED_MAX_FRAME_HEIGHT_TOKEN "--forced-max-frame-height"

#define COLOR_PRIMARIES_NEW_TOKEN "--color-primaries"
#define TRANSFER_CHARACTERISTICS_NEW_TOKEN "--transfer-characteristics"
#define MATRIX_COEFFICIENTS_NEW_TOKEN "--matrix-coefficients"
#define COLOR_RANGE_NEW_TOKEN "--color-range"
#define CHROMA_SAMPLE_POSITION_TOKEN "--chroma-sample-position"
#define MASTERING_DISPLAY_TOKEN "--mastering-display"
#define CONTENT_LIGHT_LEVEL_TOKEN "--content-light"

#define SFRAME_DIST_TOKEN "--sframe-dist"
#define SFRAME_MODE_TOKEN "--sframe-mode"

#define ENABLE_QM_TOKEN "--enable-qm"
#define MIN_QM_LEVEL_TOKEN "--qm-min"
#define MAX_QM_LEVEL_TOKEN "--qm-max"

#define STARTUP_MG_SIZE_TOKEN "--startup-mg-size"

#define ROI_MAP_FILE_TOKEN "--roi-map-file"

static EbErrorType validate_error(EbErrorType err, const char *token, const char *value) {
    switch (err) {
    case EB_ErrorNone: return EB_ErrorNone;
    default: fprintf(stderr, "Error: Invalid parameter '%s' with value '%s'\n", token, value); return err;
    }
}

/* copied from EbEncSettings.c */
static EbErrorType str_to_int64(const char *token, const char *nptr, int64_t *out) {
    char   *endptr;
    int64_t val;

    val = strtoll(nptr, &endptr, 0);

    if (endptr == nptr || *endptr)
        return validate_error(EB_ErrorBadParameter, token, nptr);

    *out = val;
    return EB_ErrorNone;
}

static EbErrorType str_to_int(const char *token, const char *nptr, int32_t *out) {
    char   *endptr;
    int32_t val;

    val = strtol(nptr, &endptr, 0);

    if (endptr == nptr || *endptr)
        return validate_error(EB_ErrorBadParameter, token, nptr);

    *out = val;
    return EB_ErrorNone;
}

static EbErrorType str_to_uint(const char *token, const char *nptr, uint32_t *out) {
    char    *endptr;
    uint32_t val;

    if (strtol(nptr, NULL, 0) < 0) {
        fprintf(stderr,
                "Error: Invalid parameter '%s' with value '%s'. Token unable to accept negative "
                "values\n",
                token,
                nptr);
        return EB_ErrorBadParameter;
    }

    val = strtoul(nptr, &endptr, 0);

    if (endptr == nptr || *endptr)
        return validate_error(EB_ErrorBadParameter, token, nptr);

    *out = val;
    return EB_ErrorNone;
}

static EbErrorType str_to_str(const char *nptr, char **out, const char *token) {
    (void)token;
    if (*out) {
        free(*out);
        *out = NULL;
    }
    const size_t len = strlen(nptr) + 1;
    char        *buf = (char *)malloc(len);
    if (!buf)
        return validate_error(EB_ErrorInsufficientResources, token, nptr);
    if (strcpy_s(buf, len, nptr)) {
        free(buf);
        return validate_error(EB_ErrorInsufficientResources, token, nptr);
    }
    *out = buf;
    return EB_ErrorNone;
}

#ifdef _WIN32
static HANDLE get_file_handle(FILE *fp) { return (HANDLE)_get_osfhandle(_fileno(fp)); }
#endif

static Bool fopen_and_lock(FILE **file, const char *name, Bool write) {
    if (!file || !name)
        return FALSE;

    const char *mode = write ? "wb" : "rb";
    FOPEN(*file, name, mode);
    if (!*file)
        return FALSE;

#ifdef _WIN32
    HANDLE handle = get_file_handle(*file);
    if (handle == INVALID_HANDLE_VALUE)
        return FALSE;
    if (LockFile(handle, 0, 0, MAXDWORD, MAXDWORD))
        return TRUE;
#else
    int fd = fileno(*file);
    if (flock(fd, LOCK_EX | LOCK_NB) == 0)
        return TRUE;
#endif
    fprintf(stderr, "ERROR: locking %s failed, is it used by other encoder?\n", name);
    return FALSE;
}

static EbErrorType open_file(FILE **file, const char *token, const char *name, const char *mode) {
    if (!file || !name)
        return validate_error(EB_ErrorBadParameter, token, "");

    if (*file) {
        fclose(*file);
        *file = NULL;
    }

    FILE *f;
    FOPEN(f, name, mode);
    if (!f)
        return validate_error(EB_ErrorBadParameter, token, name);

    *file = f;
    return EB_ErrorNone;
}

/**********************************
 * Set Cfg Functions
 **********************************/
// file options
static EbErrorType set_cfg_input_file(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    if (cfg->input_file && !cfg->input_file_is_fifo)
        fclose(cfg->input_file);

    if (!value) {
        cfg->input_file = NULL;
        return validate_error(EB_ErrorBadParameter, token, "");
    }

    if (!strcmp(value, "stdin") || !strcmp(value, "-")) {
        cfg->input_file         = stdin;
        cfg->input_file_is_fifo = TRUE;
    } else
        FOPEN(cfg->input_file, value, "rb");

    if (cfg->input_file == NULL) {
        return validate_error(EB_ErrorBadParameter, token, value);
    }
    if (cfg->input_file != stdin) {
#ifdef _WIN32
        HANDLE handle = (HANDLE)_get_osfhandle(_fileno(cfg->input_file));
        if (handle == INVALID_HANDLE_VALUE)
            return validate_error(EB_ErrorBadParameter, token, value);
        cfg->input_file_is_fifo = GetFileType(handle) == FILE_TYPE_PIPE;
#else
        int         fd = fileno(cfg->input_file);
        struct stat statbuf;
        fstat(fd, &statbuf);
        cfg->input_file_is_fifo = S_ISFIFO(statbuf.st_mode);
#endif
    }

    cfg->y4m_input = check_if_y4m(cfg);
    return EB_ErrorNone;
}
static EbErrorType set_cfg_stream_file(EbConfig *cfg, const char *token, const char *value) {
    if (!strcmp(value, "stdout") || !strcmp(value, "-")) {
        if (cfg->bitstream_file && cfg->bitstream_file != stdout) {
            fclose(cfg->bitstream_file);
        }
        cfg->bitstream_file = stdout;
        return EB_ErrorNone;
    }
    return open_file(&cfg->bitstream_file, token, value, "wb");
}
static EbErrorType set_cfg_error_file(EbConfig *cfg, const char *token, const char *value) {
    if (!strcmp(value, "stderr")) {
        if (cfg->error_log_file && cfg->error_log_file != stderr) {
            fclose(cfg->error_log_file);
        }
        cfg->error_log_file = stderr;
        return EB_ErrorNone;
    }
    return open_file(&cfg->error_log_file, token, value, "w+");
}
static EbErrorType set_cfg_recon_file(EbConfig *cfg, const char *token, const char *value) {
    return open_file(&cfg->recon_file, token, value, "wb");
}
static EbErrorType set_cfg_qp_file(EbConfig *cfg, const char *token, const char *value) {
    return open_file(&cfg->qp_file, token, value, "r");
}
static EbErrorType set_cfg_stat_file(EbConfig *cfg, const char *token, const char *value) {
    return open_file(&cfg->stat_file, token, value, "wb");
}
static EbErrorType set_cfg_roi_map_file(EbConfig *cfg, const char *token, const char *value) {
    return open_file(&cfg->roi_map_file, token, value, "r");
}

static EbErrorType set_two_pass_stats(EbConfig *cfg, const char *token, const char *value) {
    return str_to_str(value, (char **)&cfg->stats, token);
}

static EbErrorType set_passes(EbConfig *cfg, const char *token, const char *value) {
    (void)cfg;
    (void)token;
    (void)value;
    /* empty function, we will handle passes at higher level*/
    return EB_ErrorNone;
}

static EbErrorType set_cfg_frames_to_be_encoded(EbConfig *cfg, const char *token, const char *value) {
    return str_to_int64(token, value, &cfg->frames_to_be_encoded);
}
static EbErrorType set_cfg_frames_to_be_skipped(EbConfig *cfg, const char *token, const char *value) {
    EbErrorType ret = str_to_int64(token, value, &cfg->frames_to_be_skipped);
    if (cfg->frames_to_be_skipped > 0)
        cfg->need_to_skip = true;
    return ret;
}
static EbErrorType set_buffered_input(EbConfig *cfg, const char *token, const char *value) {
    return str_to_int(token, value, &cfg->buffered_input);
}
static EbErrorType set_cfg_force_key_frames(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    struct forced_key_frames fkf;
    fkf.specifiers = NULL;
    fkf.frames     = NULL;
    fkf.count      = 0;

    if (!value)
        return EB_ErrorBadParameter;
    const char *p = value;
    while (p) {
        const size_t len       = strcspn(p, ",");
        char        *specifier = (char *)calloc(sizeof(*specifier), len + 1);
        if (!specifier)
            goto err;
        memcpy(specifier, p, len);
        char **tmp = (char **)realloc(fkf.specifiers, sizeof(*fkf.specifiers) * (fkf.count + 1));
        if (!tmp) {
            free(specifier);
            goto err;
        }
        fkf.specifiers            = tmp;
        fkf.specifiers[fkf.count] = specifier;
        fkf.count++;
        if ((p = strchr(p, ',')))
            ++p;
    }

    if (!fkf.count)
        goto err;

    fkf.frames = (uint64_t *)calloc(sizeof(*fkf.frames), fkf.count);
    if (!fkf.frames)
        goto err;
    for (size_t i = 0; i < cfg->forced_keyframes.count; ++i) free(cfg->forced_keyframes.specifiers[i]);
    free(cfg->forced_keyframes.specifiers);
    free(cfg->forced_keyframes.frames);
    cfg->forced_keyframes = fkf;
    svt_av1_enc_parse_parameter(&cfg->config, "enable-force-key-frames", "true");
    return EB_ErrorNone;
err:
    fputs("Error parsing forced key frames list\n", stderr);
    for (size_t i = 0; i < fkf.count; ++i) free(fkf.specifiers[i]);
    free(fkf.specifiers);
    return EB_ErrorBadParameter;
}
static EbErrorType set_no_progress(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    switch (value ? *value : '1') {
    case '0': cfg->progress = 1; break; // equal to --progress 1
    default: cfg->progress = 0; break; // equal to --progress 0
    }
    return EB_ErrorNone;
}
static EbErrorType set_progress(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    switch (value ? *value : '1') {
    case '0': cfg->progress = 0; break; // no progress printed
    case '2': cfg->progress = 2; break; // aomenc style progress
    default: cfg->progress = 1; break; // default progress
    }
    return EB_ErrorNone;
}
static EbErrorType set_frame_rate(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    cfg->config.frame_rate_numerator   = strtoul(value, NULL, 0);
    cfg->config.frame_rate_denominator = 1;
    return EB_ErrorNone;
}

/**
 * @brief split colon separated string into key=value pairs
 *
 * @param[in]  str colon separated string of key=val
 * @param[out] opt key and val, both need to be freed
 */
struct ParseOpt {
    char *key;
    char *val;
};

static struct ParseOpt split_colon_keyequalval_pairs(const char **p) {
    const char     *str        = *p;
    struct ParseOpt opt        = {NULL, NULL};
    const size_t    string_len = strcspn(str, ":");

    const char *val = strchr(str, '=');
    if (!val || !*++val)
        return opt;

    const size_t key_len = val - str - 1;
    const size_t val_len = string_len - key_len - 1;
    if (!key_len || !val_len)
        return opt;

    opt.key = (char *)malloc(key_len + 1);
    opt.val = (char *)malloc(val_len + 1);
    if (!opt.key || !opt.val) {
        free(opt.key);
        opt.key = NULL;
        free(opt.val);
        opt.val = NULL;
        return opt;
    }

    memcpy(opt.key, str, key_len);
    memcpy(opt.val, val, val_len);
    opt.key[key_len] = '\0';
    opt.val[val_len] = '\0';
    str += string_len;
    if (*str)
        str++;
    *p = str;
    return opt;
}

static EbErrorType parse_svtav1_params(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    const char *p   = value;
    EbErrorType err = EB_ErrorNone;
    while (*p) {
        struct ParseOpt opt = split_colon_keyequalval_pairs(&p);
        if (!opt.key || !opt.val)
            continue;
        err = (EbErrorType)(err | svt_av1_enc_parse_parameter(&cfg->config, opt.key, opt.val));
        if (err != EB_ErrorNone) {
            fprintf(stderr, "Warning: failed to set parameter '%s' with key '%s'\n", opt.key, opt.val);
        }
        free(opt.key);
        free(opt.val);
    }
    return err;
}

static EbErrorType set_cdef_enable(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    // Set CDEF to either DEFAULT or 0
    int32_t     cdef_enable = DEFAULT;
    EbErrorType err         = str_to_int(token, value, &cdef_enable);
    cfg->config.cdef_level  = (cdef_enable == 0) ? 0 : DEFAULT;
    return err;
};

static EbErrorType set_level(EbConfig *cfg, const char *token, const char *value) {
    (void)token;
    if (strtoul(value, NULL, 0) != 0 || strcmp(value, "0") == 0)
        cfg->config.level = (uint32_t)(10 * strtod(value, NULL));
    else
        cfg->config.level = 9999999;
    return EB_ErrorNone;
};
static EbErrorType set_injector(EbConfig *cfg, const char *token, const char *value) {
    return str_to_uint(token, value, &cfg->injector);
}

static EbErrorType set_injector_frame_rate(EbConfig *cfg, const char *token, const char *value) {
    return str_to_uint(token, value, &cfg->injector_frame_rate);
}

static EbErrorType set_cfg_generic_token(EbConfig *cfg, const char *token, const char *value) {
    if (!strncmp(token, "--", 2))
        token += 2;
    if (!strncmp(token, "-", 1))
        token += 1;
    return validate_error(svt_av1_enc_parse_parameter(&cfg->config, token, value), token, value);
}

enum CfgType {
    SINGLE_INPUT, // Configuration parameters that have only 1 value input
    ARRAY_INPUT // Configuration parameters that have multiple values as input
};

/**********************************
 * Config Entry Struct
 **********************************/
typedef struct config_entry_s {
    enum CfgType type;
    const char  *token;
    const char  *name;
    EbErrorType (*scf)(EbConfig *cfg, const char *token, const char *value);
} ConfigEntry;

/**********************************
 * Config Entry Array
 **********************************/
ConfigEntry config_entry_options[] = {
    // File I/O
    {SINGLE_INPUT, HELP_TOKEN, "Shows the command line options currently available", set_cfg_input_file},
    {SINGLE_INPUT, VERSION_TOKEN, "Shows the version of the library that's linked to the library", set_cfg_input_file},
    {SINGLE_INPUT,
     INPUT_FILE_TOKEN,
     "Input raw video (y4m and yuv) file path, use `stdin` or `-` to read from pipe",
     set_cfg_input_file},
    {SINGLE_INPUT,
     INPUT_FILE_LONG_TOKEN,
     "Input raw video (y4m and yuv) file path, use `stdin` or `-` to read from pipe",
     set_cfg_input_file},

    {SINGLE_INPUT,
     OUTPUT_BITSTREAM_TOKEN,
     "Output compressed (ivf) file path, use `stdout` or `-` to write to pipe",
     set_cfg_stream_file},
    {SINGLE_INPUT,
     OUTPUT_BITSTREAM_LONG_TOKEN,
     "Output compressed (ivf) file path, use `stdout` or `-` to write to pipe",
     set_cfg_stream_file},

    {SINGLE_INPUT, CONFIG_FILE_TOKEN, "Configuration file path", set_cfg_input_file},
    {SINGLE_INPUT, CONFIG_FILE_LONG_TOKEN, "Configuration file path", set_cfg_input_file},

    {SINGLE_INPUT, ERROR_FILE_TOKEN, "Error file path, defaults to stderr", set_cfg_error_file},
    {SINGLE_INPUT, OUTPUT_RECON_TOKEN, "Reconstructed yuv file path", set_cfg_recon_file},
    {SINGLE_INPUT, OUTPUT_RECON_LONG_TOKEN, "Reconstructed yuv file path", set_cfg_recon_file},

    {SINGLE_INPUT,
     STAT_FILE_TOKEN,
     "PSNR / SSIM per picture stat output file path, requires `--enable-stat-report 1`",
     set_cfg_stat_file},

    {SINGLE_INPUT,
     PROGRESS_TOKEN,
     "Verbosity of the output, default is 1 [0: no progress is printed, 2: aomenc style output]",
     set_progress},
    {SINGLE_INPUT,
     NO_PROGRESS_TOKEN,
     "Do not print out progress, default is 0 [1: `" PROGRESS_TOKEN " 0`, 0: `" PROGRESS_TOKEN " 1`]",
     set_no_progress},

    {SINGLE_INPUT,
     PRESET_TOKEN,
     "Encoder preset, presets < 0 are for debugging. Higher presets means faster encodes, but with "
     "a quality tradeoff, default is 10 [-1-13]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     SVTAV1_PARAMS,
     "colon separated list of key=value pairs of parameters with keys based on config file options",
     parse_svtav1_params},

    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_global_options[] = {
    // Picture Dimensions
    {SINGLE_INPUT,
     WIDTH_TOKEN,
     "Frame width in pixels, inferred if y4m, default is 0 [64-16384]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     WIDTH_LONG_TOKEN,
     "Frame width in pixels, inferred if y4m, default is 0 [64-16384]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     HEIGHT_TOKEN,
     "Frame height in pixels, inferred if y4m, default is 0 [64-8704]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     HEIGHT_LONG_TOKEN,
     "Frame height in pixels, inferred if y4m, default is 0 [64-8704]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     FORCED_MAX_FRAME_WIDTH_TOKEN,
     "Maximum frame width value to force, default is 0 [64-16384]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     FORCED_MAX_FRAME_HEIGHT_TOKEN,
     "Maximum frame height value to force, default is 0 [64-8704]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     NUMBER_OF_PICTURES_TOKEN,
     "Number of frames to encode. If `n` is larger than the input, the encoder will loop back and "
     "continue encoding, default is 0 [0: until EOF, 1-`(2^63)-1`]",
     set_cfg_frames_to_be_encoded},

    {SINGLE_INPUT,
     NUMBER_OF_PICTURES_TO_SKIP,
     "Number of frames to skip. Default is 0 [0: don`t skip, 1-`(2^63)-1`]",
     set_cfg_frames_to_be_skipped},

    {SINGLE_INPUT,
     NUMBER_OF_PICTURES_LONG_TOKEN,
     "Number of frames to encode. If `n` is larger than the input, the encoder will loop back and "
     "continue encoding, default is 0 [0: until EOF, 1-`(2^63)-1`]",
     set_cfg_frames_to_be_encoded},

    {SINGLE_INPUT,
     BUFFERED_INPUT_TOKEN,
     "Buffer `n` input frames into memory and use them to encode, default is -1 [-1: no frames "
     "buffered, 1-`(2^31)-1`]",
     set_buffered_input},
    {SINGLE_INPUT,
     ENCODER_COLOR_FORMAT,
     "Color format, only yuv420 is supported at this time, default is 1 [0: yuv400, 1: yuv420, 2: "
     "yuv422, 3: yuv444]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     PROFILE_TOKEN,
     "Bitstream profile, default is 0 [0: main, 1: high, 2: professional]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     LEVEL_TOKEN,
     "Bitstream level, defined in A.3 of the av1 spec, default is 0 [0: autodetect from input, "
     "2.0-7.3]",
     set_level},
    {SINGLE_INPUT,
     HDR_INPUT_NEW_TOKEN,
     "Enable writing of HDR metadata in the bitstream, default is 0 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     FRAME_RATE_TOKEN,
     "Input video frame rate, integer values only, inferred if y4m, default is 60 [1-240]",
     set_frame_rate},
    {SINGLE_INPUT,
     FRAME_RATE_NUMERATOR_TOKEN,
     "Input video frame rate numerator, default is 60000 [0-2^32-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     FRAME_RATE_DENOMINATOR_TOKEN,
     "Input video frame rate denominator, default is 1000 [0-2^32-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     INPUT_DEPTH_TOKEN,
     "Input video file and output bitstream bit-depth, default is 8 [8, 10]",
     set_cfg_generic_token},
    // Latency
    {SINGLE_INPUT,
     INJECTOR_TOKEN,
     "Inject pictures to the library at defined frame rate, default is 0 [0-1]",
     set_injector},
    {SINGLE_INPUT,
     INJECTOR_FRAMERATE_TOKEN,
     "Set injector frame rate, only applicable with `--inj 1`, default is 60 [0-240]",
     set_injector_frame_rate},
    {SINGLE_INPUT,
     STAT_REPORT_NEW_TOKEN,
     "Calculates and outputs PSNR SSIM metrics at the end of encoding, default is 0 [0-1]",
     set_cfg_generic_token},

    // Asm Type
    {SINGLE_INPUT,
     ASM_TYPE_TOKEN,
     "Limit assembly instruction set, only applicable to x86, default is max [c, mmx, sse, sse2, "
     "sse3, ssse3, sse4_1, sse4_2, avx, avx2, avx512, max]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     THREAD_MGMNT,
     "Target (best effort) number of logical cores to be used. 0 means all. Refer to Appendix A.1 "
     "of the user "
     "guide, default is 0 [0, core count of the machine]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     PIN_TOKEN,
     "Pin the execution to the first --lp cores. Overwritten to 1 when `--ss` is set. Refer to "
     "Appendix "
     "A.1 of the user guide, default is 0 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     TARGET_SOCKET,
     "Specifies which socket to run on, assumes a max of two sockets. Refer to Appendix A.1 of the "
     "user guide, default is -1 [-1, 0, -1]",
     set_cfg_generic_token},
    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_rc[] = {
    // Rate Control
    {SINGLE_INPUT,
     RATE_CONTROL_ENABLE_TOKEN,
     "Rate control mode, default is 0 [0: CRF or CQP (if `--aq-mode` is 0), 1: VBR, 2: CBR]",
     set_cfg_generic_token},
    {SINGLE_INPUT, QP_TOKEN, "Initial QP level value, default is 35 [1-63]", set_cfg_generic_token},
    {SINGLE_INPUT, QP_LONG_TOKEN, "Initial QP level value, default is 35 [1-63]", set_cfg_generic_token},
    {SINGLE_INPUT,
     CRF_LONG_TOKEN,
     "Constant Rate Factor value, setting this value is equal to `--rc 0 --aq-mode 2 --qp "
     "x`, default is 35 [1-63]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     TARGET_BIT_RATE_TOKEN,
     "Target Bitrate (kbps), only applicable for VBR and CBR encoding, default is 7000 [1-100000]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     MAX_BIT_RATE_TOKEN,
     "Maximum Bitrate (kbps) only applicable for CRF encoding, default is 0 [1-100000]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     USE_QP_FILE_TOKEN,
     "Overwrite the encoder default picture based QP assignments and use QP values from "
     "`--qp-file`, default is 0 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     QP_FILE_NEW_TOKEN,
     "Path to a file containing per picture QP value separated by newlines",
     set_cfg_qp_file},
    {SINGLE_INPUT,
     MAX_QP_TOKEN,
     "Maximum (highest) quantizer, only applicable for VBR and CBR, default is 63 [1-63]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     MIN_QP_TOKEN,
     "Minimum (lowest) quantizer, only applicable for VBR and CBR, default is 1 [1-63]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     ADAPTIVE_QP_ENABLE_NEW_TOKEN,
     "Set adaptive QP level, default is 2 [0: off, 1: variance base using AV1 segments, 2: deltaq "
     "pred efficiency]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     USE_FIXED_QINDEX_OFFSETS_TOKEN,
     "Overwrite the encoder default hierarchical layer based QP assignment and use fixed Q index "
     "offsets, default is 0 [0-2]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     KEY_FRAME_QINDEX_OFFSET_TOKEN,
     "Overwrite the encoder default keyframe Q index assignment, default is 0 [-256-255]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     KEY_FRAME_CHROMA_QINDEX_OFFSET_TOKEN,
     "Overwrite the encoder default chroma keyframe Q index assignment, default is 0 [-256-255]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     QINDEX_OFFSETS_TOKEN,
     "list of luma Q index offsets per hierarchical layer, separated by `,` with each offset in "
     "the range of [-256-255], default is `0,0,..,0`",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     CHROMA_QINDEX_OFFSETS_TOKEN,
     "list of chroma Q index offsets per hierarchical layer, separated by `,` with each offset in "
     "the range of [-256-255], default is `0,0,..,0`",
     set_cfg_generic_token},
    {SINGLE_INPUT, LUMA_Y_DC_QINDEX_OFFSET_TOKEN, "Luma Y DC Qindex Offset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_U_DC_QINDEX_OFFSET_TOKEN, "Chroma U DC Qindex Offset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_U_AC_QINDEX_OFFSET_TOKEN, "Chroma U AC Qindex Offset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_V_DC_QINDEX_OFFSET_TOKEN, "Chroma V DC Qindex Offset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_V_AC_QINDEX_OFFSET_TOKEN, "Chroma V AC Qindex Offset", set_cfg_generic_token},
    {SINGLE_INPUT,
     LAMBDA_SCALE_FACTORS_TOKEN,
     "list of scale factor for lambda values used for different frame types defined by SvtAv1FrameUpdateType, separated by `,` \
      with each scale factor as integer. \
      value divided by 128 is the actual scale factor in float, default is `128,128,..,128`",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     UNDER_SHOOT_PCT_TOKEN,
     "Only for VBR and CBR, allowable datarate undershoot (min) target (percentage), default is "
     "25, but can change based on rate control [0-100]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     OVER_SHOOT_PCT_TOKEN,
     "Only for VBR and CBR, allowable datarate overshoot (max) target (percentage), default is 25, "
     "but can change based on rate control [0-100]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     MBR_OVER_SHOOT_PCT_TOKEN,
     "Only for Capped CRF, allowable datarate overshoot (max) target (percentage), default is 50, "
     "but can change based on rate control [0-100]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     GOP_CONSTRAINT_RC_TOKEN,
     "Enable GoP constraint rc.  When enabled, the rate control matches the target rate for each "
     "GoP, default is 0 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     BUFFER_SIZE_TOKEN,
     "Client buffer size (ms), only applicable for CBR, default is 6000 [0-10000]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     BUFFER_INITIAL_SIZE_TOKEN,
     "Client initial buffer size (ms), only applicable for CBR, default is 4000 [0-10000]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     BUFFER_OPTIMAL_SIZE_TOKEN,
     "Client optimal buffer size (ms), only applicable for CBR, default is 5000 [0-10000]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     RECODE_LOOP_TOKEN,
     "Recode loop level, refer to \"Recode loop level table\" in the user guide for more info [0: "
     "off, 4: preset based]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     VBR_BIAS_PCT_TOKEN,
     "CBR/VBR bias, default is 50 [0: CBR-like, 1-99, 100: VBR-like]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     VBR_MIN_SECTION_PCT_TOKEN,
     "GOP min bitrate (expressed as a percentage of the target rate), default is 0 [0-100]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     VBR_MAX_SECTION_PCT_TOKEN,
     "GOP max bitrate (expressed as a percentage of the target rate), default is 2000 [0-10000]",
     set_cfg_generic_token},
    {SINGLE_INPUT, ENABLE_QM_TOKEN, "Enable quantisation matrices, default is 0 [0-1]", set_cfg_generic_token},
    {SINGLE_INPUT, MIN_QM_LEVEL_TOKEN, "Min quant matrix flatness, default is 8 [0-15]", set_cfg_generic_token},
    {SINGLE_INPUT, MAX_QM_LEVEL_TOKEN, "Max quant matrix flatness, default is 15 [0-15]", set_cfg_generic_token},
    {SINGLE_INPUT,
     ROI_MAP_FILE_TOKEN,
     "Enable Region Of Interest and specify a picture based QP Offset map file, default is off",
     set_cfg_roi_map_file},
    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_2p[] = {
    // 2 pass
    {SINGLE_INPUT,
     PASS_TOKEN,
     "Multi-pass selection, pass 3 is only available for VBR, default is 0 [0: single pass encode, "
     "1: first pass, 2: second pass, 3: third pass]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     TWO_PASS_STATS_TOKEN,
     "Filename for multi-pass encoding, default is \"svtav1_2pass.log\"",
     set_two_pass_stats},
    {SINGLE_INPUT,
     PASSES_TOKEN,
     "Number of encoding passes, default is preset dependent but generally 1 [1: one pass encode, "
     "2: multi-pass encode]",
     set_passes},
    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_intra_refresh[] = {
    {SINGLE_INPUT,
     KEYINT_TOKEN,
     "GOP size (frames), default is -2 [-2: ~5 seconds, -1: \"infinite\" and only applicable for "
     "CRF, 0: same as -1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     INTRA_REFRESH_TYPE_TOKEN,
     "Intra refresh type, default is 2 [1: FWD Frame (Open GOP), 2: KEY Frame (Closed GOP)]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     SCENE_CHANGE_DETECTION_TOKEN,
     "Scene change detection control, default is 0 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     LOOKAHEAD_NEW_TOKEN,
     "Number of frames in the future to look ahead, not including minigop, temporal filtering, and "
     "rate "
     "control, default is -1 [-1: auto, 0-120]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     HIERARCHICAL_LEVELS_TOKEN,
     "Set hierarchical levels beyond the base layer, default is <=M12: 5, else: 4 [2: 3 temporal "
     "layers, 3: 4 "
     "temporal layers, 4: 5 "
     "layers, 5: 6 layers]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     PRED_STRUCT_TOKEN,
     "Set prediction structure, default is 2 [1: low delay frames, 2: "
     "random access]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     FORCE_KEY_FRAMES_TOKEN,
     "Force key frames at the comma separated specifiers. `#f` for frames, `#.#s` for seconds",
     set_cfg_force_key_frames},
    {SINGLE_INPUT,
     STARTUP_MG_SIZE_TOKEN,
     "Specify another mini-gop configuration for the first mini-gop after the key-frame, default "
     "is 0 [0: OFF, "
     "2: 3 temporal layers, 3: 4 temporal layers, 4: 5 temporal layers]",
     set_cfg_generic_token},
    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_specific[] = {
    {SINGLE_INPUT,
     TILE_ROW_TOKEN,
     "Number of tile rows to use, `TileRow == log2(x)`, default changes per resolution but is 1 "
     "[0-6]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     TILE_COL_TOKEN,
     "Number of tile columns to use, `TileCol == log2(x)`, default changes per resolution but is 1 "
     "[0-4]",
     set_cfg_generic_token},

    // DLF
    {SINGLE_INPUT, LOOP_FILTER_ENABLE, "Deblocking loop filter control, default is 1 [0-1]", set_cfg_generic_token},
    // CDEF
    {SINGLE_INPUT,
     CDEF_ENABLE_TOKEN,
     "Enable Constrained Directional Enhancement Filter, default is 1 [0-1]",
     set_cdef_enable},
    // RESTORATION
    {SINGLE_INPUT,
     ENABLE_RESTORATION_TOKEN,
     "Enable loop restoration filter, default is 1 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     ENABLE_TPL_LA_TOKEN,
     "Temporal Dependency model control, currently forced on library side, only applicable for "
     "CRF/CQP, default is 1 [0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     MFMV_ENABLE_NEW_TOKEN,
     "Motion Field Motion Vector control, default is -1 [-1: auto, 0-1]",
     set_cfg_generic_token},
    {SINGLE_INPUT, DG_ENABLE_NEW_TOKEN, "Dynamic GoP control, default is 1 [0-1]", set_cfg_generic_token},
    {SINGLE_INPUT, FAST_DECODE_TOKEN, "Fast Decoder levels, default is 0 [0-1]", set_cfg_generic_token},
    // --- start: ALTREF_FILTERING_SUPPORT
    {SINGLE_INPUT,
     ENABLE_TF_TOKEN,
     "Enable ALT-REF (temporally filtered) frames, default is 1 [0-1]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     ENABLE_OVERLAYS,
     "Enable the insertion of overlayer pictures which will be used as an additional reference "
     "frame for the base layer picture, default is 0 [0-1]",
     set_cfg_generic_token},
    // --- end: ALTREF_FILTERING_SUPPORT
    {SINGLE_INPUT,
     TUNE_TOKEN,
     "Specifies whether to use PSNR or VQ as the tuning metric [0 = VQ, 1 = PSNR, 2 = SSIM], "
     "default is 1 "
     "[0-2]",
     set_cfg_generic_token},
    // MD Parameters
    {SINGLE_INPUT,
     SCREEN_CONTENT_TOKEN,
     "Set screen content detection level, default is 2 [0: off, 1: on, 2: content adaptive]",
     set_cfg_generic_token},
    // Optional Features
    {SINGLE_INPUT,
     RESTRICTED_MOTION_VECTOR,
     "Restrict motion vectors from reaching outside the picture boundary, default is 0 [0-1]",
     set_cfg_generic_token},

    // Annex A parameters
    {SINGLE_INPUT,
     FILM_GRAIN_TOKEN,
     "Enable film grain, default is 0 [0: off, 1-50: level of denoising for film grain]",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     FILM_GRAIN_DENOISE_APPLY_TOKEN,
     "Apply denoising when film grain is ON, default is 1 [0: no denoising, film grain data is "
     "still in frame header, "
     "1: level of denoising is set by the film-grain parameter]",
     set_cfg_generic_token},

    // --- start: SUPER-RESOLUTION SUPPORT
    {SINGLE_INPUT,
     SUPERRES_MODE_INPUT,
     "Enable super-resolution mode, refer to the super-resolution section in the user guide, "
     "default is 0 [0: off, 1-3, 4: auto-select mode]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     SUPERRES_DENOM,
     "Super-resolution denominator, only applicable for mode == 1, default is 8 [8: no scaling, "
     "9-15, 16: half-scaling]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     SUPERRES_KF_DENOM,
     "Super-resolution denominator for key frames, only applicable for mode == 1, default is 8 [8: "
     "no scaling, 9-15, 16: half-scaling]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     SUPERRES_QTHRES,
     "Super-resolution q-threshold, only applicable for mode == 3, default is 43 [0-63]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     SUPERRES_KF_QTHRES,
     "Super-resolution q-threshold for key frames, only applicable for mode == 3, default is 43 "
     "[0-63]",
     set_cfg_generic_token},
    // --- end: SUPER-RESOLUTION SUPPORT

    // --- start: SWITCH_FRAME SUPPORT
    {SINGLE_INPUT, SFRAME_DIST_TOKEN, "S-Frame interval (frames) (0: OFF[default], > 0: ON)", set_cfg_generic_token},
    {SINGLE_INPUT,
     SFRAME_MODE_TOKEN,
     "S-Frame insertion mode ([1-2], 1: the considered frame will be made into an S-Frame only if "
     "it is an altref frame,"
     " 2: the next altref frame will be made into an S-Frame[default])",
     set_cfg_generic_token},
    // --- end: SWITCH_FRAME SUPPORT
    // --- start: REFERENCE SCALING SUPPORT
    {SINGLE_INPUT,
     RESIZE_MODE_INPUT,
     "Enable resize mode [0: none, 1: fixed scale, 2: random scale, 3: dynamic scale, 4: random "
     "access]",
     set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_DENOM, "Resize denominator, only applicable for mode == 1 [8-16]", set_cfg_generic_token},
    {SINGLE_INPUT,
     RESIZE_KF_DENOM,
     "Resize denominator for key frames, only applicable for mode == 1 [8-16]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     RESIZE_FRAME_EVTS,
     "Resize frame events, in a list separated by ',', a reference scaling process starts from the "
     "given frame number with new denominators, only applicable for mode == 4",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     RESIZE_FRAME_KF_DENOMS,
     "Resize denominator for key frames in event, in a list separated by ',', only applicable for "
     "mode == 4",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     RESIZE_FRAME_DENOMS,
     "Resize denominator in event, in a list separated by ',', only applicable for mode == 4",
     set_cfg_generic_token},
    // --- end: REFERENCE SCALING SUPPORT

    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry_color_description[] = {
    // Color description
    {SINGLE_INPUT,
     COLOR_PRIMARIES_NEW_TOKEN,
     "Color primaries, refer to Appendix A.2 of the user guide, default is 2 [0-12, 22]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     TRANSFER_CHARACTERISTICS_NEW_TOKEN,
     "Transfer characteristics, refer to Appendix A.2 of the user guide, default is 2 [0-22]",
     set_cfg_generic_token},
    {SINGLE_INPUT,
     MATRIX_COEFFICIENTS_NEW_TOKEN,
     "Matrix coefficients, refer to Appendix A.2 of the user guide, default is 2 [0-14]",
     set_cfg_generic_token},
    {SINGLE_INPUT, COLOR_RANGE_NEW_TOKEN, "Color range, default is 0 [0: Studio, 1: Full]", set_cfg_generic_token},
    {SINGLE_INPUT,
     CHROMA_SAMPLE_POSITION_TOKEN,
     "Chroma sample position, default is 'unknown' ['unknown', 'vertical'/'left', "
     "'colocated'/'topleft']",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     MASTERING_DISPLAY_TOKEN,
     "Mastering display metadata in the format of \"G(x,y)B(x,y)R(x,y)WP(x,y)L(max,min)\", refer "
     "to the user guide Appendix A.2",
     set_cfg_generic_token},

    {SINGLE_INPUT,
     CONTENT_LIGHT_LEVEL_TOKEN,
     "Set content light level in the format of \"max_cll,max_fall\", refer to the user guide "
     "Appendix A.2",
     set_cfg_generic_token},
    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

ConfigEntry config_entry[] = {
    // Options
    {SINGLE_INPUT, INPUT_FILE_TOKEN, "InputFile", set_cfg_input_file},
    {SINGLE_INPUT, INPUT_FILE_LONG_TOKEN, "InputFile", set_cfg_input_file},
    {SINGLE_INPUT, OUTPUT_BITSTREAM_TOKEN, "StreamFile", set_cfg_stream_file},
    {SINGLE_INPUT, OUTPUT_BITSTREAM_LONG_TOKEN, "StreamFile", set_cfg_stream_file},
    {SINGLE_INPUT, ERROR_FILE_TOKEN, "ErrorFile", set_cfg_error_file},
    {SINGLE_INPUT, OUTPUT_RECON_TOKEN, "ReconFile", set_cfg_recon_file},
    {SINGLE_INPUT, OUTPUT_RECON_LONG_TOKEN, "ReconFile", set_cfg_recon_file},
    {SINGLE_INPUT, STAT_FILE_TOKEN, "StatFile", set_cfg_stat_file},
    {SINGLE_INPUT, PROGRESS_TOKEN, "Progress", set_progress},
    {SINGLE_INPUT, NO_PROGRESS_TOKEN, "NoProgress", set_no_progress},
    {SINGLE_INPUT, PRESET_TOKEN, "EncoderMode", set_cfg_generic_token},
    {SINGLE_INPUT, SVTAV1_PARAMS, "SvtAv1Params", parse_svtav1_params},

    // Encoder Global Options
    //   Picture Dimensions
    {SINGLE_INPUT, WIDTH_TOKEN, "SourceWidth", set_cfg_generic_token},
    {SINGLE_INPUT, WIDTH_LONG_TOKEN, "SourceWidth", set_cfg_generic_token},
    {SINGLE_INPUT, HEIGHT_TOKEN, "SourceHeight", set_cfg_generic_token},
    {SINGLE_INPUT, HEIGHT_LONG_TOKEN, "SourceHeight", set_cfg_generic_token},
    {SINGLE_INPUT, FORCED_MAX_FRAME_WIDTH_TOKEN, "ForcedMaximumFrameWidth", set_cfg_generic_token},
    {SINGLE_INPUT, FORCED_MAX_FRAME_HEIGHT_TOKEN, "ForcedMaximumFrameHeight", set_cfg_generic_token},
    // Prediction Structure
    {SINGLE_INPUT, NUMBER_OF_PICTURES_TOKEN, "FrameToBeEncoded", set_cfg_frames_to_be_encoded},
    {SINGLE_INPUT, NUMBER_OF_PICTURES_LONG_TOKEN, "FrameToBeEncoded", set_cfg_frames_to_be_encoded},
    {SINGLE_INPUT, BUFFERED_INPUT_TOKEN, "BufferedInput", set_buffered_input},

    {SINGLE_INPUT, NUMBER_OF_PICTURES_TO_SKIP, "FrameToBeSkipped", set_cfg_frames_to_be_skipped},

    //   Annex A parameters
    {SINGLE_INPUT, TIER_TOKEN, "Tier", set_cfg_generic_token}, // Lacks a command line flag for now
    {SINGLE_INPUT, ENCODER_COLOR_FORMAT, "EncoderColorFormat", set_cfg_generic_token},
    {SINGLE_INPUT, PROFILE_TOKEN, "Profile", set_cfg_generic_token},
    {SINGLE_INPUT, LEVEL_TOKEN, "Level", set_level},
    {SINGLE_INPUT, HDR_INPUT_NEW_TOKEN, "HighDynamicRangeInput", set_cfg_generic_token},

    //   Frame Rate tokens
    {SINGLE_INPUT, FRAME_RATE_TOKEN, "FrameRate", set_frame_rate},
    {SINGLE_INPUT, FRAME_RATE_NUMERATOR_TOKEN, "FrameRateNumerator", set_cfg_generic_token},
    {SINGLE_INPUT, FRAME_RATE_DENOMINATOR_TOKEN, "FrameRateDenominator", set_cfg_generic_token},

    //   Bit depth tokens
    {SINGLE_INPUT, INPUT_DEPTH_TOKEN, "EncoderBitDepth", set_cfg_generic_token},

    //   Latency
    {SINGLE_INPUT, INJECTOR_TOKEN, "Injector", set_injector},
    {SINGLE_INPUT, INJECTOR_FRAMERATE_TOKEN, "InjectorFrameRate", set_injector_frame_rate},

    {SINGLE_INPUT, STAT_REPORT_NEW_TOKEN, "StatReport", set_cfg_generic_token},

    //   Asm Type
    {SINGLE_INPUT, ASM_TYPE_TOKEN, "Asm", set_cfg_generic_token},

    //   Thread Management
    {SINGLE_INPUT, THREAD_MGMNT, "LogicalProcessors", set_cfg_generic_token},
    {SINGLE_INPUT, PIN_TOKEN, "PinnedExecution", set_cfg_generic_token},
    {SINGLE_INPUT, TARGET_SOCKET, "TargetSocket", set_cfg_generic_token},

    // Rate Control Options
    {SINGLE_INPUT, RATE_CONTROL_ENABLE_TOKEN, "RateControlMode", set_cfg_generic_token},
    {SINGLE_INPUT, QP_TOKEN, "QP", set_cfg_generic_token},
    {SINGLE_INPUT, QP_LONG_TOKEN, "QP", set_cfg_generic_token},
    {SINGLE_INPUT, CRF_LONG_TOKEN, "CRF", set_cfg_generic_token},
    {SINGLE_INPUT, TARGET_BIT_RATE_TOKEN, "TargetBitRate", set_cfg_generic_token},
    {SINGLE_INPUT, MAX_BIT_RATE_TOKEN, "MaxBitRate", set_cfg_generic_token},

    {SINGLE_INPUT, USE_QP_FILE_TOKEN, "UseQpFile", set_cfg_generic_token},
    {SINGLE_INPUT, QP_FILE_NEW_TOKEN, "QpFile", set_cfg_qp_file},

    {SINGLE_INPUT, MAX_QP_TOKEN, "MaxQpAllowed", set_cfg_generic_token},
    {SINGLE_INPUT, MIN_QP_TOKEN, "MinQpAllowed", set_cfg_generic_token},

    {SINGLE_INPUT, ADAPTIVE_QP_ENABLE_NEW_TOKEN, "AdaptiveQuantization", set_cfg_generic_token},

    //   qindex offsets
    {SINGLE_INPUT, USE_FIXED_QINDEX_OFFSETS_TOKEN, "UseFixedQIndexOffsets", set_cfg_generic_token},
    {SINGLE_INPUT, KEY_FRAME_QINDEX_OFFSET_TOKEN, "KeyFrameQIndexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, KEY_FRAME_CHROMA_QINDEX_OFFSET_TOKEN, "KeyFrameChromaQIndexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, QINDEX_OFFSETS_TOKEN, "QIndexOffsets", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_QINDEX_OFFSETS_TOKEN, "ChromaQIndexOffsets", set_cfg_generic_token},
    {SINGLE_INPUT, LUMA_Y_DC_QINDEX_OFFSET_TOKEN, "LumaYDCQindexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_U_DC_QINDEX_OFFSET_TOKEN, "ChromaUDCQindexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_U_AC_QINDEX_OFFSET_TOKEN, "ChromaUACQindexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_V_DC_QINDEX_OFFSET_TOKEN, "ChromaVDCQindexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_V_AC_QINDEX_OFFSET_TOKEN, "ChromaVACQindexOffset", set_cfg_generic_token},
    {SINGLE_INPUT, LAMBDA_SCALE_FACTORS_TOKEN, "LambdaScaleFactors", set_cfg_generic_token},
    {SINGLE_INPUT, UNDER_SHOOT_PCT_TOKEN, "UnderShootPct", set_cfg_generic_token},
    {SINGLE_INPUT, OVER_SHOOT_PCT_TOKEN, "OverShootPct", set_cfg_generic_token},
    {SINGLE_INPUT, MBR_OVER_SHOOT_PCT_TOKEN, "MbrOverShootPct", set_cfg_generic_token},
    {SINGLE_INPUT, GOP_CONSTRAINT_RC_TOKEN, "GopConstraintRc", set_cfg_generic_token},
    {SINGLE_INPUT, BUFFER_SIZE_TOKEN, "BufSz", set_cfg_generic_token},
    {SINGLE_INPUT, BUFFER_INITIAL_SIZE_TOKEN, "BufInitialSz", set_cfg_generic_token},
    {SINGLE_INPUT, BUFFER_OPTIMAL_SIZE_TOKEN, "BufOptimalSz", set_cfg_generic_token},
    {SINGLE_INPUT, RECODE_LOOP_TOKEN, "RecodeLoop", set_cfg_generic_token},
    {SINGLE_INPUT, VBR_BIAS_PCT_TOKEN, "VBRBiasPct", set_cfg_generic_token},
    {SINGLE_INPUT, VBR_MIN_SECTION_PCT_TOKEN, "MinSectionPct", set_cfg_generic_token},
    {SINGLE_INPUT, VBR_MAX_SECTION_PCT_TOKEN, "MaxSectionPct", set_cfg_generic_token},

    // Multi-pass Options
    {SINGLE_INPUT, PASS_TOKEN, "Pass", set_cfg_generic_token},
    {SINGLE_INPUT, TWO_PASS_STATS_TOKEN, "Stats", set_two_pass_stats},
    {SINGLE_INPUT, PASSES_TOKEN, "Passes", set_passes},

    // GOP size and type Options
    {SINGLE_INPUT, INTRA_PERIOD_TOKEN, "IntraPeriod", set_cfg_generic_token},
    {SINGLE_INPUT, KEYINT_TOKEN, "Keyint", set_cfg_generic_token},
    {SINGLE_INPUT, INTRA_REFRESH_TYPE_TOKEN, "IntraRefreshType", set_cfg_generic_token},
    {SINGLE_INPUT, SCENE_CHANGE_DETECTION_TOKEN, "SceneChangeDetection", set_cfg_generic_token},
    {SINGLE_INPUT, LOOKAHEAD_NEW_TOKEN, "Lookahead", set_cfg_generic_token},
    //   Prediction Structure
    {SINGLE_INPUT, HIERARCHICAL_LEVELS_TOKEN, "HierarchicalLevels", set_cfg_generic_token},
    {SINGLE_INPUT, PRED_STRUCT_TOKEN, "PredStructure", set_cfg_generic_token},
    {SINGLE_INPUT, FORCE_KEY_FRAMES_TOKEN, "ForceKeyFrames", set_cfg_force_key_frames},
    {SINGLE_INPUT, STARTUP_MG_SIZE_TOKEN, "StartupMgSize", set_cfg_generic_token},

    // AV1 Specific Options
    {SINGLE_INPUT, TILE_ROW_TOKEN, "TileRow", set_cfg_generic_token},
    {SINGLE_INPUT, TILE_COL_TOKEN, "TileCol", set_cfg_generic_token},
    {SINGLE_INPUT, LOOP_FILTER_ENABLE, "LoopFilterEnable", set_cfg_generic_token},
    {SINGLE_INPUT, CDEF_ENABLE_TOKEN, "CDEFLevel", set_cdef_enable},
    {SINGLE_INPUT, ENABLE_RESTORATION_TOKEN, "EnableRestoration", set_cfg_generic_token},
    {SINGLE_INPUT, ENABLE_TPL_LA_TOKEN, "EnableTPLModel", set_cfg_generic_token},
    {SINGLE_INPUT, MFMV_ENABLE_NEW_TOKEN, "Mfmv", set_cfg_generic_token},
    {SINGLE_INPUT, DG_ENABLE_NEW_TOKEN, "EnableDg", set_cfg_generic_token},
    {SINGLE_INPUT, FAST_DECODE_TOKEN, "FastDecode", set_cfg_generic_token},
    {SINGLE_INPUT, TUNE_TOKEN, "Tune", set_cfg_generic_token},
    //   ALT-REF filtering support
    {SINGLE_INPUT, ENABLE_TF_TOKEN, "EnableTf", set_cfg_generic_token},
    {SINGLE_INPUT, ENABLE_OVERLAYS, "EnableOverlays", set_cfg_generic_token},
    {SINGLE_INPUT, SCREEN_CONTENT_TOKEN, "ScreenContentMode", set_cfg_generic_token},
    {SINGLE_INPUT, RESTRICTED_MOTION_VECTOR, "RestrictedMotionVector", set_cfg_generic_token},
    {SINGLE_INPUT, FILM_GRAIN_TOKEN, "FilmGrain", set_cfg_generic_token},
    {SINGLE_INPUT, FILM_GRAIN_DENOISE_APPLY_TOKEN, "FilmGrainDenoise", set_cfg_generic_token},

    //   Super-resolution support
    {SINGLE_INPUT, SUPERRES_MODE_INPUT, "SuperresMode", set_cfg_generic_token},
    {SINGLE_INPUT, SUPERRES_DENOM, "SuperresDenom", set_cfg_generic_token},
    {SINGLE_INPUT, SUPERRES_KF_DENOM, "SuperresKfDenom", set_cfg_generic_token},
    {SINGLE_INPUT, SUPERRES_QTHRES, "SuperresQthres", set_cfg_generic_token},
    {SINGLE_INPUT, SUPERRES_KF_QTHRES, "SuperresKfQthres", set_cfg_generic_token},

    // Switch frame support
    {SINGLE_INPUT, SFRAME_DIST_TOKEN, "SframeInterval", set_cfg_generic_token},
    {SINGLE_INPUT, SFRAME_MODE_TOKEN, "SframeMode", set_cfg_generic_token},
    // Reference Scaling support
    {SINGLE_INPUT, RESIZE_MODE_INPUT, "ResizeMode", set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_DENOM, "ResizeDenom", set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_KF_DENOM, "ResizeKfDenom", set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_FRAME_EVTS, "ResizeFrameEvts", set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_FRAME_KF_DENOMS, "ResizeFrameKFDenoms", set_cfg_generic_token},
    {SINGLE_INPUT, RESIZE_FRAME_DENOMS, "ResizeFrameDenoms", set_cfg_generic_token},

    // Color Description Options
    {SINGLE_INPUT, COLOR_PRIMARIES_NEW_TOKEN, "ColorPrimaries", set_cfg_generic_token},
    {SINGLE_INPUT, TRANSFER_CHARACTERISTICS_NEW_TOKEN, "TransferCharacteristics", set_cfg_generic_token},
    {SINGLE_INPUT, MATRIX_COEFFICIENTS_NEW_TOKEN, "MatrixCoefficients", set_cfg_generic_token},
    {SINGLE_INPUT, COLOR_RANGE_NEW_TOKEN, "ColorRange", set_cfg_generic_token},
    {SINGLE_INPUT, CHROMA_SAMPLE_POSITION_TOKEN, "ChromaSamplePosition", set_cfg_generic_token},
    {SINGLE_INPUT, MASTERING_DISPLAY_TOKEN, "MasteringDisplay", set_cfg_generic_token},
    {SINGLE_INPUT, CONTENT_LIGHT_LEVEL_TOKEN, "ContentLightLevel", set_cfg_generic_token},

    // QM
    {SINGLE_INPUT, ENABLE_QM_TOKEN, "EnableQM", set_cfg_generic_token},
    {SINGLE_INPUT, MIN_QM_LEVEL_TOKEN, "MinQmLevel", set_cfg_generic_token},
    {SINGLE_INPUT, MAX_QM_LEVEL_TOKEN, "MaxQmLevel", set_cfg_generic_token},

    // ROI
    {SINGLE_INPUT, ROI_MAP_FILE_TOKEN, "RoiMapFile", set_cfg_roi_map_file},

    // Termination
    {SINGLE_INPUT, NULL, NULL, NULL}};

/**********************************
 * Constructor
 **********************************/
EbConfig *svt_config_ctor() {
    EbConfig *app_cfg = (EbConfig *)calloc(1, sizeof(EbConfig));
    if (!app_cfg)
        return NULL;
    app_cfg->error_log_file      = stderr;
    app_cfg->buffered_input      = -1;
    app_cfg->progress            = 1;
    app_cfg->injector_frame_rate = 60;
    app_cfg->roi_map_file        = NULL;
    return app_cfg;
}

/**********************************
 * Destructor
 **********************************/
void svt_config_dtor(EbConfig *app_cfg) {
    if (!app_cfg)
        return;
    // Close any files that are open
    if (app_cfg->input_file) {
        if (!app_cfg->input_file_is_fifo)
            fclose(app_cfg->input_file);
        app_cfg->input_file = (FILE *)NULL;
    }

    if (app_cfg->bitstream_file) {
        if (!fseek(app_cfg->bitstream_file, 0, SEEK_SET))
            write_ivf_stream_header(app_cfg, app_cfg->frames_encoded);
        fclose(app_cfg->bitstream_file);
        app_cfg->bitstream_file = (FILE *)NULL;
    }

    if (app_cfg->recon_file) {
        fclose(app_cfg->recon_file);
        app_cfg->recon_file = (FILE *)NULL;
    }

    if (app_cfg->error_log_file && app_cfg->error_log_file != stderr) {
        fclose(app_cfg->error_log_file);
        app_cfg->error_log_file = (FILE *)NULL;
    }

    if (app_cfg->qp_file) {
        fclose(app_cfg->qp_file);
        app_cfg->qp_file = (FILE *)NULL;
    }

    if (app_cfg->stat_file) {
        fclose(app_cfg->stat_file);
        app_cfg->stat_file = (FILE *)NULL;
    }

    if (app_cfg->output_stat_file) {
        fclose(app_cfg->output_stat_file);
        app_cfg->output_stat_file = (FILE *)NULL;
    }

    if (app_cfg->roi_map_file) {
        fclose(app_cfg->roi_map_file);
        app_cfg->roi_map_file = (FILE *)NULL;
    }

    for (size_t i = 0; i < app_cfg->forced_keyframes.count; ++i) free(app_cfg->forced_keyframes.specifiers[i]);
    free(app_cfg->forced_keyframes.specifiers);
    free(app_cfg->forced_keyframes.frames);

    free((void *)app_cfg->stats);
    free(app_cfg);
    return;
}
EbErrorType enc_channel_ctor(EncChannel *c) {
    c->app_cfg = svt_config_ctor();
    if (!c->app_cfg)
        return EB_ErrorInsufficientResources;

    c->exit_cond        = APP_ExitConditionError;
    c->exit_cond_output = APP_ExitConditionError;
    c->exit_cond_recon  = APP_ExitConditionError;
    c->exit_cond_input  = APP_ExitConditionError;
    c->active           = FALSE;
    return svt_av1_enc_init_handle(&c->app_cfg->svt_encoder_handle, c->app_cfg, &c->app_cfg->config);
}

void enc_channel_dctor(EncChannel *c, uint32_t inst_cnt) {
    EbConfig *ctx = c->app_cfg;
    if (ctx && ctx->svt_encoder_handle) {
        svt_av1_enc_deinit(ctx->svt_encoder_handle);
        de_init_encoder(ctx, inst_cnt);
    }
    svt_config_dtor(c->app_cfg);
}

/**
 * @brief Find token and its argument
 * @param argc      Argument count
 * @param argv      Argument array
 * @param token     The token to look for
 * @param configStr Output buffer to write the argument to or NULL
 * @return 0 if found, non-zero otherwise
 *
 * @note The configStr buffer must be at least
 *       COMMAND_LINE_MAX_SIZE bytes big.
 *       The argv must contain an additional NULL
 *       element to terminate it, so that
 *       argv[argc] == NULL.
 */
static int32_t find_token(int32_t argc, char *const argv[], char const *token, char *configStr) {
    assert(argv[argc] == NULL);

    if (argc == 0)
        return -1;

    for (int32_t i = argc - 1; i >= 0; i--) {
        if (strcmp(argv[i], token) != 0)
            continue;

        // The argument matches the token.
        // If given, try to copy its argument to configStr
        if (configStr && argv[i + 1] != NULL) {
            strcpy_s(configStr, COMMAND_LINE_MAX_SIZE, argv[i + 1]);
        } else if (configStr) {
            configStr[0] = '\0';
        }

        return 0;
    }
    return -1;
}

/**
 * @brief Finds the config entry for a given config token
 *
 * @param name config token
 * @return ConfigEntry*
 */
static ConfigEntry *find_entry(const char *name) {
    for (size_t i = 0; config_entry[i].name != NULL; ++i) {
        if (!strcmp(config_entry[i].name, name))
            return &config_entry[i];
    }
    return NULL;
}

/**
 * @brief Reads a word from a file, but skips commented lines, also splits on ':'
 *
 * @param fp file to read from
 * @return char* malloc'd word, or NULL if EOF or error
 */
static char *read_word(FILE *fp) {
    char  *word     = NULL;
    size_t word_len = 0;
    int    c;
    while ((c = fgetc(fp)) != EOF) {
        if (c == '#') {
            // skip to end of line
            while ((c = fgetc(fp)) != EOF && c != '\n')
                ;
            if (c == '\n')
                continue;
            if (c == EOF)
                break;
        } else if (isspace(c)) {
            // skip whitespace
            continue;
        }
        // read word
        do {
            if (c == ':')
                break;
            char *temp = (char *)realloc(word, ++word_len + 1);
            if (!temp) {
                free(word);
                return NULL;
            }
            word               = temp;
            word[word_len - 1] = c;
            word[word_len]     = '\0';
        } while ((c = fgetc(fp)) != EOF && !isspace(c));
        if (c == EOF || word)
            break;
    }
    return word;
}

static EbErrorType set_config_value(EbConfig *app_cfg, const char *word, const char *value, unsigned instance_idx) {
    const ConfigEntry *entry = find_entry(word);
    if (!entry) {
        fprintf(stderr, "Error channel %u: Config File contains unknown token %s\n", instance_idx + 1, word);
        return EB_ErrorBadParameter;
    }
    const EbErrorType err = entry->scf(app_cfg, entry->token, value);
    if (err != EB_ErrorNone) {
        fprintf(stderr,
                "Error channel %u: Config File contains invalid value %s for token %s\n",
                instance_idx + 1,
                value,
                word);
        return EB_ErrorBadParameter;
    }
    return EB_ErrorNone;
}

/**********************************
* Read Config File
**********************************/
static EbErrorType read_config_file(EbConfig *app_cfg, const char *config_path, uint32_t instance_idx) {
    FILE *config_file;

    // Open the config file
    FOPEN(config_file, config_path, "rb");
    if (!config_file) {
        fprintf(stderr, "Error channel %u: Couldn't open Config File: %s\n", instance_idx + 1, config_path);
        return EB_ErrorBadParameter;
    }

    EbErrorType return_error = EB_ErrorNone;
    char       *word         = NULL;
    char       *value        = NULL;
    while (return_error == EB_ErrorNone && (word = read_word(config_file))) {
        value = read_word(config_file);
        if (value && !strcmp(value, ":")) {
            free(value);
            value = read_word(config_file);
        }
        if (!value) {
            fprintf(stderr,
                    "Error channel %u: Config File: %s is missing a value for %s\n",
                    instance_idx + 1,
                    config_path,
                    word);
            return_error = EB_ErrorBadParameter;
            break;
        }
        return_error = set_config_value(app_cfg, word, value, instance_idx);
    }
    free(word);
    free(value);
    fclose(config_file);
    return return_error;
}

/* get config->rc_stats_buffer from config->input_stat_file */
Bool load_twopass_stats_in(EbConfig *cfg) {
    EbSvtAv1EncConfiguration *config = &cfg->config;
#ifdef _WIN32
    int          fd = _fileno(cfg->input_stat_file);
    struct _stat file_stat;
    int          ret = _fstat(fd, &file_stat);
#else
    int         fd = fileno(cfg->input_stat_file);
    struct stat file_stat;
    int         ret         = fstat(fd, &file_stat);
#endif
    if (ret) {
        return FALSE;
    }
    config->rc_stats_buffer.buf = malloc(file_stat.st_size);
    if (config->rc_stats_buffer.buf) {
        config->rc_stats_buffer.sz = (uint64_t)file_stat.st_size;
        if (fread(config->rc_stats_buffer.buf, 1, file_stat.st_size, cfg->input_stat_file) !=
            (size_t)file_stat.st_size) {
            return FALSE;
        }
        if (file_stat.st_size == 0) {
            return FALSE;
        }
    }
    return config->rc_stats_buffer.buf != NULL;
}
EbErrorType handle_stats_file(EbConfig *app_cfg, EncPass enc_pass, const SvtAv1FixedBuf *rc_stats_buffer,
                              uint32_t channel_number) {
    switch (enc_pass) {
    case ENC_SINGLE_PASS: {
        const char *stats = app_cfg->stats ? app_cfg->stats : "svtav1_2pass.log";
        if (app_cfg->config.pass == 1) {
            if (!fopen_and_lock(&app_cfg->output_stat_file, stats, TRUE)) {
                fprintf(app_cfg->error_log_file,
                        "Error instance %u: can't open stats file %s for write \n",
                        channel_number + 1,
                        stats);
                return EB_ErrorBadParameter;
            }
        }

        // Multi pass VBR has 3 passes, and pass = 2 is the middle pass
        // In this pass, data is read from the file, copied to memory, updated and
        // written back to the same file
        else if (app_cfg->config.pass == 2 && app_cfg->config.rate_control_mode == SVT_AV1_RC_MODE_VBR) {
            if (!fopen_and_lock(&app_cfg->input_stat_file, stats, FALSE)) {
                fprintf(app_cfg->error_log_file,
                        "Error instance %u: can't read stats file %s for read\n",
                        channel_number + 1,
                        stats);
                return EB_ErrorBadParameter;
            }
            // Copy from file to memory
            if (!load_twopass_stats_in(app_cfg)) {
                fprintf(app_cfg->error_log_file, "Error instance %u: can't load file %s\n", channel_number + 1, stats);
                return EB_ErrorBadParameter;
            }
            // Close the input stat file
            if (app_cfg->input_stat_file) {
                fclose(app_cfg->input_stat_file);
                app_cfg->input_stat_file = (FILE *)NULL;
            }
            // Open the file in write mode
            if (!fopen_and_lock(&app_cfg->output_stat_file, stats, TRUE)) {
                fprintf(app_cfg->error_log_file,
                        "Error instance %u: can't open stats file %s for write \n",
                        channel_number + 1,
                        stats);
                return EB_ErrorBadParameter;
            }
        }
        // Final pass: pass = 2 for CRF and pass = 3 for VBR
        else if ((app_cfg->config.pass == 2 && app_cfg->config.rate_control_mode == SVT_AV1_RC_MODE_CQP_OR_CRF) ||
                 (app_cfg->config.pass == 3 && app_cfg->config.rate_control_mode == SVT_AV1_RC_MODE_VBR)) {
            if (!fopen_and_lock(&app_cfg->input_stat_file, stats, FALSE)) {
                fprintf(app_cfg->error_log_file,
                        "Error instance %u: can't read stats file %s for read\n",
                        channel_number + 1,
                        stats);
                return EB_ErrorBadParameter;
            }
            if (!load_twopass_stats_in(app_cfg)) {
                fprintf(app_cfg->error_log_file, "Error instance %u: can't load file %s\n", channel_number + 1, stats);
                return EB_ErrorBadParameter;
            }
        }
        break;
    }

    case ENC_FIRST_PASS: {
        // for combined two passes,
        // we only ouptut first pass stats when user explicitly set the --stats
        if (app_cfg->stats) {
            if (!fopen_and_lock(&app_cfg->output_stat_file, app_cfg->stats, TRUE)) {
                fprintf(app_cfg->error_log_file,
                        "Error instance %u: can't open stats file %s for write \n",
                        channel_number + 1,
                        app_cfg->stats);
                return EB_ErrorBadParameter;
            }
        }
        break;
    }

    case ENC_SECOND_PASS: {
        if (!rc_stats_buffer->sz) {
            fprintf(app_cfg->error_log_file,
                    "Error instance %u: combined multi passes need stats in for the middle pass \n",
                    channel_number + 1);
            return EB_ErrorBadParameter;
        }
        app_cfg->config.rc_stats_buffer = *rc_stats_buffer;
        break;
    }

    case ENC_THIRD_PASS: {
        if (!rc_stats_buffer->sz) {
            fprintf(app_cfg->error_log_file,
                    "Error instance %u: combined multi passes need stats in for the final pass \n",
                    channel_number + 1);
            return EB_ErrorBadParameter;
        }
        app_cfg->config.rc_stats_buffer = *rc_stats_buffer;
        break;
    }

    default: {
        assert(0);
        break;
    }
    }
    return EB_ErrorNone;
}
/******************************************
* Verify Settings
******************************************/
static EbErrorType app_verify_config(EbConfig *app_cfg, uint32_t channel_number) {
    EbErrorType return_error = EB_ErrorNone;

    // Check Input File
    if (app_cfg->input_file == (FILE *)NULL) {
        fprintf(app_cfg->error_log_file, "Error instance %u: Invalid Input File\n", channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->frames_to_be_encoded <= -1) {
        fprintf(app_cfg->error_log_file,
                "Error instance %u: FrameToBeEncoded must be greater than 0\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->buffered_input == 0) {
        fprintf(app_cfg->error_log_file, "Error instance %u: Buffered Input cannot be 0\n", channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->buffered_input < -1) {
        fprintf(app_cfg->error_log_file,
                "Error instance %u: Invalid buffered_input. buffered_input must be -1 or greater "
                "than or equal to 1\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->buffered_input != -1 && app_cfg->y4m_input) {
        fprintf(app_cfg->error_log_file,
                "Error instance %u: Buffered input is currently not available with y4m inputs\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->buffered_input > app_cfg->frames_to_be_encoded) {
        fprintf(app_cfg->error_log_file,
                "Error instance %u: Invalid buffered_input. buffered_input must be less or equal "
                "to the number of frames to be encoded\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->config.use_qp_file == TRUE && app_cfg->qp_file == NULL) {
        fprintf(app_cfg->error_log_file,
                "Error instance %u: Could not find QP file, UseQpFile is set to 1\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->injector > 1) {
        fprintf(app_cfg->error_log_file, "Error Instance %u: Invalid injector [0 - 1]\n", channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    if (app_cfg->injector_frame_rate > 240 && app_cfg->injector) {
        fprintf(app_cfg->error_log_file,
                "Error Instance %u: The maximum allowed injector_frame_rate is 240 fps\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }
    // Check that the injector frame_rate is non-zero
    if (!app_cfg->injector_frame_rate && app_cfg->injector) {
        fprintf(app_cfg->error_log_file,
                "Error Instance %u: The injector frame rate should be greater than 0 fps \n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }
    if (app_cfg->config.frame_rate_numerator == 0 || app_cfg->config.frame_rate_denominator == 0) {
        fprintf(app_cfg->error_log_file,
                "Error Instance %u: The frame_rate_numerator and frame_rate_denominator should be "
                "greater than 0\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    } else if (app_cfg->config.frame_rate_numerator / app_cfg->config.frame_rate_denominator > 240) {
        fprintf(app_cfg->error_log_file,
                "Error Instance %u: The maximum allowed frame_rate is 240 fps\n",
                channel_number + 1);
        return_error = EB_ErrorBadParameter;
    }

    return return_error;
}

static const char *TOKEN_READ_MARKER  = "THIS_TOKEN_HAS_BEEN_READ";
static const char *TOKEN_ERROR_MARKER = "THIS_TOKEN_HAS_ERROR";

/**
 * @brief Finds the arguments for a specific token
 *
 * @param nch number of channels (number of arguemnts to find for a token)
 * @param argc argc from main()
 * @param argv argv from main()
 * @param token token to find
 * @param configStr array of pointers to store the arguments into
 * @param cmd_copy array of tokens based on splitting argv
 * @param arg_copy array of arguments based on splitting argv
 * @return true token was found and configStr was populated
 * @return false token was not found and configStr was not populated
 */
static bool find_token_multiple_inputs(unsigned nch, int argc, char *const argv[], const char *token,
                                       char *configStr[MAX_CHANNEL_NUMBER], const char *cmd_copy[MAX_NUM_TOKENS],
                                       const char *arg_copy[MAX_NUM_TOKENS]) {
    bool return_error   = false;
    bool has_duplicates = false;
    // Loop over all the arguments
    for (int i = 0; i < argc; ++i) {
        if (strcmp(argv[i], token))
            continue;
        if (return_error)
            has_duplicates = true;
        return_error = true;
        if (i + 1 >= argc) {
            // if the token is at the end of the command line without arguments
            // set sentinel value
            strcpy_s(configStr[0], COMMAND_LINE_MAX_SIZE, " ");
            return return_error;
        }
        cmd_copy[i] = TOKEN_READ_MARKER; // mark token as read
        // consume arguments
        for (unsigned count = 0; count < nch; ++count) {
            const int j = i + 1 + count;
            if (j >= argc || cmd_copy[j]) {
                // stop if we ran out of arguments or if we hit a token
                strcpy_s(configStr[count], COMMAND_LINE_MAX_SIZE, " ");
                continue;
            }
            strcpy_s(configStr[count], COMMAND_LINE_MAX_SIZE, argv[j]);
            arg_copy[j] = TOKEN_READ_MARKER;
        }
    }

    if (return_error && !strcmp(configStr[0], " ")) {
        // if no argument was found, print an error message
        // we don't support flip switches, so this will need to be changed if we ever do.
        fprintf(stderr, "[SVT-Error]: No argument found for token `%s`\n", token);
        strcpy_s(configStr[0], COMMAND_LINE_MAX_SIZE, TOKEN_ERROR_MARKER);
    }

    if (has_duplicates) {
        fprintf(stderr, "\n[SVT-Warning]: Duplicate option %s specified, only `%s", token, token);
        for (unsigned count = 0; count < nch; ++count) fprintf(stderr, " %s", configStr[count]);
        fprintf(stderr, "` will apply\n\n");
    }

    return return_error;
}

static int check_long(ConfigEntry cfg_entry, ConfigEntry cfg_entry_next) {
    return cfg_entry_next.name ? !strcmp(cfg_entry.name, cfg_entry_next.name) : 0;
}

int get_version(int argc, char *argv[]) {
#ifdef NDEBUG
    static int debug_build = 1;
#else
    static int  debug_build = 0;
#endif
    if (find_token(argc, argv, VERSION_TOKEN, NULL))
        return 0;
    printf("SVT-AV1 %s (%s)\n", svt_av1_get_version(), debug_build ? "release" : "debug");
    return 1;
}

uint32_t get_help(int32_t argc, char *const argv[]) {
    char config_string[COMMAND_LINE_MAX_SIZE];
    if (find_token(argc, argv, HELP_TOKEN, config_string))
        return 0;

    printf(
        "Usage: SvtAv1EncApp <options> <-b dst_filename> -i src_filename\n"
        "\n"
        "Examples:\n"
        "Multi-pass encode (VBR):\n"
        "    SvtAv1EncApp <--stats svtav1_2pass.log> --passes 2 --rc 1 --tbr 1000 -b dst_filename "
        "-i src_filename\n"
        "Multi-pass encode (CRF):\n"
        "    SvtAv1EncApp <--stats svtav1_2pass.log> --passes 2 --rc 0 --crf 43 -b dst_filename -i "
        "src_filename\n"
        "Single-pass encode (VBR):\n"
        "    SvtAv1EncApp --passes 1 --rc 1 --tbr 1000 -b dst_filename -i src_filename\n"
        "\n"
        "Options:\n");
    for (ConfigEntry *options_token_index = config_entry_options; options_token_index->token; ++options_token_index) {
        // this only works if short and long token are one after another
        switch (check_long(*options_token_index, options_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n",
                   options_token_index->token,
                   options_token_index[1].token,
                   options_token_index->name);
            ++options_token_index;
            break;
        default:
            printf(options_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   options_token_index->token,
                   options_token_index->name);
        }
    }
    printf("\nEncoder Global Options:\n");
    for (ConfigEntry *global_options_token_index = config_entry_global_options; global_options_token_index->token;
         ++global_options_token_index) {
        switch (check_long(*global_options_token_index, global_options_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n",
                   global_options_token_index->token,
                   global_options_token_index[1].token,
                   global_options_token_index->name);
            ++global_options_token_index;
            break;
        default:
            printf(global_options_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   global_options_token_index->token,
                   global_options_token_index->name);
        }
    }
    printf("\nRate Control Options:\n");
    for (ConfigEntry *rc_token_index = config_entry_rc; rc_token_index->token; ++rc_token_index) {
        switch (check_long(*rc_token_index, rc_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n", rc_token_index->token, rc_token_index[1].token, rc_token_index->name);
            ++rc_token_index;
            break;
        default:
            printf(rc_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   rc_token_index->token,
                   rc_token_index->name);
        }
    }
    printf("\nMulti-pass Options:\n");
    for (ConfigEntry *two_p_token_index = config_entry_2p; two_p_token_index->token; ++two_p_token_index) {
        switch (check_long(*two_p_token_index, two_p_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n",
                   two_p_token_index->token,
                   two_p_token_index[1].token,
                   two_p_token_index->name);
            ++two_p_token_index;
            break;
        default:
            printf(two_p_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   two_p_token_index->token,
                   two_p_token_index->name);
        }
    }
    printf("\nGOP size and type Options:\n");
    for (ConfigEntry *kf_token_index = config_entry_intra_refresh; kf_token_index->token; ++kf_token_index) {
        switch (check_long(*kf_token_index, kf_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n", kf_token_index->token, kf_token_index[1].token, kf_token_index->name);
            ++kf_token_index;
            break;
        default:
            printf(kf_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   kf_token_index->token,
                   kf_token_index->name);
        }
    }
    printf("\nAV1 Specific Options:\n");
    for (ConfigEntry *sp_token_index = config_entry_specific; sp_token_index->token; ++sp_token_index) {
        switch (check_long(*sp_token_index, sp_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n", sp_token_index->token, sp_token_index[1].token, sp_token_index->name);
            ++sp_token_index;
            break;
        default:
            printf(sp_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   sp_token_index->token,
                   sp_token_index->name);
        }
    }
    printf("\nColor Description Options:\n");
    for (ConfigEntry *cd_token_index = config_entry_color_description; cd_token_index->token; ++cd_token_index) {
        switch (check_long(*cd_token_index, cd_token_index[1])) {
        case 1:
            printf("  %s, %-25s    %-25s\n", cd_token_index->token, cd_token_index[1].token, cd_token_index->name);
            ++cd_token_index;
            break;
        default:
            printf(cd_token_index->token[1] == '-' ? "      %-25s    %-25s\n" : "      -%-25s   %-25s\n",
                   cd_token_index->token,
                   cd_token_index->name);
        }
    }
    return 1;
}

/******************************************************
* Get the number of channels and validate it with input
******************************************************/
uint32_t get_number_of_channels(int32_t argc, char *const argv[]) {
    char config_string[COMMAND_LINE_MAX_SIZE];
    if (find_token(argc, argv, CHANNEL_NUMBER_TOKEN, config_string) == 0) {
        // Set the input file
        uint32_t channel_number = strtol(config_string, NULL, 0);
        if ((channel_number > MAX_CHANNEL_NUMBER) || channel_number == 0) {
            fprintf(
                stderr, "[SVT-Error]: The number of channels has to be within the range [1,%u]\n", MAX_CHANNEL_NUMBER);
            return 0;
        }
        return channel_number;
    }
    return 1;
}

static Bool check_two_pass_conflicts(int32_t argc, char *const argv[]) {
    char        config_string[COMMAND_LINE_MAX_SIZE];
    const char *conflicts[] = {
        PASS_TOKEN,
        NULL,
    };
    int         i = 0;
    const char *token;
    while ((token = conflicts[i])) {
        if (find_token(argc, argv, token, config_string) == 0) {
            fprintf(stderr, "[SVT-Error]: --passes is not accepted in combination with %s\n", token);
            return TRUE;
        }
        i++;
    }
    return FALSE;
}
/*
* Returns the number of passes, multi_pass_mode
*/
uint32_t get_passes(int32_t argc, char *const argv[], EncPass enc_pass[MAX_ENC_PASS]) {
    char           config_string[COMMAND_LINE_MAX_SIZE];
    MultiPassModes multi_pass_mode;

    int rc_mode = 0;
    // copied from str_to_rc_mode()
    const struct {
        const char *name;
        uint32_t    mode;
    } rc[] = {
        {"0", 0},
        {"1", 1},
        {"2", 2},
        {"cqp", 0},
        {"crf", 0},
        {"vbr", 1},
        {"cbr", 2},
    };
    const size_t rc_size  = sizeof(rc) / sizeof(rc[0]);
    int          enc_mode = 0;
    // Read required inputs to decide on the number of passes and check the validity of their ranges
    if (find_token(argc, argv, RATE_CONTROL_ENABLE_TOKEN, config_string) == 0) {
        for (size_t i = 0; i < rc_size; i++) {
            if (!strcmp(config_string, rc[i].name)) {
                rc_mode = rc[i].mode;
                break;
            }
        }
        if (rc_mode > 2 || rc_mode < 0) {
            fprintf(stderr, "Error: The rate control mode must be [0 - 2] \n");
            return 0;
        }
    }

    int32_t passes     = -1;
    int     using_fifo = 0;

    if (find_token(argc, argv, INPUT_FILE_LONG_TOKEN, config_string) == 0 ||
        find_token(argc, argv, INPUT_FILE_TOKEN, config_string) == 0) {
        if (!strcmp(config_string, "stdin")) {
            using_fifo = 1;
        } else {
#ifdef _WIN32
            HANDLE in_file = CreateFile(config_string, 0, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
            if (in_file != INVALID_HANDLE_VALUE) {
                using_fifo = GetFileType(in_file) == FILE_TYPE_PIPE;
                CloseHandle(in_file);
            }
#else
            struct stat st;
            using_fifo = !stat(config_string, &st) && S_ISFIFO(st.st_mode);
#endif
        }
    }
    if (find_token(argc, argv, PRESET_TOKEN, config_string) == 0) {
        enc_mode = strtol(config_string, NULL, 0);
        if (enc_mode > MAX_ENC_PRESET || enc_mode < -1) {
            fprintf(stderr, "Error: EncoderMode must be in the range of [-1-%d]\n", MAX_ENC_PRESET);
            return 0;
        }
    }

    if (!find_token(argc, argv, INTRA_PERIOD_TOKEN, NULL) && !find_token(argc, argv, KEYINT_TOKEN, NULL)) {
        fprintf(stderr,
                "[SVT-Warning]: --keyint and --intra-period specified, --keyint will take "
                "precedence!\n");
    }

    if (find_token(argc, argv, INTRA_PERIOD_TOKEN, config_string) == 0 ||
        find_token(argc, argv, KEYINT_TOKEN, config_string) == 0) {
        const bool               is_keyint  = find_token(argc, argv, KEYINT_TOKEN, NULL) == 0;
        const int                max_keyint = 2 * ((1 << 30) - 1);
        int                      ip         = -1;
        EbSvtAv1EncConfiguration c;
        c.multiply_keyint = false;

        svt_av1_enc_parse_parameter(&c, is_keyint ? "keyint" : "intra-period", config_string);
        // temporarily set intraperiod to the max if we are using seconds based keyint
        // we don't know the fps at this point, so we can't get the actual keyint at this point
        ip = c.multiply_keyint && c.intra_period_length > 0 ? max_keyint : c.intra_period_length;
        if (!is_keyint)
            fputs("[SVT-Warning]: --intra-period is deprecated for --keyint\n", stderr);
        if ((ip < -2 || ip > max_keyint) && rc_mode == 0) {
            fprintf(stderr, "[SVT-Error]: The intra period must be [-2, 2^31-2], input %d\n", ip);
            return 0;
        }
        if ((ip < 0) && rc_mode == 1) {
            fprintf(stderr, "[SVT-Error]: The intra period must be > 0 for RateControlMode %d \n", rc_mode);
            return 0;
        }
    }

    if (find_token(argc, argv, PASSES_TOKEN, config_string) == 0) {
        if (str_to_int(PASSES_TOKEN, config_string, &passes))
            return 0;
        if (passes == 0 || passes > 2) {
            fprintf(stderr,
                    "[SVT-Error]: The number of passes has to be within the range [1,2], 2 being "
                    "multi-pass encoding\n");
            return 0;
        }
    }

    if (passes != -1 && check_two_pass_conflicts(argc, argv))
        return 0;

    // set default passes to 1 if not specified by the user
    passes = (passes == -1) ? 1 : passes;

    if (using_fifo && passes > 1) {
        fprintf(stderr, "[SVT-Warning]: The number of passes has to be 1 when using a fifo, using 1-pass\n");
        multi_pass_mode = SINGLE_PASS;
        passes          = 1;
    }
    // Determine the number of passes in CRF mode
    if (rc_mode == 0) {
        if (passes != 1) {
            passes = 1;
            fprintf(stderr,
                    "[SVT-Warning]: Multipass CRF is not supported. Switching to "
                    "1-pass encoding\n\n");
        }
        multi_pass_mode = passes == 2 ? TWO_PASS_IPP_FINAL : SINGLE_PASS;
    }
    // Determine the number of passes in rate control mode
    else if (rc_mode == 1) {
        if (passes == 1)
            multi_pass_mode = SINGLE_PASS;
        else if (passes > 1) {
            if (enc_mode > ENC_M12) {
                fprintf(stderr,
                        "[SVT-Warning]: Multipass VBR is not supported for preset %d. Switching to "
                        "1-pass encoding\n\n",
                        enc_mode);
                passes          = 1;
                multi_pass_mode = SINGLE_PASS;
            } else {
                passes          = 3;
                multi_pass_mode = THREE_PASS_IPP_SAMEPRED_FINAL;
            }
        }
    } else {
        if (passes > 1) {
            fprintf(stderr, "[SVT-Warning]: Multipass CBR is not supported. Switching to 1-pass encoding\n\n");
            passes = 1;
        }
        multi_pass_mode = SINGLE_PASS;
    }

    // Set the settings for each pass based on multi_pass_mode
    switch (multi_pass_mode) {
    case SINGLE_PASS: enc_pass[0] = ENC_SINGLE_PASS; break;
    case TWO_PASS_IPP_FINAL:
        enc_pass[0] = ENC_FIRST_PASS;
        enc_pass[1] = ENC_SECOND_PASS;
        break;
    case THREE_PASS_IPP_SAMEPRED_FINAL:
        enc_pass[0] = ENC_FIRST_PASS;
        enc_pass[1] = ENC_SECOND_PASS;
        enc_pass[2] = ENC_THIRD_PASS;
        break;
    default: break;
    }

    return passes;
}

static Bool is_negative_number(const char *string) {
    char *end;
    return strtol(string, &end, 10) < 0 && *end == '\0';
}

// Computes the number of frames in the input file
int32_t compute_frames_to_be_encoded(EbConfig *app_cfg) {
    uint64_t file_size   = 0;
    int32_t  frame_count = 0;
    uint32_t frame_size;

    // Pipes contain data streams whose end we cannot know before we reach it.
    // For pipes, we leave it up to the eof logic to detect how many frames to eventually encode.
    if (app_cfg->input_file == stdin || app_cfg->input_file_is_fifo)
        return -1;

    if (app_cfg->input_file) {
        uint64_t curr_loc = ftello(app_cfg->input_file); // get current fp location
        fseeko(app_cfg->input_file, 0L, SEEK_END);
        file_size = ftello(app_cfg->input_file);
        fseeko(app_cfg->input_file, curr_loc, SEEK_SET); // seek back to that location
    }
    frame_size = app_cfg->input_padded_width * app_cfg->input_padded_height; // Luma
    frame_size += 2 * (frame_size >> (3 - app_cfg->config.encoder_color_format)); // Add Chroma
    frame_size = frame_size << ((app_cfg->config.encoder_bit_depth == 10) ? 1 : 0);

    if (frame_size == 0)
        return -1;

    frame_count = (int32_t)(file_size / frame_size);

    if (frame_count == 0)
        return -1;

    return frame_count;
}

static Bool warn_legacy_token(const char *const token) {
    static struct warn_set {
        const char *old_token;
        const char *new_token;
    } warning_set[] = {
        {"-adaptive-quantization", ADAPTIVE_QP_ENABLE_NEW_TOKEN},
        {"-bit-depth", INPUT_DEPTH_TOKEN},
        {"-enc-mode", PRESET_TOKEN},
        {"-hdr", HDR_INPUT_NEW_TOKEN},
        {"-intra-period", KEYINT_TOKEN},
        {"-lad", LOOKAHEAD_NEW_TOKEN},
        {"-mfmv", MFMV_ENABLE_NEW_TOKEN},
        {"-qp-file", QP_FILE_NEW_TOKEN},
        {"-stat-report", STAT_REPORT_NEW_TOKEN},
        {NULL, NULL},
    };
    for (struct warn_set *tok = warning_set; tok->old_token; ++tok) {
        if (strcmp(token, tok->old_token))
            continue;
        fprintf(stderr, "[SVT-Error]: %s has been removed, use %s instead\n", tok->old_token, tok->new_token);
        return TRUE;
    }
    return FALSE;
}

static void free_config_strings(unsigned nch, char *config_strings[MAX_CHANNEL_NUMBER]) {
    for (unsigned i = 0; i < nch; ++i) free(config_strings[i]);
}

/******************************************
* Read Command Line
******************************************/
EbErrorType read_command_line(int32_t argc, char *const argv[], EncChannel *channels, uint32_t num_channels) {
    EbErrorType return_error = EB_ErrorNone;
    char        config_string[COMMAND_LINE_MAX_SIZE]; // for one input options
    char       *config_strings[MAX_CHANNEL_NUMBER]; // for multiple input options
    const char *cmd_copy[MAX_NUM_TOKENS]; // keep track of extra tokens
    const char *arg_copy[MAX_NUM_TOKENS]; // keep track of extra arguments
    uint32_t    index = 0;
    int32_t     ret_y4m;

    for (index = 0; index < num_channels; ++index)
        config_strings[index] = (char *)malloc(sizeof(char) * COMMAND_LINE_MAX_SIZE);
    for (int i = 0; i < MAX_NUM_TOKENS; ++i) {
        cmd_copy[i] = NULL;
        arg_copy[i] = NULL;
    }

    // Copy tokens into a temp token buffer hosting all tokens that are passed through the command line
    for (int32_t token_index = 0; token_index < argc; ++token_index) {
        if (!is_negative_number(argv[token_index])) {
            if (argv[token_index][0] == '-' && argv[token_index][1] != '\0')
                cmd_copy[token_index] = argv[token_index];
            else if (token_index)
                arg_copy[token_index] = argv[token_index];
        }
    }

    // First handle --nch and --passes as a single argument options
    find_token_multiple_inputs(1, argc, argv, CHANNEL_NUMBER_TOKEN, config_strings, cmd_copy, arg_copy);
    find_token_multiple_inputs(1, argc, argv, PASSES_TOKEN, config_strings, cmd_copy, arg_copy);

    /***************************************************************************************************/
    /****************  Find configuration files tokens and call respective functions  ******************/
    /***************************************************************************************************/
    // Find the Config File Path in the command line
    if (find_token_multiple_inputs(num_channels, argc, argv, CONFIG_FILE_TOKEN, config_strings, cmd_copy, arg_copy)) {
        // Parse the config file
        for (index = 0; index < num_channels; ++index) {
            EncChannel *c   = channels + index;
            c->return_error = (EbErrorType)read_config_file(c->app_cfg, config_strings[index], index);
            return_error    = (EbErrorType)(return_error & c->return_error);
        }
    } else if (find_token_multiple_inputs(
                   num_channels, argc, argv, CONFIG_FILE_LONG_TOKEN, config_strings, cmd_copy, arg_copy)) {
        // Parse the config file
        for (index = 0; index < num_channels; ++index) {
            EncChannel *c   = channels + index;
            c->return_error = (EbErrorType)read_config_file(c->app_cfg, config_strings[index], index);
            return_error    = (EbErrorType)(return_error & c->return_error);
        }
    } else {
        if (find_token(argc, argv, CONFIG_FILE_TOKEN, config_string) == 0) {
            fprintf(stderr, "Error: Config File Token Not Found\n");
            free_config_strings(num_channels, config_strings);
            return EB_ErrorBadParameter;
        }
        return_error = EB_ErrorNone;
    }

    /********************************************************************************************************/
    /***********   Find SINGLE_INPUT configuration parameter tokens and call respective functions  **********/
    /********************************************************************************************************/

    // Check tokens for invalid tokens
    {
        bool next_is_value = false;
        for (char *const *indx = argv + 1; *indx; ++indx) {
            // stop at --
            if (!strcmp(*indx, "--"))
                break;
            // skip the token if the previous token was an argument
            // assumes all of our tokens flip flop between being an argument and a value
            if (next_is_value) {
                next_is_value = false;
                continue;
            }
            // Check removed tokens
            if (warn_legacy_token(*indx)) {
                free_config_strings(num_channels, config_strings);
                return EB_ErrorBadParameter;
            }
            // exclude single letter tokens
            if ((*indx)[0] == '-' && (*indx)[1] != '-' && (*indx)[2] != '\0') {
                fprintf(stderr, "[SVT-Error]: single dash long tokens have been removed!\n");
                free_config_strings(num_channels, config_strings);
                return EB_ErrorBadParameter;
            }
            next_is_value = true;
        }
    }

    // Parse command line for tokens
    for (ConfigEntry *entry = config_entry; entry->token; ++entry) {
        if (entry->type != SINGLE_INPUT)
            continue;
        if (!find_token_multiple_inputs(num_channels, argc, argv, entry->token, config_strings, cmd_copy, arg_copy))
            continue;
        if (!strcmp(TOKEN_ERROR_MARKER, config_strings[0])) {
            free_config_strings(num_channels, config_strings);
            return EB_ErrorBadParameter;
        }
        // When a token is found mark it as found in the temp token buffer
        // Fill up the values corresponding to each channel
        for (uint32_t chan = 0; chan < num_channels; ++chan) {
            if (!strcmp(config_strings[chan], " "))
                break;
            // Mark the value as found in the temp argument buffer
            EbErrorType err             = (entry->scf)(channels[chan].app_cfg, entry->token, config_strings[chan]);
            channels[chan].return_error = (EbErrorType)(channels[chan].return_error | err);
            return_error                = (EbErrorType)(return_error & channels[chan].return_error);
        }
    }

    /***************************************************************************************************/
    /********************** Parse parameters from input file if in y4m format **************************/
    /********************** overriding config file and command line inputs    **************************/
    /***************************************************************************************************/

    for (index = 0; index < num_channels; ++index) {
        EncChannel *c = channels + index;
        if (c->app_cfg->y4m_input == TRUE) {
            ret_y4m = read_y4m_header(c->app_cfg);
            if (ret_y4m == EB_ErrorBadParameter) {
                fprintf(stderr, "Error found when reading the y4m file parameters.\n");
                free_config_strings(num_channels, config_strings);
                return EB_ErrorBadParameter;
            }
        }
    }
    /***************************************************************************************************/
    /**************************************   Verify configuration parameters   ************************/
    /***************************************************************************************************/
    // Verify the config values
    if (return_error == 0) {
        return_error = EB_ErrorBadParameter;
        for (index = 0; index < num_channels; ++index) {
            EncChannel *c = channels + index;
            if (c->return_error == EB_ErrorNone) {
                EbConfig *app_cfg = c->app_cfg;
                c->return_error   = app_verify_config(app_cfg, index);
                // set inj_frame_rate to q16 format
                if (c->return_error == EB_ErrorNone && app_cfg->injector == 1)
                    app_cfg->injector_frame_rate <<= 16;

                // Assuming no errors, add padding to width and height
                if (c->return_error == EB_ErrorNone) {
                    app_cfg->input_padded_width  = app_cfg->config.source_width;
                    app_cfg->input_padded_height = app_cfg->config.source_height;
                }

                const int32_t input_frame_count = compute_frames_to_be_encoded(app_cfg);
                const bool    n_specified       = app_cfg->frames_to_be_encoded != 0;

                // Assuming no errors, set the frames to be encoded to the number of frames in the input yuv
                if (c->return_error == EB_ErrorNone && !n_specified)
                    app_cfg->frames_to_be_encoded = input_frame_count - app_cfg->frames_to_be_skipped;

                // For pipe input it is fine if we have -1 here (we will update on end of stream)
                if (app_cfg->frames_to_be_encoded == -1 && app_cfg->input_file != stdin &&
                    !app_cfg->input_file_is_fifo) {
                    fprintf(app_cfg->error_log_file,
                            "Error instance %u: Input yuv does not contain enough frames \n",
                            index + 1);
                    c->return_error = EB_ErrorBadParameter;
                }
                if (input_frame_count != -1 && app_cfg->frames_to_be_skipped >= input_frame_count) {
                    fprintf(app_cfg->error_log_file,
                            "Error instance %u: FramesToBeSkipped is greater than or equal to the "
                            "number of frames detected\n",
                            index + 1);
                    c->return_error = EB_ErrorBadParameter;
                }

                // Force the injector latency mode, and injector frame rate when speed control is on
                if (c->return_error == EB_ErrorNone && app_cfg->speed_control_flag == 1)
                    app_cfg->injector = 1;
            }
            return_error = (EbErrorType)(return_error & c->return_error);
        }
    }

    bool has_cmd_notread = false;
    for (int i = 0; i < argc; ++i) {
        if (cmd_copy[i] && strcmp(TOKEN_READ_MARKER, cmd_copy[i])) {
            if (!has_cmd_notread)
                fprintf(stderr, "Unprocessed tokens: ");
            fprintf(stderr, "%s ", argv[i]);
            has_cmd_notread = true;
        }
    }
    if (has_cmd_notread) {
        fprintf(stderr, "\n\n");
        return_error = EB_ErrorBadParameter;
    }
    bool has_arg_notread = false;
    bool maybe_token     = false;
    for (int i = 0; i < argc; ++i) {
        if (arg_copy[i] && strcmp(TOKEN_READ_MARKER, arg_copy[i])) {
            if (!has_arg_notread)
                fprintf(stderr, "Unprocessed arguments: ");
            fprintf(stderr, "%s ", argv[i]);
            maybe_token |= !!strchr(arg_copy[i], '-');
            has_arg_notread = true;
        }
    }
    if (maybe_token) {
        fprintf(stderr, "\nMaybe missing spacing between tokens");
    }
    if (has_arg_notread) {
        fprintf(stderr, "\n\n");
        return_error = EB_ErrorBadParameter;
    }

    for (index = 0; index < num_channels; ++index) free(config_strings[index]);

    return return_error;
}
