//
// Generated by dtk
// Translation Unit: JKRAram.cpp
//

#include "JSystem/JSystem.h" // IWYU pragma: keep

#include "JSystem/JKernel/JKRAram.h"
#include "JSystem/JKernel/JKRAramPiece.h"
#include "JSystem/JKernel/JKRAramStream.h"
#include "JSystem/JKernel/JKRDecomp.h"
#include "JSystem/JKernel/JKRExpHeap.h"
#include "JSystem/JUtility/JUTAssert.h"
#include "string.h"
#include "dolphin/ar/ar.h"
#include "dolphin/ar/arq.h"
#include "dolphin/os/OS.h"
#include "global.h"

static int JKRDecompressFromAramToMainRam(u32, void*, u32, u32, u32);
static int decompSZS_subroutine(u8*, u8*);
static u8* firstSrcData();
static u8* nextSrcData(u8*);

JKRAram* JKRAram::sAramObject;

/* 802B42C4-802B4360       .text create__7JKRAramFUlUllll */
JKRAram* JKRAram::create(u32 aram_audio_buffer_size, u32 aram_audio_graph_size, s32 stream_priority, s32 decomp_priority, s32 piece_priority) {
    if (!sAramObject) {
        sAramObject = new (JKRHeap::getSystemHeap(), 0)
            JKRAram(aram_audio_buffer_size, aram_audio_graph_size, piece_priority);
    }

    JKRCreateAramStreamManager(stream_priority);
    JKRCreateDecompManager(decomp_priority);
    sAramObject->resume();
    return sAramObject;
}

OSMessage JKRAram::sMessageBuffer[4] = { NULL, NULL, NULL, NULL };
OSMessageQueue JKRAram::sMessageQueue = {0};

/* 802B4360-802B44D8       .text __ct__7JKRAramFUlUll */
JKRAram::JKRAram(u32 audio_buffer_size, u32 audio_graph_size, s32 priority) : JKRThread(0x4000, 0x10, priority) {
    u32 aramBase = ARInit(mStackArray, ARRAY_SIZE(mStackArray));
    ARQInit();

    u32 aramSize = ARGetSize();
    OSReport("ARAM size = %08x\n", aramSize);

    mAudioMemorySize = audio_buffer_size;
    if (audio_graph_size == 0xFFFFFFFF) {
        mGraphMemorySize = (aramSize - audio_buffer_size) - aramBase;
        mAramMemorySize = 0;
    } else {
        mGraphMemorySize = audio_graph_size;
        mAramMemorySize = (aramSize - (audio_buffer_size + audio_graph_size)) - aramBase;
    }

    mAudioMemoryPtr = ARAlloc(mAudioMemorySize);
    mGraphMemoryPtr = ARAlloc(mGraphMemorySize);

    if (mAramMemorySize) {
        mAramMemoryPtr = ARAlloc(mAramMemorySize);
    } else {
        mAramMemoryPtr = NULL;
    }
    OSReport("ARAM audio area %08x: %08x\n", mAudioMemoryPtr, mAudioMemorySize);
    OSReport("ARAM graph area %08x: %08x\n", mGraphMemoryPtr, mGraphMemorySize);
    OSReport("ARAM  user area %08x: %08x\n", mAramMemoryPtr, mAramMemorySize);

    mAramHeap = new (JKRHeap::getSystemHeap(), 0) JKRAramHeap(mGraphMemoryPtr, mGraphMemorySize);
}

/* 802B44D8-802B4568       .text __dt__7JKRAramFv */
JKRAram::~JKRAram() {
    sAramObject = NULL;
    if (mAramHeap) {
        delete mAramHeap;
    }
}

/* 802B4568-802B45D4       .text run__7JKRAramFv */
void* JKRAram::run() {
    int result;
    JKRAMCommand* command;
    JKRAramPiece::Message* message;
    OSInitMessageQueue(&sMessageQueue, sMessageBuffer, 4);
    do {
        OSReceiveMessage(&sMessageQueue, (OSMessage*)&message, OS_MESSAGE_BLOCK);
        result = message->field_0x00;
        command = message->command;
        delete message;

        switch (result) {
        case 1:
            JKRAramPiece::startDMA(command);
            break;
        }
    } while (true);
}

