/*
 * Copyright (c) 2006-2021, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 */

#include <stdio.h>
#include <rthw.h>
#include <rtdevice.h>
#include <rtthread.h>
#include <rtdevice.h>

/* uart driver */
struct console_uart
{
    int rx_ready;

    struct rt_ringbuffer rb;
    rt_uint8_t rx_buffer[256];
} _console_uart;
static struct rt_serial_device _serial;

#define SAVEKEY(key)  do { char ch = key; rt_ringbuffer_put_force(&(_console_uart.rb), &ch, 1); } while (0)

#ifdef _WIN32
#include  <windows.h>
#include  <mmsystem.h>
#include  <conio.h>
extern int getch(void);

/*
 * Handler for OSKey Thread
 */
static HANDLE       OSKey_Thread;
static DWORD        OSKey_ThreadID;

static DWORD WINAPI ThreadforKeyGet(LPVOID lpParam);
void console_lowlevel_init(void)
{
    /*
     * create serial thread that receive key input from keyboard
     */

    OSKey_Thread = CreateThread(NULL,
                                0,
                                (LPTHREAD_START_ROUTINE)ThreadforKeyGet,
                                0,
                                CREATE_SUSPENDED,
                                &OSKey_ThreadID);
    if (OSKey_Thread == NULL)
    {
        //Display Error Message
        return;
    }

    SetThreadPriority(OSKey_Thread,
                      THREAD_PRIORITY_NORMAL);
    SetThreadPriorityBoost(OSKey_Thread,
                           TRUE);
    SetThreadAffinityMask(OSKey_Thread,
                          0x01);
    /*
     * Start OS get key Thread
     */
    ResumeThread(OSKey_Thread);
}

static DWORD WINAPI ThreadforKeyGet(LPVOID lpParam)
#else /* POSIX version */

#include <pthread.h>
#include <semaphore.h>
#include <stdlib.h>
#include <signal.h>
#include <termios.h> /* for tcxxxattr, ECHO, etc */
#include <unistd.h> /* for STDIN_FILENO */

static void * ThreadforKeyGet(void * lpParam);
static pthread_t OSKey_Thread;

void console_lowlevel_init(void)
{
    int res;

    res = pthread_create(&OSKey_Thread, NULL, &ThreadforKeyGet, NULL);
    if (res)
    {
        printf("pthread create faild, <%d>\n", res);
        exit(EXIT_FAILURE);
    }
}

static struct termios oldt, newt;
/*simulate windows' getch(), it works!!*/
static void set_stty(void)
{
    /* get terminal input's attribute */
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;

    /* set termios' local mode */
    newt.c_lflag &= ~(ECHO|ICANON);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
}

void restore_stty(void)
{
   /* recover terminal's attribute */
   tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
}

#define getch  getchar
static void * ThreadforKeyGet(void * lpParam)
#endif /* not _WIN32*/
{
 /*
 * left  key(左): 0xe04b
 * up    key(上): 0xe048
 * right key(右): 0xe04d
 * down  key(下): 0xe050
 */
    unsigned char key;

#ifndef _WIN32
    sigset_t  sigmask, oldmask;
    /* set the getchar without buffer */
    sigfillset(&sigmask);
    pthread_sigmask(SIG_BLOCK, &sigmask, &oldmask);
    set_stty();
#endif

    (void)lpParam;              //prevent compiler warnings

    for (;;)
    {
        key = getch();
#ifdef _WIN32
        if (key == 0xE0)
        {
            key = getch();

            if (key == 0x48) //up key , 0x1b 0x5b 0x41
            {
                SAVEKEY(0x1b);
                SAVEKEY(0x5b);
                SAVEKEY(0x41);
            }
            else if (key == 0x50)//0x1b 0x5b 0x42
            {
                SAVEKEY(0x1b);
                SAVEKEY(0x5b);
                SAVEKEY(0x42);
            }
            else if (key == 0x4b)//<- 0x1b 0x5b 0x44
            {
                SAVEKEY(0x1b);
                SAVEKEY(0x5b);
                SAVEKEY(0x44);
            }
            else if (key == 0x4d)//<- 0x1b 0x5b 0x43
            {
                SAVEKEY(0x1b);
                SAVEKEY(0x5b);
                SAVEKEY(0x43);
            }

            continue;
        }
#endif
        SAVEKEY(key);

        /* Notfiy serial ISR */
        rt_hw_serial_isr(&_serial, RT_SERIAL_EVENT_RX_IND);
    }
    return RT_NULL;
} /*** ThreadforKeyGet ***/

static rt_err_t console_configure(struct rt_serial_device *serial, struct serial_configure *cfg)
{
    /* no baudrate, nothing */

    return RT_EOK;
}

static rt_err_t console_control(struct rt_serial_device *serial, int cmd, void *arg)
{
    struct console_uart* uart;

    RT_ASSERT(serial != RT_NULL);
    uart = (struct console_uart *)serial->parent.user_data;

    switch (cmd)
    {
    case RT_DEVICE_CTRL_CLR_INT:
        uart->rx_ready = 0;
        break;
    case RT_DEVICE_CTRL_SET_INT:
        uart->rx_ready = 1;
        break;
    }

    return RT_EOK;
}

static int console_putc(struct rt_serial_device *serial, char c)
{
    int level;
    RT_ASSERT(serial != RT_NULL);

#if 0 /* Enable it if you want to save the console log */
    {
        static FILE* fp = NULL;

        if (fp == NULL)
            fp = fopen("log.txt", "wb+");

        if (fp != NULL)
            fwrite(buffer, size, 1, fp);
    }
#endif

    level = rt_hw_interrupt_disable();
    fwrite(&c, 1, 1, stdout);
    fflush(stdout);
    rt_hw_interrupt_enable(level);
    return 1;
}

static int console_getc(struct rt_serial_device *serial)
{
    char ch;
    struct console_uart* uart;

    RT_ASSERT(serial != RT_NULL);
    uart = (struct console_uart *)serial->parent.user_data;

    if (rt_ringbuffer_getchar(&(uart->rb), &ch)) return ch;

    return -1;
}

static const struct rt_uart_ops console_uart_ops =
{
    console_configure,
    console_control,
    console_putc,
    console_getc,
};

int uart_console_init(void)
{
    struct serial_configure config = RT_SERIAL_CONFIG_DEFAULT;
    struct console_uart* uart;
    struct rt_serial_device* serial;

    uart = &_console_uart;
    serial = &_serial;

    uart->rx_ready = 0;

    serial->ops    = &console_uart_ops;
    serial->config = config;
    /* initialize ring buffer */
    rt_ringbuffer_init(&uart->rb, uart->rx_buffer, sizeof(uart->rx_buffer));

    /* register UART device */
    rt_hw_serial_register(serial, "console",
                          RT_DEVICE_FLAG_RDWR | RT_DEVICE_FLAG_INT_RX,
                          uart);

    console_lowlevel_init();

    return 0;
}