#include "JSystem/JKernel/JKRAram.h"
#include "JSystem/JKernel/JKRDecomp.h"
#include "JSystem/JKernel/JKRHeap.h"
#include "JSystem/JKernel/JKRMacro.h"
#include "JSystem/JSystem.h"
#include "dolphin/ar.h"
#include "dolphin/os.h" /* TODO: OSReport lives in libforest in AC */
#include "dolphin/os/OSCache.h"
#include "dolphin/os/OSMessage.h"
#include "types.h"

JSUList<JKRAMCommand> JKRAramPiece::sAramPieceCommandList;
OSMutex JKRAramPiece::mMutex;

JKRAMCommand* JKRAramPiece::prepareCommand(int direction, u32 source, u32 destination, u32 length,
                                           JKRAramBlock* aramBlock, JKRAMCommand::AMCommandCallback callback) {
    JKRAMCommand* cmd = new (JKRGetSystemHeap(), -4) JKRAMCommand();
    cmd->mDirection = direction;
    cmd->mSource = source;
    cmd->mDestination = destination;
    cmd->mAramBlock = aramBlock;
    cmd->mLength = length;
    cmd->mCallback = callback;

    return cmd;
}

void JKRAramPiece::sendCommand(JKRAMCommand* cmd) {
    JKRAramPiece::startDMA(cmd);
}

JKRAMCommand* JKRAramPiece::orderAsync(int direction, u32 source, u32 destination, u32 length, JKRAramBlock* aramBlock,
                                       JKRAMCommand::AMCommandCallback callback) {
    JKRAramPiece::lock();

    if (!JKR_ISALIGNED32(source) || !JKR_ISALIGNED32(destination)) {
        JLOGF("direction = %x\n", direction);
        JLOGF("source = %x\n", source);
        JLOGF("destination = %x\n", destination);
        JLOGF("length = %x\n", length);
        JPANICLINE(102);
    }

    JKRAramCommand* aramCmd = new (JKRGetSystemHeap(), -4) JKRAramCommand();
    JKRAMCommand* cmd = JKRAramPiece::prepareCommand(direction, source, destination, length, aramBlock, callback);
    aramCmd->setting(TRUE, cmd);
    OSSendMessage((OSMessageQueue*)&JKRAram::sMessageQueue, (OSMessage)aramCmd, OS_MESSAGE_BLOCK);
    if (cmd->mCallback != nullptr) {
        JKRAramPiece::sAramPieceCommandList.append(&cmd->mAramPieceCommandLink);
    }

    JKRAramPiece::unlock();
    return cmd;
}

bool JKRAramPiece::sync(JKRAMCommand* cmd, BOOL noBlock) {
    OSMessage msg[1];

    JKRAramPiece::lock();

    if (!noBlock) {
        OSReceiveMessage(&cmd->mMesgQueue, msg, OS_MESSAGE_BLOCK);
        JKRAramPiece::sAramPieceCommandList.remove(&cmd->mAramPieceCommandLink);
        JKRAramPiece::unlock();
        return true;
    } else {
        if (!OSReceiveMessage(&cmd->mMesgQueue, msg, OS_MESSAGE_NOBLOCK)) {
            JKRAramPiece::unlock();
            return false;
        } else {
            JKRAramPiece::sAramPieceCommandList.remove(&cmd->mAramPieceCommandLink);
            JKRAramPiece::unlock();
            return true;
        }
    }
}

bool JKRAramPiece::orderSync(int direction, u32 source, u32 destination, u32 length, JKRAramBlock* aramBlock) {
    JKRAramPiece::lock();

    JKRAMCommand* cmd = JKRAramPiece::orderAsync(direction, source, destination, length, aramBlock, nullptr);
    bool res = JKRAramPiece::sync(cmd, FALSE);
    delete cmd;

    JKRAramPiece::unlock();
    return res;
}

void JKRAramPiece::startDMA(JKRAMCommand* cmd) {
    if (cmd->mDirection == ARAM_DIR_ARAM_TO_MRAM) {
        DCInvalidateRange((u8*)cmd->mDestination, cmd->mLength);
    } else { /* cmd->mDirection == ARAM_DIR_MRAM_TO_ARAM */
        DCStoreRange((u8*)cmd->mSource, cmd->mLength);
    }

    ARQPostRequest(cmd, 0, cmd->mDirection, 0, cmd->mSource, cmd->mDestination, cmd->mLength, JKRAramPiece::doneDMA);
}

void JKRAramPiece::doneDMA(u32 param) {
    JKRAMCommand* cmd = (JKRAMCommand*)param;
    if (cmd->mDirection == ARAM_DIR_ARAM_TO_MRAM) {
        DCInvalidateRange((u8*)cmd->mDestination, cmd->mLength);
    }
    if (cmd->mCallbackType != ARAMPIECE_DONE_CALLBACK) {
        if (cmd->mCallbackType == ARAMPIECE_DONE_DECOMPRESS) {
            JKRDecomp::sendCommand(cmd->mDecompCommand);
        }
    } else {
        if (cmd->mCallback != nullptr) {
            (*cmd->mCallback)(param);
        } else {
            if (cmd->mCompletedMesgQueue != nullptr) {
                OSSendMessage(cmd->mCompletedMesgQueue, (OSMessage)cmd, OS_MESSAGE_NOBLOCK);
            } else {
                OSSendMessage(&cmd->mMesgQueue, (OSMessage)cmd, OS_MESSAGE_NOBLOCK);
            }
        }
    }
}

JKRAMCommand::JKRAMCommand() : mAramPieceCommandLink(this), mLink30(this) {
    OSInitMessageQueue(&this->mMesgQueue, this->mMesgBuffer, 1);
    this->mCallback = nullptr;
    this->mCompletedMesgQueue = nullptr;
    this->mCallbackType = ARAMPIECE_DONE_CALLBACK;
    this->_8C = nullptr;
    this->_90 = nullptr;
    this->_94 = nullptr;
}

JKRAMCommand::~JKRAMCommand() {
    if (this->_8C != nullptr) {
        delete this->_8C;
    }

    if (this->_90 != nullptr) {
        delete this->_90;
    }

    if (this->_94 != nullptr) {
        JKRFree(this->_94);
    }
}
