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

extern OSThread* __osRunQueue;
extern OSIntMask __OSGlobalIntMask;
void __osLeoAbnormalResume(void);
void __osLeoResume(void);

STACK(leoDiskStack, 0x1000);

s32 __osLeoInterrupt(void) {
    u32 stat = 0;
    volatile u32 pi_stat;
    u32 bm_stat;
    __OSTranxInfo* info = &__osDiskHandle->transferInfo;
    __OSBlockInfo* blockInfo = &info->block[info->blockNum];

    pi_stat = IO_READ(PI_STATUS_REG);
    if (pi_stat & PI_STATUS_DMA_BUSY) {
        __OSGlobalIntMask &= ~0x800;
        blockInfo->errStatus = 0x1D;
        __osLeoResume();
        return 1;
    }

    pi_stat = IO_READ(PI_STATUS_REG);
    while (pi_stat & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) {
        pi_stat = IO_READ(PI_STATUS_REG);
    }

    stat = IO_READ(ASIC_STATUS);
    if (stat & 0x2000000) {
        pi_stat = IO_READ(PI_STATUS_REG);
        while (pi_stat & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) {
            pi_stat = IO_READ(PI_STATUS_REG);
        }

        IO_WRITE(ASIC_BM_CTL, info->bmCtlShadow | 0x1000000);
        blockInfo->errStatus = 0;
        return 0;
    }
    if (info->cmdType == 2) {
        return 1;
    }
    if (stat & 0x8000000) {
        pi_stat = IO_READ(PI_STATUS_REG);
        while (pi_stat & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) {
            pi_stat = IO_READ(PI_STATUS_REG);
        }

        stat = IO_READ(ASIC_STATUS);
        blockInfo->errStatus = 0x16;
        __osLeoResume();
        IO_WRITE(PI_STATUS_REG, 2);
        __OSGlobalIntMask |= 0x100401;
        return 1;
    }
    if (info->cmdType == 1) {
        if (!(stat & 0x40000000)) {
            if ((info->sectorNum + 1) != (info->transferMode * 0x55)) {
                blockInfo->errStatus = 0x18;
                __osLeoAbnormalResume();
                return 1;
            }
            IO_WRITE(PI_STATUS_REG, 2);
            __OSGlobalIntMask |= 0x100401;
            blockInfo->errStatus = 0;
            __osLeoResume();
            return 1;
        } else {
            blockInfo->dramAddr = (u8*)(blockInfo->dramAddr) + blockInfo->sectorSize;
            info->sectorNum++;
            __osEPiRawStartDma(__osDiskHandle, 1, ASIC_SECTOR_BUFF, blockInfo->dramAddr, blockInfo->sectorSize);
            return 1;
        }
    } else if (info->cmdType == 0) {
        if (info->transferMode == 3) {
            if (((s32)blockInfo->C1ErrNum + 0x11) < info->sectorNum) {
                blockInfo->errStatus = 0;
                __osLeoAbnormalResume();
                return 1;
            }
            if ((stat & 0x40000000) == 0) {
                blockInfo->errStatus = 0x17;
                __osLeoAbnormalResume();
                return 1;
            }
        } else {
            blockInfo->dramAddr = (u8*)(blockInfo->dramAddr) + blockInfo->sectorSize;
        }

        bm_stat = IO_READ(ASIC_BM_STATUS);
        if (((bm_stat & 0x200000) && (bm_stat & 0x400000)) || (bm_stat & 0x2000000)) {
            if (blockInfo->C1ErrNum >= 4) {
                if ((info->transferMode != 3) || (info->sectorNum >= 0x53)) {
                    blockInfo->errStatus = 0x17;
                    __osLeoAbnormalResume();
                    return 1;
                }
            } else {
                int errNum = blockInfo->C1ErrNum;

                blockInfo->C1ErrSector[errNum] = info->sectorNum + 1;
            }
            blockInfo->C1ErrNum++;
        }

        if (stat & 0x10000000) {
            if (info->sectorNum != 0x57) {
                blockInfo->errStatus = 0x18;
                __osLeoAbnormalResume();
                return 1;
            }
            if ((info->transferMode == 2) && (info->blockNum == 0)) {
                info->blockNum = 1;
                info->sectorNum = -1;
                info->block[1].dramAddr = (u8*)(info->block[1].dramAddr) - info->block[1].sectorSize;
                blockInfo->errStatus = 0x16;
            } else {
                IO_WRITE(PI_STATUS_REG, 2);
                __OSGlobalIntMask |= 0x100401;
                info->cmdType = 2;
                blockInfo->errStatus = 0;
            }
            __osEPiRawStartDma(__osDiskHandle, 0, ASIC_C2_BUFF, blockInfo->C2Addr, blockInfo->sectorSize * 4);
            return 1;
        }
        if ((info->sectorNum == -1) && (info->transferMode == 2) && (info->blockNum == 1)) {
            __OSBlockInfo* bptr = info->block;

            if (bptr->C1ErrNum == 0) {
                if (((u32*)bptr->C2Addr)[0] | ((u32*)bptr->C2Addr)[1] | ((u32*)bptr->C2Addr)[2] |
                    ((u32*)bptr->C2Addr)[3]) {
                    bptr->errStatus = 0x17;
                    __osLeoAbnormalResume();
                    return 1;
                }
            }
            bptr->errStatus = 0;
            __osLeoResume();
        }
        info->sectorNum++;
        if (stat & 0x40000000) {
            if (info->sectorNum >= 0x55) {
                blockInfo->errStatus = 0x18;
                __osLeoAbnormalResume();
                return 1;
            }
            __osEPiRawStartDma(__osDiskHandle, 0, ASIC_SECTOR_BUFF, blockInfo->dramAddr, blockInfo->sectorSize);
            blockInfo->errStatus = 0;
            return 1;
        } else if (info->sectorNum < 0x55) {
            blockInfo->errStatus = 0x18;
            __osLeoAbnormalResume();
            return 1;
        }
        return 1;
    } else {
        blockInfo->errStatus = 4;
        __osLeoAbnormalResume();
        return 1;
    }
}

// static
void __osLeoAbnormalResume(void) {
    __OSTranxInfo* info;
    u32 pi_stat;

    info = &__osDiskHandle->transferInfo;
    pi_stat = IO_READ(PI_STATUS_REG);
    while (pi_stat & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) {
        pi_stat = IO_READ(PI_STATUS_REG);
    }

    IO_WRITE(ASIC_BM_CTL, info->bmCtlShadow | 0x10000000);
    pi_stat = IO_READ(PI_STATUS_REG);
    while (pi_stat & (PI_STATUS_DMA_BUSY | PI_STATUS_IO_BUSY)) {
        pi_stat = IO_READ(PI_STATUS_REG);
    }

    IO_WRITE(ASIC_BM_CTL, info->bmCtlShadow);
    __osLeoResume();
    IO_WRITE(PI_STATUS_REG, 2);
    __OSGlobalIntMask |= 0x100401;
}

// static
void __osLeoResume(void) {
    __OSEventState* es;
    OSMesgQueue* mq;
    s32 last;

    es = &__osEventStateTab[8];
    mq = es->queue;
    if ((mq == 0) || (mq->validCount >= mq->msgCount)) {
        return;
    }
    last = (mq->first + mq->validCount) % mq->msgCount;
    mq->msg[last] = es->msg;
    mq->validCount++;
    if (mq->mtqueue->next != NULL) {
        __osEnqueueThread(&__osRunQueue, __osPopThread(&mq->mtqueue));
    }
}
