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

#include "lcd_console.h"
#include "lcd_font_20.h"

#define LCD_CONSOLE_FLUSH_NOW    1

#define CONSOLE_NAME    "hdmi"
#define COLOR_DELTA     0.05

#ifndef LCD_CONSOLE_FLUSH_NOW
static rt_thread_t console_flush_thread_tid = RT_NULL;
#define CONSOLE_FLUSH_THREAD_STACK_SIZE   (1024)
#define CONSOLE_FLUSH_THREAD_PRIORITY     (20)
#define CONSOLE_FLUSH_THREAD_TIMESLICE    (10)
#define LCD_CONSOLE_DELAY                 (100)  //100ms
#endif

static rt_device_t console_dev = RT_NULL;

static fb_t console_fb;
static rt_uint8_t* virt_buffer;

static rt_uint32_t CHAR_W = 8;
static rt_uint32_t CHAR_H = 20;

static int prev_x_offset = 0;

static void newline(fb_t* fb)
{
    uint8_t* to;
    uint8_t* from;
    int i;
    fb->y++;
    fb->x = 5 * fb->depth;

    if (fb->y == (fb->height / CHAR_H))
    {
        to = (uint8_t*) fb->vaddr;
        from = to + (CHAR_H * fb->pitch);

        for (i = 0; i < ((fb->height - CHAR_H) * fb->pitch); i++)
        {
            *to++ = *from++;
        }

        if(fb->depth >= 3)
        {
            uint32_t *addr_32bit = (uint32_t*) (fb->vaddr) + (fb->height - CHAR_H) * fb->width;

            for (i = 0; i < (CHAR_H * fb->width); i++)
            {
                *addr_32bit++ = fb->back;
            }
        }
        else
        {
            uint16_t *addr_16bit = (uint16_t*) (fb->vaddr) + (fb->height - CHAR_H) * fb->width;

            for (i = 0; i < (CHAR_H * fb->width); i++)
            {
                *addr_16bit++ = fb->back;
            }
        }
        fb->y =  fb->y - 1;
    }
}


static void fb_draw_char(fb_t *fb, char s)
{
    unsigned char* addr = (unsigned char*) fb->vaddr;
    unsigned char *glyph = (unsigned char *)lcd_console_font_dejavu_20_glyph_bitmap + lcd_console_font_dejavu_20_glyph_dsc[s - 32].glyph_index;
    CHAR_W = lcd_console_font_dejavu_20_glyph_dsc[s - 32].w_px;

    fb->x = fb->x + prev_x_offset * fb->depth;

    int i, j, line, mask, bytesperline = (CHAR_W + 7) / 8;
    int kk = (bytesperline) * 8;
    prev_x_offset = CHAR_W + 2;
    // calculate the offset on screen
    int offs = (fb->y * CHAR_H * fb->pitch) + fb->x;

    // display a character
    for (j = 0; j < CHAR_H; j++)
    {
        // display one row
        line = offs;
        mask = 1;
        mask = 0x80;
        for (i = 0; i < kk; i++)
        {
            if(fb->depth >= 3)
            {
                *((unsigned int*) (addr + line)) = ((int) *(glyph + ((i)/8)) * 1) & mask ? fb->fore : fb->back;
            }
            else
            {
                *((unsigned short*) (addr + line)) = ((int) *(glyph + ((i)/8)) * 1) & mask ? fb->fore : fb->back;
            }

            mask >>= 1;
            if(mask == 0)
            {
                mask = 0x80;
            }
            line += fb->depth;
        }
        // adjust to next line
        glyph += bytesperline;
        offs += fb->pitch;
    }
}

