#include "ui.h"

#include <stddef.h>

#include "main.h"
#include "player.h"

#define ROWS 25
#define COLS 80

#define TRACK_ROWS 16
#define CHANNELS   4

static uint8_t dirty = 1;
static uint8_t clear = 1;

static uint8_t cursor_row = 0;
static uint8_t cursor_channel = 0;

static uint8_t input_state = 0;
static uint8_t input_octave = 4;

static uint8_t do_action = ACTION_NONE;
static pattern_t * pattern = NULL;


static void serial_puts(char * s){
    while(*s!='\0'){
        serial_write(*s);
        s++;
    }
}

static void cursor_set(uint8_t x, uint8_t y){
    // Buffer for "\x1b[yyy;xxxH\0" -> max 12 bytes needed
    char buf[12];
    char *p = buf;

    // 1. Write ANSI prefix
    *p++ = 0x1B; // ESC
    *p++ = '[';

    // 2. Convert Y (Row) to ASCII digits
    if (y >= 100) {
        *p++ = '0' + (y / 100);
        y %= 100;
        *p++ = '0' + (y / 10);
    } else if (y >= 10) {
        *p++ = '0' + (y / 10);
    }
    *p++ = '0' + (y % 10);

    // 3. Write separator
    *p++ = ';';

    // 4. Convert X (Col) to ASCII digits
    if (x >= 100) {
        *p++ = '0' + (x / 100);
        x %= 100;
        *p++ = '0' + (x / 10);
    } else if (x >= 10) {
        *p++ = '0' + (x / 10);
    }
    *p++ = '0' + (x % 10);

    // 5. Write ANSI suffix and null-terminator
    *p++ = 'H';
    *p = '\0';

    // 6. Send via your serial routine
    serial_puts(buf);
}

static void cls(){
    serial_puts("\033[2J");
    cursor_set(1, 1);
}

static void reverse_on(void){
    serial_puts("\x1b[7m");
}

static void reverse_off(void){
    serial_puts("\x1b[0m");
}

static void cursor_hide(void){
    serial_puts("\x1b[?25l");
}

static void cursor_show(void){
    serial_puts("\x1b[?25h");
}

static void print_hex_digit(uint8_t n){
    n &= 0x0f;

    if (n < 10)
        serial_write('0' + n);
    else
        serial_write('A' + (n - 10));
}

static void print_hex_byte(uint8_t n){
    print_hex_digit(n >> 4);
    print_hex_digit(n);
}

static void print_note(uint8_t note){
    static const char note_names[12][2] = {
        {'C', '-'},
        {'C', '#'},
        {'D', '-'},
        {'D', '#'},
        {'E', '-'},
        {'F', '-'},
        {'F', '#'},
        {'G', '-'},
        {'G', '#'},
        {'A', '-'},
        {'A', '#'},
        {'B', '-'}
    };

    uint8_t n;
    uint8_t octave;

    if (note == 0) {
        serial_puts("---");
        return;
    }

    if (note == 0xff) {
        serial_puts("OFF");
        return;
    }

    note -= 12;

    n = note % 12;
    octave = (note / 12);

    serial_write(note_names[n][0]);
    serial_write(note_names[n][1]);

    if (octave < 10)
        serial_write('0' + octave);
    else
        serial_write('?');
}

static void draw_cell(uint8_t row, uint8_t channel){
    cell_t *cell = &pattern->rows[row].channel[channel];

    if (row == cursor_row && channel == cursor_channel)
        reverse_on();

    print_note(cell->note);

    serial_write(' ');

    // if (cell->instrument == 0) {
        serial_puts("--");
    // }
    // else {
        // print_hex_byte(cell->instrument);
    // }

    if (row == cursor_row && channel == cursor_channel)
        reverse_off();
}

static void draw_row(uint8_t row){
    uint8_t channel;

    print_hex_byte(row);
    serial_puts("  ");

    for (channel = 0; channel < CHANNELS; channel++) {
        draw_cell(row, channel);

        if (channel != CHANNELS - 1)
            serial_puts("   ");
    }

    serial_puts("\r\n");
}

static void draw(void){
    uint8_t row;

    if (clear) {
        cls();
        clear = 0;
    }else {
        cursor_set(1, 1);
    }

    cursor_hide();

    serial_puts("Z80 TRACKER\r\n");
    serial_puts("O [");
    print_hex_digit(input_octave);
    serial_puts("]\r\n");

    serial_puts("    CH1      CH2      CH3      NOISE\r\n");
    serial_puts("    -------  -------  -------  -------\r\n");

    for (row = 0; row < TRACK_ROWS; row++) {
        draw_row(row);
    }

    serial_puts("\r\n");
    serial_puts("Arrows: move   A-K: notes   X: clear   Space: off\r\n");
    serial_puts("P: play        O: stop      Q: quit    Numbers: oct\r\n");

    /*
     * Keep the real terminal cursor out of the tracker area.
     */
    cursor_set(1, 25);
}

