#include <cstdio>
#include <cstring>
#include "main_task.h"
#include "logo50.h"
#include "icon_enter.h"
#include "icon_esc.h"
#include "icon_nav.h"
#include "icon_p.h"

main_task::main_task()
        : task("Main", MAIN_STACKSIZE),
          _cs(SD_CS_PIN),
          _spi(SD_SPI0, SD_MISO_PIN, SD_MOSI_PIN, SD_SCLK_PIN, _cs),
          _sd(_spi),
          _next(NEXT_BUTTON),
          _part(PART_BUTTON),
          _ps(PICO_PS),
          _fs(_sd),
          _lcd_rst(LCD_RST_PIN),
          _lcd_dc(LCD_DC_PIN),
          _lcd_cs(LCD_CS_PIN),
          _lcd_spi(LCD_SPI1, LCD_MISO_PIN, LCD_MOSI_PIN, LCD_SCK_PIN, _lcd_cs),
          _lcd(_lcd_spi,_lcd_rst,_lcd_dc,ili9488_drv::PICO320),
          _gui(_lcd),
          _sel_index(0),
          _page_index(0),
          _last_page_index(0),
          _last_sel_index(0),
          update_sel(0),
          update_required(1),
          playing(0),
          last_play_pos(0),
          play_pos(0),
          num_files(0),
          pause_flag(0),
          play_flag(0){

    _next.gpioMode(GPIO::INPUT | GPIO::PULLUP);
    _part.gpioMode(GPIO::INPUT | GPIO::PULLUP);
    _ps.gpioMode(GPIO::OUTPUT | GPIO::INIT_HIGH);
    _gui.FontSelect(&FONT_8X14);
    _gui.SetForecolor(C_GAINSBORO);

    for(auto & menu_item : menu_items){
        menu_item.fsize = 0;
        menu_item.fname = nullptr;
        menu_item.x = 0;
        menu_item.y = 0;
    }
}

void main_task::draw_string(int x,int y,const char*str,uint8_t fnt = 1){

    if(fnt == 0) {
        _gui.FontSelect(&FONT_6X8);
    }
    if(fnt == 1) {
        _gui.FontSelect(&FONT_8X14);
    }
    if(fnt == 2) {
        _gui.FontSelect(&FONT_12X20);
    }

    _gui.SetForecolor(C_GAINSBORO);
    _gui.SetBackcolor(C_BLACK);
    _gui.PutString(x,y,str);
}
void main_task::draw_highlight_string(int x,int y,const char*str){
    _gui.FontSelect(&FONT_8X14);
    _gui.SetForecolor(C_BLACK);
    _gui.SetBackcolor(C_GAINSBORO);
    _gui.PutString(x,y,str);
}

void main_task::draw_char(int x,int y ,char c) {
    _gui.FontSelect(&FONT_8X14);
    _gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true);
}

void main_task::draw_big_char(int x,int y ,char c) {
    _gui.FontSelect(&FONT_12X20);
    _gui.PutChar(c,x,y,C_GAINSBORO,C_BLACK, true);
}
void main_task::draw_bar(int x1, int x2,UG_COLOR c) {
    uint8_t  offsetx = 10;
   _gui.FillFrame(x1+offsetx,280,x2+offsetx,285,c);
}


#define SCREEN_WIDTH 320
#define SCREEN_HEIGHT 280
#define FONT_WIDTH 12
#define FONT_HEIGHT 20
#define MAX_CHARS_PER_LINE (SCREEN_WIDTH / FONT_WIDTH)
#define MAX_LINES 2

void main_task::draw_wrap_text(const char *text) {
    int line_count = 0;
    const char *ptr = text;


    char lines[MAX_LINES][MAX_CHARS_PER_LINE + 1] = {{0}};
    while (*ptr && line_count < MAX_LINES) {
        int i = 0;
        while (*ptr && i < MAX_CHARS_PER_LINE) {
            lines[line_count][i++] = *ptr++;
        }
        lines[line_count][i] = '\0';
        line_count++;
    }


    int text_height = line_count * FONT_HEIGHT;
    int y_offset = (SCREEN_HEIGHT - text_height) / 2;


    for (int line = 0; line < line_count; line++) {
        int char_count = strlen(lines[line]);
        int x_offset = (SCREEN_WIDTH - char_count * FONT_WIDTH) / 2;
        for (int j = 0; j < char_count; j++) {
            draw_big_char(x_offset + j * FONT_WIDTH, y_offset + line * FONT_HEIGHT, lines[line][j]);
        }
    }
}

