#include "ultra64/leo.h"
#include "ultra64/leoappli.h"
#include "ultra64/leodrive.h"
#include "attributes.h"

#include "ultra64.h"

extern vu16 LEOrw_flags;

static void (*cmd_tbl[16])(void) = {
    NULL,          leoClr_queue, leoInquiry,    leoTest_unit_rdy, leoRezero,    leoRead,
    leoWrite,      leoSeek,      leoStart_stop, leoRd_capacity,   leoTranslate, leoMode_sel,
    leoReadDiskId, leoReadTimer, leoSetTimer,   leoClr_reset,
};

static LEOCmdRead system_read_cmd = {
    { LEO_COMMAND_READ, 0, 0, 0, 0, 0, 0, 0, 0 }, 12, 1, 0, 0,
};

static const u8 system_lba[8] = { 0, 1, 8, 9, 0, 0, 0, 0 };

OSPiHandle* LEOPiInfo;
OSIoMesg LEOPiDmaParam;
s32 currentCommand;

u8 leoRead_system_area(void);

void leomain(void* arg0) {
    u32 cur_status;
    u32 sense_code;
    u8 disktype_bak;

    ((u8*)&LEO_country_code)[0] = *(vu8*)OS_PHYSICAL_TO_K1(0x10);
    ((u8*)&LEO_country_code)[1] = *(vu8*)OS_PHYSICAL_TO_K1(0x90);
    ((u8*)&LEO_country_code)[2] = *(vu8*)OS_PHYSICAL_TO_K1(0x110);
    ((u8*)&LEO_country_code)[3] = *(vu8*)OS_PHYSICAL_TO_K1(0x190);

    LEOasic_seq_ctl_shadow = 0;
    LEOasic_bm_ctl_shadow = 0;
    LEOdrive_flag = 0;
    LEOclr_que_flag = 0;
    leoInitUnit_atten();
    LEOPiInfo = osLeoDiskInit();
    LEOPiDmaParam.hdr.pri = 1;
    LEOPiDmaParam.hdr.retQueue = &LEOdma_que;
    osEPiReadIo(LEOPiInfo, ASIC_STATUS, &cur_status);
    if (!(cur_status & 0x400000) && (cur_status & 0x06800000)) {
        leoDrive_reset();
    }

    while (true) {
        osRecvMesg(&LEOcommand_que, (OSMesg*)&LEOcur_command, OS_MESG_BLOCK);
        currentCommand = LEOcur_command->header.command;
        if (LEOcur_command->header.command == 0) {
            leoDrive_reset();
            osRecvMesg(&LEOevent_que, NULL, OS_MESG_NOBLOCK);
            continue;
        }

        sense_code = leoChk_asic_ready(0x10001);
        cur_status = leoChkUnit_atten();

        do {
            if (cur_status == 0) {
                if (sense_code == 0) {
                    continue;
                }
            } else {
                switch (sense_code) {
                    case 3:
                    case 37:
                    case 41:
                    case 43:
                        break;
                    case 49:
                        if (leoRetUnit_atten() == 43) {
                            sense_code = 43;
                        }
                        break;
                    default:
                        sense_code = leoRetUnit_atten();
                }
            }

            switch (sense_code) {
                case 47:
                    switch (LEOcur_command->header.command) {
                        case 2:
                        case 8:
                        case 11:
                        case 12:
                        case 13:
                        case 14:
                        case 15:
                            continue;
                    }
                    break;
                case 49:
                    switch (LEOcur_command->header.command) {
                        case 2:
                        case 11:
                        case 13:
                        case 14:
                        case 15:
                            continue;
                        default:
                            sense_code = 42;
                    }
                    break;
                case 43:
                    switch (LEOcur_command->header.command) {
                        case 15:
                            leoClrUA_RESET();
                            FALLTHROUGH;
                        case 2:
                        case 13:
                        case 14:
                            continue;
                    }
                    break;
                default:
                    /* empty */;
            }

            if (LEOcur_command->header.command == 3) {
                LEOcur_command->data.modeSelect.reserve1 = leoChk_cur_drvmode();
            }
            LEOcur_command->header.sense = sense_code;
            LEOcur_command->header.status = 2;
            goto post_exe;
        } while (0);

        if (LEOdrive_flag == 0) {
            switch (LEOcur_command->header.command) {
                case 2:
                case 3:
                case 8:
                case 11:
                case 13:
                case 14:
                case 15:
                    break;
                default:
                    if (LEO_country_code == 0) {
                        osEPiReadIo(LEOPiInfo, ASIC_ID_REG, &cur_status);
                        if ((cur_status & 0x70000) != 0x40000) {
                            while (true) {}
                        }
                    }

                    if (leoRead_system_area() != 0) {
                        LEOcur_command->header.status = 2;
                        goto post_exe;
                    }

                    if ((LEOcur_command->header.sense =
                             leoSend_asic_cmd_w(0xB0001, LEO_sys_data.param.disk_type << 16)) != 0) {
                        LEOcur_command->header.status = 2;
                        goto post_exe;
                    }

                    if ((LEO_sys_data.param.disk_type & 0xF0) != 0x10) {
                        goto invalid_disktype;
                    }

                    LEOdisk_type = (LEO_sys_data.param.disk_type & 0xF);
                    if (LEOdisk_type >= 7) {
                    invalid_disktype:
                        LEOcur_command->header.sense = 0xBU;
                        LEOcur_command->header.status = 2;
                        goto post_exe;
                    }

                    LEOdrive_flag = -1;
            }
        }

        cmd_tbl[LEOcur_command->header.command]();

    post_exe:
        if (LEOcur_command->header.control & 0x80) {
            osSendMesg(LEOcur_command->header.post, (void*)(s32)LEOcur_command->header.sense, OS_MESG_BLOCK);
        }
        if (LEOclr_que_flag != 0) {
            leoClr_queue();
        }
    }
}

