/******************************************************************************\
* Copyright (c) 2019, Robert van Engelen, Genivia Inc. All rights reserved.    *
*                                                                              *
* Redistribution and use in source and binary forms, with or without           *
* modification, are permitted provided that the following conditions are met:  *
*                                                                              *
*   (1) Redistributions of source code must retain the above copyright notice, *
*       this list of conditions and the following disclaimer.                  *
*                                                                              *
*   (2) Redistributions in binary form must reproduce the above copyright      *
*       notice, this list of conditions and the following disclaimer in the    *
*       documentation and/or other materials provided with the distribution.   *
*                                                                              *
*   (3) The name of the author may not be used to endorse or promote products  *
*       derived from this software without specific prior written permission.  *
*                                                                              *
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR IMPLIED *
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF         *
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO   *
* EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,       *
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, *
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;  *
* OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,     *
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR      *
* OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF       *
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.                                   *
\******************************************************************************/

/**
@file      screen.hpp
@brief     ANSI SGR code controlled screen API - static, not thread safe
@author    Robert van Engelen - engelen@genivia.com
@copyright (c) 2019-2025, Robert van Engelen, Genivia Inc. All rights reserved.
@copyright (c) BSD-3 License - see LICENSE.txt
*/

#ifndef SCREEN_HPP
#define SCREEN_HPP

#include "vkey.hpp"
#include <cinttypes>

#ifndef OS_WIN
#include <unistd.h>
#endif

class Screen {

 public:

  // emit ANSI SGR CSI sequence with one numeric parameter
  static void CSI(int code, int num);

  // emit ANSI SGR CSI sequence with two numeric parameters
  static void CSI(int code, int num1, int num2);

  // clear screen, normal font and colors
  static void clear()
  {
    put("\033[1;1H\033[m\033[J");
  }

  // erase from cursor to the end of the line
  static void erase()
  {
    put("\033[K");
  }

  // erase from cursor to the end of the screen
  static void end()
  {
    put("\033[J");
  }

  // move cursor home (0,0)
  static void home()
  {
    put("\033[1;1H");
  }

  // move cursor up
  static void up(int num = 1)
  {
    CSI('A', num);
  }

  // move cursor down
  static void down(int num = 1)
  {
    CSI('B', num);
  }

  // move cursor forward
  static void forward(int num = 1)
  {
    CSI('C', num);
  }

  // move cursor back
  static void back(int num = 1)
  {
    CSI('D', num);
  }

  // scroll screen up
  static void pan_up(int num = 1)
  {
    CSI('S', num);
  }

  // scroll screen down
  static void pan_down(int num = 1)
  {
    CSI('T', num);
  }

  // normal font and colors
  static void normal()
  {
    put("\033[m");
  }

  // show selections
  static void select()
  {
    invert();
    sel = true;
  }

  // hide selections
  static void deselect()
  {
    noinvert();
    sel = false;
  }
  // enable bold font and/or bright colors
  static void bold()
  {
    put("\033[1m");
  }

  // disable bold font and/or bright colors
  static void nobold()
  {
    put("\033[21m");
  }

  // enable underline
  static void underline()
  {
    put("\033[4m");
  }

  // disable underline
  static void nounderline()
  {
    put("\033[24m");
  }

  // enable invert (reverse video)
  static void invert()
  {
    put("\033[7m");
  }

  // disable invert (reverse video)
  static void noinvert()
  {
    put("\033[27m");
  }

  // save the cursor position
  static void save()
  {
    put("\0337");
  }

  // restore the cursor position, when saved
  static void restore()
  {
    put("\0338");
  }

  // emit alert (bell)
  static void alert()
  {
    put('\a');
  }

  // set the cursor position, where (0,0) is home
  static void setpos(int row, int col);

  // get the cursor position
  static void getpos(int *row, int *col);

  // get the screen size Screen::rows and Screen::cols, returns Screen::cols
  static size_t getsize();

  // setup screen using an alternative screen buffer and optional title
  static bool setup(const char *title = NULL);

  // cleanup to restore main screen buffer
  static void cleanup();

  // return character width, 0 (invalid character), 1 (single width) or 2 (double width)
  static int wchar_width(uint32_t wc);

  // return UCS-4 code of the specified UTF-8 sequence, or 0 for invalid UTF-8
  static uint32_t wchar(const char *ptr, const char **endptr);

  // return character width of the specified UTF-8 sequence, 0 (invalid or non-spacing character), 1 (single width) or 2 (double width)
  static int mbchar_width(const char *ptr, const char **endptr)
  {
    return wchar_width(wchar(ptr, endptr));
  }

  // return the total number of screen columns to display a string
  static int mbstring_width(const char *ptr)
  {
    int num = 0;
    while (*ptr != '\0')
      num += mbchar_width(ptr++, NULL);
    return num;
  }

  // return pointer to string after pos screen columns
  static const char *mbstring_pos(const char *ptr, int pos)
  {
    while (pos > 0 && *ptr != '\0')
      if (wchar(ptr, &ptr) > 0)
        --pos;
    const char *skp = ptr;
    while (*skp != '\0' && wchar(skp, &skp) == 0)
      ptr = skp;
    return ptr;
  }

  // return pointer to string after pos screen columns
  static char *mbstring_pos(char *ptr, int pos)
  {
    while (pos > 0 && *ptr != '\0')
      if (wchar(ptr, const_cast<const char**>(&ptr)) > 0)
        --pos;
    char *skp = ptr;
    while (*skp != '\0' && wchar(skp, const_cast<const char**>(&skp)) == 0)
      ptr = skp;
    return ptr;
  }

  // emit text
  static void put(const char *text, size_t size)
  {
#ifdef OS_WIN
    DWORD nwritten;
    good = WriteFile(hConOut, text, static_cast<DWORD>(size), &nwritten, NULL) && good;
#else
    good = write(tty, text, size) == static_cast<ssize_t>(size) && good;
#endif
  }

  // emit character
  static void put(int ch)
  {
    char buf = static_cast<char>(ch);
    put(&buf, 1);
  }

  // emit text
  static void put(const char *text)
  {
    put(text, strlen(text));
  }

  // emit text at the specified screen position, where (0,0) is home, return row number of the updated cursor position
  static int put(int row, int col, const std::string& text, int skip = 0, int wrap = -1, int nulls = 0)
  {
    return put(row, col, text.c_str(), text.size(), skip, wrap, nulls);
  }

  // emit text at the specified screen position, where (0,0) is home, return row number updated when wrap >= 0
  static int put(int row, int col, const char *text, size_t size = std::string::npos, int skip = 0, int wrap = -1, int nulls = 0);

  static int  rows; // number of screen rows
  static int  cols; // number of screen columns
  static bool mono; // monochrome
  static bool good; // true if all previous screen operations were successful after setup()

#ifdef OS_WIN

  // Windows console
  static HANDLE hConOut;
  static DWORD  oldOutMode;

#else

  // Unix/Linux terminal
  static int tty;

#endif

 protected:

  // convert integer to text
  static void itoa(int num, char **pptr);

  // SIGWINCH signal handler
  static void sigwinch(int);

  static bool sel;
  static bool double_width;
  static bool double_width_Emoji;
  static bool double_width_CJK;

};

#endif