int main_task::enum_files() {
    char tmp[16];
    _gui.FontSelect(&FONT_8X14);
    _gui.ConsoleSetForecolor(C_GAINSBORO);
    _gui.ConsoleSetBackcolor(C_BLACK);
    _gui.ConsolePutString("Init FatFS");
    FatFs::FRESULT res = _fs.mount(0);
    if(res == FatFs::FR_OK){
        _gui.ConsolePutString("[OK]");
    }else{
        sprintf(tmp,"[NG] code=0x%x",res);
        _gui.ConsolePutString(tmp);
        return -1;
    }
    _gui.ConsolePutString("\n");
    _gui.ConsolePutString("File List");

    num_files = 0;
    res = _fs.findfirst(&_dir, &_finfo, "", "*.mp3");
    // Loop over all MP3 files
    while (res == FatFs::FR_OK && _finfo.fname[0] && strlen(_finfo.fname) > 4 ) {
        printf("%s\n",_finfo.fname);
        menu_items[num_files].fname = (char*)malloc(strlen(_finfo.fname) + 1);
        strcpy(menu_items[num_files].fname, _finfo.fname);
        menu_items[num_files].fsize = _finfo.fsize;

        res = _fs.findnext(&_dir, &_finfo);
        num_files++;
        if(num_files>=MAX_FILES){
            break;
        }
    }
    if (res != FatFs::FR_OK) {
        sprintf(tmp, "[NG] code=0x%x", (int)res);
        _gui.ConsolePutString( tmp);
        return -1;
    }
    _fs.closedir(&_dir);
    _gui.ConsolePutString("[OK]");
    return num_files;
}

//In ffconf.h,
// set
// #define FF_MAX_LFN		33
// #define FF_LFN_BUF		33
/*
void truncate_string(const char *input, char *output, size_t max_length) {
    size_t input_len = strlen(input);

    if (input_len <= max_length) {
        strcpy(output, input);
    } else {
        strncpy(output, input, max_length - 3);
        output[max_length - 3] = '\0';
        strcat(output, "...");
    }
}
*/
#define HEADER_HEIGHT 20
int main_task::select_mp3() {
    char tmp[34];
    uint16_t start_x = 11;
    uint16_t start_y= HEADER_HEIGHT+3;
    /*               header
     * ------------------------------------------
     * 3px
     * [item]
     * 3px
     * [item]
     * 3px
     * [item]
     * 3px
     */

    for (int i = _page_index; i < _page_index+ITEMS_PER_PAGE;i++) {
        if(i >= num_files) break;
        //truncate_string(file_list[_page_index+i],tmp,33);
        menu_items[i].x = start_x;
        menu_items[i].y = start_y;

        draw_string(menu_items[i].x, menu_items[i].y, menu_items[i].fname);
        start_y+=23;
    }

    return 0;
}
void main_task::draw_cursor() {
    char buf[12];
    //sprintf(buf, "%02d", _sel_index + 1);
    //draw_string(0,_lcd.getSizeY()-20,buf);
    printf("%d %d %d\n",_last_sel_index,_sel_index,_page_index);
    uint16_t x,y;

    if(update_sel){
        x = menu_items[_last_sel_index % ITEMS_PER_PAGE].x;
        y = menu_items[_last_sel_index % ITEMS_PER_PAGE].y;

        _gui.FillFrame(10, y - 2, _lcd.getSizeX() - 10, y + 15, C_BLACK);
        draw_string(x, y, menu_items[_last_sel_index].fname);
    }
    x = menu_items[_sel_index % ITEMS_PER_PAGE].x;
    y = menu_items[_sel_index % ITEMS_PER_PAGE].y;

    _gui.FillFrame(10,y-2,_lcd.getSizeX()-10,y+15,C_GAINSBORO);
    //draw_string(0, (_sel_index % ITEMS_PER_PAGE) * 20+20, "=>");
    draw_highlight_string(11, y, menu_items[_sel_index].fname);

}

void main_task::clear_screen() {
    _lcd.clearScreen(C_BLACK);
}
void main_task::clear_menu() {
    //clear menu section area, not whole screen
    _lcd.fillArea(0,20,_lcd.getSizeX()-1,_lcd.getSizeY()-20,C_BLACK);
}
void main_task::boot_menu() {
    _page_index = (_sel_index / ITEMS_PER_PAGE) * ITEMS_PER_PAGE;
    if(_page_index!= _last_page_index){
        clear_menu();
        _last_page_index = _page_index;
        update_required = 1;
        update_sel = 0;
    }

    if(update_required) {
        clear_menu();
        draw_header("Playlist");
        select_mp3();
        draw_cursor();
        draw_footer(1);
        update_required = 0;
        last_play_pos = 0;
        play_pos = 0;

    }

    if(update_sel){
        draw_cursor();
        update_sel = 0;
    }
}

