#include "PageFrameAllocator.h"

uint64_t freeMemory;
uint64_t reservedMemory;
uint64_t usedMemory;
bool Initialized = false;

void PageFrameAllocator::ReadEFIMemoryMap(EFI_MEMORY_DESCRIPTOR* mMap, size_t mMapSize, size_t mMapDescSize){
    if (Initialized) return;

    Initialized = true;

    uint64_t mMapEntries = mMapSize / mMapDescSize;

    void* largestFreeMemSeg = NULL;
    size_t largestFreeMemSegSize = 0;

    for (int i = 0; i < mMapEntries; i++) {
        EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((uint64_t)mMap + (i * mMapDescSize));
        if (desc->type == 7) {
            if (desc -> numPages * 4096 > largestFreeMemSegSize) {
                largestFreeMemSeg = desc-> physAddr;
                largestFreeMemSegSize = desc -> numPages * 4096;
            }
        }
    }

    uint64_t memorySize = GetMemorySize(mMap, mMapEntries, mMapDescSize);
    freeMemory = memorySize;
    uint64_t bitmapSize = memorySize / 4096 / 8 + 1;

    InitBitmap(bitmapSize, largestFreeMemSeg);

    // lock pages of bitmap
	LockPages(&PageBitmap, PageBitmap.Size / 4096 + 1);

    //reserve pages of unsable/reserverd memory
	for (int i = 0; i < mMapEntries; i++) {
        EFI_MEMORY_DESCRIPTOR* desc = (EFI_MEMORY_DESCRIPTOR*)((uint64_t)mMap + (i * mMapDescSize));
        if (desc->type != 7) { // not efiConventionalMemory
			ReservePages(desc->physAddr, desc->numPages);
		}
	}
}

void PageFrameAllocator::InitBitmap(size_t bitmapSize, void* bufferAddress){
    PageBitmap.Size = bitmapSize;
    PageBitmap.Buffer = (uint8_t*)bufferAddress;
    for (int i = 0; i < bitmapSize; i++){
        *(uint8_t*)(PageBitmap.Buffer + i) = 0; 
    }
}

void PageFrameAllocator::FreePage(void* address) {
	uint64_t index = (uint64_t)address / 4096;
	if (PageBitmap[index] == false) return; // page is already free

	// free page in bitmap
	PageBitmap.Set(index, false);
	freeMemory += 4096;
	usedMemory -= 4096;
}

void PageFrameAllocator::FreePages(void* address, uint64_t pageCount) {
	for (int t = 0; t < pageCount; t++) {
		FreePage((void*)((uint64_t)address + (t * 4096)));
	}
}

void PageFrameAllocator::LockPage(void* address) {
	uint64_t index = (uint64_t)address / 4096;
	if (PageBitmap[index] == true) return; // page is already locked

	// lock page in bitmap
	PageBitmap.Set(index, true);
	freeMemory -= 4096;
	usedMemory += 4096;
}

void PageFrameAllocator::LockPages(void* address, uint64_t pageCount) {
	for (int t = 0; t < pageCount; t++) {
		LockPage((void*)((uint64_t)address + (t * 4096)));
	}
}

void PageFrameAllocator::UnreservePage(void* address) {
	uint64_t index = (uint64_t)address / 4096;
	if (PageBitmap[index] == false) return; // page is already free

	// free page in bitmap
	PageBitmap.Set(index, false);
	freeMemory += 4096;
	reservedMemory -= 4096;
}

void PageFrameAllocator::UnreservePages(void* address, uint64_t pageCount) {
	for (int t = 0; t < pageCount; t++) {
		UnreservePage((void*)((uint64_t)address + (t * 4096)));
	}
}

void PageFrameAllocator::ReservePage(void* address) {
	uint64_t index = (uint64_t)address / 4096;
	if (PageBitmap[index] == true) return; // page is already locked

	// lock page in bitmap
	PageBitmap.Set(index, true);
	freeMemory -= 4096;
	reservedMemory += 4096;
}

void PageFrameAllocator::ReservePages(void* address, uint64_t pageCount) {
	for (int t = 0; t < pageCount; t++) {
		ReservePage((void*)((uint64_t)address + (t * 4096)));
	}
}


uint64_t PageFrameAllocator::GetFreeRAM() {
	return freeMemory;
}

uint64_t PageFrameAllocator::GetUsedRAM() {
	return usedMemory;
}

uint64_t PageFrameAllocator::GetReservedRAM() {
	return reservedMemory;
}