/*
   This file is part of Fennix Kernel.

   Fennix Kernel is free software: you can redistribute it and/or
   modify it under the terms of the GNU General Public License as
   published by the Free Software Foundation, either version 3 of
   the License, or (at your option) any later version.

   Fennix Kernel is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with Fennix Kernel. If not, see <https://www.gnu.org/licenses/>.
*/

#include "../crashhandler.hpp"
#include "chfcts.hpp"

#include <display.hpp>
#include <bitmap.hpp>
#include <convert.h>
#include <printf.h>
#include <lock.hpp>
#include <rand.hpp>
#include <uart.hpp>
#include <debug.h>
#include <smp.hpp>
#include <cpu.hpp>
#include <io.h>

#if defined(a64)
#include "../../Architecture/amd64/cpu/gdt.hpp"
#include "../Architecture/amd64/cpu/apic.hpp"
#elif defined(a32)
#include "../../Architecture/i386/cpu/gdt.hpp"
#include "../Architecture/i386/cpu/apic.hpp"
#elif defined(aa64)
#endif

#include "../../kernel.h"
#include "../../DAPI.hpp"

NewLock(UserInputLock);

namespace CrashHandler
{
    uintptr_t PageFaultAddress = 0;
    void *EHIntFrames[INT_FRAMES_MAX];
    static bool ExceptionOccurred = false;
    int SBIdx = 255;

    SafeFunction void printfWrapper(char c, void *unused)
    {
        Display->Print(c, SBIdx, true);
        UNUSED(unused);
    }

    SafeFunction void EHPrint(const char *Format, ...)
    {
        va_list args;
        va_start(args, Format);
        vfctprintf(printfWrapper, NULL, Format, args);
        va_end(args);
    }

    SafeFunction void EHDumpData(void *Address, unsigned long Length)
    {
        EHPrint("-------------------------------------------------------------------------\n");
        Display->SetBuffer(SBIdx);
        unsigned char *AddressChar = (unsigned char *)Address;
        unsigned char Buffer[17];
        unsigned long Iterate;
        for (Iterate = 0; Iterate < Length; Iterate++)
        {
            if ((Iterate % 16) == 0)
            {
                if (Iterate != 0)
                    EHPrint("  \e8A78FF%s\eAABBCC\n", Buffer);
                EHPrint("  \e9E9E9E%04x\eAABBCC ", Iterate);
                Display->SetBuffer(SBIdx);
            }
            EHPrint(" \e4287f5%02x\eAABBCC", AddressChar[Iterate]);
            if ((AddressChar[Iterate] < 0x20) || (AddressChar[Iterate] > 0x7e))
                Buffer[Iterate % 16] = '.';
            else
                Buffer[Iterate % 16] = AddressChar[Iterate];
            Buffer[(Iterate % 16) + 1] = '\0';
        }

        while ((Iterate % 16) != 0)
        {
            EHPrint("   ");
            Display->SetBuffer(SBIdx);
            Iterate++;
        }

        EHPrint("  \e8A78FF%s\eAABBCC\n", Buffer);
        EHPrint("-------------------------------------------------------------------------\n\n.");
        Display->SetBuffer(SBIdx);
    }

    SafeFunction char *TrimWhiteSpace(char *str)
    {
        char *end;
        while (*str == ' ')
            str++;
        if (*str == 0)
            return str;
        end = str + strlen(str) - 1;
        while (end > str && *end == ' ')
            end--;
        *(end + 1) = 0;
        return str;
    }

    CRData crashdata = {};

    SafeFunction void DisplayTopOverlay()
    {
        Video::ScreenBuffer *sb = Display->GetBuffer(SBIdx);
        Video::Font *f = Display->GetCurrentFont();
        Video::FontInfo fi = f->GetInfo();

        for (uint32_t i = 0; i < sb->Width; i++)
            for (uint32_t j = 0; j < fi.Height + 8; j++)
                Display->SetPixel(i, j, 0x282828, SBIdx);

        Display->SetBufferCursor(SBIdx, 8, (fi.Height + 8) / 6);
        switch (SBIdx)
        {
        case 255:
        {
            EHPrint("\eAAAAAAMAIN \e606060DETAILS \e606060FRAMES \e606060TASKS \e606060CONSOLE");
            break;
        }
        case 254:
        {
            EHPrint("\e606060MAIN \eAAAAAADETAILS \e606060FRAMES \e606060TASKS \e606060CONSOLE");
            break;
        }
        case 253:
        {
            EHPrint("\e606060MAIN \e606060DETAILS \eAAAAAAFRAMES \e606060TASKS \e606060CONSOLE");
            break;
        }
        case 252:
        {
            EHPrint("\e606060MAIN \e606060DETAILS \e606060FRAMES \eAAAAAATASKS \e606060CONSOLE");
            break;
        }
        case 251:
        {
            EHPrint("\e606060MAIN \e606060DETAILS \e606060FRAMES \e606060TASKS \eAAAAAACONSOLE");
            break;
        }
        default:
        {
            EHPrint("\e606060MAIN \e606060DETAILS \e606060FRAMES \e606060TASKS \e606060CONSOLE");
            break;
        }
        }
        EHPrint("  \e00AAFF%ldMB / %ldMB (%ldMB Reserved)",
                TO_MB(KernelAllocator.GetUsedMemory()),
                TO_MB(KernelAllocator.GetTotalMemory()),
                TO_MB(KernelAllocator.GetReservedMemory()));
        EHPrint("  \eAA0F0F%s", CPU::Hypervisor());
        EHPrint(" \eAAF00F%s", CPU::Vendor());
        EHPrint(" \eAA00FF%s", CPU::Name());
        Display->SetBufferCursor(SBIdx, 0, fi.Height + 10);

        /* https://imgflip.com/i/77slbl */
        if ((Random::rand32() % 100) >= 98)
        {
            debug("Easter egg activated!");
            int BaseXOffset = sb->Width - 14;
            int BaseYOffset = 8;
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 0, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 0, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 0, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 7, BaseYOffset + 0, 0x21852E, SBIdx);

            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 7, BaseYOffset + 1, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 8, BaseYOffset + 1, 0x21852E, SBIdx);

            Display->SetPixel(BaseXOffset + 1, BaseYOffset + 2, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 2, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 2, 0xFFFFFF, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 2, 0x000000, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 2, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 2, 0xFFFFFF, SBIdx);
            Display->SetPixel(BaseXOffset + 7, BaseYOffset + 2, 0x000000, SBIdx);
            Display->SetPixel(BaseXOffset + 8, BaseYOffset + 2, 0x21852E, SBIdx);