void fb_print(char *s)
{
    fb_t *fb = &console_fb;
    // draw next character if it's not zero
    while (*s)
    {
        // handle carrige return
        if (*s == '\r')
        {
            fb->x = 5 * fb->depth;
        }
        else if (*s == '\n')
        {
            newline(fb);
        }
        else if (*s == '\t')
        {
            //tab is 8 spaces
            if((fb->x + 8 * fb->depth) < (fb->width) * fb->depth)
            {
                fb->x = fb->x + 8 * fb->depth;
            }
        }
        else if (*s == '\b')
        {
            if (fb->x > 5 * fb->depth)
            {
                fb->x = fb->x - prev_x_offset * fb->depth;
                fb_draw_char(fb, ' ');
            }
        }
        else if((fb->x + prev_x_offset * fb->depth + 5 * fb->depth) >= (fb->width * fb->depth))
        {
            newline(fb);
            fb_draw_char(fb, *s);
        }
        else
        {
            fb_draw_char(fb, *s);
        }
        s++;
    }

#ifdef LCD_CONSOLE_FLUSH_NOW
    rt_memcpy((void *)fb->paddr, (void *)fb->vaddr, fb->size);
    if(console_dev != RT_NULL)
    {
        rt_device_control(console_dev,RTGRAPHIC_CTRL_RECT_UPDATE, RT_NULL);
    }
#endif
}

#ifndef LCD_CONSOLE_FLUSH_NOW
void lcd_console_task_entry(void *param)
{
    fb_t *fb = (fb_t *)param;
    while (1)
    {
        rt_memcpy((void *)fb->paddr, (void *)fb->vaddr, fb->size);
        if(console_dev != RT_NULL)
        {
            rt_device_control(console_dev,RTGRAPHIC_CTRL_RECT_UPDATE, RT_NULL);
        }
        rt_thread_mdelay(LCD_CONSOLE_DELAY);
    }

}
#endif

int lcd_console_init(void)
{
    struct rt_device_graphic_info info;
    console_dev = rt_device_find(CONSOLE_NAME);
    if(console_dev == RT_NULL)
    {
        rt_kprintf("no console dev!\n");
        return 0;
    }

    if(console_dev->ref_count >= 1)
    {
        rt_kprintf("lcd console has open!\n");
        return 0;
    }

    rt_device_open(console_dev,RT_DEVICE_OFLAG_RDWR);

    rt_device_control(console_dev, RTGRAPHIC_CTRL_GET_INFO, &info);

    virt_buffer = (rt_uint8_t* )rt_malloc(info.width * info.height * (info.bits_per_pixel/8));
    rt_memset(virt_buffer, 0 , info.width * info.height * (info.bits_per_pixel/8));
    console_fb.width = info.width;
    console_fb.height = info.height;
    console_fb.pitch = info.width * (info.bits_per_pixel/8);
    console_fb.vaddr = (rt_uint32_t)virt_buffer;
    console_fb.paddr = (rt_uint32_t)info.framebuffer;
    console_fb.size = info.width * info.height * (info.bits_per_pixel/8);
    console_fb.depth = info.bits_per_pixel/8;
    console_fb.x = 0;
    console_fb.y = 0;
    if(console_fb.depth >= 3)
    {
        console_fb.fore = CONSOLE_WHITE_32;
        console_fb.back = CONSOLE_BLACK_32;
    }
    else
    {
        console_fb.fore = CONSOLE_WHITE_16;
        console_fb.back = CONSOLE_BLACK_16;
    }

#ifndef LCD_CONSOLE_FLUSH_NOW
    console_flush_thread_tid = rt_thread_create("lcd_console", lcd_console_task_entry, (void *)&console_fb,
                                       CONSOLE_FLUSH_THREAD_STACK_SIZE,
                                       CONSOLE_FLUSH_THREAD_PRIORITY, CONSOLE_FLUSH_THREAD_TIMESLICE);
    if (console_flush_thread_tid != RT_NULL)
        rt_thread_startup(console_flush_thread_tid);
#endif
    /*
    * note:
    * if serial console and lcd console together
    * you can add /src/kservice.c:rt_kprintf
    * #ifdef USING_LCD_CONSOLE
    *     fb_print((char*)rt_log_buf);
    * #endif
    *
    * remove rt_console_set_device(CONSOLE_NAME);
    */
    rt_console_set_device(CONSOLE_NAME);

    rt_show_version();//show rt-thread logo

    return 0;
}
INIT_APP_EXPORT(lcd_console_init);