/*
 * Copyright (c) 2022 Samsung Electronics Co., Ltd.
 * All Rights Reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 *   this list of conditions and the following disclaimer.
 *
 * - Redistributions in binary form must reproduce the above copyright notice,
 *   this list of conditions and the following disclaimer in the documentation
 *   and/or other materials provided with the distribution.
 *
 * - Neither the name of the copyright owner, nor the names of its contributors
 *   may be used to endorse or promote products derived from this software
 *   without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "oapv.h"
#include "oapv_app_util.h"
#include "oapv_app_args.h"
#include "oapv_app_y4m.h"

#define MAX_BS_BUF   (128 * 1024 * 1024)
#define MAX_NUM_FRMS (1)           // supports only 1-frame in an access unit
#define FRM_IDX      (0)           // supports only 1-frame in an access unit
#define MAX_NUM_CC   (OAPV_MAX_CC) // Max number of color componets (upto 4:4:4:4)

typedef enum _STATES {
    STATE_ENCODING,
    STATE_SKIPPING,
    STATE_STOP
} STATES;

// clang-format off

/* define various command line options as a table */
static const args_opt_t enc_args_opts[] = {
    {
        'v',  "verbose", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "verbose (log) level\n"
        "      - 0: no message\n"
        "      - 1: only error message\n"
        "      - 2: simple messages\n"
        "      - 3: frame-level messages"
    },
    {
        'i', "input", ARGS_VAL_TYPE_STRING | ARGS_VAL_TYPE_MANDATORY, 0, NULL,
        "file name of input video"
    },
    {
        'o', "output", ARGS_VAL_TYPE_STRING, 0, NULL,
        "file name of output bitstream"
    },
    {
        'r', "recon", ARGS_VAL_TYPE_STRING, 0, NULL,
        "file name of reconstructed video"
    },
    {
        'w',  "width", ARGS_VAL_TYPE_INTEGER | ARGS_VAL_TYPE_MANDATORY, 0, NULL,
        "pixel width of input video"
    },
    {
        'h',  "height", ARGS_VAL_TYPE_INTEGER | ARGS_VAL_TYPE_MANDATORY, 0, NULL,
        "pixel height of input video"
    },
    {
        'q',  "qp", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "QP value (0~51)"
    },
    {
        'z',  "fps", ARGS_VAL_TYPE_STRING | ARGS_VAL_TYPE_MANDATORY, 0, NULL,
        "frame rate (frame per second))"
    },
    {
        'm',  "threads", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "force to use a specific number of threads"
    },
    {
        ARGS_NO_KEY,  "preset", ARGS_VAL_TYPE_STRING, 0, NULL,
        "encoder preset [fastest, fast, medium, slow, placebo]"
    },
    {
        'd',  "input-depth", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "input bit depth (8, 10) "
    },
    {
        ARGS_NO_KEY,  "input-csp", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "input color space (chroma format)\n"
        "      - 0: YUV400\n"
        "      - 1: YUV420\n"
        "      - 2: YUV422\n"
        "      - 3: YUV444\n"
        "      - 4: YUV4444\n"
        "      - 5: P2(Planar Y, Combined UV, 422)"
    },
    {
        ARGS_NO_KEY,  "profile", ARGS_VAL_TYPE_STRING, 0, NULL,
        "profile setting flag  (422-10)"
    },
    {
        ARGS_NO_KEY,  "level", ARGS_VAL_TYPE_STRING, 0, NULL,
        "level setting (1, 1.1, 2, 2.1, 3, 3.1, 4, 4.1, 5, 5.1, 6, 6.1, 7, 7.1)"
    },
    {
        ARGS_NO_KEY,  "band", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "band setting (0, 1, 2, 3)"
    },
    {
        ARGS_NO_KEY,  "max-au", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "maximum number of access units to be encoded"
    },
    {
        ARGS_NO_KEY,  "seek", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "number of skipped access units before encoding"
    },
    {
        ARGS_NO_KEY,  "qp-cb-offset", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "QP offset value for Cb"
    },
    {
        ARGS_NO_KEY,  "qp-cr-offset", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "QP offset value for Cr"
    },
    {
        ARGS_NO_KEY,  "tile-w-mb", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "width of tile in units of MBs"
    },
    {
        ARGS_NO_KEY,  "tile-h-mb", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "height of tile in units of MBs"
    },
    {
        ARGS_NO_KEY,  "bitrate", ARGS_VAL_TYPE_STRING, 0, NULL,
        "enable ABR rate control\n"
        "      bitrate in terms of kilo-bits per second: Kbps(none,K,k), Mbps(M,m)\n"
        "      ex) 100 = 100K = 0.1M"
    },
    {
        ARGS_NO_KEY,  "use-filler", ARGS_VAL_TYPE_INTEGER, 0, NULL,
        "user filler flag"
    },
    {
        ARGS_NO_KEY,  "q-matrix-y", ARGS_VAL_TYPE_STRING, 0, NULL,
        "custom quantization matrix for Y \"q1 q2 ... q63 q64\""
    },
    {
        ARGS_NO_KEY,  "q-matrix-u", ARGS_VAL_TYPE_STRING, 0, NULL,
        "custom quantization matrix for U \"q1 q2 ... q63 q64\""
    },
    {
        ARGS_NO_KEY,  "q-matrix-v", ARGS_VAL_TYPE_STRING, 0, NULL,
        "custom quantization matrix for V \"q1 q2 ... q63 q64\""
    },
    {
        ARGS_NO_KEY,  "q-matrix-x", ARGS_VAL_TYPE_STRING, 0, NULL,
        "custom quantization matrix for X \"q1 q2 ... q63 q64\""
    },
    {
        ARGS_NO_KEY,  "hash", ARGS_VAL_TYPE_NONE, 0, NULL,
        "embed frame hash value for conformance checking in decoding"
    },
    {ARGS_END_KEY, "", ARGS_VAL_TYPE_NONE, 0, NULL, ""} /* termination */
};

