/******************************************************************************
 * Copyright (c) Huawei Technologies Co., Ltd. 2018-2019. All rights reserved.
 * iSulad licensed under the Mulan PSL v1.
 * You can use this software according to the terms and conditions of the Mulan PSL v1.
 * You may obtain a copy of Mulan PSL v1 at:
 *     http://license.coscl.org.cn/MulanPSL
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR
 * PURPOSE.
 * See the Mulan PSL v1 for more details.
 * Author: lifeng
 * Create: 2018-11-08
 * Description: provide container run functions
 ******************************************************************************/
#include <sys/ioctl.h>
#include <unistd.h>
#include <pthread.h>
#include <termios.h>

#include "run.h"
#include "arguments.h"
#include "log.h"
#include "utils.h"
#include "isula_connect.h"
#include "console.h"
#include "error.h"

const char g_cmd_run_desc[] = "Run a command in a new container";
const char g_cmd_run_usage[] = "run [OPTIONS] ROOTFS|IMAGE [COMMAND] [ARG...]";
static int run_checker(struct client_arguments *args);
struct client_arguments g_cmd_run_args = {
    .runtime = "",
    .restart = "no",
    .log_file = NULL,
    .log_file_size = "1MB",
    .log_file_rotate = 7,
};

static int local_cmd_start(struct client_arguments *args, uint32_t *exit_code)
{
    int ret = 0;
    bool reset_tty = false;
    struct termios oldtios;
    struct command_fifo_config *console_fifos = NULL;

    ret = client_start(&g_cmd_run_args, &reset_tty, &oldtios, &console_fifos);
    if (ret != 0) {
        goto free_out;
    }

    if (!g_cmd_run_args.detach) {
        ret = client_wait(&g_cmd_run_args, exit_code);
        if (ret != 0) {
            goto free_out;
        }
        ret = (int)(*exit_code);
    }

    client_wait_fifo_exit(&g_cmd_run_args);
free_out:
    client_restore_console(reset_tty, &oldtios, console_fifos);
    return ret;
}

static int remote_cmd_start_set_tty(const struct client_arguments *args, bool *reset_tty, struct termios *oldtios)
{
    int istty = 0;

    istty = isatty(0);
    if (istty && args->custom_conf.tty && args->custom_conf.attach_stdin) {
        if (setup_tios(0, oldtios)) {
            ERROR("Failed to setup terminal properties");
            return -1;
        }
        *reset_tty = true;
    }
    return 0;
}

static int remote_cmd_start(const struct client_arguments *args, uint32_t *exit_code)
{
    int ret = 0;
    bool reset_tty = false;
    isula_connect_ops *ops = NULL;
    struct isula_start_request request = { 0 };
    struct isula_start_response *response = NULL;
    client_connect_config_t config = { 0 };
    struct termios oldtios;

    ops = get_connect_client_ops();
    if (ops == NULL || ops->container.remote_start == NULL) {
        ERROR("Unimplemented ops");
        ret = ECOMMON;
        goto out;
    }

    if (remote_cmd_start_set_tty(args, &reset_tty, &oldtios) != 0) {
        ret = ECOMMON;
        goto out;
    }

    request.name = args->name;
    request.attach_stdin = args->custom_conf.attach_stdin;
    request.attach_stdout = args->custom_conf.attach_stdout;
    request.attach_stderr = args->custom_conf.attach_stderr;
    response = util_common_calloc_s(sizeof(struct isula_start_response));
    if (response == NULL) {
        ERROR("Out of memory");
        ret = ECOMMON;
        goto out;
    }

    config = get_connect_config(args);
    ret = ops->container.remote_start(&request, response, &config);
    if (ret) {
        client_print_error(response->cc, response->server_errono, response->errmsg);
        ret = ECOMMON;
        if (response->server_errono ||
            (response->errmsg && !strcmp(response->errmsg, errno_to_error_message(ISULAD_ERR_CONNECT)))) {
            ret = ESERVERERROR;
        }
        goto out;
    }

out:
    isula_start_response_free(response);
    if (reset_tty && tcsetattr(0, TCSAFLUSH, &oldtios) < 0) {
        ERROR("Failed to reset terminal properties");
        return -1;
    }
    return ret;
}

static int do_resize_run_console(const struct client_arguments *args, unsigned int height, unsigned int width)
{
    int ret = 0;
    isula_connect_ops *ops = NULL;
    struct isula_resize_request request = { 0 };
    struct isula_resize_response *response = NULL;
    client_connect_config_t config = { 0 };

    ops = get_connect_client_ops();
    if (ops == NULL || ops->container.resize == NULL) {
        ERROR("Unimplemented ops");
        ret = -1;
        goto out;
    }

    request.id = args->name;
    request.height = height;
    request.width = width;

    response = util_common_calloc_s(sizeof(struct isula_resize_response));
    if (response == NULL) {
        ERROR("Out of memory");
        ret = -1;
        goto out;
    }

    config = get_connect_config(args);
    ret = ops->container.resize(&request, response, &config);
    if (ret != 0) {
        ERROR("Failed to call resize");
        goto out;
    }

out:
    isula_resize_response_free(response);
    return ret;
}

