#pragma once
/**
 * File: rlutil.h
 *
 * About: Description
 * This file provides some useful utilities for console mode
 * roguelike game development with C and C++. It is aimed to
 * be cross-platform (at least Windows and Linux).
 *
 * About: Copyright
 * (C) 2010 Tapio Vierros
 *
 * About: Licensing
 * See <License>
 */

/// Define: RLUTIL_USE_ANSI
/// Define this to use ANSI escape sequences also on Windows
/// (defaults to using WinAPI instead).
#if 0
#define RLUTIL_USE_ANSI
#endif

/// Define: RLUTIL_STRING_T
/// Define/typedef this to your preference to override rlutil's string type.
///
/// Defaults to std::string with C++ and char* with C.
#if 0
#define RLUTIL_STRING_T char*
#endif

#ifndef RLUTIL_INLINE
#ifdef _MSC_VER
#define RLUTIL_INLINE __inline
#else
#define RLUTIL_INLINE static __inline__
#endif
#endif

#ifdef __cplusplus
/// Common C++ headers
#include <cstdio> // for getch()
#include <iostream>
#include <string>
/// Namespace forward declarations
namespace rlutil
{
    RLUTIL_INLINE void locate(int x, int y);
}
#else
#include <stdio.h>  // for getch() / printf()
#include <string.h> // for strlen()
RLUTIL_INLINE void locate(int x,
                          int y); // Forward declare for C to avoid warnings
#endif              // __cplusplus

#ifdef _WIN32
#include <windows.h> // for WinAPI and Sleep()
#define _NO_OLDNAMES // for MinGW compatibility
#include <conio.h>   // for getch() and kbhit()
#define getch _getch
#define kbhit _kbhit
#else
#include <sys/ioctl.h> // for getkey()
#include <sys/time.h>  // for kbhit()
#include <sys/types.h> // for kbhit()
#include <termios.h>   // for getch() and kbhit()
#include <unistd.h>    // for getch(), kbhit() and (u)sleep()

/// Function: getch
/// Get character without waiting for Return to be pressed.
/// Windows has this in conio.h
RLUTIL_INLINE int getch(void)
{
    // Here be magic.
    struct termios oldt, newt;
    int ch;
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    ch = getchar();
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    return ch;
}

/// Function: kbhit
/// Determines if keyboard has been hit.
/// Windows has this in conio.h
RLUTIL_INLINE int kbhit(void)
{
    // Here be dragons.
    static struct termios oldt, newt;
    int cnt = 0;
    tcgetattr(STDIN_FILENO, &oldt);
    newt = oldt;
    newt.c_lflag &= ~(ICANON | ECHO);
    newt.c_iflag = 0;     // input mode
    newt.c_oflag = 0;     // output mode
    newt.c_cc[VMIN] = 1;  // minimum time to wait
    newt.c_cc[VTIME] = 1; // minimum characters to wait for
    tcsetattr(STDIN_FILENO, TCSANOW, &newt);
    ioctl(0, FIONREAD, &cnt); // Read count
    struct timeval tv;
    tv.tv_sec = 0;
    tv.tv_usec = 100;
    select(STDIN_FILENO + 1, NULL, NULL, NULL, &tv); // A small time delay
    tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
    return cnt; // Return number of characters
}
#endif                 // _WIN32

#ifndef gotoxy
/// Function: gotoxy
/// Same as <rlutil.locate>.
RLUTIL_INLINE void gotoxy(int x, int y)
{
#ifdef __cplusplus
    rlutil::
#endif
        locate(x, y);
}
#endif // gotoxy

#ifdef __cplusplus
/// Namespace: rlutil
/// In C++ all functions except <getch>, <kbhit> and <gotoxy> are arranged
/// under namespace rlutil. That is because some platforms have them defined
/// outside of rlutil.
namespace rlutil
{
#endif