/* 802B45D4-802B4664       .text checkOkAddress__7JKRAramFPUcUlP12JKRAramBlockUl */
bool JKRAram::checkOkAddress(u8* addr, u32 size, JKRAramBlock* block, u32 param_4) {
    if (!IS_ALIGNED((u32)addr, 0x20) && !IS_ALIGNED(size, 0x20)) {
        OSPanic(__FILE__, 226, ":::address not 32Byte aligned.");
        return false;
    }

    if (block && !IS_ALIGNED((u32)block->getAddress() + param_4, 0x20)) {
        OSPanic(__FILE__, 235, ":::address not 32Byte aligned.");
        return false;
    }
    return true;
}

/* 802B4664-802B46C0       .text changeGroupIdIfNeed__7JKRAramFPUci */
void JKRAram::changeGroupIdIfNeed(u8* data, int groupId) {
#if VERSION > VERSION_DEMO
    if (groupId < 0) {
        return;
    }
#endif
    JKRHeap* currentHeap = JKRHeap::getCurrentHeap();
    if (currentHeap->getHeapType() == 'EXPH') {
        JKRExpHeap::CMemBlock* block = JKRExpHeap::CMemBlock::getBlock(data);
#if VERSION == VERSION_DEMO
        if (groupId < 0) {
            return;
        }
#endif
        block->newGroupId(groupId);
    }
}

/* 802B46C0-802B490C       .text mainRamToAram__7JKRAramFPUcUlUl15JKRExpandSwitchUlP7JKRHeapi */
JKRAramBlock* JKRAram::mainRamToAram(u8 *buf, u32 bufSize, u32 alignedSize, JKRExpandSwitch expandSwitch, u32 fileSize, JKRHeap* heap, int id) {
    JKRAramBlock *block = NULL;
    checkOkAddress(buf, bufSize, NULL, 0);
    if (expandSwitch == EXPAND_SWITCH_UNKNOWN1) {
        expandSwitch = (JKRCheckCompressed(buf) == COMPRESSION_NONE) ? EXPAND_SWITCH_UNKNOWN0 : EXPAND_SWITCH_UNKNOWN1;
    }
    if (expandSwitch == EXPAND_SWITCH_UNKNOWN1) {
        u32 expandSize = JKRCheckCompressed(buf) != COMPRESSION_NONE ? JKRDecompExpandSize(buf) : 0;
        if (fileSize == 0 || fileSize > expandSize) {
            fileSize = expandSize;
        }
        if (bufSize == 0) {
            JKRAramBlock* allocatedBlock = (JKRAramBlock*)JKRAllocFromAram(fileSize, JKRAramHeap::HEAD);
            block = (JKRAramBlock*)allocatedBlock;
            if (allocatedBlock == NULL)
                return NULL;

            allocatedBlock->newGroupID(decideAramGroupId(id));
            bufSize = allocatedBlock->getAddress();
        }
        if (alignedSize == 0 || alignedSize > expandSize)
            alignedSize = expandSize;

        if (fileSize > alignedSize)
            fileSize = alignedSize;

        void *allocatedMem = JKRAllocFromHeap(heap, fileSize, -32);
        if (allocatedMem == NULL) {
            if (block != NULL) {
                JKRFreeToAram(block);
            }
            block = NULL;
        }
        else {
            JKRDecompress(buf, (u8 *)allocatedMem, fileSize, 0);
            JKRAramPcs(0, (u32)allocatedMem, bufSize, alignedSize, block);
            JKRFreeToHeap(heap, allocatedMem);
            block = block == NULL ? (JKRAramBlock *)-1 : block;
        }
    }
    else {
        if (bufSize == 0) {

            JKRAramBlock* allocatedBlock = (JKRAramBlock*)JKRAllocFromAram(alignedSize, JKRAramHeap::HEAD);
            block = allocatedBlock;
            block->newGroupID(decideAramGroupId(id));
            if (block == NULL)
                return NULL;

            bufSize = allocatedBlock->getAddress();
        }

        JKRAramPcs(0, (u32)buf, bufSize, alignedSize, block);
        block = block == NULL ? (JKRAramBlock *)-1 : block;
    }
    return block;
}

