#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <getopt.h>
#include <dirent.h>
#include <signal.h>
#include <sys/ioctl.h>
#include <pthread.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include "gdc_json_parser.h"
#include "hb_vin_data_info.h"
#include "hb_vpm_data_info.h"
#include "hb_vio_interface.h"

#define OK  (0)
#define ERR (-1)

#define gdc_print_err(format, ...) printf("[ERR] gdc: " format, ##__VA_ARGS__)
#define gdc_print_info(format, ...) printf("[INFO] gdc: " format, ##__VA_ARGS__)
#define gdc_print_dbg(format, ...) printf("[DBG] gdc: " format, ##__VA_ARGS__)

char g_cam_config[256] = "no input cam json";    // cam config_file
char g_vpm_config[256] = "no input vpm json";
char g_gdc_config[256] = "no input gdc json";
char g_yuv_file[256] = "no input yuv file";
int g_cam_index = 0;          // cam index --> mipi host/dev
int g_gdc_mode = 0;           // gdc_mode: 0-from file 1-from cam
int g_gdc_default_pipe = 0;
int g_yuv_x_size = 0;
int g_yuv_y_size = 0;
int g_out_x_size = 0;
int g_out_y_size = 0;
int g_gdc_show = 0;
int g_gdc_save = 0;
int g_loop_count = 0;
static int g_gdc_process_status = 1;
int g_gdc_degree = 0;


static void get_exit_signal(int signal) {
    if (signal == SIGINT) {
        gdc_print_info("\nCtrl+C Exit gdc test...\n");
        g_gdc_process_status = 0;
    }
}

static void print_opts(const char *prog)
{
    printf("\n**********************show help**********************\n");
    printf("Usage: %s \n", prog);
    puts("  -v --vpm_config_file          vpm.json\n"
         "  -g --gdc_config_file          gdc.json\n"
         "  -p --cam_default_pipe         configure the default pipeline id\n"
         "  -y --input_yuv_img_file       input yuv420sp file\n"
         "  -l --loop_count               loop count for every pipe running\n"
         "  -s --save_to_yuvfile          0: off, 1: save 1 yuv file\n"
         "  -d --degree_config            degree of rotation\n"
         "Example: \n"
         "   ./gdc_sample -v vpm.json -g gdc.json -y 1080p.yuv -p 0 -l 10 -s 1 -d 0\n");
         printf("********************show help end********************\n");
    exit(1);
}

int get_opts_for_gdc(int argc, char *argv[])
{
    int ret = OK;

    if (argc < 2) {
        print_opts(argv[0]);
        return ERR;
    }

    while (1) {
        static const char short_options[] =
        "v:g:y:p:l:s:d:";
        static const struct option long_options[] = {
            {"vpm_config_file",     1, 0, 'v'},
            {"gdc_config_file",     1, 0, 'g'},
            {"input_yuv_img_file",  1, 0, 'y'},
            {"cam_default_pipe",    1, 0, 'p'},
            {"loop_count",          1, 0, 'l'},
            {"save_to_yuvfile",     1, 0, 's'},
            {"degree_config",     1, 0, 'd'},
            {NULL, 0, 0, 0},
        };

        int cmd_ret;
        cmd_ret =
            getopt_long(argc, argv, short_options, long_options, NULL);
        if (cmd_ret == -1) {
            break;
        }

        switch (cmd_ret) {
        case 'v':
            realpath(optarg, g_vpm_config);
            // printf("vpm_config_file = %s\n", g_vpm_config);
            break;
        case 'g':
            realpath(optarg, g_gdc_config);
            // printf("gdc_config_file = %s\n", g_gdc_config);
            break;
        case 'y':
            realpath(optarg, g_yuv_file);
            // printf("yuv_file = %s\n", g_yuv_file);
            break;
        case 'p':
            g_gdc_default_pipe = atoi(optarg);
            // printf("cam_default_pipe = %d\n", g_gdc_default_pipe);
            break;
        case 'l':
            g_loop_count = atoi(optarg);
            // printf("g_loop_count = %d\n", g_loop_count);
            break;
        case 's':
            g_gdc_save = atoi(optarg);
            // printf("save_to_yuvfile = %d\n", g_gdc_save);
            break;
        case 'd':
            g_gdc_degree = atoi(optarg);
            // printf("save_to_yuvfile = %d\n", g_gdc_save);
            break;
        default:
            print_opts(argv[0]);
            break;
        }
    }

    return ret;
}