void main_task::menu_up() {
    _last_sel_index = _sel_index;
    if (_sel_index == 0) {
        _sel_index = num_files - 1;
    } else {
        _sel_index--;
    }
    update_sel = 1;
}
void main_task::menu_down() {
    _last_sel_index = _sel_index;
    if (_sel_index == num_files - 1) {
        _sel_index = 0;
    } else {
        _sel_index++;
    }
    update_sel = 1;
}

void main_task::menu_start(sd_reader_task&sd_reader,mp3_decoder_task &decoder,pcm_pwm_rp2040_drv &pcm_drv) {
    FatFs::FRESULT res = FatFs::FR_OK;
    printf("start play: %s\n",menu_items[_sel_index].fname);
    res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ);
    assert(res == FatFs::FR_OK);
    sd_reader.start(&_fs, &_file);
    decoder.reset();
    decoder.start();
    update_required = 1;
    playing =1;
    last_play_pos = 0;
    play_pos = 0;
    pause_flag = 0;

}

//play through the list start from sel_index;
void main_task::menu_start_all(sd_reader_task &sd_reader, mp3_decoder_task &decoder, pcm_pwm_rp2040_drv &pcm_drv) {
    FatFs::FRESULT res = FatFs::FR_OK;
    printf("start play all: %d\n",_sel_index);
    res = _fs.open(&_file, menu_items[_sel_index].fname, FA_OPEN_EXISTING | FA_READ);
    assert(res == FatFs::FR_OK);
    sd_reader.start(&_fs, &_file);
    decoder.reset();
    decoder.start();
    update_required = 1;
    playing =1;
    last_play_pos = 0;
    play_pos = 0;
    pause_flag = 0;
    play_flag = 1;
}

void main_task::draw_logo(){
    uint16_t start_x,start_y;

    start_x = (_lcd.getSizeX() - logo_width)/2;
    start_y = (_lcd.getSizeY() - logo_height)/2;

    unsigned char *data = (unsigned char *)header_data;
    for (unsigned int y = 0; y < logo_height; ++y) {
        for (unsigned int x = 0; x < logo_width; ++x) {
            unsigned char pixel[3];
            HEADER_PIXEL(data, pixel);

            uint8_t r = pixel[0];
            uint8_t g = pixel[1];
            uint8_t b = pixel[2];

            uint32_t color = (r << 16) | (g << 8) | b; // RGB888
            _lcd.drawPixel(start_x+x,start_y+y, color);
        }
    }
}
//header Height 20
void main_task::draw_header(char*title) {
    _gui.FontSelect(&FONT_8X14);
    _gui.SetForecolor(C_GAINSBORO);
    _gui.SetBackcolor(C_BLACK);
    _gui.PutString(11,(20-14)/2,"                    ");
    _gui.PutString(11,(20-14)/2,title);
    _lcd.drawHLine(10,19,_lcd.getSizeX()-10,C_LIGHT_GRAY);
}

void main_task::draw_footer_esc() {
    uint16_t start_x,start_y;

    start_x = 261;
    start_y = _lcd.getSizeY() - esc_height-5;

    unsigned char *data = (unsigned char *)esc_header_data;
    for (unsigned int y = 0; y < esc_height; ++y) {
        for (unsigned int x = 0; x < esc_width; ++x) {
            unsigned char pixel[3];
            ESC_PIXEL(data, pixel);

            uint8_t r = pixel[0];
            uint8_t g = pixel[1];
            uint8_t b = pixel[2];

            uint32_t color = (r << 16) | (g << 8) | b; // RGB888
            _lcd.drawPixel(start_x+x,start_y+y, color);
        }
    }
    draw_string(285,start_y+3,"Stop",0);
}