/* 802B490C-802B49DC       .text mainRamToAram__7JKRAramFPUcP12JKRAramBlockUl15JKRExpandSwitchUlP7JKRHeapi */
JKRAramBlock* JKRAram::mainRamToAram(u8 *buf, JKRAramBlock* block, u32 alignedSize, JKRExpandSwitch expandSwitch, u32 fileSize, JKRHeap* heap, int id) {
    checkOkAddress(buf, 0, block, 0);
    if (!block) {
        return mainRamToAram(buf, u32(0), alignedSize, expandSwitch, fileSize, heap, id);
    }
    u32 blockSize = block->mSize;
    if (expandSwitch == 1) {
        fileSize = fileSize >= blockSize ? blockSize : fileSize;
    }
    return mainRamToAram(buf, block->mAddress, alignedSize > blockSize ? blockSize : alignedSize, expandSwitch, fileSize, heap, id);
}

/* 802B49DC-802B4C54       .text aramToMainRam__7JKRAramFUlPUcUl15JKRExpandSwitchUlP7JKRHeapiPUl */
u8* JKRAram::aramToMainRam(u32 address, u8 *buf, u32 p3, JKRExpandSwitch expandSwitch, u32 p5, JKRHeap* heap, int id, u32 *pSize) {
    JKRCompression compression = COMPRESSION_NONE;
    if (pSize != NULL)
        *pSize = 0;

    checkOkAddress(buf, address, NULL, 0);

    u32 expandSize;
    if (expandSwitch == EXPAND_SWITCH_UNKNOWN1) {
        u8 buffer[64];
        u8 *bufPtr = (u8 *)ALIGN_NEXT((u32)buffer, 32);
        JKRAramPcs(1, address, (u32)bufPtr, sizeof(buffer) / 2, NULL); // probably change sizeof(buffer) / 2 to 32
        compression = JKRCheckCompressed(bufPtr);
        expandSize = JKRDecompExpandSize(bufPtr);
    }

    if (compression == COMPRESSION_YAZ0) { // SZS
        if (p5 != 0 && p5 < expandSize)
            expandSize = p5;

        if (buf == NULL)
            buf = (u8 *)JKRAllocFromHeap(heap, expandSize, 32);
        if (buf == NULL)
            return NULL;
        else {
            changeGroupIdIfNeed(buf, id);
            JKRDecompressFromAramToMainRam(address, buf, p3, expandSize, 0);
            if (pSize != NULL) {
                *pSize = expandSize;
            }
            return buf;
        }
    }
    else if (compression == COMPRESSION_YAY0) { // SZP
        u8 *szpSpace = (u8 *)JKRAllocFromHeap(heap, p3, -32);
        if (szpSpace == NULL) {
            return NULL;
        }
        else {
            JKRAramPcs(1, address, (u32)szpSpace, p3, NULL);
            if (p5 != 0 && p5 < expandSize)
                expandSize = p5;

            u8* rv;
            if (buf == NULL) {
                rv = (u8 *)JKRAllocFromHeap(heap, expandSize, 32);
            } else {
                rv = buf;
            }

            if (rv == NULL) {
                JKRFree(szpSpace);
                return NULL;
            }
            else {
                changeGroupIdIfNeed(rv, id);
                JKRDecompress(szpSpace, rv, expandSize, 0);
                JKRFreeToHeap(heap, szpSpace);
                if (pSize != NULL) {
                    *pSize = expandSize;
                }
                return rv;
            }
        }
    }
    else { // Not compressed or ASR
        if (buf == NULL)
            buf = (u8 *)JKRAllocFromHeap(heap, p3, 32);
        if (buf == NULL) {
            return NULL;
        }
        else {
            changeGroupIdIfNeed(buf, id);
            JKRAramPcs(1, address, (u32)buf, p3, NULL);
            if (pSize != NULL) {
                *pSize = p3;
            }
            return buf;
        }
    }
}