int dumpToFile2plane(char *filename, char *srcBuf, char *srcBuf1,
                     unsigned int size, unsigned int size1)
{
    FILE *yuvFd = NULL;
    char *buffer = NULL;

    yuvFd = fopen(filename, "w+");

    if (yuvFd == NULL) {
        gdc_print_dbg("open(%s) fail", filename);
        return -1;
    }

    buffer = (char *)malloc(size + size1);  // NOLINT

    if (buffer == NULL) {
        gdc_print_dbg("ERR:malloc file");
        fclose(yuvFd);
        return -1;
    }

    memcpy(buffer, srcBuf, size);
    memcpy(buffer + size, srcBuf1, size1);

    fflush(stdout);

    fwrite(buffer, 1, size + size1, yuvFd);

    fflush(yuvFd);

    if (yuvFd)
        fclose(yuvFd);
    if (buffer)
        free(buffer);

    gdc_print_dbg("filedump(%s, size(%d) is successed\n", filename, size);

    return 0;
}

int read_yuv420_file(const char *filename, char *addr0, char *addr1, uint32_t y_size)
{
    if (filename == NULL || addr0 == NULL || y_size == 0) {
            gdc_print_dbg("ERR(%s):null param.\n", __func__);
            return -1;
    }

    FILE *Fd = NULL;
    Fd = fopen(filename, "r");
    char *buffer = NULL;

    if (Fd == NULL) {
        gdc_print_dbg("ERR(%s):open(%s) fail\n", __func__, filename);
        return -1;
    }

    buffer = (char *)malloc(y_size + y_size / 2);  // NOLINT

    if (fread(buffer, 1, y_size, Fd) != y_size) {
        gdc_print_dbg("read bin(%s) to addr fail\n", filename);
        return -1;
    }

    if (fread(buffer + y_size, 1, y_size/2, Fd) != y_size/2) {
        gdc_print_dbg("read bin(%s) to addr fail\n", filename);
        return -1;
    }

    memcpy(addr0, buffer, y_size);
    memcpy(addr1, buffer + y_size, y_size/2);

    fflush(Fd);

    if (Fd)
        fclose(Fd);
    if (buffer)
        free(buffer);

    gdc_print_dbg("(%s):file read(%s), size(%d)\n", __func__, filename, y_size);
    return 0;
}

int gdc_dst_show_and_save(hb_vio_buffer_t* src_buf, hb_vio_buffer_t* dst_buf, int loop_cnt)
{
    int ret = OK;
    char file_name[100];

    if (loop_cnt <= g_gdc_save) {
        snprintf(file_name, sizeof(file_name),
                "./gdc_out_%dx%d_%d.yuv",
                dst_buf->img_addr.stride_size,
                dst_buf->img_addr.height, loop_cnt);
        dumpToFile2plane(file_name,
                        dst_buf->img_addr.addr[0],
                        dst_buf->img_addr.addr[1],
                        dst_buf->img_addr.stride_size * dst_buf->img_addr.height,
                        dst_buf->img_addr.stride_size * dst_buf->img_addr.height / 2);
    }

    return ret;
}

static int gdc_cfg_bin_gen(const char *layout_file, char* config_file, void **cfg_buf, uint64_t *config_size)
{
    int ret = 0;
    window_t* windows = NULL;
    uint32_t  wnd_num = 0;
    param_t gdc_param;

    FILE* f = fopen(layout_file, "r");
    if (!f) {
        gdc_print_err("Can't open gdc layout file %s\n", layout_file);
        return -1;
    }

    fseek(f, 0, SEEK_END);
    size_t json_sz = ftell(f);
    char* json = (char*)malloc(json_sz+1);  // NOLINT
    rewind(f);
    json[fread(json, sizeof(char), json_sz, f)] = 0;
    fclose(f);
    memset(&gdc_param, 0, sizeof(gdc_param));

    if (gdc_parse_json(json, &gdc_param, &windows, &wnd_num)) {
        gdc_print_err("ERROR: Can't process json\n");
        gdc_parse_json_clean(&windows, wnd_num);
        return -1;
    }

    free(json);
    json = NULL;

    if(gdc_param.format == FMT_UNKNOWN) {
        gdc_print_err("Can't process json: unknown frame format.\n");
        gdc_parse_json_clean(&windows, wnd_num);
        return -1;
    }

    if(wnd_num == 0) {
        gdc_print_err("Warning: no windows are specified.\n");
        gdc_parse_json_clean(&windows, wnd_num);
        return -1;
    }

    ret = hb_vio_gen_gdc_cfg(&gdc_param,
                            windows,
                            wnd_num,
                            cfg_buf,
                            config_size);

    if (ret == 0 && config_file != NULL) {
        FILE *f = fopen(config_file, "wb");
        if (f != NULL) {
            fwrite(*cfg_buf, sizeof(char), *config_size, f);
            fclose(f);
        }
    }
    gdc_print_info("gdc gen cfg_buf %p, size %lu \n", *cfg_buf, *config_size);
    gdc_parse_json_clean(&windows, wnd_num);
    return ret;
}