// clang-format on

#define NUM_ARGS_OPT ((int)(sizeof(enc_args_opts) / sizeof(enc_args_opts[0])))

typedef struct args_var {
    /* variables for options */
    char           fname_inp[256];
    char           fname_out[256];
    char           fname_rec[256];
    int            max_au;
    int            hash;
    int            input_depth;
    int            input_csp;
    int            seek;
    int            threads;
    char           profile[32];
    char           level[32];
    int            band;
    char           bitrate[64];
    char           fps[256];
    char           q_matrix_y[512];
    char           q_matrix_u[512];
    char           q_matrix_v[512];
    char           q_matrix_x[512];
    char           preset[32];
    oapve_param_t *param;
} args_var_t;

static args_var_t *args_init_vars(args_parser_t *args, oapve_param_t *param)
{
    args_opt_t *opts;
    args_var_t *vars;

    opts = args->opts;
    vars = malloc(sizeof(args_var_t));
    assert_rv(vars != NULL, NULL);
    memset(vars, 0, sizeof(args_var_t));

    vars->param = param;

    /*args_set_variable_by_key_long(opts, "config", args->fname_cfg);*/
    args_set_variable_by_key_long(opts, "input", vars->fname_inp);
    args_set_variable_by_key_long(opts, "output", vars->fname_out);
    args_set_variable_by_key_long(opts, "recon", vars->fname_rec);
    args_set_variable_by_key_long(opts, "max-au", &vars->max_au);
    args_set_variable_by_key_long(opts, "hash", &vars->hash);
    args_set_variable_by_key_long(opts, "verbose", &op_verbose);
    op_verbose = VERBOSE_SIMPLE; /* default */
    args_set_variable_by_key_long(opts, "input-depth", &vars->input_depth);
    vars->input_depth = 10; /* default */
    args_set_variable_by_key_long(opts, "input-csp", &vars->input_csp);
    vars->input_csp = -1;
    args_set_variable_by_key_long(opts, "seek", &vars->seek);
    args_set_variable_by_key_long(opts, "profile", vars->profile);
    strncpy(vars->profile, "422-10", sizeof(vars->profile) - 1);
    args_set_variable_by_key_long(opts, "level", vars->level);
    strncpy(vars->level, "4.1", sizeof(vars->level) - 1);
    args_set_variable_by_key_long(opts, "band", &vars->band);
    vars->band = 2; /* default */
    args_set_variable_by_key_long(opts, "bitrate", vars->bitrate);
    args_set_variable_by_key_long(opts, "fps", vars->fps);
    strncpy(vars->fps, "60", sizeof(vars->fps) - 1);
    args_set_variable_by_key_long(opts, "q-matrix-y", vars->q_matrix_y);
    strncpy(vars->q_matrix_y, "", sizeof(vars->q_matrix_y) - 1);
    args_set_variable_by_key_long(opts, "q-matrix-u", vars->q_matrix_u);
    strncpy(vars->q_matrix_u, "", sizeof(vars->q_matrix_y) - 1);
    args_set_variable_by_key_long(opts, "q-matrix-v", vars->q_matrix_v);
    strncpy(vars->q_matrix_v, "", sizeof(vars->q_matrix_y) - 1);
    args_set_variable_by_key_long(opts, "q-matrix-x", vars->q_matrix_x);
    strncpy(vars->q_matrix_x, "", sizeof(vars->q_matrix_x) - 1);
    args_set_variable_by_key_long(opts, "threads", &vars->threads);
    vars->threads = 1; /* default */
    args_set_variable_by_key_long(opts, "preset", vars->preset);
    strncpy(vars->preset, "", sizeof(vars->preset) - 1);

    ARGS_SET_PARAM_VAR_KEY(opts, param, w);
    ARGS_SET_PARAM_VAR_KEY(opts, param, h);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, qp);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, use_filler);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, tile_w_mb);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, tile_h_mb);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, qp_cb_offset);
    ARGS_SET_PARAM_VAR_KEY_LONG(opts, param, qp_cr_offset);

    return vars;
}

