#ifndef __FENNIX_KERNEL_INTERNAL_MEMORY_H__
#define __FENNIX_KERNEL_INTERNAL_MEMORY_H__

#ifdef __cplusplus
#include <boot/binfo.h>
#include <bitmap.hpp>
#include <lock.hpp>
#endif // __cplusplus
#include <types.h>

#ifdef __cplusplus

extern uint64_t _kernel_start, _kernel_end;
extern uint64_t _kernel_text_end, _kernel_data_end, _kernel_rodata_end;

// kilobyte
#define TO_KB(d) (d / 1024)
// megabyte
#define TO_MB(d) (d / 1024 / 1024)
// gigabyte
#define TO_GB(d) (d / 1024 / 1024 / 1024)
// terabyte
#define TO_TB(d) (d / 1024 / 1024 / 1024 / 1024)
// petabyte
#define TO_PB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024)
// exobyte
#define TO_EB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024 / 1024)
// zettabyte
#define TO_ZB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024)
// yottabyte
#define TO_YB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024)
// brontobyte
#define TO_BB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024)
// geopbyte
#define TO_GPB(d) (d / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024 / 1024)

#define PAGE_SIZE 0x1000       // 4KB
#define STACK_SIZE 0x4000      // 16kb
#define USER_STACK_SIZE 0x2000 // 8kb

// to pages
#define TO_PAGES(d) (d / PAGE_SIZE + 1)
// from pages
#define FROM_PAGES(d) (d * PAGE_SIZE - 1)

#define NORMAL_VMA_OFFSET 0xFFFF800000000000
#define KERNEL_VMA_OFFSET 0xFFFFFFFF80000000

/**
 * @brief KERNEL_HEAP_BASE is the base address of the kernel heap
 */
#define KERNEL_HEAP_BASE 0xFFFFA00000000000
/**
 * @brief USER_HEAP_BASE is the base address of the user heap allocated by the kernel
 */
#define USER_HEAP_BASE 0xFFFFB00000000000

/**
 * @brief USER_STACK_BASE is the base address of the user stack
 */
#define USER_STACK_BASE 0xFFFFEFFFFFFF0000

namespace Memory
{
    enum MemoryAllocatorType
    {
        None,
        Pages,
        XallocV1,
        liballoc11
    };

    /**
     * @brief https://wiki.osdev.org/images/4/41/64-bit_page_tables1.png
     * @brief https://wiki.osdev.org/images/6/6b/64-bit_page_tables2.png
     */
    enum PTFlag
    {
        /** @brief Present */
        P = 1 << 0,

        /** @brief Read/Write */
        RW = 1 << 1,

        /** @brief User/Supervisor */
        US = 1 << 2,

        /** @brief Write-Through */
        PWT = 1 << 3,

        /** @brief Cache Disable */
        PCD = 1 << 4,

        /** @brief Accessed */
        A = 1 << 5,

        /** @brief Dirty */
        D = 1 << 6,

        /** @brief Page Size */
        PS = 1 << 7,

        /** @brief Global */
        G = 1 << 8,

        /** @brief Available 0 */
        AVL0 = 1 << 9,

        /** @brief Available 1 */
        AVL1 = 1 << 10,

        /** @brief Available 2 */
        AVL2 = 1 << 11,

        /** @brief Page Attribute Table */
        PAT = 1 << 12,

        /** @brief Available 3 */
        AVL3 = (uint64_t)1 << 52,

        /** @brief Available 4 */
        AVL4 = (uint64_t)1 << 53,

        /** @brief Available 5 */
        AVL5 = (uint64_t)1 << 54,

        /** @brief Available 6 */
        AVL6 = (uint64_t)1 << 55,

        /** @brief Available 7 */
        AVL7 = (uint64_t)1 << 56,

        /** @brief Available 8 */
        AVL8 = (uint64_t)1 << 57,

        /** @brief Available 9 */
        AVL9 = (uint64_t)1 << 58,

        /** @brief Protection Key 0 */
        PK0 = (uint64_t)1 << 59,

        /** @brief Protection Key 1 */
        PK1 = (uint64_t)1 << 60,

        /** @brief Protection Key 2 */
        PK2 = (uint64_t)1 << 61,

        /** @brief Protection Key 3 */
        PK3 = (uint64_t)1 << 62,

        /** @brief Execute Disable */
        XD = (uint64_t)1 << 63
    };

    /* 2.2 Paging in IA-32e Mode - https://composter.com.ua/documents/TLBs_Paging-Structure_Caches_and_Their_Invalidation.pdf */