    /**
 * Defs: Internal typedefs and macros
 * RLUTIL_STRING_T - String type depending on which one of C or C++ is used
 * RLUTIL_PRINT(str) - Printing macro independent of C/C++
 */

#ifdef __cplusplus
#ifndef RLUTIL_STRING_T
    typedef std::string RLUTIL_STRING_T;
#endif // RLUTIL_STRING_T

#define RLUTIL_PRINT(st) \
    do                   \
    {                    \
        std::cout << st; \
    } while (false)
#else // __cplusplus
#ifndef RLUTIL_STRING_T
typedef const char* RLUTIL_STRING_T;
#endif // RLUTIL_STRING_T

#define RLUTIL_PRINT(st) printf("%s", st)
#endif // __cplusplus

    /**
 * Enums: Color codes
 *
 * BLACK - Black
 * BLUE - Blue
 * GREEN - Green
 * CYAN - Cyan
 * RED - Red
 * MAGENTA - Magenta / purple
 * BROWN - Brown / dark yellow
 * GREY - Grey / dark white
 * DARKGREY - Dark grey / light black
 * LIGHTBLUE - Light blue
 * LIGHTGREEN - Light green
 * LIGHTCYAN - Light cyan
 * LIGHTRED - Light red
 * LIGHTMAGENTA - Light magenta / light purple
 * YELLOW - Yellow (bright)
 * WHITE - White (bright)
 */
    enum
    {
        BLACK,
        BLUE,
        GREEN,
        CYAN,
        RED,
        MAGENTA,
        BROWN,
        GREY,
        DARKGREY,
        LIGHTBLUE,
        LIGHTGREEN,
        LIGHTCYAN,
        LIGHTRED,
        LIGHTMAGENTA,
        YELLOW,
        WHITE
    };

