/*
 * Copyright (C) 2020, Samsung Electronics Co., Ltd.
 *
 * TUI LL device driver common functions
 */

#include <errno.h>
#include <ioctx.h>
#include <macros.h>
#include <sched.h>
#include <string.h>

#include "tuill_client_drv.h"
#include "tuill_defs.h"
#include "tuill_log.h"
#include "tuill_socket_lib.h"


#define SOCKET_RECONNECT_TIME 50

struct drv_connect_ctx {
    struct tuill_thread thr;
    pthread_t eventloop_thr;
    char *server_name;
    struct socket_callbacks cb;
    void *client_ctx;
};

void tuill_send_connecting_thread_event(struct drv_connect_ctx *ctx)
{
    TUILL_CALL_TRACE();
    atomic_init(ctx->thr.wake_up, true);
    pthread_mutex_lock(&ctx->thr.mutex);
    pthread_cond_signal(&ctx->thr.cond);
    pthread_mutex_unlock(&ctx->thr.mutex);
}

static void *connecting_thread(void *arg)
{
    struct drv_connect_ctx *ctx = (struct drv_connect_ctx *)arg;
    openlog(log_tag, LOG_PID, 0);
    TUILL_CALL_TRACE();
    while (atomic_read(ctx->thr.run)) {
        int32_t ret = -1;
        while (ret) {
            ret = tuill_socket_create_client(ctx->client_ctx, ctx->server_name, &ctx->cb, NULL);
            if (ret) {
                TEE_Wait(SOCKET_RECONNECT_TIME);
            }
        }

        atomic_init(ctx->thr.wake_up, false);
        pthread_mutex_lock(&ctx->thr.mutex);
        while (!atomic_read(ctx->thr.wake_up)) {
            pthread_cond_wait(&ctx->thr.cond, &ctx->thr.mutex);
        }

        pthread_mutex_unlock(&ctx->thr.mutex);
    }

    atomic_init(ctx->thr.finished, true);
    return NULL;
}

static int start_connecting_thread(struct drv_connect_ctx *ctx)
{
    TUILL_CALL_TRACE();
    syslog(LOG_DEBUG, "%s\n", ctx->server_name);
    atomic_init(ctx->thr.run, true);
    atomic_init(ctx->thr.wake_up, false);
    atomic_init(ctx->thr.finished, false);
    if (pthread_create(&ctx->thr.tid, NULL, connecting_thread, ctx) != 0) {
        atomic_init(ctx->thr.run, false);
        syslog(LOG_ERR, "pthread_create returned %d\n", errno);
        return -TUILLE_GENERIC;
    }

    return 0;
}

static void stop_connecting_thread(struct drv_connect_ctx *ctx)
{
    TUILL_CALL_TRACE();
    if (atomic_read(ctx->thr.run)) {
        atomic_init(ctx->thr.run, false);
        tuill_send_connecting_thread_event(ctx);
        while (!atomic_read(ctx->thr.finished)) {
            sched_yield();
        }
    }
}

static void *event_loop_thread(void *arg)
{
    tuill_socket_run_event_loop(arg);
    return NULL;
}

int tuill_init_client_drv(void **ctx, struct socket_callbacks *cb)
{
    int ret;
    struct drv_connect_ctx *con_ctx;
    TUILL_CALL_TRACE();

    con_ctx = calloc(1, sizeof(struct drv_connect_ctx));
    if (con_ctx == NULL) {
        syslog(LOG_ERR, "Can't allocate memory.\n");
        return -TUILLE_OUT_OF_MEMORY;
    }

    *ctx = con_ctx;
    con_ctx->cb = *cb;
    pthread_mutex_init(&con_ctx->thr.mutex, NULL);
    pthread_cond_init(&con_ctx->thr.cond, NULL);
    con_ctx->server_name = OS_SWD_SOCKET_NAME;

    syslog(LOG_DEBUG, "con_ctx=%p con_ctx->client_ctx=%p\n", con_ctx, con_ctx->client_ctx);
    ret = tuill_socket_init(&con_ctx->client_ctx);
    if (ret != 0) {
        syslog(LOG_ERR, "init_client returned error\n");
        goto error;
    }

    if (pthread_create(&con_ctx->eventloop_thr,
                       NULL,
                       &event_loop_thread,
                       con_ctx->client_ctx) != 0) {
        syslog(LOG_ERR, "pthread_create returned error\n");
        goto error;
    }

    syslog(LOG_DEBUG, "con_ctx=%p con_ctx->client_ctx=%p\n", con_ctx, con_ctx->client_ctx);
    ret = start_connecting_thread(con_ctx);
    if (ret != 0) {
        syslog(LOG_ERR, "start_connecting_thread returned error\n");
        goto error;
    }

    return 0;
error:
    tuill_uninit_client_drv(con_ctx);
    *ctx = NULL;
    return -TUILLE_COMMUNICATION;
}

void tuill_uninit_client_drv(void *ctx)
{
    TUILL_CALL_TRACE();
    if (ctx) {
        struct drv_connect_ctx *con_ctx = (struct drv_connect_ctx *)ctx;
        syslog(LOG_DEBUG, "con_ctx=%p con_ctx->client_ctx=%p\n", con_ctx, con_ctx->client_ctx);
        stop_connecting_thread(con_ctx);
        syslog(LOG_DEBUG, "con_ctx=%p con_ctx->client_ctx=%p\n", con_ctx, con_ctx->client_ctx);
        tuill_socket_stop_event_loop(con_ctx->client_ctx);
        ALWAYS_ZERO(pthread_join(con_ctx->eventloop_thr, NULL));
        tuill_socket_uninit(con_ctx->client_ctx);
        free(ctx);
    }
}

int32_t tuill_drv_send_to_server(void *ctx, struct tuill_internal_command *cmd)
{
    TUILL_CALL_TRACE();
    syslog(LOG_DEBUG, "ctx=%p, cmd=%p\n", ctx, cmd);
    struct drv_connect_ctx *con_ctx = (struct drv_connect_ctx *)ctx;
    return tuill_socket_client_send_to_server(con_ctx->client_ctx, cmd);
}