static void print_usage(const char **argv)
{
    int            i;
    char           str[1024];
    args_parser_t *args;
    args_var_t    *args_var = NULL;
    oapve_param_t  default_param;

    oapve_param_default(&default_param);
    args = args_create(enc_args_opts, NUM_ARGS_OPT);
    if(args == NULL)
        goto ERR;
    args_var = args_init_vars(args, &default_param);
    if(args_var == NULL)
        goto ERR;

    logv2("Syntax: \n");
    logv2("  %s -i 'input-file' [ options ] \n\n", argv[0]);

    logv2("Options:\n");
    logv2("  --help\n    : list options\n");
    for(i = 0; i < args->num_option; i++) {
        if(args->get_help(args, i, str) < 0)
            return;
        logv2("%s\n", str);
    }

ERR:
    if(args)
        args->release(args);
    if(args_var)
        free(args_var);
}

static int check_conf(oapve_cdesc_t *cdesc, args_var_t *vars)
{
    int i;
    for(i = 0; i < cdesc->max_num_frms; i++) {
        if(vars->hash && strlen(vars->fname_rec) == 0) {
            logerr("cannot use frame hash without reconstructed picture option!\n");
            return -1;
        }
    }
    return 0;
}

static int set_extra_config(oapve_t id, args_var_t *vars, oapve_param_t *param)
{
    int ret = 0, size, value;

    if(vars->hash) {
        value = 1;
        size = 4;
        ret = oapve_config(id, OAPV_CFG_SET_USE_FRM_HASH, &value, &size);
        if(OAPV_FAILED(ret)) {
            logerr("failed to set config for using frame hash\n");
            return -1;
        }
    }
    return ret;
}

static void print_commandline(int argc, const char **argv)
{
    int i;
    if(op_verbose < VERBOSE_FRAME)
        return;

    logv3("Command line: ");
    for(i = 0; i < argc; i++) {
        logv3("%s ", argv[i]);
    }
    logv3("\n\n");
}

static void print_config(args_var_t *vars, oapve_param_t *param)
{
    if(op_verbose < VERBOSE_FRAME)
        return;

    logv3_line("Configurations");
    logv3("Input sequence : %s \n", vars->fname_inp);
    if(strlen(vars->fname_out) > 0) {
        logv3("Output bitstream : %s \n", vars->fname_out);
    }
    if(strlen(vars->fname_rec) > 0) {
        logv3("Reconstructed sequence : %s \n", vars->fname_rec);
    }
    logv3("    profile             = %s\n", vars->profile);
    logv3("    width               = %d\n", param->w);
    logv3("    height              = %d\n", param->h);
    logv3("    FPS                 = %.2f\n", (float)param->fps_num / param->fps_den);
    logv3("    QP                  = %d\n", param->qp);
    logv3("    max number of AUs   = %d\n", vars->max_au);
    logv3("    rate control type   = %s\n", (param->rc_type == OAPV_RC_ABR) ? "average Bitrate" : "constant QP");
    if(param->rc_type == OAPV_RC_ABR) {
        logv3("    target bitrate      = %dkbps\n", param->bitrate);
    }
    logv3("    tile size           = %d x %d\n", param->tile_w_mb * OAPV_MB_W, param->tile_h_mb * OAPV_MB_H);
}

static void print_stat_au(oapve_stat_t *stat, int au_cnt, oapve_param_t *param, int max_au, double bitrate_tot, oapv_clk_t clk_au, oapv_clk_t clk_tot)
{
    if(op_verbose >= VERBOSE_FRAME) {
        logv3_line("");
        logv3("AU %-5d  %10d-bytes  %3d-frame(s) %10d msec\n", au_cnt, stat->write, stat->aui.num_frms, oapv_clk_msec(clk_au));
    }
    else {
        int total_time = ((int)oapv_clk_msec(clk_tot) / 1000);
        int h = total_time / 3600;
        total_time = total_time % 3600;
        int m = total_time / 60;
        total_time = total_time % 60;
        int    s = total_time;
        double curr_bitrate = bitrate_tot;
        curr_bitrate *= (((float)param->fps_num / param->fps_den) * 8);
        curr_bitrate /= (au_cnt + 1);
        curr_bitrate /= 1000;
        logv2("[ %d / %d AU(s) ] [ %.2f AU/sec ] [ %.4f kbps ] [ %2dh %2dm %2ds ] \r",
              au_cnt, max_au, ((float)(au_cnt + 1) * 1000) / ((float)oapv_clk_msec(clk_tot)), curr_bitrate, h, m, s);
        fflush(stdout);
    }
}

