#include "asm/asm.h"
#include "common/video_character_display.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const size_t block_size = 512;
const size_t block_amount = 32;

void write_program(FILE* fp);

int main(void)
{
    FILE* fp = fopen("build/image", "wb");
    if (!fp) {
        fprintf(
            stderr, "error: could not open build/image: %s\n", strerror(errno));
        return -1;
    }

    printf("clearing disk...\n");
    uint8_t* data = calloc(block_size, 1);
    for (size_t i = 0; i < block_amount; ++i) {
        fwrite(data, 1, block_size, fp);
    }
    free(data);
    fseek(fp, 0, SEEK_SET);
    printf("done!\n");

    write_program(fp);

    fclose(fp);
}

void write_program(FILE* fp)
{
    int label_ids = 0;

    int main_loop = label_ids++;
    int interrupt_table = label_ids++;
    int keyboard_interrupt = label_ids++;
    int keyboard_interrupt_0 = label_ids++;
    int keyboard_interrupt_1 = label_ids++;
    int keyboard_interrupt_2 = label_ids++;
    int keyboard_interrupt_3 = label_ids++;
    int keyboard_interrupt_4 = label_ids++;
    int put_char = label_ids++;
    int put_char_0 = label_ids++;
    int put_char_1 = label_ids++;
    int screen_x = label_ids++;
    int screen_y = label_ids++;

#define L(LABEL) s_label(LABEL)

    Line program_asm[] = {
        // clang-format off

            // rsp points *at* the top element
            s_mov16_r_i(Rbp, 2048),
            s_mov16_r_i(Rsp, 2048 - 2),

            s_lit_l(interrupt_table),
            s_or_i(Rfl, Rfl, 1 << Fl_Int),

            s_or_i(Rfl, Rfl, 1 << Fl_Vcd),

            s_mov16_r_i(R0, 512),
            s_mov16_r_i(R1, 1),
            s_int(Int_DiskRead),

        L(main_loop),
            s_hlt(),
            s_jmp_l(main_loop),

        L(interrupt_table),
            // size
            s_data_i(1),
            s_data_l(keyboard_interrupt),
            s_nop(),

        L(keyboard_interrupt),
            s_and_i(Rfl, Rfl, (uint16_t)~(1 << Fl_Int)),
            s_push_r(Rbp),
            s_mov16_r_r(Rbp, Rsp),
            s_push_r(R0),
            s_push_r(R1),
            s_push_r(R2),
            s_push_r(R3),

            s_in_i(R0, Device_Keyboard),

            s_cmp_i(R0, 44),
            s_mov16_r_r(R1, Rfl),
            s_and_i(R1, R1, 1 << Fl_Eq),
            s_jnz_l(R1, keyboard_interrupt_0),

            s_cmp_i(R0, 42),
            s_mov16_r_r(R1, Rfl),
            s_and_i(R1, R1, 1 << Fl_Eq),
            s_jnz_l(R1, keyboard_interrupt_1),

            s_cmp_i(R0, 40),
            s_mov16_r_r(R1, Rfl),
            s_and_i(R1, R1, 1 << Fl_Eq),
            s_jnz_l(R1, keyboard_interrupt_2),

            s_jmp_l(keyboard_interrupt_3),

        L(keyboard_interrupt_0),
            s_mov16_r_i(R0, ' '),
            s_call_l(put_char),
            s_jmp_l(keyboard_interrupt_4),

        L(keyboard_interrupt_1),
            s_mov16_r_ml(R1, screen_x),
            s_cmp_i(R1, 0),
            s_mov16_r_r(R2, Rfl),
            s_and_i(R2, R2, 1 << Fl_Eq),
            s_jnz_l(R2, keyboard_interrupt_4),
            s_sub_i(R1, R1, 1),
            s_mov16_ml_r(screen_x, R1),
            s_mov16_r_i(R0, ' '),
            s_call_l(put_char),
            s_mov16_r_ml(R1, screen_x),
            s_sub_i(R1, R1, 1),
            s_mov16_ml_r(screen_x, R1),
            s_jmp_l(keyboard_interrupt_4),

        L(keyboard_interrupt_2),
            s_mov16_r_ml(R1, screen_y),
            s_add_i(R1, R1, 1),
            s_mov16_ml_r(screen_y, R1),
            s_mov16_r_i(R1, 0),
            s_mov16_ml_r(screen_x, R1),
            s_jmp_l(keyboard_interrupt_4),

        L(keyboard_interrupt_3),
            s_add_i(R0, R0, 'A' - 4),
            s_call_l(put_char),
            s_jmp_l(keyboard_interrupt_4),

        L(keyboard_interrupt_4),

            s_pop_r(R3),
            s_pop_r(R2),
            s_pop_r(R1),
            s_pop_r(R0),
            s_mov16_r_r(Rsp, Rbp),
            s_pop_r(Rbp),
            s_or_i(Rfl, Rfl, 1 << Fl_Int),
            s_iret(),

        L(put_char),
            s_push_r(Rbp),
            s_mov16_r_r(Rbp, Rsp),
            s_push_r(R1),
            s_push_r(R2),

            s_mov16_r_ml(R2, screen_y),
            s_mul_i(R2, R2, vcd_width_in_ch),
            s_mov16_r_ml(R1, screen_x),
            s_add_i(R1, R1, 0x0c00),
            s_add_r(R1, R1, R2),
            s_mov8_mr_r(R1, 0, R0),

            s_mov16_r_ml(R1, screen_x),
            s_add_i(R1, R1, 1),
            s_mov16_ml_r(screen_x, R1),

            s_cmp_i(R1, vcd_width_in_ch),
            s_mov16_r_r(R2, Rfl),
            s_and_i(R2, R2, 1 << Fl_Eq),
            s_jnz_l(R2, put_char_0),
            s_jmp_l(put_char_1),

        L(put_char_0),
            s_mov16_r_ml(R1, screen_y),
            s_add_i(R1, R1, 1),
            s_mov16_ml_r(screen_y, R1),
            s_mov16_r_i(R1, 0),
            s_mov16_ml_r(screen_x, R1),

        L(put_char_1),

            s_pop_r(R1),
            s_pop_r(R2),
            s_mov16_r_r(Rsp, Rbp),
            s_pop_r(Rbp),
            s_ret(),

        L(screen_x),
            s_data_i(0),
        L(screen_y),
            s_data_i(0),

        // clang-format on
    };

    size_t program_asm_size = sizeof(program_asm) / sizeof(program_asm[0]);

    uint16_t* program = calloc(512 * 2, sizeof(uint16_t));

    printf("assembling program...\n");
    uint16_t program_size
        = assemble_lines_with_labels(program, program_asm, program_asm_size);
    printf("done!\n");
    printf("program size = %d\n", program_size);

    printf("writing program to disk...\n");
    fwrite(program, sizeof(uint16_t), program_size, fp);
    printf("done!\n");

    free(program);
}