    union __attribute__((packed)) PageTableEntry
    {
        struct
        {
            bool Present : 1;            // 0
            bool ReadWrite : 1;          // 1
            bool UserSupervisor : 1;     // 2
            bool WriteThrough : 1;       // 3
            bool CacheDisable : 1;       // 4
            bool Accessed : 1;           // 5
            bool Dirty : 1;              // 6
            bool PageAttributeTable : 1; // 7
            bool Global : 1;             // 8
            uint8_t Available0 : 3;      // 9-11
            uint64_t Address : 40;       // 12-51
            uint32_t Available1 : 7;     // 52-58
            uint8_t ProtectionKey : 4;   // 59-62
            bool ExecuteDisable : 1;     // 63
        };
        uint64_t raw;

        /** @brief Set Address */
        void SetAddress(uint64_t _Address)
        {
#if defined(__amd64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#elif defined(__i386__)
            _Address &= 0x000FFFFF;
            this->raw &= 0xFFC00003;
            this->raw |= (_Address << 12);
#elif defined(__aarch64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#endif
        }

        /** @brief Get Address */
        uint64_t GetAddress()
        {
#if defined(__amd64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#elif defined(__i386__)
            return (this->raw & 0x003FFFFF000) >> 12;
#elif defined(__aarch64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#endif
        }
    };

    struct __attribute__((packed)) PageTableEntryPtr
    {
        PageTableEntry Entries[511];
    };

    union __attribute__((packed)) PageDirectoryEntry
    {
        struct
        {
            bool Present : 1;         // 0
            bool ReadWrite : 1;       // 1
            bool UserSupervisor : 1;  // 2
            bool WriteThrough : 1;    // 3
            bool CacheDisable : 1;    // 4
            bool Accessed : 1;        // 5
            bool Available0 : 1;      // 6
            bool PageSize : 1;        // 7
            uint8_t Available1 : 4;   // 8-11
            uint64_t Address : 40;    // 12-51
            uint32_t Available2 : 11; // 52-62
            bool ExecuteDisable : 1;  // 63
        };
        uint64_t raw;

        /** @brief Set PageTableEntryPtr address */
        void SetAddress(uint64_t _Address)
        {
#if defined(__amd64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#elif defined(__i386__)
            _Address &= 0x000FFFFF;
            this->raw &= 0xFFC00003;
            this->raw |= (_Address << 12);
#elif defined(__aarch64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#endif
        }

        /** @brief Get PageTableEntryPtr address */
        uint64_t GetAddress()
        {
#if defined(__amd64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#elif defined(__i386__)
            return (this->raw & 0x003FFFFF000) >> 12;
#elif defined(__aarch64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#endif
        }
    };

    struct __attribute__((packed)) PageDirectoryEntryPtr
    {
        PageDirectoryEntry Entries[511];
    };

    union __attribute__((packed)) PageDirectoryPointerTableEntry
    {
        struct
        {
            bool Present : 1;         // 0
            bool ReadWrite : 1;       // 1
            bool UserSupervisor : 1;  // 2
            bool WriteThrough : 1;    // 3
            bool CacheDisable : 1;    // 4
            bool Accessed : 1;        // 5
            bool Available0 : 1;      // 6
            bool PageSize : 1;        // 7
            uint8_t Available1 : 4;   // 8-11
            uint64_t Address : 40;    // 12-51
            uint32_t Available2 : 11; // 52-62
            bool ExecuteDisable : 1;  // 63
        };
        uint64_t raw;

        /** @brief Set PageDirectoryEntryPtr address */
        void SetAddress(uint64_t _Address)
        {
#if defined(__amd64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#elif defined(__i386__)
            _Address &= 0x000FFFFF;
            this->raw &= 0xFFC00003;
            this->raw |= (_Address << 12);
#elif defined(__aarch64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#endif
        }

        /** @brief Get PageDirectoryEntryPtr address */
        uint64_t GetAddress()
        {
#if defined(__amd64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#elif defined(__i386__)
            return (this->raw & 0x003FFFFF000) >> 12;
#elif defined(__aarch64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#endif
        }
    };

    struct __attribute__((packed)) PageDirectoryPointerTableEntryPtr
    {
        PageDirectoryPointerTableEntry Entries[511];
    };

    union __attribute__((packed)) PageMapLevel4
    {
        struct
        {
            bool Present : 1;         // 0
            bool ReadWrite : 1;       // 1
            bool UserSupervisor : 1;  // 2
            bool WriteThrough : 1;    // 3
            bool CacheDisable : 1;    // 4
            bool Accessed : 1;        // 5
            bool Available0 : 1;      // 6
            bool Reserved0 : 1;       // 7
            uint8_t Available1 : 4;   // 8-11
            uint64_t Address : 40;    // 12-51
            uint32_t Available2 : 11; // 52-62
            bool ExecuteDisable : 1;  // 63
        };
        uint64_t raw;