static void print_stat_frms(oapve_stat_t *stat, oapv_frms_t *ifrms, oapv_frms_t *rfrms, double psnr_avg[MAX_NUM_FRMS][MAX_NUM_CC])
{
    int              i, j;
    oapv_frm_info_t *finfo;
    double           psnr[MAX_NUM_FRMS][MAX_NUM_CC] = { 0 };

    assert(stat->aui.num_frms == ifrms->num_frms);
    assert(stat->aui.num_frms <= MAX_NUM_FRMS);

    // calculate PSNRs
    if(rfrms != NULL) {
        for(i = 0; i < stat->aui.num_frms; i++) {
            if(rfrms->frm[i].imgb) {
                measure_psnr(ifrms->frm[i].imgb, rfrms->frm[i].imgb, psnr[i], OAPV_CS_GET_BIT_DEPTH(ifrms->frm[i].imgb->cs));
                for(j = 0; j < MAX_NUM_CC; j++) {
                    psnr_avg[i][j] += psnr[i][j];
                }
            }
        }
    }
    // print verbose messages
    if(op_verbose < VERBOSE_FRAME)
        return;

    finfo = stat->aui.frm_info;

    for(i = 0; i < stat->aui.num_frms; i++) {
        // clang-format off
        const char* str_frm_type = finfo[i].pbu_type == OAPV_PBU_TYPE_PRIMARY_FRAME ? "PRIMARY"
                                 : finfo[i].pbu_type == OAPV_PBU_TYPE_NON_PRIMARY_FRAME ? "NON-PRIMARY"
                                 : finfo[i].pbu_type == OAPV_PBU_TYPE_PREVIEW_FRAME ? "PREVIEW"
                                 : finfo[i].pbu_type == OAPV_PBU_TYPE_DEPTH_FRAME ? "DEPTH"
                                 : finfo[i].pbu_type == OAPV_PBU_TYPE_ALPHA_FRAME ? "ALPHA"
                                 : "UNKNOWN";
        // clang-format on

        logv3("- FRM %-2d GID %-5d %-11s %9d-bytes %8.4fdB %8.4fdB %8.4fdB\n",
              i, finfo[i].group_id, str_frm_type, stat->frm_size[i], psnr[i][0], psnr[i][1], psnr[i][2]);
    }
    fflush(stdout);
    fflush(stderr);
}

static int kbps_str_to_int(char *str)
{
    int kbps;
    if(strchr(str, 'K') || strchr(str, 'k')) {
        char *tmp = strtok(str, "Kk ");
        kbps = (int)(atof(tmp));
    }
    else if(strchr(str, 'M') || strchr(str, 'm')) {
        char *tmp = strtok(str, "Mm ");
        kbps = (int)(atof(tmp) * 1000);
    }
    else {
        kbps = atoi(str);
    }
    return kbps;
}