/* 802B4C54-802B4D4C       .text aramToMainRam__7JKRAramFP12JKRAramBlockPUcUlUl15JKRExpandSwitchUlP7JKRHeapiPUl */
u8* JKRAram::aramToMainRam(JKRAramBlock* block, u8 *buf, u32 p3, u32 p4, JKRExpandSwitch expandSwitch, u32 p6, JKRHeap* heap, int id, u32 *pSize) {
    if (pSize) {
        *pSize = 0;
    }
    checkOkAddress(buf, 0, block, p4);
    if (!block) {
        OSPanic(__FILE__, VERSION_SELECT(668, 690, 683, 683), ":::Bad Aram Block specified.\n");
    }
    if (p4 >= block->mSize) {
        return NULL;
    }
    p3 = p3 == 0 ? block->mSize : p3;
    if (p4 + p3 > block->mSize) {
        p3 = block->mSize - p4;
    }
    return aramToMainRam(p4 + block->mAddress, buf, p3, expandSwitch, p6, heap, id, pSize);
}

static void dummy() {
    OSReport("---------------- BAD SYNC. you'd set callback, but now call sync.\n");
}
JSUList<JKRAMCommand> JKRAram::sAramCommandList;
u32 JKRAram::sSzpBufferSize = 0x00000400;
static u8* szpBuf;
static u8* szpEnd;
static u8* refBuf;
static u8* refEnd;
static u8* refCurrent;
static u32 srcOffset;
static u32 transLeft;
static u8* srcLimit;
static u32 srcAddress;
static u32 fileOffset;
static u32 readCount;
static u32 maxDest;

#if VERSION > VERSION_DEMO
static OSMutex decompMutex;
static bool isInitMutex;
#endif

/* 802B4D4C-802B4F20       .text JKRDecompressFromAramToMainRam__FUlPvUlUlUl */
static int JKRDecompressFromAramToMainRam(u32 src, void* dst, u32 srcLength, u32 dstLength, u32 offset) {
#if VERSION > VERSION_DEMO
    BOOL interrupts = OSDisableInterrupts();
    if (isInitMutex == false) {
        OSInitMutex(&decompMutex);
        isInitMutex = true;
    }
    OSRestoreInterrupts(interrupts);
    OSLockMutex(&decompMutex);
#endif

    u32 szsBufferSize = JKRAram::getSzpBufferSize();
    szpBuf = (u8 *)JKRAllocFromSysHeap(szsBufferSize, 32);
    JUT_ASSERT(VERSION_SELECT(1048, 1091, 1077, 1077), szpBuf != NULL);

    szpEnd = szpBuf + szsBufferSize;
    if (offset != 0) {
        refBuf = (u8 *)JKRAllocFromSysHeap(0x1120, 0);
        JUT_ASSERT(VERSION_SELECT(1057, 1100, 1086, 1086), refBuf != NULL);
        refEnd = refBuf + 0x1120;
        refCurrent = refBuf;
    }
    else {
        refBuf = NULL;
    }
    srcAddress = src;
    srcOffset = 0;
    transLeft = (srcLength != 0) ? srcLength : -1;
    fileOffset = offset;
    readCount = 0;
    maxDest = dstLength;

    u8* data = firstSrcData();
    u32 decompressedSize = ((u32*)data)[1];
    decompSZS_subroutine(data, (u8 *)dst);
    JKRFree(szpBuf);
    if (refBuf) {
        JKRFree(refBuf);
    }

    DCStoreRangeNoSync(dst, decompressedSize);

#if VERSION > VERSION_DEMO
    OSUnlockMutex(&decompMutex);
#endif

    return 0;
}