        /** @brief Set PageDirectoryPointerTableEntryPtr address */
        void SetAddress(uint64_t _Address)
        {
#if defined(__amd64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#elif defined(__i386__)
            _Address &= 0x000FFFFF;
            this->raw &= 0xFFC00003;
            this->raw |= (_Address << 12);
#elif defined(__aarch64__)
            _Address &= 0x000000FFFFFFFFFF;
            this->raw &= 0xFFF0000000000FFF;
            this->raw |= (_Address << 12);
#endif
        }

        /** @brief Get PageDirectoryPointerTableEntryPtr address */
        uint64_t GetAddress()
        {
#if defined(__amd64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#elif defined(__i386__)
            return (this->raw & 0x003FFFFF000) >> 12;
#elif defined(__aarch64__)
            return (this->raw & 0x000FFFFFFFFFF000) >> 12;
#endif
        }
    };

    struct PageTable4
    {
        PageMapLevel4 Entries[511];
    } __attribute__((aligned(0x1000)));

    struct __attribute__((packed)) PageMapLevel5
    {
        /* FIXME: NOT IMPLEMENTED! */
    };

    struct PageTable5
    {
        PageMapLevel5 Entries[511];
    } __attribute__((aligned(0x1000)));

    class Physical
    {
    private:
        NewLock(MemoryLock);

        uint64_t TotalMemory = 0;
        uint64_t FreeMemory = 0;
        uint64_t ReservedMemory = 0;
        uint64_t UsedMemory = 0;
        uint64_t PageBitmapIndex = 0;
        Bitmap PageBitmap;

        void ReservePage(void *Address);
        void ReservePages(void *Address, uint64_t PageCount);
        void UnreservePage(void *Address);
        void UnreservePages(void *Address, uint64_t PageCount);

    public:
        Bitmap GetPageBitmap() { return PageBitmap; }
        /**
         * @brief Get Total Memory
         *
         * @return uint64_t
         */
        uint64_t GetTotalMemory();
        /**
         * @brief Get Free Memory
         *
         * @return uint64_t
         */
        uint64_t GetFreeMemory();
        /**
         * @brief Get Reserved Memory
         *
         * @return uint64_t
         */
        uint64_t GetReservedMemory();
        /**
         * @brief Get Used Memory
         *
         * @return uint64_t
         */
        uint64_t GetUsedMemory();

        /**
         * @brief Swap page
         *
         * @param Address Address of the page
         * @return true if swap was successful
         * @return false if swap was unsuccessful
         */
        bool SwapPage(void *Address);
        /**
         * @brief Swap pages
         *
         * @param Address Address of the pages
         * @param PageCount Number of pages
         * @return true if swap was successful
         * @return false if swap was unsuccessful
         */
        bool SwapPages(void *Address, uint64_t PageCount);
        /**
         * @brief Unswap page
         *
         * @param Address Address of the page
         * @return true if unswap was successful
         * @return false if unswap was unsuccessful
         */
        bool UnswapPage(void *Address);
        /**
         * @brief Unswap pages
         *
         * @param Address Address of the pages
         * @param PageCount Number of pages
         * @return true if unswap was successful
         * @return false if unswap was unsuccessful
         */
        bool UnswapPages(void *Address, uint64_t PageCount);

        /**
         * @brief Lock page
         *
         * @param Address Address of the page
         */
        void LockPage(void *Address);
        /**
         * @brief Lock pages
         *
         * @param Address Address of the pages
         * @param PageCount Number of pages
         */
        void LockPages(void *Address, uint64_t PageCount);

        /**
         * @brief Request page
         *
         * @return void* Allocated page address
         */
        void *RequestPage();
        /**
         * @brief Request pages
         *
         * @param PageCount Number of pages
         * @return void* Allocated pages address
         */
        void *RequestPages(uint64_t Count);
        /**
         * @brief Free page
         *
         * @param Address Address of the page
         */
        void FreePage(void *Address);
        /**
         * @brief Free pages
         *
         * @param Address Address of the pages
         * @param PageCount Number of pages
         */
        void FreePages(void *Address, uint64_t Count);
        /** @brief Do not use. */
        void Init(BootInfo *Info);
        /** @brief Do not use. */
        Physical();
        /** @brief Do not use. */
        ~Physical();
    };

    class Virtual
    {
    private:
        NewLock(MemoryLock);
        PageTable4 *Table = nullptr;

