/****************************************************************************
** Copyright (c) 2016, Fougue SAS <https://www.fougue.pro>
** SPDX-License-Identifier: BSD-2-Clause
****************************************************************************/

#include "console.h"

#include "../base/global.h"

#ifdef MAYO_OS_WINDOWS
#  include <io.h>
#  include <windows.h>
#else
#  include <sys/ioctl.h> // ioctl() and TIOCGWINSZ
#  include <unistd.h>    // for STDOUT_FILENO
#  include <iostream>
#endif

namespace Mayo {

void consoleSetTextColor([[maybe_unused]]ConsoleColor color)
{
    [[maybe_unused]]constexpr bool isBrightText = true;
#ifdef MAYO_OS_WINDOWS
    auto fnColorFlags = [](ConsoleColor color) {
        switch (color) {
        case ConsoleColor::Black:   return 0;
        case ConsoleColor::Red:     return FOREGROUND_RED;
        case ConsoleColor::Green:   return FOREGROUND_GREEN;
        case ConsoleColor::Blue:    return FOREGROUND_BLUE;
        case ConsoleColor::Yellow:  return FOREGROUND_RED | FOREGROUND_GREEN;
        case ConsoleColor::Cyan:    return FOREGROUND_GREEN | FOREGROUND_BLUE;
        case ConsoleColor::Magenta: return FOREGROUND_RED | FOREGROUND_BLUE;
        case ConsoleColor::Default:
        case ConsoleColor::White:
        default: return FOREGROUND_RED | FOREGROUND_GREEN | FOREGROUND_BLUE;
        }
    };

    // There is no difference between STD_OUTPUT_HANDLE/STD_ERROR_HANDLE for std::cout/std::cerr
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdout && hStdout != INVALID_HANDLE_VALUE) {
        const bool isDefaultColor = color == ConsoleColor::Default;
        const WORD brightFlag = isBrightText && !isDefaultColor ? FOREGROUND_INTENSITY : 0;
        const WORD flags = fnColorFlags(color) | brightFlag;
        SetConsoleTextAttribute(hStdout, flags);
    }
#elif defined(MAYO_OS_WASM)
    // Terminal capabilities are undefined on this platform
    // std::cout could be redirected to HTML page, into terminal or somewhere else
#else
    auto fnCode = [](ConsoleColor color, bool isBrightText) {
        switch (color) {
        case ConsoleColor::Black:   return isBrightText ? "\x1B[30;1m" : "\x1B[30m";
        case ConsoleColor::Red:     return isBrightText ? "\x1B[31;1m" : "\x1B[31m";
        case ConsoleColor::Green:   return isBrightText ? "\x1B[32;1m" : "\x1B[32m";
        case ConsoleColor::Yellow:  return isBrightText ? "\x1B[33;1m" : "\x1B[33m";
        case ConsoleColor::Blue:    return isBrightText ? "\x1B[34;1m" : "\x1B[34m";
        case ConsoleColor::Magenta: return isBrightText ? "\x1B[35;1m" : "\x1B[35m";
        case ConsoleColor::Cyan:    return isBrightText ? "\x1B[36;1m" : "\x1B[36m";
        case ConsoleColor::White:   return isBrightText ? "\x1B[37;1m" : "\x1B[37m";
        case ConsoleColor::Default:
        default: return "\x1B[0m";
        }
    };
    std::cout << fnCode(color, isBrightText);
#endif
}

void consoleCursorMoveUp(int lines)
{
    if (lines == 0)
        return;

#ifdef MAYO_OS_WINDOWS
    auto hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdout) {
        CONSOLE_SCREEN_BUFFER_INFO buffInfo;
        GetConsoleScreenBufferInfo(hStdout, &buffInfo);
        COORD cursor;
        cursor.X = buffInfo.dwCursorPosition.X;
        cursor.Y = buffInfo.dwCursorPosition.Y - lines;
        SetConsoleCursorPosition(hStdout, cursor);
    }
#else
    std::cout << "\033[" << lines << "A";
#endif
}

void consoleCursorShow(bool on)
{
#ifdef MAYO_OS_WINDOWS
    HANDLE hStdout = GetStdHandle(STD_OUTPUT_HANDLE);
    if (hStdout) {
        CONSOLE_CURSOR_INFO cursorInfo;
        GetConsoleCursorInfo(hStdout, &cursorInfo);
        cursorInfo.bVisible = on;
        SetConsoleCursorInfo(hStdout, &cursorInfo);
    }
#else
    std::fputs(on ? "\033[?25h" : "\033[?25l", stdout);
#endif
}

std::pair<int, int> consoleSize()
{
#ifdef MAYO_OS_WINDOWS
    CONSOLE_SCREEN_BUFFER_INFO buffInfo;
    int cols, rows;
    GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &buffInfo);
    cols = buffInfo.srWindow.Right - buffInfo.srWindow.Left + 1;
    rows = buffInfo.srWindow.Bottom - buffInfo.srWindow.Top + 1;
    return { rows, cols };
#else
    struct winsize size{};
    ioctl(STDOUT_FILENO, TIOCGWINSZ, &size);
    return { size.ws_row, size.ws_col };
#endif
}

int consoleWidth() {
    return consoleSize().second;
}

void consoleSendEnterKey()
{
#ifdef MAYO_OS_WINDOWS
    HWND consoleWnd = GetConsoleWindow();
    if (IsWindow(consoleWnd))
        PostMessage(consoleWnd, WM_KEYUP, VK_RETURN, 0);
#endif
}

} // namespace Mayo