/* 802B4F20-802B51A4       .text decompSZS_subroutine__FPUcPUc */
static int decompSZS_subroutine(u8 *src, u8 *dest) {
    u8 *endPtr;
    s32 validBitCount = 0;
    s32 currCodeByte = 0;
    u32 ts = 0;

    if (src[0] != 'Y' || src[1] != 'a' || src[2] != 'z' || src[3] != '0') {
        return -1;
    }

    SYaz0Header *header = (SYaz0Header *)src;
    endPtr = dest + (header->length - fileOffset);
    if (endPtr > dest + maxDest) {
        endPtr = dest + maxDest;
    }

    src += 0x10;
    do {
        if (validBitCount == 0) {
            if ((src > srcLimit) && transLeft) {
                src = nextSrcData(src);
            }
            currCodeByte = *src;
            validBitCount = 8;
            src++;
        }
        if (currCodeByte & 0x80) {
            if (fileOffset != 0) {
                if (readCount >= fileOffset) {
                    *dest = *src;
                    dest++;
                    ts++;
                    if (dest == endPtr)
                    {
                        break;
                    }
                }
                *(refCurrent++) = *src;
                if (refCurrent == refEnd) {
                    refCurrent = refBuf;
                }
                src++;
            }
            else {
                *dest = *src;
                dest++;
                src++;
                ts++;
                if (dest == endPtr) {
                    break;
                }
            }
            readCount++;
        }
        else {
            u32 dist = ((src[0] & 0x0f) << 8) | src[1];
            s32 numBytes = src[0] >> 4;
            src += 2;
            u8 *copySource;
            if (fileOffset != 0) {
                copySource = refCurrent - dist - 1;
                if (copySource < refBuf) {
                    copySource += refEnd - refBuf;
                }
            }
            else {
                copySource = dest - dist - 1;
            }
            if (numBytes == 0) {
                numBytes = *src + 0x12;
                src += 1;
            }
            else {
                numBytes += 2;
            }
            if (fileOffset != 0) {
                do {
                    if (readCount >= fileOffset) {
                        *dest = *copySource;
                        dest++;
                        ts++;
                        if (dest == endPtr) {
                            break;
                        }
                    }
                    *(refCurrent++) = *copySource;
                    if (refCurrent == refEnd) {
                        refCurrent = refBuf;
                    }
                    copySource++;
                    if (copySource == refEnd) {
                        copySource = refBuf;
                    }
                    readCount++;
                    numBytes--;
                } while (numBytes != 0);
            }
            else {
                do {
                    *dest = *copySource;
                    dest++;
                    ts++;
                    if (dest == endPtr) {
                        break;
                    }
                    readCount++;
                    numBytes--;
                    copySource++;
                } while (numBytes != 0);
            }
        }
        currCodeByte <<= 1;
        validBitCount--;
    } while (dest < endPtr);
    return 0;
}

/* 802B51A4-802B5248       .text firstSrcData__Fv */
static u8* firstSrcData() {
    srcLimit = szpEnd - 0x19;
    u8* buffer = szpBuf;

    u32 length;
    u32 size = szpEnd - szpBuf;
    if (transLeft < size) {
        length = transLeft;
    } else {
        length = size;
    }

    u32 src = (u32)(srcAddress + srcOffset);
    u32 dst = (u32)buffer;
    u32 alignedLength = ALIGN_NEXT(length, 0x20);
    JKRAramPcs(1, src, dst, alignedLength, NULL);

    srcOffset += length;
    transLeft -= length;
#if VERSION > VERSION_DEMO
    if (!transLeft) {
        srcLimit = buffer + length;
    }
#endif

    return buffer;
}

/* 802B5248-802B5350       .text nextSrcData__FPUc */
static u8* nextSrcData(u8* current) {
    u8 *dest;
    u32 left = (u32)(szpEnd - current);
    if (IS_NOT_ALIGNED(left, 0x20)) {
        dest = szpBuf + 0x20 - (left & (0x20 - 1));
    } else {
        dest = szpBuf;
    }

    memcpy(dest, current, left);
    u32 transSize = (u32)(szpEnd - (dest + left));
    if (transSize > transLeft) {
        transSize = transLeft;
    }
    JUT_ASSERT(VERSION_SELECT(1321, 1376, 1361, 1361), transSize > 0);

    JKRAramPcs(1, (u32)(srcAddress + srcOffset), ((u32)dest + left), ALIGN_NEXT(transSize, 0x20),
               NULL);
    srcOffset += transSize;
    transLeft -= transSize;

    if (transLeft == 0)
        srcLimit = (dest + left) + transSize;

    return dest;
}