    public:
        class PageMapIndexer
        {
        public:
            uint64_t PMLIndex = 0;
            uint64_t PDPTEIndex = 0;
            uint64_t PDEIndex = 0;
            uint64_t PTEIndex = 0;
            PageMapIndexer(uint64_t VirtualAddress);
        };

        /**
         * @brief Check if page is present
         *
         * @param VirtualAddress Virtual address of the page
         * @param Flag Flag to check
         * @return true if page is present
         * @return false if page is not present
         */
        bool Check(void *VirtualAddress, PTFlag Flag = PTFlag::P);

        /**
         * @brief Map page.
         *
         * @param VirtualAddress Virtual address of the page.
         * @param PhysicalAddress Physical address of the page.
         * @param Flags Flags of the page. Check PTFlag enum.
         */
        void Map(void *VirtualAddress, void *PhysicalAddress, uint64_t Flags);

        /**
         * @brief Map multiple pages.
         *
         * @param VirtualAddress First virtual address of the page.
         * @param PhysicalAddress First physical address of the page.
         * @param PageCount Number of pages.
         * @param Flags Flags of the page. Check PTFlag enum.
         */
        void Map(void *VirtualAddress, void *PhysicalAddress, uint64_t PageCount, uint64_t Flags);

        /**
         * @brief Unmap page.
         *
         * @param VirtualAddress Virtual address of the page.
         */
        void Unmap(void *VirtualAddress);

        /**
         * @brief Unmap multiple pages.
         *
         * @param VirtualAddress First virtual address of the page.
         * @param PageCount Number of pages.
         */
        void Unmap(void *VirtualAddress, uint64_t PageCount);

        /**
         * @brief Remap page.
         *
         * @param VirtualAddress Virtual address of the page.
         * @param PhysicalAddress Physical address of the page.
         * @param Flags Flags of the page. Check PTFlag enum.
         */
        void Remap(void *VirtualAddress, void *PhysicalAddress, uint64_t Flags);

        /**
         * @brief Construct a new Virtual object
         *
         * @param Table Page table. If null, it will use the current page table.
         */
        Virtual(PageTable4 *Table = nullptr);

        /**
         * @brief Destroy the Virtual object
         *
         */
        ~Virtual();
    };

    class StackGuard
    {
    private:
        void *StackBottom = nullptr;
        void *StackTop = nullptr;
        void *SGB = nullptr;
        void *SGT = nullptr;
        uint64_t Size = 0;
        bool UserMode = false;
        PageTable4 *Table = nullptr;

    public:
        /** @brief For general info */
        void *GetStackBottom() { return StackBottom; }
        /** @brief For RSP */
        void *GetStackTop() { return StackTop; }
        /** @brief Called by exception handler */
        bool Expand(uint64_t FaultAddress);
        /**
         * @brief Construct a new Stack Guard object
         * @param User Stack for user mode?
         */
        StackGuard(bool User, PageTable4 *Table);
        /**
         * @brief Destroy the Stack Guard object
         */
        ~StackGuard();
    };
}

/**
 * @brief // stub namespace for std::align_val_t and new operator
 * @note // Found on https://gcc.gnu.org/legacy-ml/gcc-patches/2016-09/msg00628.html for "_ZnwmSt11align_val_t" compiler error
 */
namespace std
{
    typedef __SIZE_TYPE__ size_t;
    enum class align_val_t : std::size_t
    {
    };
}

void InitializeMemoryManagement(BootInfo *Info);

void *operator new(size_t Size);
void *operator new[](size_t Size);
void *operator new(size_t Size, std::align_val_t Alignment);
void operator delete(void *Pointer);
void operator delete[](void *Pointer);
void operator delete(void *Pointer, long unsigned int Size);
void operator delete[](void *Pointer, long unsigned int Size);

extern Memory::Physical KernelAllocator;
extern Memory::PageTable4 *KernelPageTable;
extern Memory::PageTable4 *UserspaceKernelOnlyPageTable;

#endif // __cplusplus

extern void *KPT;

EXTERNC void *HeapMalloc(uint64_t Size);
EXTERNC void *HeapCalloc(uint64_t n, uint64_t Size);
EXTERNC void *HeapRealloc(void *Address, uint64_t Size);
EXTERNC void HeapFree(void *Address);

#define kmalloc(Size) HeapMalloc(Size)
#define kcalloc(n, Size) HeapCalloc(n, Size)
#define krealloc(Address, Size) HeapRealloc(Address, Size)
#define kfree(Address) HeapFree(Address)

#endif // !__FENNIX_KERNEL_INTERNAL_MEMORY_H__