#include "arm11/atp.h"
#include "arm11/acf.h"
#include "arm11/fmt.h"
#include "arm11/drivers/hid.h"
#include "arm11/console.h"
#include "drivers/gfx.h"

#define TITLE_MAX 8
#define TIPS_MAX 64
static char ta[TIPS_MAX] = {'\0'};
static char tb[TIPS_MAX] = {'\0'};

#define ATP_COLOR_SIZE (ATP_COLOR_WHITE+1)

static uint16_t color_tbl[] = {
    RGB8_to_565(0xc2, 0xcc, 0xd0),      // 亮色
    RGB8_to_565(0xff, 0x33, 0),         // 红色
    RGB8_to_565(0xaf, 0xdd, 0x22),      // 绿色
    RGB8_to_565(0xfa, 0xff, 0x72),      // 黄色
    RGB8_to_565(0x4c, 0x8d, 0xb4),      // 蓝色
    RGB8_to_565(0xe0, 0x00, 0x97),      // 品红
    RGB8_to_565(0x21, 0xa6, 0x75),      // 青色
    RGB8_to_565(0xf0, 0xfc, 0xff)       // 白色
};

//---------------------------------------------------
// basic print helper

static void set_screen_color( acf_callerdata_t data, acf_position_t tx, acf_position_t ty, acf_color_t b )
{
    if( b == 0 ) return;

    int *draw_data = data;
    u16 *frame = consoleGet()->frameBuffer;

    int x = tx + draw_data[0];
    int y = ty + draw_data[1];

    // see: https://www.zhihu.com/question/264505093/answer/281849883
    if( ( x | (draw_data[2]-x) | y | (draw_data[3]-y) ) > 0 ) // 0 <= x && x < draw_data[2] && 0 <= y && y < draw_data[3]
    {
        frame[ x*draw_data[3] + (draw_data[3]-1-y) ] = (u16)draw_data[4];
    }
}

const char *acf_put_text(int x, int y, int width, int height, int maxwidth, u8 color, u8 placement, const char* text)
{
    uint8_t draw_data[sizeof(int)*5];

    int *draw_data_int = (int*)&draw_data;
    draw_data_int[0] = x;
    draw_data_int[1] = y;
    draw_data_int[2] = width;
    draw_data_int[3] = height;
    draw_data_int[4] = (int)color_tbl[color % ATP_COLOR_SIZE];

    const char *retval = text;
    acf_rectedge_t realwid;
    acf_canvas_t canvas = acf_get_canvas(maxwidth, text, &realwid, NULL, &retval);
    if( !canvas ) return retval;

    if( placement == ATP_PLACEMENT_RIGHT ) draw_data_int[0] += maxwidth - realwid;
    else if( placement == ATP_PLACEMENT_CENTER ) draw_data_int[0] += (maxwidth-realwid) >> 1;
    acf_recycle( acf_use_canvas(canvas, 1, set_screen_color, draw_data) );
    return retval;
}

//---------------------------------------------------

#define CONTAINER_LEFTTOP_X 20
#define CONTAINER_LEFTTOP_Y 10
#define CONTAINER_RECT_WIDTH 282
#define CONTAINER_MAX_LINES 13
#define WINDOW_WIDTH 320
#define WINDOW_HEIGHT 240

#define FONT_HEIGHT 15

static void screen_clean() 
{
    memset(consoleGet()->frameBuffer, 0, WINDOW_WIDTH*WINDOW_HEIGHT*sizeof(uint16_t));
    if( ta[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_LEFT, ta );
    if( tb[0] ) acf_put_text( 5, 215, WINDOW_WIDTH, WINDOW_HEIGHT, WINDOW_WIDTH-10, ATP_COLOR_CYAN, ATP_PLACEMENT_RIGHT, tb );
}

static void screen_clean_zone( int x, int y, int w, int h )
{
    if( x < 0 || y < 0 || x+w >= WINDOW_WIDTH || y+h >=WINDOW_HEIGHT )
        return;
    
    for( int i=0; i < w; i++ ) memset( consoleGet()->frameBuffer + WINDOW_HEIGHT*(i+x) + (WINDOW_HEIGHT-1-y-h), 0, h*sizeof(u16) );
}

// wait key pressed, if power key is pressed, return 0
static uint32_t waitKey()
{
    uint32_t ek, down;
    do{
        GFX_waitForVBlank0();
        hidScanInput();
        ek = hidGetExtraKeys(0);
        if( ek & (KEY_POWER_HELD | KEY_POWER) )
            return 0;
        
        down = hidKeysDown();
    }while ( down == 0 );
    
    return down;
}

#define easy_put(text, align, color, row)   acf_put_text( \
                                                CONTAINER_LEFTTOP_X, \
                                                CONTAINER_LEFTTOP_Y+FONT_HEIGHT*(row), \
                                                WINDOW_WIDTH, \
                                                WINDOW_HEIGHT, \
                                                CONTAINER_RECT_WIDTH, \
                                                (color) >= ATP_COLOR_SIZE ? ATP_COLOR_WHITE : (color), \
                                                (align) > ATP_PLACEMENT_CENTER ? ATP_PLACEMENT_CENTER : (align), \
                                                (text) \
                                            )

