/******************************************************************************
*	File		-	terminal.cpp
*	Author		-	Joey Pollack
*	Date		-	2022/05/13 (y/m/d)
*	Mod Date	-	2022/05/13 (y/m/d)
*	Description	-	API for the cross-platform terminal
******************************************************************************/

#include "terminal.h"
#include <iostream>

#if defined(LPLATFORM_WINDOWS)
#define WIN32_LEAN_AN_MEAN
#include <windows.h>

#elif defined(LPLATFORM_LINUX)

#endif

namespace lunarium
{
    u32 Terminal::WinPrevOutMode;
    u32 Terminal::WinPrevInMode;
    OpRes Terminal::Initialize()
    {
    #if defined(LPLATFORM_WINDOWS)
        return InitWindowsTerm();
    #elif defined(LPLATFORM_LINUX)
        // No init needed for linux
    #endif
    }

    void Terminal::Shutdown()
    {
        // Reset colors
        std::cout << Color(TermColor::TC_DEFAULT);

        #if defined(LPLATFORM_WINDOWS)
            ShutdownWinTerm();
        #elif defined(LPLATFORM_LINUX)
            // No shutdown needed for linux
        #endif
    }

    const char* Terminal::Color(TermColor color)
    {
        switch (color)
        {
            case TermColor::TC_WHITE:   return "\033[37m";
            case TermColor::TC_BLACK:   return "\033[30m";
            case TermColor::TC_BLUE:    return "\033[34m";
            case TermColor::TC_CYAN:    return "\033[36m";
            case TermColor::TC_GREEN:   return "\033[32m";
            case TermColor::TC_MAGENTA: return "\033[35m";
            case TermColor::TC_RED:     return "\033[31m";
            case TermColor::TC_YELLOW:  return "\033[33m";
            case TermColor::TC_DEFAULT: return "\033[0m";
        }
        return "";
    }

    void Terminal::SetDefaultColor()
    {
        std::cout << "\033[0m";
    }

    OpRes Terminal::InitWindowsTerm()
    {
        // This code is from microsoft docs:
        // https://docs.microsoft.com/en-us/windows/console/console-virtual-terminal-sequences

        // Set output mode to handle virtual terminal sequences
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
        if (hOut == INVALID_HANDLE_VALUE)
        {
            return OpRes::Fail("Unable to get std out handle");
        }
        HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
        if (hIn == INVALID_HANDLE_VALUE)
        {
            return OpRes::Fail("Unable to get std in handle");
        }

        DWORD dwOriginalOutMode = 0;
        DWORD dwOriginalInMode = 0;
        if (!GetConsoleMode(hOut, &dwOriginalOutMode))
        {
            return OpRes::Fail("Unable to get current out console mode");
        }
        if (!GetConsoleMode(hIn, &dwOriginalInMode))
        {
            return OpRes::Fail("Unable to get current in console mode");
        }

        DWORD dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING | DISABLE_NEWLINE_AUTO_RETURN;
        DWORD dwRequestedInModes = ENABLE_VIRTUAL_TERMINAL_INPUT;

        GetConsoleMode(hOut, (LPDWORD)&WinPrevOutMode);
        GetConsoleMode(hIn, (LPDWORD)&WinPrevInMode);

        DWORD dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
        if (!SetConsoleMode(hOut, dwOutMode))
        {
            // we failed to set both modes, try to step down mode gracefully.
            dwRequestedOutModes = ENABLE_VIRTUAL_TERMINAL_PROCESSING;
            dwOutMode = dwOriginalOutMode | dwRequestedOutModes;
            if (!SetConsoleMode(hOut, dwOutMode))
            {
                // Failed to set any VT mode, can't do anything here.
                return OpRes::Fail("Unable to set console mode");
            }
        }

        DWORD dwInMode = dwOriginalInMode | ENABLE_VIRTUAL_TERMINAL_INPUT;
        if (!SetConsoleMode(hIn, dwInMode))
        {
            // Failed to set VT input mode, can't do anything here.
            return OpRes::Fail("Unable to set console mode");
        }

        return OpRes::OK();
    }

    void Terminal::ShutdownWinTerm()
    {
        // Assume we'll get valid handles since init worked
        HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);       
        HANDLE hIn = GetStdHandle(STD_INPUT_HANDLE);
        
        // Reset console mode
        SetConsoleMode(hOut, WinPrevOutMode);
        SetConsoleMode(hIn, WinPrevInMode);
    }
}