void main_task::draw_footer_enter() {
    uint16_t start_x,start_y;

    start_x = 260;
    start_y = _lcd.getSizeY() - enter_height-5;

    unsigned char *data = (unsigned char *)enter_header_data;
    for (unsigned int y = 0; y < enter_height; ++y) {
        for (unsigned int x = 0; x < enter_width; ++x) {
            unsigned char pixel[3];
            ENTER_PIXEL(data, pixel);

            uint8_t r = pixel[0];
            uint8_t g = pixel[1];
            uint8_t b = pixel[2];

            uint32_t color = (r << 16) | (g << 8) | b; // RGB888
            _lcd.drawPixel(start_x+x,start_y+y, color);
        }
    }
    draw_string(285,start_y+3,"Play",0);
}

void main_task::draw_footer_nav() {

    uint16_t start_x,start_y;

    start_x = 10;
    start_y = _lcd.getSizeY() - nav_height-6;

    unsigned char *data = (unsigned char *)nav_header_data;
    for (unsigned int y = 0; y < nav_height; ++y) {
        for (unsigned int x = 0; x < nav_width; ++x) {
            unsigned char pixel[3];
            NAV_PIXEL(data, pixel);

            uint8_t r = pixel[0];
            uint8_t g = pixel[1];
            uint8_t b = pixel[2];

            uint32_t color = (r << 16) | (g << 8) | b; // RGB888
            _lcd.drawPixel(start_x+x,start_y+y, color);
        }
    }
    draw_string(23,start_y+3,"Nav.",0);

}

void main_task::draw_footer_pause() {
    uint16_t start_x,start_y;

    start_x = 210;
    start_y = _lcd.getSizeY() - icon_p_height-5;

    unsigned char *data = (unsigned char *)icon_p_header_data;
    for (unsigned int y = 0; y < icon_p_height; ++y) {
        for (unsigned int x = 0; x < icon_p_width; ++x) {
            unsigned char pixel[3];
            ICON_P_PIXEL(data, pixel);

            uint8_t r = pixel[0];
            uint8_t g = pixel[1];
            uint8_t b = pixel[2];

            uint32_t color = (r << 16) | (g << 8) | b; // RGB888
            _lcd.drawPixel(start_x+x,start_y+y, color);
        }
    }
    draw_string(225,start_y+3,"Pause",0);
}

void main_task::draw_footer_pagenumber() {
    // page1/4

    char tmp[12];
    int total_page = (num_files / ITEMS_PER_PAGE)+1;
    int cur_page = (_sel_index / ITEMS_PER_PAGE)+1;
    if(total_page > 1) {
        sprintf(tmp, "%d/%d", cur_page, total_page);
        draw_string(150, _lcd.getSizeY() - nav_height-2, tmp, 0);
    }
}

void main_task::draw_footer(uint8_t stat) {
    //stat 1 == menu_list   ==> nav icon, Enter icon
    //stat 2 == playing music ==>  only Esc icon
    _gui.FillFrame(0,_lcd.getSizeY()-20,_lcd.getSizeX()-1,_lcd.getSizeY()-1,C_BLACK);
    if(stat == 1) {
        draw_footer_nav();
        draw_footer_enter();
        draw_footer_pagenumber();
    }
    if(stat == 2) {
        draw_footer_pause();
        draw_footer_esc();
    }

    _lcd.drawHLine(10,_lcd.getSizeY()-20,_lcd.getSizeX()-10,C_LIGHT_GRAY);
}

void main_task::draw_playing() {

    char * song_name = menu_items[_sel_index].fname;
    draw_wrap_text(song_name);

}