    /**
 * Consts: ANSI escape strings
 *
 * ANSI_CLS                - Clears screen
 * ANSI_CONSOLE_TITLE_PRE  - Prefix for changing the window title, print the
 * window title in between ANSI_CONSOLE_TITLE_POST - Suffix for changing the
 * window title, print the window title in between ANSI_ATTRIBUTE_RESET    -
 * Resets all attributes ANSI_CURSOR_HIDE        - Hides the cursor
 * ANSI_CURSOR_SHOW        - Shows the cursor
 * ANSI_CURSOR_HOME        - Moves the cursor home (0,0)
 * ANSI_BLACK              - Black
 * ANSI_RED                - Red
 * ANSI_GREEN              - Green
 * ANSI_BROWN              - Brown / dark yellow
 * ANSI_BLUE               - Blue
 * ANSI_MAGENTA            - Magenta / purple
 * ANSI_CYAN               - Cyan
 * ANSI_GREY               - Grey / dark white
 * ANSI_DARKGREY           - Dark grey / light black
 * ANSI_LIGHTRED           - Light red
 * ANSI_LIGHTGREEN         - Light green
 * ANSI_YELLOW             - Yellow (bright)
 * ANSI_LIGHTBLUE          - Light blue
 * ANSI_LIGHTMAGENTA       - Light magenta / light purple
 * ANSI_LIGHTCYAN          - Light cyan
 * ANSI_WHITE              - White (bright)
 * ANSI_BACKGROUND_BLACK   - Black background
 * ANSI_BACKGROUND_RED     - Red background
 * ANSI_BACKGROUND_GREEN   - Green background
 * ANSI_BACKGROUND_YELLOW  - Yellow background
 * ANSI_BACKGROUND_BLUE    - Blue background
 * ANSI_BACKGROUND_MAGENTA - Magenta / purple background
 * ANSI_BACKGROUND_CYAN    - Cyan background
 * ANSI_BACKGROUND_WHITE   - White background
 */
    const RLUTIL_STRING_T ANSI_CLS = "\033[2J\033[3J";
    const RLUTIL_STRING_T ANSI_CONSOLE_TITLE_PRE = "\033]0;";
    const RLUTIL_STRING_T ANSI_CONSOLE_TITLE_POST = "\007";
    const RLUTIL_STRING_T ANSI_ATTRIBUTE_RESET = "\033[0m";
    const RLUTIL_STRING_T ANSI_CURSOR_HIDE = "\033[?25l";
    const RLUTIL_STRING_T ANSI_CURSOR_SHOW = "\033[?25h";
    const RLUTIL_STRING_T ANSI_CURSOR_HOME = "\033[H";
    const RLUTIL_STRING_T ANSI_BLACK = "\033[22;30m";
    const RLUTIL_STRING_T ANSI_RED = "\033[22;31m";
    const RLUTIL_STRING_T ANSI_GREEN = "\033[22;32m";
    const RLUTIL_STRING_T ANSI_BROWN = "\033[22;33m";
    const RLUTIL_STRING_T ANSI_BLUE = "\033[22;34m";
    const RLUTIL_STRING_T ANSI_MAGENTA = "\033[22;35m";
    const RLUTIL_STRING_T ANSI_CYAN = "\033[22;36m";
    const RLUTIL_STRING_T ANSI_GREY = "\033[22;37m";
    const RLUTIL_STRING_T ANSI_DARKGREY = "\033[01;30m";
    const RLUTIL_STRING_T ANSI_LIGHTRED = "\033[01;31m";
    const RLUTIL_STRING_T ANSI_LIGHTGREEN = "\033[01;32m";
    const RLUTIL_STRING_T ANSI_YELLOW = "\033[01;33m";
    const RLUTIL_STRING_T ANSI_LIGHTBLUE = "\033[01;34m";
    const RLUTIL_STRING_T ANSI_LIGHTMAGENTA = "\033[01;35m";
    const RLUTIL_STRING_T ANSI_LIGHTCYAN = "\033[01;36m";
    const RLUTIL_STRING_T ANSI_WHITE = "\033[01;37m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_BLACK = "\033[40m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_RED = "\033[41m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_GREEN = "\033[42m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_YELLOW = "\033[43m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_BLUE = "\033[44m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_MAGENTA = "\033[45m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_CYAN = "\033[46m";
    const RLUTIL_STRING_T ANSI_BACKGROUND_WHITE = "\033[47m";
    // Remaining colors not supported as background colors

    /**
 * Enums: Key codes for keyhit()
 *
 * KEY_ESCAPE  - Escape
 * KEY_ENTER   - Enter
 * KEY_SPACE   - Space
 * KEY_INSERT  - Insert
 * KEY_HOME    - Home
 * KEY_END     - End
 * KEY_DELETE  - Delete
 * KEY_PGUP    - PageUp
 * KEY_PGDOWN  - PageDown
 * KEY_UP      - Up arrow
 * KEY_DOWN    - Down arrow
 * KEY_LEFT    - Left arrow
 * KEY_RIGHT   - Right arrow
 * KEY_F1      - F1
 * KEY_F2      - F2
 * KEY_F3      - F3
 * KEY_F4      - F4
 * KEY_F5      - F5
 * KEY_F6      - F6
 * KEY_F7      - F7
 * KEY_F8      - F8
 * KEY_F9      - F9
 * KEY_F10     - F10
 * KEY_F11     - F11
 * KEY_F12     - F12
 * KEY_NUMDEL  - Numpad del
 * KEY_NUMPAD0 - Numpad 0
 * KEY_NUMPAD1 - Numpad 1
 * KEY_NUMPAD2 - Numpad 2
 * KEY_NUMPAD3 - Numpad 3
 * KEY_NUMPAD4 - Numpad 4
 * KEY_NUMPAD5 - Numpad 5
 * KEY_NUMPAD6 - Numpad 6
 * KEY_NUMPAD7 - Numpad 7
 * KEY_NUMPAD8 - Numpad 8
 * KEY_NUMPAD9 - Numpad 9
 */
    enum
    {
        KEY_ESCAPE = 0,
        KEY_ENTER = 1,
        KEY_SPACE = 32,

        KEY_INSERT = 2,
        KEY_HOME = 3,
        KEY_PGUP = 4,
        KEY_DELETE = 5,
        KEY_END = 6,
        KEY_PGDOWN = 7,
#ifndef _WIN32
        KEY_BACKSPACE = 127,
#else
    KEY_BACKSPACE = 8,
#endif

        KEY_UP = 14,
        KEY_DOWN = 15,
        KEY_LEFT = 16,
        KEY_RIGHT = 17,

        KEY_F1 = 18,
        KEY_F2 = 19,
        KEY_F3 = 20,
        KEY_F4 = 21,
        KEY_F5 = 22,
        KEY_F6 = 23,
        KEY_F7 = 24,
        KEY_F8 = 25,
        KEY_F9 = 26,
        KEY_F10 = 27,
        KEY_F11 = 28,
        KEY_F12 = 29,

        KEY_NUMDEL = 30,
        KEY_NUMPAD0 = 31,
        KEY_NUMPAD1 = 127,
        KEY_NUMPAD2 = 128,
        KEY_NUMPAD3 = 129,
        KEY_NUMPAD4 = 130,
        KEY_NUMPAD5 = 131,
        KEY_NUMPAD6 = 132,
        KEY_NUMPAD7 = 133,
        KEY_NUMPAD8 = 134,
        KEY_NUMPAD9 = 135
    };

    /// Function: getkey
    /// Reads a key press (blocking) and returns a key code.
    ///
    /// See <Key codes for keyhit()>
    ///
    /// Note:
    /// Only Arrows, Esc, Enter and Space are currently working properly.
    RLUTIL_INLINE int getkey(void)
    {
#ifndef _WIN32
        int cnt = kbhit(); // for ANSI escapes processing
#endif
        int k = getch();
        switch (k)
        {
            case 0:
            {
                int kk;
                switch (kk = getch())
                {
                    case 71:
                        return KEY_NUMPAD7;
                    case 72:
                        return KEY_NUMPAD8;
                    case 73:
                        return KEY_NUMPAD9;
                    case 75:
                        return KEY_NUMPAD4;
                    case 77:
                        return KEY_NUMPAD6;
                    case 79:
                        return KEY_NUMPAD1;
                    case 80:
                        return KEY_NUMPAD2;
                    case 81:
                        return KEY_NUMPAD3;
                    case 82:
                        return KEY_NUMPAD0;
                    case 83:
                        return KEY_NUMDEL;
                    default:
                        return kk - 59 + KEY_F1; // Function keys
                }
            }
            case 224:
            {
                int kk;
                switch (kk = getch())
                {
                    case 71:
                        return KEY_HOME;
                    case 72:
                        return KEY_UP;
                    case 73:
                        return KEY_PGUP;
                    case 75:
                        return KEY_LEFT;
                    case 77:
                        return KEY_RIGHT;
                    case 79:
                        return KEY_END;
                    case 80:
                        return KEY_DOWN;
                    case 81:
                        return KEY_PGDOWN;
                    case 82:
                        return KEY_INSERT;
                    case 83:
                        return KEY_DELETE;
                    default:
                        return kk - 123 + KEY_F1; // Function keys
                }
            }
            case 13:
                return KEY_ENTER;
#ifdef _WIN32
            case 27:
                return KEY_ESCAPE;
#else  // _WIN32
        case 155: // single-character CSI
        case 27:
        {
            // Process ANSI escape sequences
            if (cnt >= 3 && getch() == '[')
            {
                switch (k = getch())
                {
                    case 'A':
                        return KEY_UP;
                    case 'B':
                        return KEY_DOWN;
                    case 'C':
                        return KEY_RIGHT;
                    case 'D':
                        return KEY_LEFT;
                }
            }
            else
                return KEY_ESCAPE;
            [[fallthrough]];
        }
#endif // _WIN32
            default:
                return k;
        }
    }

    /// Function: nb_getch
    /// Non-blocking getch(). Returns 0 if no key was pressed.
    RLUTIL_INLINE int nb_getch(void)
    {
        if (kbhit())
            return getch();
        else
            return 0;
    }

    /// Function: getANSIColor
    /// Return ANSI color escape sequence for specified number 0-15.
    ///
    /// See <Color Codes>
    RLUTIL_INLINE RLUTIL_STRING_T getANSIColor(const int c)
    {
        switch (c)
        {
            case BLACK:
                return ANSI_BLACK;
            case BLUE:
                return ANSI_BLUE; // non-ANSI
            case GREEN:
                return ANSI_GREEN;
            case CYAN:
                return ANSI_CYAN; // non-ANSI
            case RED:
                return ANSI_RED; // non-ANSI
            case MAGENTA:
                return ANSI_MAGENTA;
            case BROWN:
                return ANSI_BROWN;
            case GREY:
                return ANSI_GREY;
            case DARKGREY:
                return ANSI_DARKGREY;
            case LIGHTBLUE:
                return ANSI_LIGHTBLUE; // non-ANSI
            case LIGHTGREEN:
                return ANSI_LIGHTGREEN;
            case LIGHTCYAN:
                return ANSI_LIGHTCYAN; // non-ANSI;
            case LIGHTRED:
                return ANSI_LIGHTRED; // non-ANSI;
            case LIGHTMAGENTA:
                return ANSI_LIGHTMAGENTA;
            case YELLOW:
                return ANSI_YELLOW; // non-ANSI
            case WHITE:
                return ANSI_WHITE;
            default:
                return "";
        }
    }

    /// Function: getANSIBackgroundColor
    /// Return ANSI background color escape sequence for specified number 0-15.
    ///
    /// See <Color Codes>
    RLUTIL_INLINE RLUTIL_STRING_T getANSIBackgroundColor(const int c)
    {
        switch (c)
        {
            case BLACK:
                return ANSI_BACKGROUND_BLACK;
            case BLUE:
                return ANSI_BACKGROUND_BLUE;
            case GREEN:
                return ANSI_BACKGROUND_GREEN;
            case CYAN:
                return ANSI_BACKGROUND_CYAN;
            case RED:
                return ANSI_BACKGROUND_RED;
            case MAGENTA:
                return ANSI_BACKGROUND_MAGENTA;
            case BROWN:
                return ANSI_BACKGROUND_YELLOW;
            case GREY:
                return ANSI_BACKGROUND_WHITE;
            default:
                return "";
        }
    }

    /// Function: setColor
    /// Change color specified by number (Windows / QBasic colors).
    /// Don't change the background color
    ///
    /// See <Color Codes>
    RLUTIL_INLINE void setColor(int c)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_SCREEN_BUFFER_INFO csbi;

        GetConsoleScreenBufferInfo(hConsole, &csbi);

        SetConsoleTextAttribute(
            hConsole,
            (csbi.wAttributes & 0xFFF0) |
                (WORD)c); // Foreground colors take up the least significant byte
#else
    RLUTIL_PRINT(getANSIColor(c));
#endif
    }

    /// Function: setBackgroundColor
    /// Change background color specified by number (Windows / QBasic colors).
    /// Don't change the foreground color
    ///
    /// See <Color Codes>
    RLUTIL_INLINE void setBackgroundColor(int c)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_SCREEN_BUFFER_INFO csbi;

        GetConsoleScreenBufferInfo(hConsole, &csbi);

        SetConsoleTextAttribute(
            hConsole, (csbi.wAttributes & 0xFF0F) |
                          (((WORD)c) << 4)); // Background colors take up the
                                             // second-least significant byte
#else
    RLUTIL_PRINT(getANSIBackgroundColor(c));
#endif
    }

    /// Function: saveDefaultColor
    /// Call once to preserve colors for use in resetColor()
    /// on Windows without ANSI, no-op otherwise
    ///
    /// See <Color Codes>
    /// See <resetColor>
    RLUTIL_INLINE int saveDefaultColor(void)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        static char initialized = 0; // bool
        static WORD attributes;

        if (!initialized)
        {
            CONSOLE_SCREEN_BUFFER_INFO csbi;
            GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
            attributes = csbi.wAttributes;
            initialized = 1;
        }
        return (int)attributes;
#else
    return -1;
#endif
    }

    /// Function: resetColor
    /// Reset color to default
    /// Requires a call to saveDefaultColor() to set the defaults
    ///
    /// See <Color Codes>
    /// See <setColor>
    /// See <saveDefaultColor>
    RLUTIL_INLINE void resetColor(void)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE),
                                (WORD)saveDefaultColor());
