/*
 * Copyright (C) 2020, Samsung Electronics Co., Ltd.
 *
 * TUI LL touch driver module
 *
 */

#include <errno.h>
#include <macros.h>
#include <string.h>
#include <time.h>

#include <tee_tui_low_api.h>

#include "touch_drv_engine.h"
#include "touch_queue.h"
#include "tuill_defs.h"
#include "tuill_client_drv.h"
#include "tuill_drv.h"
#include "tuill_log.h"
#include "tuiHal.h"

struct tuill_thread read_thr;

void stop_reading_thread(void)
{
    TUILL_CALL_TRACE();
    atomic_init(read_thr.run, false);
    pthread_join(read_thr.tid, NULL);
}

#ifndef TUI_MODEL_RANCHU
static int read_touch_value(mt_touch_info_t *ti)
{
    TUILL_CALL_TRACE();
    int ret = 0;
    /* If touch queue is empty, try to process to not lose any pending irqs */
    int i = touch_event_count();
    if (i == 0) {
        TEE_Result retHal = tuiHalTouchProcess();
        if (retHal != TEE_SUCCESS) {
            syslog(LOG_ERR, "tuiHalTouchProcess() failed\n");
            ret = -TUILLE_COMMUNICATION;
            goto finish_function;
        }

        i = touch_event_count();
    }

    if (i != 0) {
        ret = get_touch_event(ti, sizeof(mt_touch_info_t));
        if (ret > 0) { /* We have touch data */
            syslog(LOG_NOTICE, "got %d touch event out of %d events\n", ret, i);
        } else if (ret < 0) {
            syslog(LOG_ERR, "get_touch_event failed ret=%d\n", ret);
            ret = -TUILLE_BAD_PARAMETERS;
        }
    }

finish_function:
    syslog(LOG_NOTICE, "ret=%d\n", ret);
    return ret;
}
#endif

static void send_touch_event(touch_info_t *data)
{
    struct tuill_internal_command cmd = {};

    cmd.cmd = TUILL_ICMD_TOUCH_EVENT;
    cmd.touch_event_cmd.display = 0;

    if (data->ev_type == tui_ts_release) {
        cmd.touch_event_cmd.action = TEE_EVENT_TUI_TOUCH_ACTION_UP;
        cmd.touch_event_cmd.pressure = 0;
    } else {
        cmd.touch_event_cmd.action = TEE_EVENT_TUI_TOUCH_ACTION_DOWN;
        cmd.touch_event_cmd.pressure = 127;
    }

    cmd.touch_event_cmd.finger = data->n_finger;
    cmd.touch_event_cmd.x = data->x;
    cmd.touch_event_cmd.y = data->y;

    tuild_send_cmd(&cmd);
}

#ifndef TUI_MODEL_RANCHU
static void *reading_thread(void *arg)
{
    openlog(log_tag, LOG_PID, 0);
    TUILL_CALL_TRACE();
    struct tuill_thread *ctx = (struct tuill_thread *)arg;
    struct timespec req;
    while (atomic_read(ctx->run)) {
        mt_touch_info_t ti;
        req.tv_nsec = 10 * NUM_NANOS_IN_MILLI; /* 10 ms */
        req.tv_sec = 0;
        ALWAYS_ZERO(nanosleep(&req, NULL));
        int ret = read_touch_value(&ti);
        if (ret > 0) {
            for (uint8_t i = 0; i < ti.num; i++) {
                send_touch_event(&ti.buf[i]);
            }
        }
    }

    return NULL;
}
#endif

#ifndef TUI_MODEL_RANCHU
static uint32_t start_reading_thread(struct tuill_thread *ctx)
{
    TUILL_CALL_TRACE();
    uint32_t ret = 0;

    atomic_init(ctx->run, true);
    if (pthread_create(&ctx->tid, NULL, reading_thread, ctx) != 0) {
        atomic_init(ctx->run, false);
        ret = -TUILLE_GENERIC;
        syslog(LOG_ERR, "pthread_create returned %d\n", errno);
    }

    return ret;
}
#endif