static void *run_console_resize_thread(void *arg)
{
    int ret = 0;
    const struct client_arguments *args = arg;
    static struct winsize s_pre_wsz;
    struct winsize wsz;

    if (!isatty(STDIN_FILENO)) {
        goto out;
    }

    ret = pthread_detach(pthread_self());
    if (ret != 0) {
        CRIT("Start: set thread detach fail");
        goto out;
    }

    while (true) {
        sleep(1); // check the windows size per 1s
        ret = ioctl(STDIN_FILENO, TIOCGWINSZ, &wsz);
        if (ret < 0) {
            WARN("Failed to get window size");
            continue;
        }
        if (wsz.ws_row == s_pre_wsz.ws_row && wsz.ws_col == s_pre_wsz.ws_col) {
            continue;
        }
        ret = do_resize_run_console(args, wsz.ws_row, wsz.ws_col);
        if (ret != 0) {
            continue;
        }
        s_pre_wsz.ws_row = wsz.ws_row;
        s_pre_wsz.ws_col = wsz.ws_col;
    }

out:
    return NULL;
}

int run_client_console_resize_thread(struct client_arguments *args)
{
    int res = 0;
    pthread_t a_thread;

    res = pthread_create(&a_thread, NULL, run_console_resize_thread, (void *)(args));
    if (res != 0) {
        CRIT("Thread creation failed");
        return -1;
    }

    return 0;
}

int cmd_run_main(int argc, const char **argv)
{
    int ret = 0;
    unsigned int exit_code = 0;
    command_t cmd = { 0 };
    struct log_config lconf = { 0 };

    set_default_command_log_config(argv[0], &lconf);
    if (client_arguments_init(&g_cmd_run_args)) {
        COMMAND_ERROR("client arguments init failed");
        exit(ECOMMON);
    }
    g_cmd_run_args.custom_conf.attach_stdout = true;
    g_cmd_run_args.custom_conf.attach_stderr = true;

    g_cmd_run_args.progname = argv[0];
    g_cmd_run_args.subcommand = argv[1];
    struct command_option options[] = {
        LOG_OPTIONS(lconf),
        COMMON_OPTIONS(g_cmd_run_args),
        CREATE_OPTIONS(g_cmd_run_args),
        CREATE_EXTEND_OPTIONS(g_cmd_run_args),
        RUN_OPTIONS(g_cmd_run_args)
    };
    command_init(&cmd, options, sizeof(options) / sizeof(options[0]), argc, (const char **)argv, g_cmd_run_desc,
                 g_cmd_run_usage);
    if (command_parse_args(&cmd, &g_cmd_run_args.argc, &g_cmd_run_args.argv) || run_checker(&g_cmd_run_args)) {
        exit(EINVALIDARGS);
    }

    if (log_init(&lconf)) {
        COMMAND_ERROR("log init failed");
        exit(ECOMMON);
    }

    ret = client_create(&g_cmd_run_args);
    if (ret) {
        ERROR("Container \"%s\" create failed", g_cmd_run_args.name);
        exit(ret);
    }

    if (g_cmd_run_args.detach) {
        printf("%s\n", g_cmd_run_args.name);
    }

    if (g_cmd_run_args.custom_conf.tty && isatty(STDIN_FILENO)) {
        (void)run_client_console_resize_thread(&g_cmd_run_args);
    }

    if (strncmp(g_cmd_run_args.socket, "tcp://", strlen("tcp://")) == 0) {
        ret = remote_cmd_start(&g_cmd_run_args, &exit_code);
        if (ret != 0) {
            ERROR("Failed to execute command with remote run");
            goto free_out;
        }
    } else {
        ret = local_cmd_start(&g_cmd_run_args, &exit_code);
        if (ret != 0) {
            ERROR("Failed to execute command with local run");
            goto free_out;
        }
    }

free_out:
    exit(ret);
}

static int run_checker(struct client_arguments *args)
{
    int ret = 0;

    ret = create_checker(args);
    if (ret) {
        goto out;
    }

    /* Make detach option a high priority than terminal */
    if (args->detach) {
        args->custom_conf.attach_stdin = false;
        args->custom_conf.attach_stdout = false;
        args->custom_conf.attach_stderr = false;
    }

    if (args->custom_conf.auto_remove && ((args->restart != NULL) && (strcmp("no", args->restart) != 0))) {
        COMMAND_ERROR("Conflicting options: --restart and --rm");
        ret = -1;
        goto out;
    }

out:
    return ret;
}