#else
    RLUTIL_PRINT(ANSI_ATTRIBUTE_RESET);
#endif
    }

    /// Function: cls
    /// Clears screen, resets all attributes and moves cursor home.
    RLUTIL_INLINE void cls(void)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        // Based on
        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms682022%28v=vs.85%29.aspx
        const HANDLE hConsole = GetStdHandle(STD_OUTPUT_HANDLE);
        const COORD coordScreen = {0, 0};
        DWORD cCharsWritten;
        CONSOLE_SCREEN_BUFFER_INFO csbi;

        GetConsoleScreenBufferInfo(hConsole, &csbi);
        const DWORD dwConSize = csbi.dwSize.X * csbi.dwSize.Y;
        FillConsoleOutputCharacter(hConsole, (TCHAR)' ', dwConSize, coordScreen,
                                   &cCharsWritten);

        GetConsoleScreenBufferInfo(hConsole, &csbi);
        FillConsoleOutputAttribute(hConsole, csbi.wAttributes, dwConSize, coordScreen,
                                   &cCharsWritten);

        SetConsoleCursorPosition(hConsole, coordScreen);
#else
    RLUTIL_PRINT(ANSI_CLS);
    RLUTIL_PRINT(ANSI_CURSOR_HOME);
#endif
    }

    /// Function: locate
    /// Sets the cursor position to 1-based x,y.
    RLUTIL_INLINE void locate(int x, int y)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        COORD coord;
        // TODO: clamping/assert for x/y <= 0?
        coord.X = (SHORT)(x - 1);
        coord.Y = (SHORT)(y - 1); // Windows uses 0-based coordinates
        SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), coord);