static int update_param(args_var_t *vars, oapve_param_t *param)
{
    /* update reate controller  parameters */
    if(strlen(vars->bitrate) > 0) {
        param->bitrate = kbps_str_to_int(vars->bitrate);
        param->rc_type = OAPV_RC_ABR;
    }

    /* update q_matrix */
    int len_y = (int)strlen(vars->q_matrix_y);
    if(len_y > 0) {
        param->use_q_matrix = 1;
        char *tmp = vars->q_matrix_y;
        int   cnt = 0;
        int   len_cnt = 0;
        while(len_cnt < len_y && cnt < OAPV_BLK_D) {
            sscanf(tmp, "%d", &param->q_matrix_y[cnt]);
            if(param->q_matrix_y[cnt] < 1 || param->q_matrix_y[cnt] > 255) {
                logerr("input value of q_matrix_y is invalid\n");
                return -1;
            }
            len_cnt += (int)log10(param->q_matrix_y[cnt]) + 2;
            tmp = vars->q_matrix_y + len_cnt;
            cnt++;
        }
        if(cnt < OAPV_BLK_D) {
            logerr("input number of q_matrix_y is not enough\n");
            return -1;
        }
    }

    int len_u = (int)strlen(vars->q_matrix_u);
    if(len_u > 0) {
        param->use_q_matrix = 1;
        char *tmp = vars->q_matrix_u;
        int   cnt = 0;
        int   len_cnt = 0;
        while(len_cnt < len_u && cnt < OAPV_BLK_D) {
            sscanf(tmp, "%d", &param->q_matrix_u[cnt]);
            if(param->q_matrix_u[cnt] < 1 || param->q_matrix_u[cnt] > 255) {
                logerr("input value of q_matrix_u is invalid\n");
                return -1;
            }
            len_cnt += (int)log10(param->q_matrix_u[cnt]) + 2;
            tmp = vars->q_matrix_u + len_cnt;
            cnt++;
        }
        if(cnt < OAPV_BLK_D) {
            logerr("input number of q_matrix_u is not enough\n");
            return -1;
        }
    }

    int len_v = (int)strlen(vars->q_matrix_v);
    if(len_v > 0) {
        param->use_q_matrix = 1;
        char *tmp = vars->q_matrix_v;
        int   cnt = 0;
        int   len_cnt = 0;
        while(len_cnt < len_v && cnt < OAPV_BLK_D) {
            sscanf(tmp, "%d", &param->q_matrix_v[cnt]);
            if(param->q_matrix_v[cnt] < 1 || param->q_matrix_v[cnt] > 255) {
                logerr("input value of q_matrix_v is invalid\n");
                return -1;
            }
            len_cnt += (int)log10(param->q_matrix_v[cnt]) + 2;
            tmp = vars->q_matrix_v + len_cnt;
            cnt++;
        }
        if(cnt < OAPV_BLK_D) {
            logerr("input number of q_matrix_v is not enough\n");
            return -1;
        }
    }

    int len_x = (int)strlen(vars->q_matrix_x);
    if (len_x > 0) {
        param->use_q_matrix = 1;
        char* tmp = vars->q_matrix_x;
        int   cnt = 0;
        int   len_cnt = 0;
        while (len_cnt < len_x && cnt < OAPV_BLK_D) {
            sscanf(tmp, "%d", &param->q_matrix_x[cnt]);
            if (param->q_matrix_x[cnt] < 1 || param->q_matrix_x[cnt] > 255) {
                logerr("input value of q_matrix_x is invalid\n");
                return -1;
            }
            len_cnt += (int)log10(param->q_matrix_x[cnt]) + 2;
            tmp = vars->q_matrix_x + len_cnt;
            cnt++;
        }
        if (cnt < OAPV_BLK_D) {
            logerr("input number of q_matrix_x is not enough\n");
            return -1;
        }
    }

    if(param->use_q_matrix) {
        if(len_y == 0) {
            for(int i = 0; i < OAPV_BLK_D; i++) {
                param->q_matrix_y[i] = 16;
            }
        }

        if(len_u == 0) {
            for(int i = 0; i < OAPV_BLK_D; i++) {
                param->q_matrix_u[i] = 16;
            }
        }

        if(len_v == 0) {
            for(int i = 0; i < OAPV_BLK_D; i++) {
                param->q_matrix_v[i] = 16;
            }
        }

        if (len_x == 0) {
            for (int i = 0; i < OAPV_BLK_D; i++) {
                param->q_matrix_x[i] = 16;
            }
        }
    }

    param->csp = vars->input_csp;

    /* update level idc */
    double tmp_level = 0;
    sscanf(vars->level, "%lf", &tmp_level);
    param->level_idc = tmp_level * 30;
    /* update band idc */
    param->band_idc = vars->band;

    /* update fps */
    if(strpbrk(vars->fps, "/") != NULL) {
        sscanf(vars->fps, "%d/%d", &param->fps_num, &param->fps_den);
    }
    else if(strpbrk(vars->fps, ".") != NULL) {
        float tmp_fps = 0;
        sscanf(vars->fps, "%f", &tmp_fps);
        param->fps_num = tmp_fps * 10000;
        param->fps_den = 10000;
    }
    else {
        sscanf(vars->fps, "%d", &param->fps_num);
        param->fps_den = 1;
    }

    if(strlen(vars->preset) > 0) {
        if(strcmp(vars->preset, "fastest") == 0) {
            param->preset = OAPV_PRESET_FASTEST;
        }
        else if(strcmp(vars->preset, "fast") == 0) {
            param->preset = OAPV_PRESET_FAST;
        }
        else if(strcmp(vars->preset, "medium") == 0) {
            param->preset = OAPV_PRESET_MEDIUM;
        }
        else if(strcmp(vars->preset, "slow") == 0) {
            param->preset = OAPV_PRESET_SLOW;
        }
        else if(strcmp(vars->preset, "placebo") == 0) {
            param->preset = OAPV_PRESET_PLACEBO;
        }
        else {
            logerr("input value of preset is invalid\n");
            return -1;
        }
    }
    else {
        param->preset = OAPV_PRESET_DEFAULT;
    }

    return 0;
}

