#include "egg/core/eggExpHeap.h"

namespace EGG {

ExpHeap::ExpHeap(MEMiHeapHead *heapHandle) : Heap(heapHandle) {}

ExpHeap::~ExpHeap() {
    dispose();
    MEMDestroyExpHeap(mHeapHandle);
}

ExpHeap *ExpHeap::create(void *heapStart, size_t size, u16 attr) {
    ExpHeap *heap = nullptr;
    void *startAddr = heapStart;
    void *heapEnd = ROUND_DOWN_PTR((u8 *)heapStart + size, 0x04);
    heapStart = ROUND_UP_PTR(heapStart, 0x04);

    if (heapStart > heapEnd || ((u8 *)heapEnd - (u8 *)heapStart) < 0x38u) {
        return nullptr;
    }

    MEMiHeapHead *head = MEMCreateExpHeapEx(((ExpHeap *)heapStart) + 1, ((u8 *)heapEnd - (u8 *)heapStart) - 0x34, attr);
    if (head != nullptr) {
        heap = new (heapStart) ExpHeap(head);
        heap->mParentBlock = startAddr;
        if (sCreateCallback != nullptr) {
            (sCreateCallback)(heap);
        }
    }

    return heap;
}

ExpHeap *ExpHeap::create(size_t size, Heap *heap, u16 attr) {
    ExpHeap *newHeap = nullptr;
    if (heap == nullptr) {
        heap = sCurrentHeap;
    }

    if (size == -1) {
        size = heap->getAllocatableSize(0x04);
    }

    void *block = heap->alloc(size, 0x04);
    if (block != nullptr) {
        newHeap = ExpHeap::create(block, size, attr);
        if (newHeap == nullptr) {
            heap->free(block);
        }
    }

    return newHeap;
}

void ExpHeap::destroy() {
    if (sDestroyCallback != nullptr) {
        sDestroyCallback(this);
    }

    Heap *parentHeap = findParentHeap();
    this->~ExpHeap();
    if (parentHeap != nullptr) {
        parentHeap->free(this);
    }
}

void *ExpHeap::alloc(u32 size, s32 align) {
    if (mFlag.onBit(0)) {
#line 182
        OSError("DAME DAME\n");
    }

    void *ptr = MEMAllocFromExpHeapEx(mHeapHandle, size, align);
    if (sAllocCallback != nullptr) {
        HeapAllocArg arg;
        arg.userArg = sAllocCallbackArg;
        arg.ptr = ptr;
        arg.size = size;
        arg.align = align;
        arg.heap = this;
        (sAllocCallback)(&arg);
    }

    return ptr;
}

void ExpHeap::free(void *ptr) {
    if (sFreeCallback != nullptr) {
        HeapFreeArg arg;
        arg.userArg = sFreeCallbackArg;
        arg.ptr = ptr;
        arg.heap = this;
        (sFreeCallback)(&arg);
    }
    MEMFreeToExpHeap(mHeapHandle, ptr);
}

u32 ExpHeap::resizeForMBlock(void *block, u32 size) {
    return MEMResizeForMBlockExpHeap(mHeapHandle, block, size);
}

u32 ExpHeap::getTotalFreeSize() {
    return MEMGetTotalFreeSizeForExpHeap(mHeapHandle);
}

u32 ExpHeap::getAllocatableSize(s32 align) {
    return MEMGetAllocatableSizeForExpHeapEx(mHeapHandle, align);
}

void ExpHeap::setAllocMode(u16 allocMode) {
    MEMSetAllocModeForExpHeap(mHeapHandle, allocMode);
}

u32 ExpHeap::adjust() {
    u32 adjustedSize = MEMAdjustExpHeap(mHeapHandle);
    u32 totalSize = adjustedSize + sizeof(ExpHeap);
    if (totalSize > sizeof(ExpHeap)) {
        Heap *parent = findParentHeap();
        if (parent != nullptr) {
            parent->resizeForMBlock(mParentBlock, totalSize);
            return totalSize;
        }
    }
    return 0;
}

u32 ExpHeap::getSizeForMBlock(const void *block) {
    return MEMGetSizeForMBlockExpHeap(block);
}

Heap::eHeapKind ExpHeap::getHeapKind() const {
    return HEAP_KIND_EXPANDED;
}

} // namespace EGG
