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

#include "ultra64.h"

extern s32 currentCommand;

static u32 asic_cur_status;
static u32 unit_atten;

u8 leoAnalize_asic_status(void) {
    u32 curr_stat;

    osEPiReadIo(LEOPiInfo, ASIC_STATUS, &asic_cur_status);
    curr_stat = asic_cur_status ^ 0x1000000;

    // 0xFE3C0000?
    if (curr_stat & 0x1C3FFFF) {
        // 0xFE3E0000?
        if (curr_stat & 0x1C1FFFF) {
            LEOdrive_flag = 0;
        }
        if (curr_stat & 0xFFFF) {
            return 41;
        }
        if ((curr_stat & 0xC00000) == 0x800000) {
            return 3;
        }
        if (curr_stat & 0x400000) {
            unit_atten |= 2;
            return 43;
        }
        if (curr_stat & 0x1000000) {
            return 49;
        }
        if (curr_stat & 0x10000) {
            unit_atten |= 1;
            return 47;
        }
        if (curr_stat & 0x20000) {
            return 21;
        }
    }
    return 0;
}

u8 leoChk_asic_ready(u32 asic_cmd) {
    u32 sense_code = leoAnalize_asic_status();

    switch (sense_code) {
        case 47:
            if (asic_cmd == 0x80000) {
                return 0;
            }
            FALLTHROUGH;
        case 43:
            if (!(asic_cur_status & 0x800000)) {
                if (asic_cmd == 0x90000) {
                    return 0;
                }
                if (leoRecv_event_mesg(OS_MESG_NOBLOCK) != 0) {
                    return 37;
                }
                osEPiWriteIo(LEOPiInfo, ASIC_CMD, 0x90000);
                if (leoRecv_event_mesg(OS_MESG_BLOCK) != 0) {
                    return 37;
                }
            }
            FALLTHROUGH;
        default:
            break;

        case 49:
            if (asic_cmd & 1) {
                break;
            }
            FALLTHROUGH;
        case 21:
            return 0;
    }
    return sense_code;
}

// Which of LEO_ERROR or LEO_SENSE should be used where?
u8 leoChk_done_status(u32 asic_cmd) {
    u32 asic_data;
    u32 code = leoAnalize_asic_status();

    switch (code) {
        case 43:
        case 47:
            if (!(asic_cur_status & 0x800000)) {
                if (leoRecv_event_mesg(OS_MESG_NOBLOCK) != 0) {
                    return 37;
                }

                osEPiWriteIo(LEOPiInfo, ASIC_CMD, 0x90000);
                if (leoRecv_event_mesg(OS_MESG_BLOCK) != 0) {
                    return 37;
                }
            }
            break;

        case 49:
            if (asic_cmd & 1) {
                break;
            }
            return 0;

        case 21:
            osEPiWriteIo(LEOPiInfo, ASIC_DATA, 0);
            if (leoRecv_event_mesg(OS_MESG_NOBLOCK) != 0) {
                return 37;
            }

            osEPiWriteIo(LEOPiInfo, ASIC_CMD, 0xC0000);
            if (leoRecv_event_mesg(OS_MESG_BLOCK) != 0) {
                return 37;
            }

            osEPiReadIo(LEOPiInfo, ASIC_DATA, &asic_data);
            code = leoChk_asic_ready(0xC0000);
            if (code != 0) {
                return code;
            }

            if (asic_data & 0x10000) {
                return 2;
            }
            if (asic_data & 0x20000) {
                return 24;
            }
            if (asic_data & 0x40000) {
                return 1;
            }
            if (asic_data & 0x80000) {
                return 21;
            }
            if (asic_data & 0x200000) {
                return 11;
            }
            return 41;

        default:
            break;
    }

    return code;
}

u8 leoSend_asic_cmd_i(u32 asic_cmd, u32 asic_data) {
    u8 status = leoChk_asic_ready(asic_cmd);

    if (status != 0) {
        return LEOcur_command->header.sense = status;
    }

    osEPiWriteIo(LEOPiInfo, ASIC_DATA, asic_data);
    if (leoRecv_event_mesg(OS_MESG_NOBLOCK) != 0) {
        return LEOcur_command->header.sense = 37;
    }

    osEPiWriteIo(LEOPiInfo, ASIC_CMD, asic_cmd);
    return 0;
}

// Is this using the sense defines?
u8 leoWait_mecha_cmd_done(u32 asic_cmd) {
    u32 done_stat;

    if (leoRecv_event_mesg(OS_MESG_BLOCK)) {
        return 37;
    }

    done_stat = leoChk_done_status(asic_cmd);
    if (done_stat != 0) {
        return done_stat;
    }

    return 0;
}

u8 leoSend_asic_cmd_w(u32 asic_cmd, u32 asic_data) {
    u32 wstatus = leoSend_asic_cmd_i(asic_cmd, asic_data);

    if (wstatus != 0) {
        return wstatus;
    }

    return leoWait_mecha_cmd_done(asic_cmd);
}