int main(int argc, const char **argv)
{
    args_parser_t *args = NULL;
    args_var_t    *args_var = NULL;
    STATES         state = STATE_ENCODING;
    unsigned char *bs_buf = NULL;
    FILE          *fp_inp = NULL;
    oapve_t        id = NULL;
    oapvm_t        mid = NULL;
    oapve_cdesc_t  cdesc;
    oapve_param_t *param = NULL;
    oapv_bitb_t    bitb;
    oapve_stat_t   stat;
    oapv_imgb_t   *imgb_r = NULL; // image buffer for read
    oapv_imgb_t   *imgb_w = NULL; // image buffer for write
    oapv_imgb_t   *imgb_i = NULL; // image buffer for input
    oapv_imgb_t   *imgb_o = NULL; // image buffer for output
    oapv_frms_t    ifrms = { 0 }; // frames for input
    oapv_frms_t    rfrms = { 0 }; // frames for reconstruction
    int            ret;
    oapv_clk_t     clk_beg, clk_end, clk_tot;
    oapv_mtime_t   au_cnt, au_skip;
    double         bitrate_tot; // total bitrate (byte)
    double         psnr_avg[MAX_NUM_FRMS][MAX_NUM_CC] = { 0 };
    int            is_y4m;
    y4m_params_t   y4m;
    int            is_out = 0, is_rec = 0;
    char          *errstr = NULL;
    int            cfmt;                      // color format
    const int      num_frames = MAX_NUM_FRMS; // number of frames in an access unit

    /* help message */
    if(argc < 2 || !strcmp(argv[1], "--help") || !strcmp(argv[1], "-h")) {
        print_usage(argv);
        return 0;
    }

    /* set default parameters */
    memset(&cdesc, 0, sizeof(oapve_cdesc_t));
    param = &cdesc.param[FRM_IDX];
    ret = oapve_param_default(param);
    if(OAPV_FAILED(ret)) {
        logerr("cannot set default parameter\n");
        ret = -1;
        goto ERR;
    }
    /* parse command line */
    args = args_create(enc_args_opts, NUM_ARGS_OPT);
    if(args == NULL) {
        logerr("cannot create argument parser\n");
        ret = -1;
        goto ERR;
    }
    args_var = args_init_vars(args, param);
    if(args_var == NULL) {
        logerr("cannot initialize argument parser\n");
        ret = -1;
        goto ERR;
    }
    if(args->parse(args, argc, argv, &errstr)) {
        logerr("command parsing error (%s)\n", errstr);
        ret = -1;
        goto ERR;
    }
    // print logo
    logv2("  ____                ___   ___ _   __\n");
    logv2(" / __ \\___  ___ ___  / _ | / _ \\ | / / Encoder\n");
    logv2("/ /_/ / _ \\/ -_) _ \\/ __ |/ ___/ |/ / \n");
    logv2("\\____/ .__/\\__/_//_/_/ |_/_/   |___/  \n");
    logv2("    /_/                               \n");
    logv2("\n");

    // print command line string for information
    print_commandline(argc, argv);

    // check mandatory arguments
    if(args->check_mandatory(args, &errstr)) {
        logerr("'--%s' argument is mandatory\n", errstr);
        ret = -1;
        goto ERR;
    }

    /* try to open input file */
    fp_inp = fopen(args_var->fname_inp, "rb");
    if(fp_inp == NULL) {
        logerr("ERROR: cannot open input file = (%s)\n", args_var->fname_inp);
        ret = -1;
        goto ERR;
    }

    /* y4m header parsing  */
    is_y4m = y4m_test(fp_inp);
    if(is_y4m) {
        if(y4m_header_parser(fp_inp, &y4m)) {
            logerr("This y4m is not supported (%s)\n", args_var->fname_inp);
            ret = -1;
            goto ERR;
        }
        y4m_update_param(args, &y4m);
        cfmt = y4m.color_format;
        // clang-format off
        args_var->input_csp = (cfmt == OAPV_CF_YCBCR400 ? 0 : \
            (cfmt == OAPV_CF_YCBCR420 ? 1 : \
            (cfmt == OAPV_CF_YCBCR422 ? 2 : \
            (cfmt == OAPV_CF_YCBCR444 ? 3 : \
            (cfmt == OAPV_CF_YCBCR4444 ? 4 : \
            (cfmt == OAPV_CF_PLANAR2 ? 5 : -1))))));
        // clang-format on

        if(args_var->input_csp != -1) {
            // force "input-csp" argument has set
            args->set_flag(args, "input-csp", 1);
        }
    }
    else {
        // clang-format off
        cfmt = (args_var->input_csp == 0 ? OAPV_CF_YCBCR400 : \
            (args_var->input_csp == 1 ? OAPV_CF_YCBCR420 : \
            (args_var->input_csp == 2 ? OAPV_CF_YCBCR422 : \
            (args_var->input_csp == 3 ? OAPV_CF_YCBCR444  : \
            (args_var->input_csp == 4 ? OAPV_CF_YCBCR4444 : \
            (args_var->input_csp == 5 ? OAPV_CF_PLANAR2   : OAPV_CF_UNKNOWN))))));
        // clang-format on
    }
    if(args_var->input_csp == -1) {
        logerr("Unknown input color space. set '--input-csp' argument\n");
        ret = -1;
        goto ERR;
    }

    /* update parameters */
    if(update_param(args_var, param)) {
        logerr("parameters is not proper\n");
        ret = -1;
        goto ERR;
    }

    cdesc.max_bs_buf_size = MAX_BS_BUF; /* maximum bitstream buffer size */
    cdesc.max_num_frms = MAX_NUM_FRMS;
    cdesc.threads = args_var->threads;

    if(check_conf(&cdesc, args_var)) {
        logerr("invalid configuration\n");
        ret = -1;
        goto ERR;
    }

    if(strlen(args_var->fname_out) > 0) {
        clear_data(args_var->fname_out);
        is_out = 1;
    }

    if(strlen(args_var->fname_rec) > 0) {
        clear_data(args_var->fname_rec);
        is_rec = 1;
    }

    /* allocate bitstream buffer */
    bs_buf = (unsigned char *)malloc(MAX_BS_BUF);
    if(bs_buf == NULL) {
        logerr("cannot allocate bitstream buffer, size=%d", MAX_BS_BUF);
        ret = -1;
        goto ERR;
    }

    /* create encoder */
    id = oapve_create(&cdesc, NULL);
    if(id == NULL) {
        logerr("cannot create OAPV encoder\n");
        ret = -1;
        goto ERR;
    }

    /* create metadata handler */
    mid = oapvm_create(&ret);
    if(mid == NULL || OAPV_FAILED(ret)) {
        logerr("cannot create OAPV metadata handler\n");
        ret = -1;
        goto ERR;
    }

    if(set_extra_config(id, args_var, param)) {
        logerr("cannot set extra configurations\n");
        ret = -1;
        goto ERR;
    }

    print_config(args_var, param);

    bitrate_tot = 0;
    bitb.addr = bs_buf;
    bitb.bsize = MAX_BS_BUF;

    if(args_var->seek > 0) {
        state = STATE_SKIPPING;
    }

    clk_tot = 0;
    au_cnt = 0;
    au_skip = 0;

    // create input and reconstruction image buffers
    memset(&ifrms, 0, sizeof(oapv_frm_t));
    memset(&rfrms, 0, sizeof(oapv_frm_t));

    for(int i = 0; i < num_frames; i++) {
        if(args_var->input_depth == 10) {
            ifrms.frm[i].imgb = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, args_var->input_depth, 0));
        }
        else {
            imgb_r = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, args_var->input_depth, 0));
            ifrms.frm[i].imgb = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, 10, 0));
        }

        if(is_rec) {
            if(args_var->input_depth == 10) {
                rfrms.frm[i].imgb = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, args_var->input_depth, 0));
            }
            else {
                imgb_w = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, args_var->input_depth, 0));
                rfrms.frm[i].imgb = imgb_create(param->w, param->h, OAPV_CS_SET(cfmt, 10, 0));
            }
            rfrms.num_frms++;
        }
        ifrms.num_frms++;
    }

    /* encode pictures *******************************************************/
    while(args_var->max_au == 0 || (au_cnt < args_var->max_au)) {
        for(int i = 0; i < num_frames; i++) {
            if(args_var->input_depth == 10) {
                imgb_i = ifrms.frm[i].imgb;
            }
            else {
                imgb_i = imgb_r;
            }
            ret = imgb_read(fp_inp, imgb_i, param->w, param->h, is_y4m);
            if(ret < 0) {
                logv3("reached out the end of input file\n");
                ret = OAPV_OK;
                state = STATE_STOP;
                break;
            }
            if(args_var->input_depth != 10) {
                imgb_cpy(ifrms.frm[i].imgb, imgb_i);
            }
            ifrms.frm[i].group_id = 1; // FIX-ME : need to set properly in case of multi-frame
            ifrms.frm[i].pbu_type = OAPV_PBU_TYPE_PRIMARY_FRAME;
        }

        if(state == STATE_ENCODING) {
            /* encoding */
            clk_beg = oapv_clk_get();

            ret = oapve_encode(id, &ifrms, mid, &bitb, &stat, &rfrms);

            clk_end = oapv_clk_from(clk_beg);
            clk_tot += clk_end;

            bitrate_tot += stat.frm_size[FRM_IDX];

            print_stat_au(&stat, au_cnt, param, args_var->max_au, bitrate_tot, clk_end, clk_tot);

            for(int i = 0; i < num_frames; i++) {
                if(is_rec) {
                    if(args_var->input_depth != 10) {
                        imgb_cpy(imgb_w, rfrms.frm[i].imgb);
                        imgb_o = imgb_w;
                    }
                    else {
                        imgb_o = rfrms.frm[i].imgb;
                    }
                }

                /* store bitstream */
                if(OAPV_SUCCEEDED(ret)) {
                    if(is_out && stat.write > 0) {
                        if(write_data(args_var->fname_out, bs_buf, stat.write)) {
                            logerr("cannot write bitstream\n");
                            ret = -1;
                            goto ERR;
                        }
                    }
                }
                else {
                    logerr("failed to encode\n");
                    ret = -1;
                    goto ERR;
                }

                // store recon image
                if(is_rec) {
                    if(imgb_write(args_var->fname_rec, imgb_o)) {
                        logerr("cannot write reconstruction image\n");
                        ret = -1;
                        goto ERR;
                    }
                }

                print_stat_frms(&stat, &ifrms, &rfrms, psnr_avg);
                au_cnt++;
            }
        }
        else if(state == STATE_SKIPPING) {
            if(au_skip < args_var->seek) {
                au_skip++;
                continue;
            }
            else {
                state = STATE_ENCODING;
            }
        }
        else if(state == STATE_STOP) {
            break;
        }
        oapvm_rem_all(mid);
    }

    logv2_line("Summary");
    psnr_avg[FRM_IDX][0] /= au_cnt;
    psnr_avg[FRM_IDX][1] /= au_cnt;
    psnr_avg[FRM_IDX][2] /= au_cnt;
    if(cfmt == OAPV_CF_YCBCR4444) {
        psnr_avg[FRM_IDX][3] /= au_cnt;
    }

    logv3("  PSNR Y(dB)       : %-5.4f\n", psnr_avg[FRM_IDX][0]);
    logv3("  PSNR U(dB)       : %-5.4f\n", psnr_avg[FRM_IDX][1]);
    logv3("  PSNR V(dB)       : %-5.4f\n", psnr_avg[FRM_IDX][2]);
    if(cfmt == OAPV_CF_YCBCR4444) {
        logv3("  PSNR T(dB)       : %-5.4f\n", psnr_avg[FRM_IDX][3]);
    }
    logv3("  Total bits(bits) : %.0f\n", bitrate_tot * 8);
    bitrate_tot *= (((float)param->fps_num / param->fps_den) * 8);
    bitrate_tot /= au_cnt;
    bitrate_tot /= 1000;

    logv3("  -----------------: bitrate(kbps)\tPSNR-Y\tPSNR-U\tPSNR-V\n");
    if(cfmt == OAPV_CF_YCBCR4444) {
        logv3("  Summary          : %-4.4f\t%-5.4f\t%-5.4f\t%-5.4f\t%-5.4f\n",
              bitrate_tot, psnr_avg[FRM_IDX][0], psnr_avg[FRM_IDX][1], psnr_avg[FRM_IDX][2], psnr_avg[FRM_IDX][3]);
    }
    else {
        logv3("  Summary          : %-5.4f\t%-5.4f\t%-5.4f\t%-5.4f\n",
              bitrate_tot, psnr_avg[FRM_IDX][0], psnr_avg[FRM_IDX][1], psnr_avg[FRM_IDX][2]);
    }

    logv2("Bitrate                           = %.4f kbps\n", bitrate_tot);
    logv2("Encoded frame count               = %d\n", (int)au_cnt);
    logv2("Total encoding time               = %.3f msec,",
          (float)oapv_clk_msec(clk_tot));
    logv2(" %.3f sec\n", (float)(oapv_clk_msec(clk_tot) / 1000.0));

    logv2("Average encoding time for a frame = %.3f msec\n",
          (float)oapv_clk_msec(clk_tot) / au_cnt);
    logv2("Average encoding speed            = %.3f frames/sec\n",
          ((float)au_cnt * 1000) / ((float)oapv_clk_msec(clk_tot)));
    logv2_line(NULL);

    if(args_var->max_au > 0 && au_cnt != args_var->max_au) {
        logv3("Wrong frames count: should be %d was %d\n", args_var->max_au, (int)au_cnt);
    }
ERR:

    if(imgb_r != NULL)
        imgb_r->release(imgb_r);
    if(imgb_w != NULL)
        imgb_w->release(imgb_w);

    for(int i = 0; i < num_frames; i++) {
        if(ifrms.frm[i].imgb != NULL) {
            ifrms.frm[i].imgb->release(ifrms.frm[i].imgb);
        }
    }
    for(int i = 0; i < num_frames; i++) {
        if(rfrms.frm[i].imgb != NULL) {
            rfrms.frm[i].imgb->release(rfrms.frm[i].imgb);
        }
    }

    if(id)
        oapve_delete(id);
    if(mid)
        oapvm_delete(mid);
    if(fp_inp)
        fclose(fp_inp);
    if(bs_buf)
        free(bs_buf); /* release bitstream buffer */
    if(args)
        args->release(args);
    if(args_var)
        free(args_var);

    return ret;
}