#else // _WIN32 || USE_ANSI
#ifdef __cplusplus
    RLUTIL_PRINT("\033[" << y << ";" << x << "H");
#else  // __cplusplus
    char buf[32];
    sprintf(buf, "\033[%d;%df", y, x);
    RLUTIL_PRINT(buf);
#endif // __cplusplus
#endif // _WIN32 || USE_ANSI
    }

/// Function: setString
/// Prints the supplied string without advancing the cursor
#ifdef __cplusplus
    RLUTIL_INLINE void setString(const RLUTIL_STRING_T& str_)
    {
        const char* const str = str_.data();
        std::size_t len = str_.size();
#else  // __cplusplus
RLUTIL_INLINE void setString(RLUTIL_STRING_T str)
{
    unsigned int len = strlen(str);
#endif // __cplusplus
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
        DWORD numberOfCharsWritten;
        CONSOLE_SCREEN_BUFFER_INFO csbi;

        GetConsoleScreenBufferInfo(hConsoleOutput, &csbi);
        WriteConsoleOutputCharacter(hConsoleOutput, str, (DWORD)len,
                                    csbi.dwCursorPosition, &numberOfCharsWritten);
#else // _WIN32 || USE_ANSI
    RLUTIL_PRINT(str);
#ifdef __cplusplus
    RLUTIL_PRINT("\033[" << len << 'D');
#else  // __cplusplus
    char buf[3 + 20 +
             1]; // 20 = max length of 64-bit unsigned int when printed as dec
    sprintf(buf, "\033[%uD", len);
    RLUTIL_PRINT(buf);
#endif // __cplusplus
#endif // _WIN32 || USE_ANSI
    }

    /// Function: setChar
    /// Sets the character at the cursor without advancing the cursor
    RLUTIL_INLINE void setChar(char ch)
    {
        const char buf[] = {ch, 0};
        setString(buf);
    }

    /// Function: setCursorVisibility
    /// Shows/hides the cursor.
    RLUTIL_INLINE void setCursorVisibility(char visible)
    {
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        HANDLE hConsoleOutput = GetStdHandle(STD_OUTPUT_HANDLE);
        CONSOLE_CURSOR_INFO structCursorInfo;
        GetConsoleCursorInfo(hConsoleOutput,
                             &structCursorInfo); // Get current cursor size
        structCursorInfo.bVisible = (visible ? TRUE : FALSE);
        SetConsoleCursorInfo(hConsoleOutput, &structCursorInfo);
#else  // _WIN32 || USE_ANSI
    RLUTIL_PRINT((visible ? ANSI_CURSOR_SHOW : ANSI_CURSOR_HIDE));
#endif // _WIN32 || USE_ANSI
    }

    /// Function: hidecursor
    /// Hides the cursor.
    RLUTIL_INLINE void hidecursor(void) { setCursorVisibility(0); }

    /// Function: showcursor
    /// Shows the cursor.
    RLUTIL_INLINE void showcursor(void) { setCursorVisibility(1); }

    /// Function: msleep
    /// Waits given number of milliseconds before continuing.
    RLUTIL_INLINE void msleep(unsigned int ms)
    {
#ifdef _WIN32
        Sleep(ms);
#else
    // usleep argument must be under 1 000 000
    if (ms > 1000)
        sleep(ms / 1000000);
    usleep((ms % 1000000) * 1000);
#endif
    }

    /// Function: trows
    /// Get the number of rows in the terminal window or -1 on error.
    RLUTIL_INLINE int trows(void)
    {
#ifdef _WIN32
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
            return -1;
        else
            return csbi.srWindow.Bottom - csbi.srWindow.Top + 1; // Window height
                                                                 // return csbi.dwSize.Y; // Buffer height
#else
#ifdef TIOCGSIZE
    struct ttysize ts;
    ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
    return ts.ts_lines;
#elif defined(TIOCGWINSZ)
    struct winsize ts;
    ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
    return ts.ws_row;
#else  // TIOCGSIZE
    return -1;
#endif // TIOCGSIZE
#endif // _WIN32
    }

    /// Function: tcols
    /// Get the number of columns in the terminal window or -1 on error.
    RLUTIL_INLINE int tcols(void)
    {
#ifdef _WIN32
        CONSOLE_SCREEN_BUFFER_INFO csbi;
        if (!GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi))
            return -1;
        else
            return csbi.srWindow.Right - csbi.srWindow.Left + 1; // Window width
                                                                 // return csbi.dwSize.X; // Buffer width
#else
#ifdef TIOCGSIZE
    struct ttysize ts;
    ioctl(STDIN_FILENO, TIOCGSIZE, &ts);
    return ts.ts_cols;
#elif defined(TIOCGWINSZ)
    struct winsize ts;
    ioctl(STDIN_FILENO, TIOCGWINSZ, &ts);
    return ts.ws_col;
#else  // TIOCGSIZE
    return -1;
#endif // TIOCGSIZE
#endif // _WIN32
    }