static void paint_one_line( atp_lineprovider_t provider, acf_callerdata_t data, int idx, int row )
{
    atp_linecfg_t config;
    config.text_color = ATP_COLOR_WHITE;
    config.text_align = ATP_PLACEMENT_LEFT;
    atp_error_t err = provider( data, idx, &config );
    if( !err )
    {
        easy_put( config.text, config.text_align, config.text_color, row );
    }
}

static void container_paint( atp_lineprovider_t provider, atp_callerdata_t data, atp_counter_t nlines, int top )
{
    int end = top + CONTAINER_MAX_LINES;
    if( end > (int)nlines ) end = nlines;
    for( int n = top; n < end; ++n )
    {
        paint_one_line( provider, data, n, n-top );
    }
}

atp_error_t atp_show( atp_counter_t cnt, atp_lineprovider_t provider, atp_callerdata_t data )
{
    int idx_top = 0;
    screen_clean();
    container_paint( provider, data, cnt, idx_top );
 
    while( 1 )
    {
        int top = idx_top;
        u32 kDown = waitKey();

        if( kDown == 0 ) return ATP_POWER_OFF;

        if( kDown == KEY_B || kDown == KEY_A ) return ATP_SUCCESS;
        else if( cnt > CONTAINER_MAX_LINES )
        {       // may scroll
            if( kDown == KEY_UP || kDown == KEY_DUP )
            {
                top = idx_top - 1;
            }
            else if( kDown == KEY_DOWN || kDown == KEY_DDOWN )
            {
                top = idx_top + 1;
            }

            if( top+CONTAINER_MAX_LINES > (int)cnt ) top = (int)cnt - CONTAINER_MAX_LINES;
            else if( top < 0 ) top = 0;
        }

        if( top != idx_top )
        {
            idx_top = top;
            screen_clean();
            container_paint(provider, data, cnt, idx_top);
        }
    }
}

static atp_error_t title_paint( atp_callerdata_t datain, atp_counter_t idx, atp_linecfg_t *config )
{
    uint8_t **data = datain;
    atp_text_t title = (atp_text_t)data[0];
    uint8_t *title_offset = data[1];
    for( atp_counter_t i=0; i < idx; ++i )
        title += title_offset[i];
    config->text = title;
    config->text_color = ATP_COLOR_LIGHT;
    return ATP_SUCCESS;
}

static void set_paging( int top, int len )
{
    unsigned total = (len+CONTAINER_MAX_LINES-1) / CONTAINER_MAX_LINES;
    unsigned current = top / CONTAINER_MAX_LINES;
    char buf[32];
    ee_snprintf( buf, sizeof(buf), "页码：%d/%d", current+1, total );
    atp_tips( buf, NULL );

    screen_clean();
}

static void draw_one_option( int index, int selected, int row, atp_itemprovider_t provider, atp_callerdata_t data )
{
    atp_itemcfg_t item;
    item.value = -1;
    item.text = item.extra_text = NULL;
    item.extra_text_color = ATP_COLOR_RED;
    item.text_color = ATP_COLOR_WHITE;

    atp_error_t e = provider( data, index, &item );
    if( e == ATP_SUCCESS )
    {
        if( item.extra_text ) easy_put( item.extra_text, ATP_PLACEMENT_RIGHT, item.extra_text_color, row );
        easy_put( item.text, ATP_PLACEMENT_LEFT, index == selected ? ATP_COLOR_YELLOW : item.text_color, row );
    }
}

static void draw_options( int start_row, int start_idx, int option_cnt, int selected_idx, atp_itemprovider_t provider, atp_callerdata_t data )
{
    int max_draw = CONTAINER_MAX_LINES - start_row;
    int end_idx = start_idx + max_draw;
    if( end_idx > option_cnt ) end_idx = option_cnt;

    // draw
    for( int i=start_idx; i < end_idx; ++i )
    {
        draw_one_option( i, selected_idx, start_row+i-start_idx, provider, data );
    }
}

#define HIT( hit ) ( (hit & key) != 0 )
#define SELECTED_ROW(top, len, sel) (((top) < (len)) ? ((len)-(top)+(sel)) : ((sel)+(len)-(top)))
#define REFRESH_PAGE { \
                        set_paging( idx_top, title_len+cnt ); \
                        if( idx_top < title_len ) \
                        { \
                            uint8_t* title_data[2] = {(uint8_t*)title, title_offset}; \
                            container_paint( title_paint, (atp_callerdata_t)title_data, title_len, idx_top ); \
                            draw_options( title_len - idx_top, 0, cnt, item_sel, provider, data ); \
                        } \
                        else draw_options( 0, idx_top - title_len, cnt, item_sel, provider, data ); \
                     }
#define HANDLE_DOSELECT { \
                            if( res == NULL ) return ATP_SUCCESS; \
                            atp_error_t result = provider(data, item_sel, &config); \
                            if( ATP_SUCCESS == result ) \
                            { \
                                *res = config.value; \
                                return ATP_SUCCESS; \
                            } \
                            else \
                            { \
                                *res = result; \
                                return ATP_INVALID_VALUE; \
                            } \
                        }