            Display->SetPixel(BaseXOffset + 1, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 3, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 7, BaseYOffset + 3, 0x21852E, SBIdx);

            Display->SetPixel(BaseXOffset + 0, BaseYOffset + 4, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 1, BaseYOffset + 4, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 4, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 4, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 4, 0xA84832, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 4, 0xA84832, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 4, 0xA84832, SBIdx);
            Display->SetPixel(BaseXOffset + 7, BaseYOffset + 4, 0xA84832, SBIdx);

            Display->SetPixel(BaseXOffset + 0, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 1, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 5, 0x21852E, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 5, 0x21852E, SBIdx);

            Display->SetPixel(BaseXOffset + 0, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 1, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 2, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 3, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 4, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 5, BaseYOffset + 6, 0x1216FF, SBIdx);
            Display->SetPixel(BaseXOffset + 6, BaseYOffset + 6, 0x1216FF, SBIdx);

            Display->SetBuffer(SBIdx);
        }
    }

    SafeFunction void DisplayBottomOverlay()
    {
        Video::ScreenBuffer *sb = Display->GetBuffer(SBIdx);
        Video::Font *f = Display->GetCurrentFont();
        Video::FontInfo fi = f->GetInfo();

        for (uint32_t i = 0; i < sb->Width; i++)
            for (uint32_t j = sb->Height - fi.Height - 8; j < sb->Height; j++)
                Display->SetPixel(i, j, 0x282828, SBIdx);

        Display->SetBufferCursor(SBIdx, 8, sb->Height - fi.Height - 4);
        EHPrint("\eAAAAAA> \eFAFAFA");
    }

    SafeFunction void ArrowInput(uint8_t key)
    {
        switch (key)
        {
        case KEY_D_UP:
            if (SBIdx < 255)
                SBIdx++;
            else
                return;
            break;
        case KEY_D_LEFT:
            if (SBIdx < 255)
                SBIdx++;
            else
                return;
            break;
        case KEY_D_RIGHT:
            if (SBIdx > 251)
                SBIdx--;
            else
                return;
            break;
        case KEY_D_DOWN:
            if (SBIdx > 251)
                SBIdx--;
            else
                return;
            break;
        default:
            break;
        }
        Display->ClearBuffer(SBIdx);
        DisplayTopOverlay();
        EHPrint("\eFAFAFA");

        switch (SBIdx)
        {
        case 255:
        {
            DisplayMainScreen(crashdata);
            break;
        }
        case 254:
        {
            DisplayDetailsScreen(crashdata);
            break;
        }
        case 253:
        {
            DisplayStackFrameScreen(crashdata);
            break;
        }
        case 252:
        {
            DisplayTasksScreen(crashdata);
            break;
        }
        case 251:
        {
            DisplayConsoleScreen(crashdata);
            break;
        }
        default:
        {
            break;
        }
        }
        DisplayBottomOverlay();
        Display->SetBuffer(SBIdx);
    }

    SafeFunction void UserInput(char *Input)
    {
        SmartCriticalSection(UserInputLock);
        Display->ClearBuffer(SBIdx);
        DisplayTopOverlay();
        EHPrint("\eFAFAFA");

        if (strcmp(Input, "help") == 0)
        {
            EHPrint("Available commands are:\n");
            EHPrint("exit - Shutdown the OS.\n");
            EHPrint("reboot - Reboot the OS.\n");
            EHPrint("help - Display this help message.\n");
            EHPrint("showbuf,sb <INDEX> - Display the contents of a screen buffer.\n");
            EHPrint("       - A sleep timer will be enabled. This will cause the OS to sleep for an unknown amount of time.\n");
            EHPrint("       - \eFF4400WARNING: This can crash the system if a wrong buffer is selected.\eFAFAFA\n");
            EHPrint("ifr <COUNT> - Show interrupt frames.\n");
            EHPrint("tlb <ADDRESS> - Print the page table entries\n");
            EHPrint("bitmap - Print the memory bitmap\n");
            EHPrint("mem - Print the memory allocation\n");
            EHPrint("cr<INDEX> - Print the CPU control register\n");
            EHPrint("tss <CORE> - Print the CPU task state segment\n");
            EHPrint("dump <ADDRESS HEX> <LENGTH DEC> - Dump memory\n");
            EHPrint("       - \eFF4400WARNING: This can crash the system if you try to read from an unmapped page.\eFAFAFA\n");
            EHPrint("uartmemdmp <INDEX> <SKIP INACCESSIBLE (bool 0,1)> - Dump the memory of a UART.\n");
            EHPrint("main - Show the main screen.\n");
            EHPrint("details - Show the details screen.\n");
            EHPrint("frames - Show the stack frame screen.\n");
            EHPrint("tasks - Show the tasks screen.\n");
            EHPrint("console - Show the console screen.\n");
            EHPrint("Also, you can use the arrow keys to navigate between the screens.\n");
            EHPrint("=========================================================================\n");
            EHPrint("Kernel Compiled at: %s %s with C++ Standard: %d\n", __DATE__, __TIME__, CPP_LANGUAGE_STANDARD);
            EHPrint("C++ Language Version (__cplusplus): %ld\n", __cplusplus);
        }
        else if (strcmp(Input, "exit") == 0)
        {
            PowerManager->Shutdown();
            EHPrint("\eFFFFFFNow it's safe to turn off your computer.");
            Display->SetBuffer(SBIdx);
            CPU::Stop();
        }
        else if (strcmp(Input, "reboot") == 0)
        {
            PowerManager->Reboot();
            EHPrint("\eFFFFFFNow it's safe to reboot your computer.");
            Display->SetBuffer(SBIdx);
            CPU::Stop();
        }
        else if (strncmp(Input, "showbuf", 7) == 0 || strncmp(Input, "sb", 2) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 7);
            int tmpidx = SBIdx;
            SBIdx = atoi(arg);
            Display->SetBuffer(SBIdx);
#if defined(a86)
            for (int i = 0; i < 5000000; i++)
                inb(0x80);
#endif // a64 || a32
            SBIdx = tmpidx;
            Display->SetBuffer(SBIdx);
        }
        else if (strncmp(Input, "ifr", 3) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 3);
            int CountI = atoi(arg);
            int TotalCount = sizeof(EHIntFrames) / sizeof(EHIntFrames[0]);

            debug("Printing %ld interrupt frames.", CountI);

            if (CountI > TotalCount)
            {
                EHPrint("\eFF4400Count too big! Maximum allowed is %ld\eFAFAFA\n", TotalCount);
                Display->SetBuffer(SBIdx);
            }
            else
            {
                for (int i = 0; i < CountI; i++)
                {
                    if (EHIntFrames[i])
                    {
                        if (!Memory::Virtual().Check(EHIntFrames[i]))
                            continue;
                        EHPrint("\n\e2565CC%p", EHIntFrames[i]);
                        EHPrint("\e7925CC-");
#if defined(a64)
                        if ((uintptr_t)EHIntFrames[i] >= 0xFFFFFFFF80000000 && (uintptr_t)EHIntFrames[i] <= (uintptr_t)&_kernel_end)
#elif defined(a32)
                        if ((uintptr_t)EHIntFrames[i] >= 0xC0000000 && (uintptr_t)EHIntFrames[i] <= (uintptr_t)&_kernel_end)
#elif defined(aa64)
                        if ((uintptr_t)EHIntFrames[i] >= 0xFFFFFFFF80000000 && (uintptr_t)EHIntFrames[i] <= (uintptr_t)&_kernel_end)
#endif
                            EHPrint("\e25CCC9%s", KernelSymbolTable->GetSymbolFromAddress((uintptr_t)EHIntFrames[i]));
                        else
                            EHPrint("\eFF4CA9Outside Kernel");
#if defined(a86)
                        for (int i = 0; i < 20000; i++)
                            inb(0x80);
#endif // a64 || a32
                        Display->SetBuffer(SBIdx);
                    }
                }
            }
        }
        else if (strncmp(Input, "tlb", 3) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 3);
            uintptr_t Address = NULL;
            Address = strtol(arg, NULL, 16);
            debug("Converted %s to %#lx", arg, Address);
            Memory::PageTable *BasePageTable = (Memory::PageTable *)Address;
            if (Memory::Virtual().Check(BasePageTable))
            {
                for (int PMLIndex = 0; PMLIndex < 512; PMLIndex++)
                {
                    Memory::PageMapLevel4 PML4 = BasePageTable->Entries[PMLIndex];
                    EHPrint("\e888888# \eAABBCC%03d-%03d-%03d-%03d\e4500F5: P:%s RW:%s US:%s PWT:%s PCB:%s A:%s NX:%s Address:\e888888%#lx\n",
                            PMLIndex, 0, 0, 0,
                            PML4.Present ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.ReadWrite ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.UserSupervisor ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.WriteThrough ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.CacheDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.Accessed ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.ExecuteDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                            PML4.GetAddress() << 12);
                    Display->SetBuffer(SBIdx);
                    if (PML4.Present)
                    {
                        Memory::PageDirectoryPointerTableEntryPtr *PDPTE = (Memory::PageDirectoryPointerTableEntryPtr *)((uintptr_t)PML4.GetAddress() << 12);
                        if (PDPTE)
                        {
                            for (int PDPTEIndex = 0; PDPTEIndex < 512; PDPTEIndex++)
                            {
                                EHPrint("\e888888# \eAABBCC%03d-%03d-%03d-%03d\e4500F5: P:%s RW:%s US:%s PWT:%s PCB:%s A:%s NX:%s Address:\e888888%#lx\n",
                                        PMLIndex, PDPTEIndex, 0, 0,
                                        PDPTE->Entries[PDPTEIndex].Present ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].ReadWrite ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].UserSupervisor ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].WriteThrough ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].CacheDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].Accessed ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].ExecuteDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                        PDPTE->Entries[PDPTEIndex].GetAddress() << 12);
                                Display->SetBuffer(SBIdx);
                                if ((PDPTE->Entries[PDPTEIndex].Present))
                                {
                                    Memory::PageDirectoryEntryPtr *PDE = (Memory::PageDirectoryEntryPtr *)((uintptr_t)PDPTE->Entries[PDPTEIndex].GetAddress() << 12);
                                    if (PDE)
                                    {
                                        for (int PDEIndex = 0; PDEIndex < 512; PDEIndex++)
                                        {
                                            EHPrint("\e888888# \eAABBCC%03d-%03d-%03d-%03d\e4500F5: P:%s RW:%s US:%s PWT:%s PCB:%s A:%s NX:%s Address:\e888888%#lx\n",
                                                    PMLIndex, PDPTEIndex, PDEIndex, 0,
                                                    PDE->Entries[PDEIndex].Present ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].ReadWrite ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].UserSupervisor ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].WriteThrough ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].CacheDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].Accessed ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].ExecuteDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                    PDE->Entries[PDEIndex].GetAddress() << 12);
                                            Display->SetBuffer(SBIdx);
                                            if ((PDE->Entries[PDEIndex].Present))
                                            {
                                                Memory::PageTableEntryPtr *PTE = (Memory::PageTableEntryPtr *)((uintptr_t)PDE->Entries[PDEIndex].GetAddress() << 12);
                                                if (PTE)
                                                {
                                                    for (int PTEIndex = 0; PTEIndex < 512; PTEIndex++)
                                                    {
                                                        EHPrint("\e888888# \eAABBCC%03d-%03d-%03d-%03d\e4500F5: P:%s RW:%s US:%s PWT:%s PCB:%s A:%s D:%s PAT:%s G:%s PK:%d NX:%s Address:\e888888%#lx\n",
                                                                PMLIndex, PDPTEIndex, PDEIndex, PTEIndex,
                                                                PTE->Entries[PTEIndex].Present ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].ReadWrite ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].UserSupervisor ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].WriteThrough ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].CacheDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].Accessed ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].Dirty ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].PageAttributeTable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].Global ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].ProtectionKey,
                                                                PTE->Entries[PTEIndex].ExecuteDisable ? "\e00FF001\e4500F5" : "\eFF00000\e4500F5",
                                                                PTE->Entries[PTEIndex].GetAddress() << 12);
                                                        Display->SetBuffer(SBIdx);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        else if (strncmp(Input, "bitmap", 6) == 0)
        {
            Bitmap bm = KernelAllocator.GetPageBitmap();

            EHPrint("\n\eFAFAFA[0%%] %08ld: ", 0);
            for (size_t i = 0; i < bm.Size; i++)
            {
                if (bm.Get(i))
                    EHPrint("\eFF00001");
                else
                    EHPrint("\e00FF000");
                if (i % 128 == 127)
                {
                    short Percentage = s_cst(short, (i * 100) / bm.Size);
                    EHPrint("\n\eFAFAFA[%03ld%%] %08ld: ", Percentage, i);
                    Display->SetBuffer(SBIdx);
                }
            }
            EHPrint("\n\e22AA44--- END OF BITMAP ---\nBitmap size: %ld\n\n.", bm.Size);
            Display->SetBuffer(SBIdx);
        }
        else if (strcmp(Input, "mem") == 0)
        {
            uint64_t Total = KernelAllocator.GetTotalMemory();
            uint64_t Used = KernelAllocator.GetUsedMemory();
            uint64_t Free = KernelAllocator.GetFreeMemory();
            uint64_t Reserved = KernelAllocator.GetReservedMemory();

            EHPrint("\e22AA44Total: %ld bytes\n\eFF0000Used: %ld bytes\n\e00FF00Free: %ld bytes\n\eFF00FFReserved: %ld bytes\n", Total, Used, Free, Reserved);
            int Progress = s_cst(int, (Used * 100) / Total);
            int ReservedProgress = s_cst(int, (Reserved * 100) / Total);
            EHPrint("\e22AA44%3d%% \eCCCCCC[", Progress);
            for (int i = 0; i < Progress; i++)
                EHPrint("\eFF0000|");
            for (int i = 0; i < 100 - Progress; i++)
                EHPrint("\e00FF00|");
            for (int i = 0; i < ReservedProgress; i++)
                EHPrint("\eFF00FF|");
            EHPrint("\eCCCCCC]\n");

            Display->SetBuffer(SBIdx);
        }
        else if (strncmp(Input, "cr", 2) == 0)
        {
            char *cr = TrimWhiteSpace(Input + 2);
            switch (cr[0])
            {
            case '0':
            {
#if defined(a64)
                EHPrint("\e44AA000: %#lx\n", CPU::x64::readcr0());
#elif defined(a32)
                EHPrint("\e44AA000: %#lx\n", CPU::x32::readcr0());
#endif
                break;
            }
            case '2':
            {
#if defined(a64)
                EHPrint("\e44AA002: %#lx\n", PageFaultAddress);
#elif defined(a32)
                EHPrint("\e44AA002: %#lx\n", CPU::x32::readcr2());
#endif
                break;
            }
            case '3':
            {
#if defined(a64)
                EHPrint("\e44AA003: %#lx\n", CPU::x64::readcr3());
#elif defined(a32)
                EHPrint("\e44AA003: %#lx\n", CPU::x32::readcr3());
#endif
                break;
            }
            case '4':
            {
#if defined(a64)
                EHPrint("\e44AA004: %#lx\n", CPU::x64::readcr4());
#elif defined(a32)
                EHPrint("\e44AA004: %#lx\n", CPU::x32::readcr4());
#endif
                break;
            }
            case '8':
            {
#if defined(a64)
                EHPrint("\e44AA008: %#lx\n", CPU::x64::readcr8());
#elif defined(a32)
                EHPrint("\e44AA008: %#lx\n", CPU::x32::readcr8());
#endif
                break;
            }
            default:
                EHPrint("\eFF0000Invalid CR\n");
                break;
            }
        }
        else if (strncmp(Input, "tss", 3) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 3);
            int TSSIndex = atoi(arg);
            if (TSSIndex > SMP::CPUCores)
            {
                EHPrint("\eFF0000Invalid TSS index\n");
            }
            else
            {
#if defined(a86)
                GlobalDescriptorTable::TaskStateSegment tss = GlobalDescriptorTable::tss[TSSIndex];
                EHPrint("\eFAFAFAStack Pointer 0: \eAABB22%#lx\n", tss.StackPointer[0]);
                EHPrint("\eFAFAFAStack Pointer 1: \eAABB22%#lx\n", tss.StackPointer[1]);
                EHPrint("\eFAFAFAStack Pointer 2: \eAABB22%#lx\n", tss.StackPointer[2]);

                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[0]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[1]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[2]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[3]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[4]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[5]);
                EHPrint("\eFAFAFAInterrupt Stack Table: \eAABB22%#lx\n", tss.InterruptStackTable[6]);

                EHPrint("\eFAFAFAI/O Map Base Address Offset: \eAABB22%#lx\n", tss.IOMapBaseAddressOffset);

                EHPrint("\eFAFAFAReserved 0: \eAABB22%#lx\n", tss.Reserved0);
                EHPrint("\eFAFAFAReserved 1: \eAABB22%#lx\n", tss.Reserved1);
                EHPrint("\eFAFAFAReserved 2: \eAABB22%#lx\n", tss.Reserved2);