/// Function: anykey
/// Waits until a key is pressed.
/// In C++, it either takes no arguments
/// or a template-type-argument-deduced
/// argument.
/// In C, it takes a const char* representing
/// the message to be displayed, or NULL
/// for no message.
#ifdef __cplusplus
    RLUTIL_INLINE void anykey()
    {
        getch();
    }

    template <class T>
    void anykey(const T& msg)
    {
        RLUTIL_PRINT(msg);
#else
RLUTIL_INLINE void anykey(RLUTIL_STRING_T msg)
{
    if (msg)
        RLUTIL_PRINT(msg);
#endif // __cplusplus
        getch();
    }

    RLUTIL_INLINE void setConsoleTitle(RLUTIL_STRING_T title)
    {
        const char* true_title =
#ifdef __cplusplus
            title.c_str();
#else  // __cplusplus
        title;
#endif // __cplusplus
#if defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
        SetConsoleTitleA(true_title);
#else
    RLUTIL_PRINT(ANSI_CONSOLE_TITLE_PRE);
    RLUTIL_PRINT(true_title);
    RLUTIL_PRINT(ANSI_CONSOLE_TITLE_POST);
#endif // defined(_WIN32) && !defined(RLUTIL_USE_ANSI)
    }

    // Classes are here at the end so that documentation is pretty.

#ifdef __cplusplus
    /// Class: CursorHider
    /// RAII OOP wrapper for <rlutil.hidecursor>.
    /// Hides the cursor and shows it again
    /// when the object goes out of scope.
    struct CursorHider
    {
        CursorHider() { hidecursor(); }
        ~CursorHider() { showcursor(); }
    };

} // namespace rlutil
#endif