static int gdc_cfg_bin_update(uint32_t pipe_id, const char *layout_file)
{
    int ret = 0;
    uint32_t *cfg_buf = NULL;
    uint64_t config_size = 0;
    ret = gdc_cfg_bin_gen(layout_file, (char *)"./gdc.bin", (void **)&cfg_buf, &config_size);  // NOLINT

    if (ret == 0) {
        gdc_print_info("pipe(%u) cfg_buf(%p) size(%lu)\n",
                                pipe_id, cfg_buf, config_size);
        ret = hb_vio_set_gdc_cfg(pipe_id, cfg_buf, config_size);
        if (cfg_buf) {
                hb_vio_free_gdc_cfg(cfg_buf);
                gdc_print_info("free config_buf %p size(%lu). \n", cfg_buf, config_size);
                cfg_buf = NULL;
        }
        if (ret < 0) {
                gdc_print_err("gdc cfg bin set failed.\n");
                return -1;
        }
    } else {
        gdc_print_err("gdc cfg bin gen failed.\n");
        return -1;
    }
    gdc_print_info("pipe(%u)gdc bin update done.File %s size %lu\n", pipe_id, layout_file, config_size);
    hb_vio_free_gdc_cfg(cfg_buf);
    return ret;
}

int gdc_gdcfeedback_sample(void)
{
    int ret = OK;
    int loop_cnt = 0;
    hb_vio_buffer_t src_buf = {0};
    hb_vio_buffer_t dst_buf = {0};

    /* 1. init vps */
    ret = hb_vio_init(g_vpm_config);
    if (ret < 0) {
        gdc_print_err("hb_vio_init err\n");
        goto err1;
    }
    ret = hb_vio_start_pipeline(g_gdc_default_pipe);
    if (ret < 0) {
        gdc_print_err("vpm start %d err!, ret = %d\n", g_gdc_default_pipe, ret);
        goto err1;
    }

    /* 2. init gdc and set gdc */
    ret = gdc_cfg_bin_update(g_gdc_default_pipe, g_gdc_config);
    if (ret < 0) {
        gdc_print_err("gdc_cfg_bin_update err\n");
        return -1;
    }

    /* 3. start gdc process */
    while(loop_cnt < g_loop_count) {
        // get src buf form vpm
        loop_cnt++;
        ret = hb_vio_get_data(g_gdc_default_pipe, HB_VIO_GDC_FEEDBACK_SRC_DATA, &src_buf);
        if (ret < 0) {
                gdc_print_err("vio get gdc_buffer fail,pipeline=%d\n", g_gdc_default_pipe);
                usleep(300 * 1000);
                continue;
        }
        // push yuv data to src_buf
        int img_size = src_buf.img_addr.stride_size * src_buf.img_addr.height;
        read_yuv420_file(g_yuv_file,
                        src_buf.img_addr.addr[0],
                        src_buf.img_addr.addr[1],
                        img_size);

        // run gdc
        ret = hb_vio_run_gdc(g_gdc_default_pipe, &src_buf, &dst_buf, g_gdc_degree);
        if (ret < 0) {
                gdc_print_err("hb_vio_run_gdc error! ret = %d\n", ret);
                return -1;
        }

        // show or save buf
        gdc_dst_show_and_save(&src_buf, &dst_buf, loop_cnt);
        // free buffer
        hb_vio_free_gdcbuf(g_gdc_default_pipe, &src_buf);
        hb_vio_free_gdcbuf(g_gdc_default_pipe, &dst_buf);
        usleep(100);
    }

err1:
    // free buf
    ret = hb_vio_deinit();
    if (ret < 0) {
        gdc_print_err("hb_vin_deinit err\n");
        return ERR;
    }

    return ret;
}

int main(int argc, char *argv[])
{
    int ret = 0;

    signal(SIGINT, get_exit_signal);

    // get opts
    ret = get_opts_for_gdc(argc, argv);
    if (ret < 0) {
        gdc_print_err("get opts err!\n");
        return ERR;
    }

    ret = gdc_gdcfeedback_sample();
    if (ret < 0) {
        gdc_print_err("gdc test err!test mode = %d, ret = %d\n", g_gdc_mode, ret);
        return ERR;
    }

    return ret;
}