#elif defined(aa64)
                EHPrint("\eFF0000AArch64 does not have TSS\n");
#endif
            }
        }
        else if (strncmp(Input, "dump", 4) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 4);
            char *addr = strtok(arg, " ");
            char *len = strtok(NULL, " ");
            if (addr == NULL || len == NULL)
            {
                EHPrint("\eFF0000Invalid arguments\n");
            }
            else
            {
                uint64_t Address = strtoul(addr, NULL, 16);
                uint64_t Length = strtoul(len, NULL, 10);
                debug("Dumping %ld bytes from %#lx\n", Length, Address);
                EHDumpData((void *)Address, Length);
            }
        }
        else if (strncmp(Input, "uartmemdmp", 10) == 0)
        {
            char *arg = TrimWhiteSpace(Input + 10);
            char *cPort = strtok(arg, " ");
            char *cBoolSkip = strtok(NULL, " ");
            UniversalAsynchronousReceiverTransmitter::SerialPorts port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM1;
            switch (cPort[0])
            {
            case '1':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM1;
                break;
            case '2':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM2;
                break;
            case '3':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM3;
                break;
            case '4':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM4;
                break;
            case '5':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM5;
                break;
            case '6':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM6;
                break;
            case '7':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM7;
                break;
            case '8':
                port = UniversalAsynchronousReceiverTransmitter::SerialPorts::COM8;
                break;
            default:
                EHPrint("\eFF0000Invalid port! Defaulting to 1.\n");
                break;
            }
            EHPrint("\eF8F8F8Dumping memory to UART port %c (%#lx) and %s inaccessible pages.\n", cPort[0], port, cBoolSkip[0] == '1' ? "skipping" : "zeroing");
            Display->SetBuffer(SBIdx);
            uint64_t Length = KernelAllocator.GetTotalMemory();
            uint64_t ProgressLength = Length;
            UniversalAsynchronousReceiverTransmitter::UART uart(port);
            Memory::Virtual vma;
            uint8_t *Address = reinterpret_cast<uint8_t *>(0x0);
            int Progress = 0;
            for (size_t i = 0; i < Length; i++)
            {
                if (vma.Check(Address))
                    uart.Write(*Address);
                else if (cBoolSkip[0] == '0')
                    uart.Write((uint8_t)0);
                else
                    ProgressLength--;
                Address++;

                if (unlikely(i % 0x1000 == 0))
                {
                    int NewProgress = (int)((i * 100) / ProgressLength);
                    if (unlikely(NewProgress != Progress))
                    {
                        Progress = NewProgress;
                        EHPrint("\n%d%%\n", Progress);
                        Display->SetBuffer(SBIdx);
                    }
                    Display->Print('.', SBIdx);
                    if (unlikely(i % 0x500 == 0))
                        Display->SetBuffer(SBIdx);
                }
            }
            EHPrint("\nDone.\n");
        }
        else if (strcmp(Input, "main") == 0)
        {
            SBIdx = 255;
            DisplayTopOverlay();
            DisplayMainScreen(crashdata);
            Display->SetBuffer(SBIdx);
        }
        else if (strcmp(Input, "details") == 0)
        {
            SBIdx = 254;
            DisplayTopOverlay();
            DisplayDetailsScreen(crashdata);
            Display->SetBuffer(SBIdx);
        }
        else if (strcmp(Input, "frames") == 0)
        {
            SBIdx = 253;
            DisplayTopOverlay();
            DisplayStackFrameScreen(crashdata);
            Display->SetBuffer(SBIdx);
        }
        else if (strcmp(Input, "tasks") == 0)
        {
            SBIdx = 252;
            DisplayTopOverlay();
            DisplayTasksScreen(crashdata);
            Display->SetBuffer(SBIdx);
        }
        else if (strcmp(Input, "console") == 0)
        {
            SBIdx = 251;
            DisplayTopOverlay();
            DisplayConsoleScreen(crashdata);
            Display->SetBuffer(SBIdx);
        }
        else if (strlen(Input) > 0)
            EHPrint("Unknown command: %s", Input);

        DisplayBottomOverlay();
        Display->SetBuffer(SBIdx);
    }

    SafeFunction void StopAllCores()
    {
#if defined(a86)
        /* FIXME: Can't send IPIs to other cores
         * because it causes another exception on
         * the other cores.
         *
         * Also it makes every core to stay at 100% usage for some reason.
         */

        // if (SMP::CPUCores > 1)
        // {
        //     for (int i = 1; i < SMP::CPUCores; i++)
        //     {
        //         APIC::InterruptCommandRegisterLow icr;
        //         icr.Vector = CPU::x86::IRQ29;
        //         icr.Level = APIC::APICLevel::Assert;
        //         ((APIC::APIC *)Interrupts::apic[i])->IPI(i, icr);
        //         __sync;
        //     }
        // }
        // APIC::InterruptCommandRegisterLow icr;
        // icr.Vector = CPU::x86::IRQ29;
        // icr.Level = APIC::APICLevel::Assert;
        // icr.DestinationShorthand = APIC::APICDestinationShorthand::AllExcludingSelf;
        // ((APIC::APIC *)Interrupts::apic[0])->IPI(0, icr);
        // CPU::Interrupts(CPU::Enable);
        __sync;
        CPU::Interrupts(CPU::Disable);
        // }
#elif defined(aa64)
#endif
    }

    SafeFunction void Handle(void *Data)
    {
        // TODO: SUPPORT SMP
        CPU::Interrupts(CPU::Disable);
        SBIdx = 255;
        CHArchTrapFrame *Frame = (CHArchTrapFrame *)Data;
#if defined(a64)
        debug("-----------------------------------------------------------------------------------");
        error("Exception: %#llx", Frame->InterruptNumber);
        for (size_t i = 0; i < INT_FRAMES_MAX; i++)
            EHIntFrames[i] = Interrupts::InterruptFrames[i];
        PageFaultAddress = CPU::x64::readcr2().PFLA;

        if (Frame->cs != GDT_USER_CODE && Frame->cs != GDT_USER_DATA)
        {
            if (PageFaultAddress)
            {
                debug("Exception in kernel mode (ip: %#lx cr2: %#lx (%s))",
                      Frame->rip, PageFaultAddress, KernelSymbolTable ? KernelSymbolTable->GetSymbolFromAddress(Frame->rip) : "No symbol");
            }
            else
            {
                debug("Exception in kernel mode (ip: %#lx (%s))",
                      Frame->rip, KernelSymbolTable ? KernelSymbolTable->GetSymbolFromAddress(Frame->rip) : "No symbol");
            }

            CPUData *data = GetCurrentCPU();
            if (data)
            {
                if (data->CurrentThread)
                {
                    if (!data->CurrentThread->Security.IsCritical)
                    {
                        fixme("Exception in non-critical thread (kernel mode)");
                    }
                }
            }

            if (TaskManager)
                TaskManager->Panic();
            ForceUnlock = true;
            Display->CreateBuffer(0, 0, SBIdx);
            StopAllCores();
        }
        else
        {
            if (PageFaultAddress)
            {
                debug("Exception in user mode (ip: %#lx cr2: %#lx (%s))",
                      Frame->rip, PageFaultAddress, KernelSymbolTable ? KernelSymbolTable->GetSymbolFromAddress(Frame->rip) : "No symbol");
            }
            else
            {
                debug("Exception in user mode (ip: %#lx (%s))",
                      Frame->rip, KernelSymbolTable ? KernelSymbolTable->GetSymbolFromAddress(Frame->rip) : "No symbol");
            }
            CPUData *data = GetCurrentCPU();
            if (!data)
            {
                ForceUnlock = true;
                Display->CreateBuffer(0, 0, SBIdx);
                StopAllCores();
                EHPrint("\eFF0000Cannot get CPU data! This results in a kernel crash!");
                error("Cannot get CPU data! This results in a kernel crash!");
                error("This should never happen!");
            }
            else
            {
                debug("CPU %ld data is valid", data->ID);
                if (data->CurrentThread->Security.IsCritical)
                {
                    debug("Critical thread \"%s\"(%d) died", data->CurrentThread->Name, data->CurrentThread->ID);
                    if (TaskManager)
                        TaskManager->Panic();
                    ForceUnlock = true;
                    Display->CreateBuffer(0, 0, SBIdx);
                    StopAllCores();
                }
                else
                {
                    debug("Current thread is valid %#lx", data->CurrentThread);
                    UserModeExceptionHandler(Frame);
                    return;
                }
            }
        }

        if (ExceptionOccurred)
        {
            SBIdx = 255;
            Display->ClearBuffer(SBIdx);
            Display->SetBufferCursor(SBIdx, 0, 0);

            CPU::x64::CR0 cr0 = CPU::x64::readcr0();
            CPU::x64::CR2 cr2 = CPU::x64::CR2{.PFLA = PageFaultAddress};
            CPU::x64::CR3 cr3 = CPU::x64::readcr3();
            CPU::x64::CR4 cr4 = CPU::x64::readcr4();
            CPU::x64::CR8 cr8 = CPU::x64::readcr8();
            CPU::x64::EFER efer;
            efer.raw = CPU::x64::rdmsr(CPU::x64::MSR_EFER);
            uintptr_t ds;
            asmv("mov %%ds, %0"
                 : "=r"(ds));

            EHPrint("\eFF2525FS=%#llx  GS=%#llx  SS=%#llx  CS=%#llx  DS=%#llx\n",
                    CPU::x64::rdmsr(CPU::x64::MSR_FS_BASE), CPU::x64::rdmsr(CPU::x64::MSR_GS_BASE),
                    Frame->ss, Frame->cs, ds);
            EHPrint("R8=%#llx  R9=%#llx  R10=%#llx  R11=%#llx\n", Frame->r8, Frame->r9, Frame->r10, Frame->r11);
            EHPrint("R12=%#llx  R13=%#llx  R14=%#llx  R15=%#llx\n", Frame->r12, Frame->r13, Frame->r14, Frame->r15);
            EHPrint("RAX=%#llx  RBX=%#llx  RCX=%#llx  RDX=%#llx\n", Frame->rax, Frame->rbx, Frame->rcx, Frame->rdx);
            EHPrint("RSI=%#llx  RDI=%#llx  RBP=%#llx  RSP=%#llx\n", Frame->rsi, Frame->rdi, Frame->rbp, Frame->rsp);
            EHPrint("RIP=%#llx  RFL=%#llx  INT=%#llx  ERR=%#llx  EFER=%#llx\n", Frame->rip, Frame->rflags.raw, Frame->InterruptNumber, Frame->ErrorCode, efer.raw);
            EHPrint("CR0=%#llx  CR2=%#llx  CR3=%#llx  CR4=%#llx  CR8=%#llx\n", cr0.raw, cr2.raw, cr3.raw, cr4.raw, cr8.raw);
            EHPrint("CR0: PE:%s     MP:%s     EM:%s     TS:%s\n     ET:%s     NE:%s     WP:%s     AM:%s\n     NW:%s     CD:%s     PG:%s\n     R0:%#x R1:%#x R2:%#x\n",
                    cr0.PE ? "True " : "False", cr0.MP ? "True " : "False", cr0.EM ? "True " : "False", cr0.TS ? "True " : "False",
                    cr0.ET ? "True " : "False", cr0.NE ? "True " : "False", cr0.WP ? "True " : "False", cr0.AM ? "True " : "False",
                    cr0.NW ? "True " : "False", cr0.CD ? "True " : "False", cr0.PG ? "True " : "False",
                    cr0.Reserved0, cr0.Reserved1, cr0.Reserved2);
            EHPrint("CR2: PFLA: %#llx\n",
                    cr2.PFLA);
            EHPrint("CR3: PWT:%s     PCD:%s    PDBR:%#llx\n",
                    cr3.PWT ? "True " : "False", cr3.PCD ? "True " : "False", cr3.PDBR);
            EHPrint("CR4: VME:%s     PVI:%s     TSD:%s      DE:%s\n     PSE:%s     PAE:%s     MCE:%s     PGE:%s\n     PCE:%s    UMIP:%s  OSFXSR:%s OSXMMEXCPT:%s\n    LA57:%s    VMXE:%s    SMXE:%s   PCIDE:%s\n OSXSAVE:%s    SMEP:%s    SMAP:%s     PKE:%s\n     R0:%#x R1:%#x R2:%#x\n",
                    cr4.VME ? "True " : "False", cr4.PVI ? "True " : "False", cr4.TSD ? "True " : "False", cr4.DE ? "True " : "False",
                    cr4.PSE ? "True " : "False", cr4.PAE ? "True " : "False", cr4.MCE ? "True " : "False", cr4.PGE ? "True " : "False",
                    cr4.PCE ? "True " : "False", cr4.UMIP ? "True " : "False", cr4.OSFXSR ? "True " : "False", cr4.OSXMMEXCPT ? "True " : "False",
                    cr4.LA57 ? "True " : "False", cr4.VMXE ? "True " : "False", cr4.SMXE ? "True " : "False", cr4.PCIDE ? "True " : "False",
                    cr4.OSXSAVE ? "True " : "False", cr4.SMEP ? "True " : "False", cr4.SMAP ? "True " : "False", cr4.PKE ? "True " : "False",
                    cr4.Reserved0, cr4.Reserved1, cr4.Reserved2);
            EHPrint("CR8: TPL:%d\n", cr8.TPL);
            EHPrint("RFL: CF:%s     PF:%s     AF:%s     ZF:%s\n     SF:%s     TF:%s     IF:%s     DF:%s\n     OF:%s   IOPL:%s     NT:%s     RF:%s\n     VM:%s     AC:%s    VIF:%s    VIP:%s\n     ID:%s     AlwaysOne:%d\n     R0:%#x R1:%#x R2:%#x R3:%#x\n",
                    Frame->rflags.CF ? "True " : "False", Frame->rflags.PF ? "True " : "False", Frame->rflags.AF ? "True " : "False", Frame->rflags.ZF ? "True " : "False",
                    Frame->rflags.SF ? "True " : "False", Frame->rflags.TF ? "True " : "False", Frame->rflags.IF ? "True " : "False", Frame->rflags.DF ? "True " : "False",
                    Frame->rflags.OF ? "True " : "False", Frame->rflags.IOPL ? "True " : "False", Frame->rflags.NT ? "True " : "False", Frame->rflags.RF ? "True " : "False",
                    Frame->rflags.VM ? "True " : "False", Frame->rflags.AC ? "True " : "False", Frame->rflags.VIF ? "True " : "False", Frame->rflags.VIP ? "True " : "False",
                    Frame->rflags.ID ? "True " : "False", Frame->rflags.AlwaysOne,
                    Frame->rflags.Reserved0, Frame->rflags.Reserved1, Frame->rflags.Reserved2, Frame->rflags.Reserved3);
            EHPrint("EFER: SCE:%s      LME:%s      LMA:%s      NXE:%s\n     SVME:%s    LMSLE:%s    FFXSR:%s      TCE:%s\n     R0:%#x R1:%#x R2:%#x\n",
                    efer.SCE ? "True " : "False", efer.LME ? "True " : "False", efer.LMA ? "True " : "False", efer.NXE ? "True " : "False",
                    efer.SVME ? "True " : "False", efer.LMSLE ? "True " : "False", efer.FFXSR ? "True " : "False", efer.TCE ? "True " : "False",
                    efer.Reserved0, efer.Reserved1, efer.Reserved2);

            EHPrint("\nException occurred while handling exception! HALTED!");
            Display->SetBuffer(SBIdx);
            Interrupts::RemoveAll();
            CPU::Stop();
        }

        ExceptionOccurred = true;

        if (DriverManager)
            DriverManager->Panic();

        debug("Reading control registers...");
        crashdata.Frame = Frame;
        crashdata.cr0 = CPU::x64::readcr0();
        crashdata.cr2 = CPU::x64::CR2{.PFLA = PageFaultAddress};
        crashdata.cr3 = CPU::x64::readcr3();
        crashdata.cr4 = CPU::x64::readcr4();
        crashdata.cr8 = CPU::x64::readcr8();
        crashdata.efer.raw = CPU::x64::rdmsr(CPU::x64::MSR_EFER);
        uintptr_t ds;
        asmv("mov %%ds, %0"
             : "=r"(ds));

        // Get debug registers
        asmv("movq %%dr0, %0"
             : "=r"(crashdata.dr0));
        asmv("movq %%dr1, %0"
             : "=r"(crashdata.dr1));
        asmv("movq %%dr2, %0"
             : "=r"(crashdata.dr2));
        asmv("movq %%dr3, %0"
             : "=r"(crashdata.dr3));
        asmv("movq %%dr6, %0"
             : "=r"(crashdata.dr6));
        asmv("movq %%dr7, %0"
             : "=r"(crashdata.dr7));

        CPUData *cpudata = GetCurrentCPU();

        if (cpudata == nullptr)
        {
            EHPrint("\eFFA500Invalid CPU data!\n");
            for (long i = 0; i < MAX_CPU; i++)
            {
                cpudata = GetCPU(i);
                if (cpudata != nullptr)
                    break;
                if (i == MAX_CPU - 1)
                {
                    EHPrint("\eFF0000No CPU data found!\n");
                    cpudata = nullptr;
                }
            }
            debug("CPU ptr %#lx", cpudata);
        }

        if (cpudata != nullptr)
        {
            crashdata.ID = cpudata->ID;
            crashdata.CPUData = cpudata;
            error("Technical Informations on CPU %lld:", cpudata->ID);
        }

        if (TaskManager && cpudata != nullptr)
        {
            crashdata.Process = cpudata->CurrentProcess.load();
            crashdata.Thread = cpudata->CurrentThread.load();

            error("Current Process: %s(%ld)",
                  cpudata->CurrentProcess->Name,
                  cpudata->CurrentProcess->ID);
            error("Current Thread: %s(%ld)",
                  cpudata->CurrentThread->Name,
                  cpudata->CurrentThread->ID);
        }

        {
            error("FS=%#llx  GS=%#llx  SS=%#llx  CS=%#llx  DS=%#llx",
                  CPU::x64::rdmsr(CPU::x64::MSR_FS_BASE), CPU::x64::rdmsr(CPU::x64::MSR_GS_BASE),
                  Frame->ss, Frame->cs, ds);
            error("R8=%#llx  R9=%#llx  R10=%#llx  R11=%#llx", Frame->r8, Frame->r9, Frame->r10, Frame->r11);
            error("R12=%#llx  R13=%#llx  R14=%#llx  R15=%#llx", Frame->r12, Frame->r13, Frame->r14, Frame->r15);
            error("RAX=%#llx  RBX=%#llx  RCX=%#llx  RDX=%#llx", Frame->rax, Frame->rbx, Frame->rcx, Frame->rdx);
            error("RSI=%#llx  RDI=%#llx  RBP=%#llx  RSP=%#llx", Frame->rsi, Frame->rdi, Frame->rbp, Frame->rsp);
            error("RIP=%#llx  RFL=%#llx  INT=%#llx  ERR=%#llx  EFER=%#llx", Frame->rip, Frame->rflags.raw, Frame->InterruptNumber, Frame->ErrorCode, crashdata.efer.raw);
            error("CR0=%#llx  CR2=%#llx  CR3=%#llx  CR4=%#llx  CR8=%#llx", crashdata.cr0.raw, crashdata.cr2.raw, crashdata.cr3.raw, crashdata.cr4.raw, crashdata.cr8.raw);
            error("DR0=%#llx  DR1=%#llx  DR2=%#llx  DR3=%#llx  DR6=%#llx  DR7=%#llx", crashdata.dr0, crashdata.dr1, crashdata.dr2, crashdata.dr3, crashdata.dr6, crashdata.dr7.raw);

            error("CR0: PE:%s     MP:%s     EM:%s     TS:%s     ET:%s     NE:%s     WP:%s     AM:%s     NW:%s     CD:%s     PG:%s     R0:%#x R1:%#x R2:%#x",
                  crashdata.cr0.PE ? "True " : "False", crashdata.cr0.MP ? "True " : "False", crashdata.cr0.EM ? "True " : "False", crashdata.cr0.TS ? "True " : "False",
                  crashdata.cr0.ET ? "True " : "False", crashdata.cr0.NE ? "True " : "False", crashdata.cr0.WP ? "True " : "False", crashdata.cr0.AM ? "True " : "False",
                  crashdata.cr0.NW ? "True " : "False", crashdata.cr0.CD ? "True " : "False", crashdata.cr0.PG ? "True " : "False",
                  crashdata.cr0.Reserved0, crashdata.cr0.Reserved1, crashdata.cr0.Reserved2);

            error("CR2: PFLA: %#llx",
                  crashdata.cr2.PFLA);

            error("CR3: PWT:%s     PCD:%s    PDBR:%#llx",
                  crashdata.cr3.PWT ? "True " : "False", crashdata.cr3.PCD ? "True " : "False", crashdata.cr3.PDBR);

            error("CR4: VME:%s     PVI:%s     TSD:%s      DE:%s     PSE:%s     PAE:%s     MCE:%s     PGE:%s     PCE:%s    UMIP:%s  OSFXSR:%s OSXMMEXCPT:%s    LA57:%s    VMXE:%s    SMXE:%s   PCIDE:%s OSXSAVE:%s    SMEP:%s    SMAP:%s     PKE:%s     R0:%#x R1:%#x R2:%#x",
                  crashdata.cr4.VME ? "True " : "False", crashdata.cr4.PVI ? "True " : "False", crashdata.cr4.TSD ? "True " : "False", crashdata.cr4.DE ? "True " : "False",
                  crashdata.cr4.PSE ? "True " : "False", crashdata.cr4.PAE ? "True " : "False", crashdata.cr4.MCE ? "True " : "False", crashdata.cr4.PGE ? "True " : "False",
                  crashdata.cr4.PCE ? "True " : "False", crashdata.cr4.UMIP ? "True " : "False", crashdata.cr4.OSFXSR ? "True " : "False", crashdata.cr4.OSXMMEXCPT ? "True " : "False",
                  crashdata.cr4.LA57 ? "True " : "False", crashdata.cr4.VMXE ? "True " : "False", crashdata.cr4.SMXE ? "True " : "False", crashdata.cr4.PCIDE ? "True " : "False",
                  crashdata.cr4.OSXSAVE ? "True " : "False", crashdata.cr4.SMEP ? "True " : "False", crashdata.cr4.SMAP ? "True " : "False", crashdata.cr4.PKE ? "True " : "False",
                  crashdata.cr4.Reserved0, crashdata.cr4.Reserved1, crashdata.cr4.Reserved2);

            error("CR8: TPL:%d", crashdata.cr8.TPL);

            error("RFL: CF:%s     PF:%s     AF:%s     ZF:%s     SF:%s     TF:%s     IF:%s     DF:%s     OF:%s   IOPL:%s     NT:%s     RF:%s     VM:%s     AC:%s    VIF:%s    VIP:%s     ID:%s     AlwaysOne:%d     R0:%#x R1:%#x R2:%#x R3:%#x",
                  Frame->rflags.CF ? "True " : "False", Frame->rflags.PF ? "True " : "False", Frame->rflags.AF ? "True " : "False", Frame->rflags.ZF ? "True " : "False",
                  Frame->rflags.SF ? "True " : "False", Frame->rflags.TF ? "True " : "False", Frame->rflags.IF ? "True " : "False", Frame->rflags.DF ? "True " : "False",
                  Frame->rflags.OF ? "True " : "False", Frame->rflags.IOPL ? "True " : "False", Frame->rflags.NT ? "True " : "False", Frame->rflags.RF ? "True " : "False",
                  Frame->rflags.VM ? "True " : "False", Frame->rflags.AC ? "True " : "False", Frame->rflags.VIF ? "True " : "False", Frame->rflags.VIP ? "True " : "False",
                  Frame->rflags.ID ? "True " : "False", Frame->rflags.AlwaysOne,
                  Frame->rflags.Reserved0, Frame->rflags.Reserved1, Frame->rflags.Reserved2, Frame->rflags.Reserved3);

            error("DR7: LDR0:%s     GDR0:%s     LDR1:%s     GDR1:%s     LDR2:%s     GDR2:%s     LDR3:%s     GDR3:%s     CDR0:%s     SDR0:%s     CDR1:%s     SDR1:%s     CDR2:%s     SDR2:%s     CDR3:%s     SDR3:%s     R:%#x",
                  crashdata.dr7.LocalDR0 ? "True " : "False", crashdata.dr7.GlobalDR0 ? "True " : "False", crashdata.dr7.LocalDR1 ? "True " : "False", crashdata.dr7.GlobalDR1 ? "True " : "False",
                  crashdata.dr7.LocalDR2 ? "True " : "False", crashdata.dr7.GlobalDR2 ? "True " : "False", crashdata.dr7.LocalDR3 ? "True " : "False", crashdata.dr7.GlobalDR3 ? "True " : "False",
                  crashdata.dr7.ConditionsDR0 ? "True " : "False", crashdata.dr7.SizeDR0 ? "True " : "False", crashdata.dr7.ConditionsDR1 ? "True " : "False", crashdata.dr7.SizeDR1 ? "True " : "False",
                  crashdata.dr7.ConditionsDR2 ? "True " : "False", crashdata.dr7.SizeDR2 ? "True " : "False", crashdata.dr7.ConditionsDR3 ? "True " : "False", crashdata.dr7.SizeDR3 ? "True " : "False",
                  crashdata.dr7.Reserved);

            error("EFER: SCE:%s      LME:%s      LMA:%s      NXE:%s     SVME:%s    LMSLE:%s    FFXSR:%s      TCE:%s     R0:%#x R1:%#x R2:%#x",
                  crashdata.efer.SCE ? "True " : "False", crashdata.efer.LME ? "True " : "False", crashdata.efer.LMA ? "True " : "False", crashdata.efer.NXE ? "True " : "False",
                  crashdata.efer.SVME ? "True " : "False", crashdata.efer.LMSLE ? "True " : "False", crashdata.efer.FFXSR ? "True " : "False", crashdata.efer.TCE ? "True " : "False",
                  crashdata.efer.Reserved0, crashdata.efer.Reserved1, crashdata.efer.Reserved2);
        }
        goto CrashEnd;

#elif defined(a32)
        goto CrashEnd;
#elif defined(aa64)
        goto CrashEnd;
#endif

    CrashEnd:
        if (Config.InterruptsOnCrash)
        {
            // 255 // Main
            Display->CreateBuffer(0, 0, 254); // Details
            Display->CreateBuffer(0, 0, 253); // Frames
            Display->CreateBuffer(0, 0, 252); // Tasks
            Display->CreateBuffer(0, 0, 251); // Console
            Display->CreateBuffer(0, 0, 250); // Empty

            DisplayTopOverlay();
            DisplayMainScreen(crashdata);
            DisplayBottomOverlay();
            Display->SetBuffer(255);
            debug("Interrupts are enabled, waiting for user input");
            CPU::Interrupts(CPU::Enable);
            HookKeyboard();
        }
        else
        {
            /*
            TODO: Stuff that should be done when IOC is disabled.
            */
            Display->SetBuffer(255);
        }

        CPU::Halt(true);
    }
}