u8 leoRead_system_area(void) {
    LEOCmdRead dummy_cmd;
    void* backup_pointer;
    u32 retry_cntr;
    s32 read_mode;

    backup_pointer = LEOcur_command;
    LEOcur_command = (LEOCmd*)&dummy_cmd;
    read_mode = 0;
    retry_cntr = 0;

    while (true) {
        LEOdisk_type = 0;
        // For lba_to_phys to avoid dealing with alternative tracks on the disk
        LEO_sys_data.param.defect_num[0] = 0;
        LEOrw_flags = 0x3000;
        dummy_cmd = system_read_cmd;
        dummy_cmd.buffPtr = &LEO_sys_data;

        if (read_mode == 0) {
            // read System LBA 12 (+0, this is an offset value for leoRead_common)
            // see system_read_cmd premade struct
            leoRead_common(0);
            switch (dummy_cmd.header.sense) {
                case LEO_SENSE_NO_ADDITIONAL_SENSE_INFOMATION:
                    do {
                        // if expecting a retail disk, LBA 12 is expected to do a read error, if not then freeze
                    } while (LEO_country_code != 0);
                    retry_cntr = 0;
                    read_mode--;
                    continue;
                case LEO_SENSE_UNRECOVERED_READ_ERROR:
                    do {
                        // if expecting a development disk, LBA 12 is expected to read correctly, if not then freeze
                    } while (LEO_country_code == 0);
                    retry_cntr = 0;
                    read_mode--;
                    continue;
            }
        } else {
            // read System LBA 0,1,8,9 (or 2,3,10,11 for dev)
            dummy_cmd.lba = system_lba[retry_cntr & 3];
            if (LEO_country_code == 0) {
                dummy_cmd.lba += 2;
            }
            leoRead_common(0);
            if (dummy_cmd.header.status == LEO_STATUS_GOOD) {
                do {
                    // if disk country and set country code in libleo mismatch, then freeze
                } while (LEO_sys_data.param.country != LEO_country_code);
                goto sys_read_end;
            }
        }

    system_retry:
        if (leoChk_err_retry(dummy_cmd.header.sense) != LEO_SENSE_NO_ADDITIONAL_SENSE_INFOMATION) {
            break;
        }
        if (retry_cntr++ >= 0x40U) {
            break;
        }
        if ((retry_cntr & 7) == 0) {
            // Recalibrate drive every 8th tries
            if ((dummy_cmd.header.sense = leoSend_asic_cmd_w(0x30001, 0)) == LEO_SENSE_NO_ADDITIONAL_SENSE_INFOMATION) {
                continue;
            }
            goto system_retry;
        }
    }

sys_read_end:
    LEOcur_command = backup_pointer;
    LEOcur_command->header.sense = dummy_cmd.header.sense;
    return GET_ERROR(*LEOcur_command);
}
