/*
 *
 * Copyright (C) 2012-2019, Samsung Electronics Co., Ltd.
 *
 * Touch Queue implementation
 */

#include <atomic.h>
#include <errno.h>
#include <string.h>
#include <tee_internal_api.h>

#include "board.h"
#include "dbg.h"
#include "touch_queue.h"
#include "tuiHal.h"

static struct touch_queue TQ;

#ifndef USE_MULTI_TOUCH
static bool update_multi_touch(enum tui_ts_event ev_type, int *n_finger)
{
    static int mt_first_finger;
    static bool is_multi_touch;
    drTouchInfo_t touch_info;

    if (ev_type == tui_ts_press) {
        TQ.finger_count++;

        if (is_multi_touch == false && TQ.finger_count == 1) {
            mt_first_finger = *n_finger;
        } else if (is_multi_touch == false && TQ.finger_count == 2) {
            is_multi_touch = true;
            infoPrintf("%s: multi touch start\n", __func__);
        }
    } else if (ev_type == tui_ts_release) {
        if (TQ.finger_count > 0) {
            TQ.finger_count--;
        }

        if (is_multi_touch == true && TQ.finger_count == 0) {
            is_multi_touch = false;

            /*
             * manipulate as if first one of multi touch was released
             * at right bottom where no control is located
             */

            tuiHalTouchGetInfo(&touch_info);
            TQ.tracker[mt_first_finger].x = touch_info.width;
            TQ.tracker[mt_first_finger].y = touch_info.height;
            *n_finger = mt_first_finger;
            infoPrintf("%s: multi touch end\n", __func__);
        }
    }

    return is_multi_touch;
}
#else /* USE_MULTI_TOUCH */
static bool update_multi_touch(enum tui_ts_event ev_type, int *n_finger)
{
    (void)n_finger;

    if (ev_type == tui_ts_press) {
        TQ.finger_count++;
    } else if (ev_type == tui_ts_release) {
        if (TQ.finger_count > 0) {
            TQ.finger_count--;
        }
    }

    return false;
}
#endif /* USE_MULTI_TOUCH */

static int get_avail_rsize(int roffset, int woffset)
{
    int avail;

    if (roffset > woffset) {
        avail = TUI_TQ_SIZE - roffset + woffset;
    } else {
        avail = woffset - roffset;
    }

    return avail;
}

static bool buf_is_full(int roffset, int woffset)
{
    woffset = (woffset + 1) % TUI_TQ_SIZE;

    return woffset == roffset;
}

static int get_avail_wsize(int roffset, int woffset)
{
    int avail;

    if (woffset >= roffset) {
        avail = TUI_TQ_SIZE - woffset + roffset - 1;
    } else {
        avail = roffset - woffset - 1;
    }

    return avail;
}

int get_touch_queue_wsize(void)
{
    int ret;
    int roffset = atomic_read(TQ.roffset);
    int woffset = atomic_read(TQ.woffset);

    if (buf_is_full(roffset, woffset)) {
        return -1;
    }

    ret = get_avail_wsize(roffset, woffset);

    return ret;
}

/**
 * Get event count in a touch queue
 * @return result
 */
int touch_event_count(void)
{
    int roffset = atomic_read(TQ.roffset);
    int woffset = atomic_read(TQ.woffset);

    return get_avail_rsize(roffset, woffset);
}

/**
 * Get event from touch queue
 * @param[out] buff touch event buffer pointer
 * @param[in] buff_size buffer size
 * @return  <0 if any error occurred
 *          =0 if queue is empty
 *          >0 events in queue with current
 */
int get_touch_event(mt_touch_info_t *info, unsigned int buff_size)
{
    int roffset;
    int idx = 0;
    int last_count = touch_event_count();

    if (!last_count) {
        return last_count;
    }
    if (!info || buff_size < sizeof(mt_touch_info_t)) {
        return -1;
    }

#ifdef USE_MULTI_TOUCH
    if (last_count > MT_INFO_MAX_EVENT) {
        last_count = MT_INFO_MAX_EVENT;
    }
#else
    last_count = 1;
#endif

    roffset = atomic_read(TQ.roffset);
    while (idx < last_count) {
        TEE_MemMove(&info->buf[idx], &TQ.buf[roffset], sizeof(touch_info_t));
        roffset = (roffset + 1) % TUI_TQ_SIZE;
        idx++;
    }
    info->num = last_count;
    atomic_init(TQ.roffset, roffset);

    return last_count;
}