u8 leoSend_asic_cmd_w_nochkDiskChange(u32 asic_cmd, u32 asic_data) {
    u8 status;
    u32 done_stat;

    status = leoChk_asic_ready(asic_cmd);
    if ((status != 0x2F) && (status != 0)) {
        return LEOcur_command->header.sense = status;
    }

    osEPiWriteIo(LEOPiInfo, ASIC_DATA, asic_data);
    if (leoRecv_event_mesg(OS_MESG_NOBLOCK) != 0) {
        return LEOcur_command->header.sense = 0x25;
    }

    osEPiWriteIo(LEOPiInfo, ASIC_CMD, asic_cmd);
    if (leoRecv_event_mesg(OS_MESG_BLOCK) != 0) {
        return 0x25;
    }

    done_stat = leoChk_done_status(asic_cmd);
    if ((done_stat != 0x2F) && (done_stat != 0)) {
        return done_stat;
    }

    return 0;
}

u8 leoDetect_index_w(void) {
    return leoSend_asic_cmd_w(0xE0001, 0);
}

u8 leoRecal_i(void) {
    return leoSend_asic_cmd_i(0x30001, 0);
}

u8 leoRecal_w(void) {
    return leoSend_asic_cmd_w(0x30001, 0);
}

u8 leoSeek_i(u16 rwmode) {
    u32 tgt_tk = ((LEOtgt_param.head << 0xC) + LEOtgt_param.cylinder) << 0x10;

    if (rwmode == 0) {
        return leoSend_asic_cmd_i(0x10001, tgt_tk);
    } else {
        return leoSend_asic_cmd_i(0x20001, tgt_tk);
    }
}

u8 leoSeek_w(void) {
    u8 sksense = leoSeek_i(0);

    if (sksense != 0) {
        return sksense;
    }

    return leoWait_mecha_cmd_done(0x10001);
}

u8 leoRecv_event_mesg(s32 control) {
    u32 done_mesg;

    if (osRecvMesg(&LEOevent_que, (OSMesg*)&done_mesg, control) == 0) {
        if (done_mesg == 0xA0000) {
            leoDrive_reset();
            return -1;
        }
    }

    return 0;
}

u32 leoChk_err_retry(u32 sense) {
    if ((currentCommand == LEO_COMMAND_READ_DISK_ID) || (currentCommand == LEO_COMMAND_START_STOP)) {
        switch (sense) {
            case LEO_SENSE_POWERONRESET_DEVICERESET_OCCURED:
                unit_atten |= 2;
                FALLTHROUGH;
            case LEO_SENSE_DIAGNOSTIC_FAILURE:
            case LEO_SENSE_COMMAND_PHASE_ERROR:
            case LEO_SENSE_WAITING_NMI:
            case LEO_SENSE_DEVICE_COMMUNICATION_FAILURE:
            case LEO_SENSE_MEDIUM_NOT_PRESENT:
            case LEO_SENSE_EJECTED_ILLEGALLY_RESUME:
                LEOdrive_flag = 0;
                return -1;
        }
    } else {
        switch (sense) {
            case LEO_SENSE_POWERONRESET_DEVICERESET_OCCURED:
                unit_atten |= 2;
                FALLTHROUGH;
            case LEO_SENSE_MEDIUM_MAY_HAVE_CHANGED:
                unit_atten |= 1;
                FALLTHROUGH;
            case LEO_SENSE_DIAGNOSTIC_FAILURE:
            case LEO_SENSE_COMMAND_PHASE_ERROR:
            case LEO_SENSE_WAITING_NMI:
            case LEO_SENSE_DEVICE_COMMUNICATION_FAILURE:
            case LEO_SENSE_MEDIUM_NOT_PRESENT:
            case LEO_SENSE_EJECTED_ILLEGALLY_RESUME:
                LEOdrive_flag = 0;
                return -1;
        }
    }

    return 0;
}

u8 leoChk_cur_drvmode(void) {
    u8 devstat = 0;

    if (!(asic_cur_status & 0x1000000)) {
        devstat |= 1;
    }
    if (asic_cur_status & 0x80000) {
        devstat |= 2;
    }
    if (asic_cur_status & 0x100000) {
        devstat |= 4;
    }
    return devstat;
}

void leoDrive_reset() {
    osEPiWriteIo(LEOPiInfo, ASIC_HARD_RESET, 0xAAAA0000);
}

u32 leoChkUnit_atten(void) {
    return unit_atten;
}

u32 leoRetUnit_atten(void) {
    if (unit_atten & 2) {
        return 43;
    }
    if (unit_atten & 1) {
        return 47;
    }
    return 0;
}

void leoClrUA_RESET(void) {
    unit_atten &= ~2;
}

void leoClrUA_MEDIUM_CHANGED(void) {
    unit_atten &= ~1;
}

void leoSetUA_MEDIUM_CHANGED(void) {
    unit_atten |= 1;
}

void leoInitUnit_atten(void) {
    unit_atten = 1;
}