static void move_up(void){
    if (cursor_row > 0)
        cursor_row--;

    dirty = 1;
}

static void move_down(void){
    if (cursor_row < TRACK_ROWS - 1)
        cursor_row++;

    dirty = 1;
}

static void move_left(void){
    if (cursor_channel > 0)
        cursor_channel--;

    dirty = 1;
}

static void move_right(void){
    if (cursor_channel < CHANNELS - 1)
        cursor_channel++;

    dirty = 1;
}

/*
 * Very simple piano mapping:
 *
 *     W E   T Y U
 *    C#D#  F#G#A#
 *
 *   A S D F G H J K
 *   C D E F G A B C
 *
 * This currently enters octave 4.
 */
static uint8_t key_to_note(char key){
    uint8_t semitone;

    switch (key) {
        case 'a': case 'A': semitone = 0;  break; /* C */
        case 'w': case 'W': semitone = 1;  break; /* C# */
        case 's': case 'S': semitone = 2;  break; /* D */
        case 'e': case 'E': semitone = 3;  break; /* D# */
        case 'd': case 'D': semitone = 4;  break; /* E */
        case 'f': case 'F': semitone = 5;  break; /* F */
        case 't': case 'T': semitone = 6;  break; /* F# */
        case 'g': case 'G': semitone = 7;  break; /* G */
        case 'y': case 'Y': semitone = 8;  break; /* G# */
        case 'h': case 'H': semitone = 9;  break; /* A */
        case 'u': case 'U': semitone = 10; break; /* A# */
        case 'j': case 'J': semitone = 11; break; /* B */
        case 'k': case 'K': semitone = 12; break; /* C */

        default:
            return 0;
    }

    return 12+(input_octave*12) + semitone;
}

static void enter_note(uint8_t note){
    cell_t *cell = &pattern->rows[cursor_row].channel[cursor_channel];

    cell->note = note;
    cell->volume = 15;

    /*
     * Just give every entered note instrument 1 for now.
     */
    //if (cell->instrument == 0)
    //    cell->instrument = 1;

    /*
     * Tracker-style behavior: advance after entering a note.
     */
    if (cursor_row < TRACK_ROWS - 1)
        cursor_row++;

    dirty = 1;
}

void ui_input(char input){
    uint8_t note;

    /*
     * State 0:
     * normal keyboard input.
     */
    if (input_state == 0) {

        if ((uint8_t)input == 0x1b) {
            input_state = 1;
            return;
        }

        /*
         * Clear cell.
         */
        if (input == 'x' || input == 'X') {
            pattern->rows[cursor_row].channel[cursor_channel].note = 0;

            dirty = 1;
            return;
        }

        /*
         * Space = note off.
         */
        if (input == ' ') {
            pattern->rows[cursor_row].channel[cursor_channel].note = 0xff;

            if (cursor_row < TRACK_ROWS - 1)
                cursor_row++;

            dirty = 1;
            return;
        }

        /*
         * P = play once
         */
        if (input == 'p' || input=='P') {
            do_action = ACTION_PLAY_ONCE;
            return;
        }

        /*
         * O = stop
         */
        if (input == 'o' || input=='O') {
            do_action = ACTION_STOP;
            return;
        }

        /*
         * Q = quit
         */
        if (input == 'q' || input=='Q') {
            do_action = ACTION_QUIT;
            return;
        }

        /*
         * 0-7 = octaves
         */
        if (input >= '0' && input<='7') {
            input_octave = input-'0';
            dirty = 1;
            return;
        }

        /*
         * Piano keyboard.
         */
        note = key_to_note(input);

        if (note != 0) {
            enter_note(note);
        }

        return;
    }


    /*
     * State 1:
     *
     * We received ESC.
     *
     * Arrow keys normally send '[' next.
     */
    if (input_state == 1) {

        if (input == '[') {
            input_state = 2;
        }
        else {
            /*
             * Unknown escape sequence.
             */
            input_state = 0;
        }

        return;
    }


    /*
     * State 2:
     *
     * ESC [
     *
     * Last byte tells us which arrow.
     */
    if (input_state == 2) {

        switch (input) {
            case 'A':
                move_up();
                break;

            case 'B':
                move_down();
                break;

            case 'C':
                move_right();
                break;

            case 'D':
                move_left();
                break;
        }

        input_state = 0;
    }
}

uint8_t do_ui(){
    if(dirty){
        dirty = 0;
        draw();
    }
    if(do_action != ACTION_NONE){
        uint8_t action = do_action;
        do_action = ACTION_NONE;
        return action;
    }
    return ACTION_NONE;
}

void ui_set_pattern(pattern_t * _pattern){
    pattern = _pattern;
}

void ui_init(){
    uint8_t row;
    uint8_t channel;

    dirty = 1;
    clear = 1;

    cursor_row = 0;
    cursor_channel = 0;
    input_state = 0;
}