/**
 * Add new event to touch queue
 * @param[in] n_finger finger number (0-9)
 * @param[in] x current x coordinate
 * @param[in] y current y coordinate
 * @param[in] ev_type event type
 * @return none
 */
void add_touch_event(int n_finger, uint16_t x, uint16_t y, enum tui_ts_event ev_type)
{
    uint8_t prev_state;
    bool forbid;
    drTouchInfo_t touch_info;
    int roffset = atomic_read(TQ.roffset);
    int woffset = atomic_read(TQ.woffset);

    dbgPrintf("TOUCH event count=%d, n_finger=%d, x=%d, y=%d, type=%d\n",
              TQ.finger_count, n_finger, x, y, ev_type);

    if (n_finger < 0 || n_finger >= TUI_MAX_NUM_OF_FINGERS) {
        return;
    }

    tuiHalTouchGetInfo(&touch_info);
    if (x > touch_info.width  || y > touch_info.height) {
        errPrintf("TOUCH out of range x=%d y=%d\n", x, y);
        return;
    }

    TQ.tracker[n_finger].x = x;
    TQ.tracker[n_finger].y = y;

    /* press or release event are sent to client TA */
    if ((ev_type != tui_ts_press) && (ev_type != tui_ts_release)) {
        return;
    }

    prev_state = get_finger_state(n_finger);
    if (ev_type == prev_state) {
        return;
    }

    if (buf_is_full(roffset, woffset)) {
        errPrintf("TOUCH no space in TQ\n");
        return;
    }

    TQ.tracker[n_finger].n_finger = n_finger;
    TQ.tracker[n_finger].ev_type = (uint8_t)ev_type;

    /* do not queue in case of multi touch */
    forbid = update_multi_touch(ev_type, &n_finger);
    if (forbid) {
        return;
    }

    dbgPrintf("TOUCH TQ write count=%d, roff=%d, woff=%d, n_finger=%d\n",
              TQ.finger_count, roffset, woffset, n_finger);
    TEE_MemMove(&TQ.buf[woffset], &TQ.tracker[n_finger],
                sizeof(touch_info_t));
    dbgPrintf("TOUCH TQ write n_finger=%d, x=%d, y=%d, type=%d\n",
              TQ.buf[woffset].n_finger, TQ.buf[woffset].x,
              TQ.buf[woffset].y, TQ.buf[woffset].ev_type);

    woffset = (woffset + 1) % TUI_TQ_SIZE;
    atomic_init(TQ.woffset, woffset);
}

/**
 * @brief Release current state of all fingers
 */
void release_all_fingers(void)
{
    for (unsigned int i = 0; i < TUI_MAX_NUM_OF_FINGERS; i++) {
        if (get_finger_state(i) != tui_ts_release) {
            add_touch_event(i,
                            get_finger_xpos(i),
                            get_finger_ypos(i),
                            tui_ts_release);
        }
    }
}

/**
 * @brief Get finger state
 */
int get_finger_state(int n_finger)
{
    if (n_finger < 0 || n_finger >= TUI_MAX_NUM_OF_FINGERS) {
        errPrintf("TOUCH finger out of range finger=%d\n", n_finger);
        return -1;
    }

    return TQ.tracker[n_finger].ev_type;
}

/**
 * @brief Get x pos of finger
 */
int get_finger_xpos(int n_finger)
{
    if (n_finger < 0 || n_finger >= TUI_MAX_NUM_OF_FINGERS) {
        errPrintf("TOUCH finger out of range finger=%d\n", n_finger);
        return -1;
    }

    return TQ.tracker[n_finger].x;
}

/**
 * @brief Get y pos of finger
 */
int get_finger_ypos(int n_finger)
{
    if (n_finger < 0 || n_finger >= TUI_MAX_NUM_OF_FINGERS) {
        errPrintf("TOUCH finger out of range finger=%d\n", n_finger);
        return -1;
    }

    return TQ.tracker[n_finger].y;
}

/**
 * @brief Get finger count of TQ
 */
uint8_t get_finger_count(void)
{
    return TQ.finger_count;
}

/**
 * @brief init touch queue
 */
void init_touch_queue(void)
{
    TQ.finger_count = 0;
    atomic_init(TQ.roffset, 0);
    atomic_init(TQ.woffset, 0);

    for (unsigned int i = 0; i < TUI_MAX_NUM_OF_FINGERS; i++) {
        TQ.tracker[i].ev_type = tui_ts_release;
    }

#ifdef USE_MULTI_TOUCH
    infoPrintf("%s: multi touch support\n", __func__);
#endif
}