void main_task::run() {
    char tmp[34];
    i2c_kbd _kbd;

    pcm_pwm_rp2040_drv pcm_drv(AUDIO_LEFT, AUDIO_RIGHT);
    sd_reader_task sd_reader;
    mp3_decoder_task decoder(pcm_drv, sd_reader);

    uint8_t keycheck = 0;
    uint8_t keyread = 0;
    uint8_t key_stat = 0;//press,release, or hold
    int kbd_ret = 0;
    int c;
    static int ctrlheld = 0;
    ///const char spinner[] = {'/', '-', '|', '\\'};
    uint8_t spin_i = 0;
    uint8_t spin_timer = 0;

    int play_pos_diff = 0;

    _lcd.clearScreen(C_BLACK);
    draw_logo();
    task::sleep_ms(1000);
    clear_screen();

    num_files = enum_files();
    if (num_files <= 0) {
        printf("num_files <=0\n");
        return;
    }
    clear_screen();
    draw_header("Playlist");
    while (true){
        if(playing){
            if (sd_reader.isAlive() &&  decoder.isAlive()) {
                //printf("playing now\n");

                if(update_required) {
                    clear_menu();
                    //truncate_string(fname_list[_sel_index],tmp,33);
                    if(play_flag == 1) {
                        draw_header("Now Playing All");
                    }else{
                        draw_header("Now Playing");
                    }
                    draw_playing();
                    draw_footer(2);
                    update_required = 0;
                }
                if(spin_timer >= 100) {
                    //(bitrate*8/)filesize
                    //draw bar use last_play_pos
                    //draw_bar(0,last_play_pos,C_BLACK);
                    play_pos = decoder.get_position(menu_items[_sel_index].fsize,_lcd.getSizeY()-20);
                    ///printf("play_pos %d\n",play_pos);
                    //draw bar use play_pos
                    play_pos_diff = play_pos - last_play_pos;
                    if(play_pos_diff < 0) {// VBR
                        draw_bar(play_pos,last_play_pos,C_BLACK);
                    }else{ //>= 0
                        //draw_bar(last_play_pos,play_pos,C_BLACK);
                        draw_bar(play_pos,play_pos+play_pos_diff,C_GAINSBORO);
                    }
                    uint32_t seconds = decoder.get_total_seconds();

                    int minutes = seconds / 60;
                    int remaining_seconds = seconds % 60;

                    // "MM:SS"
                    snprintf(tmp, 6, "%02d:%02d", minutes, remaining_seconds);
                    draw_string(10,288,tmp,0);
                    ///draw_char(20, 300, spinner[spin_i % 4]);
                    spin_i++;
                    spin_timer = 0;
                    last_play_pos = play_pos;
                }
                spin_timer++;
            }else{
                printf("not playing...auto exit\n");
                _fs.close(&_file);
                playing =0;
                update_required =1;
                if(play_flag == 1) {
                    menu_down();
                    menu_start_all(sd_reader,decoder,pcm_drv);
                }
            }
        }else{
            boot_menu();
        }

        if(keycheck == 0) {
            if(keyread == 0) {
                kbd_ret = _kbd.write_i2c_kbd();
                keyread = 1;
            }else{
                kbd_ret = _kbd.read_i2c_kbd();
                keyread = 0;
            }
            keycheck = 16;
        }

        if(kbd_ret){
            if (kbd_ret == 0xA503)ctrlheld = 0;
            else if (kbd_ret == 0xA502) {
                ctrlheld = 1;
            } else if ((kbd_ret & 0xff) == 1) {//pressed
                key_stat = 1;
            } else if ((kbd_ret & 0xff) == 3) {
                key_stat = 3;
            }
            c = kbd_ret >> 8;
            int realc = -1;
            switch (c) {
                case 0xA1:
                case 0xA2:
                case 0xA3:
                case 0xA4:
                case 0xA5:
                    realc = -1;//skip shift alt ctrl keys
                    break;
                default:
                    realc = c;
                    break;
            }
            c = realc;
            if (c >= 'a' && c <= 'z' && ctrlheld)c = c - 'a' + 1;
            switch (c) {
                case 0xb5://UP
                    if(key_stat == 1 && !playing) {
                        menu_up();
                    }
                    break;
                case 0xb6://DOWN
                    if(key_stat == 1 && !playing) {
                        menu_down();
                    }
                    break;
                case 0xb4://LEFT
                    break;
                case 0xb7://RIGHT
                    break;
                case 0x0A://ENTER
                    if(key_stat == 1 && !playing) {
                        menu_start(sd_reader, decoder, pcm_drv);
                    }
                    break;
                case 'a':
                    if(key_stat == 1 && !playing) {
                        menu_start_all(sd_reader,decoder,pcm_drv);
                    }
                    break;
                case 0xB1://ESC
                    if(key_stat == 1 && playing) {
                        playing = 0;
                        if(decoder.getState()==task::SUSPENDED){//maybe is suspended
                            decoder.resume();
                        }
                        sd_reader.force_eof();
                        printf("stop playing\n");
                        pause_flag = 0;
                        update_required = 1;
                        play_flag = 0;
                    }
                    break;
                case 'p':
                    if(key_stat == 1 && playing) {
                        if(pause_flag == 0) {
                            pause_flag = 1;
                            decoder.suspend();
                            draw_header("Paused");
                        }else{
                            pause_flag = 0;
                            decoder.resume();
                            draw_header("Now Playing");
                        }
                        printf("pause playing mp3\n");
                    }
                    break;
                default:
                    break;
            }
            kbd_ret = 0;
        }

        task::sleep_ms(1);
        if(keycheck) keycheck--;
    }

}