atp_error_t atp_select( atp_text_t title, atp_counter_t cnt, atp_itemprovider_t provider, atp_keyhandler_t handler, atp_callerdata_t data, atp_counter_t index, atp_boolean_t action, atp_itemval_t *res )
{
    uint8_t title_offset[TITLE_MAX];
    int item_sel = index < cnt ? index : 0;
    int title_len = 1;
    atp_itemcfg_t config;

    // draw title
    const char *cursor = title;
    for( int i=0; i < TITLE_MAX; ++i )
    {
        const char *next;
        if( 0 == acf_calculate( CONTAINER_RECT_WIDTH, cursor, NULL, NULL, &next ) )
        {
            if( *next == '\0' ) break;

            title_offset[i] = next - cursor;
            ++title_len;
            cursor = next;
        }
    }
    for( int i=title_len; i < TITLE_MAX; ++i ) title_offset[i] = 0;

    // draw item
    int idx_top = item_sel + title_len;
    idx_top = idx_top - idx_top%CONTAINER_MAX_LINES;
    REFRESH_PAGE;

    while( 1 )
    {
        uint32_t key = waitKey();

        if( key == 0 ) return ATP_POWER_OFF;

        if( key & (KEY_A | KEY_B) )
        {
            if( key & KEY_A )
            {
                HANDLE_DOSELECT;
            }
            else 
            {
                if( action == 0 ) return ATP_NO_ACTION;
            }
        }
        else if( key & (KEY_L | KEY_R | KEY_X | KEY_Y | KEY_SELECT | KEY_START) )
        {
            if( handler == NULL ) continue;

            atp_pageopt_t opt = handler( data, item_sel, HIT(KEY_X), HIT(KEY_Y), HIT(KEY_L), HIT(KEY_R), HIT(KEY_START), HIT(KEY_SELECT) );
            switch( opt )
            {
                case ATP_PAGE_REFRESH:
                    REFRESH_PAGE;
                break;
                case ATP_PAGE_UPDATE:
                    int row = SELECTED_ROW(idx_top, title_len, item_sel);
                    screen_clean_zone( CONTAINER_LEFTTOP_X, CONTAINER_LEFTTOP_Y+FONT_HEIGHT*(row), CONTAINER_RECT_WIDTH, FONT_HEIGHT );
                    draw_one_option( item_sel, item_sel, row, provider, data );
                break;
                case ATP_POWER_OFF:
                    return ATP_POWER_OFF;
                break;
                case ATP_PAGE_DOSELECT:
                    HANDLE_DOSELECT;
                break;
                default:
                    ;
            }
        }
        else if( key & (KEY_DOWN | KEY_UP) )
        {
            int sel = item_sel + (key&KEY_DOWN ? 1 : -1);
            
            // 更新item_sel
            if( sel >= (int)cnt ) sel = 0;
            else if( sel < 0 ) sel = (int)cnt-1;

            // 更新idx_top
            int sel_row = title_len + sel;
            int top = sel_row - sel_row % CONTAINER_MAX_LINES;
            atp_boolean_t full_refresh = 0;
            if( top != idx_top )
            {
                idx_top = top;
                full_refresh = 1;
            }

            // 更新view
            if( full_refresh )
            {// 重新绘制全部
                item_sel = sel;
                REFRESH_PAGE;
            }
            else 
            {// 重新绘制item_sel和sel
                draw_one_option( item_sel, sel, SELECTED_ROW(idx_top, title_len, item_sel), provider, data );
                draw_one_option( sel, sel, SELECTED_ROW(idx_top, title_len, sel), provider, data);
                item_sel = sel;
            }
        }
        else if( key & (KEY_LEFT | KEY_RIGHT) )
        {
            const int total = title_len + cnt;
            if( total <= CONTAINER_MAX_LINES )
                continue;
            
            // 更新idx_top
            if( key & KEY_LEFT ) idx_top += CONTAINER_MAX_LINES;
            else idx_top -= CONTAINER_MAX_LINES;
            
            if( idx_top >= total ) idx_top = 0;
            else if( idx_top < 0 ) idx_top = total - total%CONTAINER_MAX_LINES;

            // 更新item_sel
            item_sel = idx_top + item_sel % CONTAINER_MAX_LINES;
            if( idx_top == 0 && item_sel >= CONTAINER_MAX_LINES-title_len )
                item_sel = 0;
            else if( item_sel >= (int)cnt ) item_sel = cnt-1;

            // 更新view
            REFRESH_PAGE;
        }
    }
}


atp_error_t atp_tips( atp_text_t tipsA, atp_text_t tipsB )
{
    if( tipsA != NULL )
    {
        strncpy( ta, tipsA, TIPS_MAX-1 );
        ta[TIPS_MAX-1] = 0;
    }
    if( tipsB != NULL )
    {
        strncpy( tb, tipsB, TIPS_MAX-1 );
        tb[TIPS_MAX-1] = 0;
    }
    return ATP_SUCCESS;
}