int touch_ioctl(struct drv_info *info, int ioctl_cmd, struct ioctl_arg *arg)
{
    (void)info;
    (void)arg;
    int ret = 0;

    if (!(drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED)) {
        syslog(LOG_ERR, "TUI state is already closed.\n");
        return -TUILLE_ACCESS_DENIED;
    }

    switch (ioctl_cmd) {
#ifdef BUILD_TYPE_debug
    case TUILLDRV_IOCTL_SEND_EVENT: {
        syslog(LOG_NOTICE, "Processing TUILLDRV_IOCTL_SEND_EVENT...\n");
        struct tuilldrv_send_event_data *virt_ioctl_data =
            (struct tuilldrv_send_event_data *)arg->input[0].iov_base;
        touch_info_t ti;
        if (arg->input_cnt) {
            ti.x = virt_ioctl_data->u.touch.x;
            ti.y = virt_ioctl_data->u.touch.y;
            ti.n_finger = virt_ioctl_data->u.touch.finger;
            if (virt_ioctl_data->u.touch.action == TEE_EVENT_TUI_TOUCH_ACTION_UP) {
                ti.ev_type = tui_ts_release;
            } else {
                ti.ev_type = tui_ts_press;
            }

            send_touch_event(&ti);
            virt_ioctl_data->ret_code = 0;
        } else {
            virt_ioctl_data->ret_code = -TUILLE_GENERIC;
        }

        break;
    }
#endif /* BUILD_TYPE_debug */
    default:
        syslog(LOG_NOTICE, "Unknown ioctl: %d\n", ioctl_cmd);
        ret = -TUILLE_BAD_PARAMETERS;
        break;
    }

    return ret;
}

static int32_t touch_income(void *user_data, struct tuill_buffer *buff)
{
    (void)user_data;
    TUILL_CALL_TRACE();
    struct tuill_internal_command rsp = {};
    struct tuill_internal_command *cmd = (struct tuill_internal_command *)buff->data;

    rsp.cmd = cmd->cmd | RESPONSE_FLAG;
    rsp.ret_code = -TUILLE_BAD_PARAMETERS;
    rsp.task_state = cmd->task_state;
    rsp.task_id = cmd->task_id;

    switch (cmd->cmd) {
#ifdef TUI_MODEL_RANCHU
    case TUILL_ICMD_OPEN_DRIVER: {
        syslog(LOG_DEBUG, "TUILL_ICMD_OPEN_DRIVER\n");
        rsp.ret_code = 0;
        drv_ctx.tuilldrv.tuill_state |= TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        break;
    }
    case TUILL_ICMD_CLOSE_DRIVER: {
        syslog(LOG_DEBUG, "TUILL_ICMD_CLOSE_DRIVER\n");
        rsp.ret_code = 0;
        drv_ctx.tuilldrv.tuill_state &= ~TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        break;
    }
#else
    case TUILL_ICMD_OPEN_DRIVER: {
        syslog(LOG_NOTICE, "TUILL_ICMD_OPEN_DRIVER\n");

        if (drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED) {
            syslog(LOG_ERR, "Already opened.\n");
            rsp.ret_code = -TUILLE_BAD_STATE;
            break;
        }

        memcpy(&rsp.open_drivers_rsp.fb, &cmd->open_drivers_cmd.fb, sizeof(struct FB_Data));

        if (tuiHalBoardInit()) {
            syslog(LOG_ERR, "tuiHalBoardInitn() FAILED\n");
            rsp.ret_code = -TUILLE_GENERIC;
            break;
        }

        if (tuiHalTouchOpen(cmd->open_drivers_cmd.fb.width,
                            cmd->open_drivers_cmd.fb.height,
                            cmd->open_drivers_cmd.fb.touch_type)) {
            syslog(LOG_ERR, "tuiHalTouchOpen() FAILED\n");
            rsp.ret_code = -TUILLE_GENERIC;
            break;
        }

        if (start_reading_thread(&read_thr)) {
            syslog(LOG_ERR, "start_reading_thread returned error\n");
            rsp.ret_code = -TUILLE_GENERIC;
            tuiHalTouchClose();
        } else {
            drv_ctx.tuilldrv.tuill_state |= TEE_PERIPHERAL_FLAG_LOCKED;
            drv_send_state();
            rsp.ret_code = 0;
        }

        break;
    }
    case TUILL_ICMD_CLOSE_DRIVER:
        syslog(LOG_NOTICE, "TUILL_ICMD_CLOSE_DRIVER\n");

        if (!(drv_ctx.tuilldrv.tuill_state & TEE_PERIPHERAL_FLAG_LOCKED)) {
            syslog(LOG_ERR, "Already closed.\n");
            rsp.ret_code = -TUILLE_BAD_STATE;
            break;
        }

        stop_reading_thread();
        tuiHalTouchClose();
        drv_ctx.tuilldrv.tuill_state &= ~TEE_PERIPHERAL_FLAG_LOCKED;
        drv_send_state();
        rsp.ret_code = 0;
        break;
#endif
    default:
        syslog(LOG_NOTICE, "Unknown command: %d\n", cmd->cmd);
        return -TUILLE_GENERIC;
    }

    return tuild_send_cmd(&rsp);
}

static int32_t touch_hangup(void *user_data)
{
    (void)user_data;
    TUILL_CALL_TRACE();
    /* TODO: add restoring after error code here */
    return 0;
}

struct socket_callbacks touch_cb = {
    .income = touch_income,
    .hangup = touch_hangup,
};
