/*
 * BRLTTY - A background process providing access to the console screen (when in
 *          text mode) for a blind person using a refreshable braille display.
 *
 * Copyright (C) 1995-2025 by The BRLTTY Developers.
 *
 * BRLTTY comes with ABSOLUTELY NO WARRANTY.
 *
 * This is free software, placed under the terms of the
 * GNU Lesser General Public License, as published by the Free Software
 * Foundation; either version 2.1 of the License, or (at your option) any
 * later version. Please see the file LICENSE-LGPL for details.
 *
 * Web Page: http://brltty.app/
 *
 * This software is maintained by Dave Mielke <dave@mielke.cc>.
 */

#include "prologue.h"

#include <stdio.h>
#include <string.h>

#include "log.h"
#include "scr.h"
#include "scr_utils.h"
#include "scr_base.h"
#include "scr_internal.h"
#include "ascii.h"

void
setScreenKeyModifiers (ScreenKey *key, ScreenKey which) {
  if (!isSpecialKey(*key)) {
    wchar_t character = *key & SCR_KEY_CHAR_MASK;
    ScreenKey modifiers = *key & ~SCR_KEY_CHAR_MASK;

    if (which & (SCR_KEY_UPPER | SCR_KEY_SHIFT)) {
      if (!(modifiers & (SCR_KEY_UPPER | SCR_KEY_SHIFT))) {
        if (iswupper(character)) {
          character = towlower(character);

          if (which & SCR_KEY_UPPER) {
            modifiers |= SCR_KEY_UPPER;
          } else {
            modifiers |= SCR_KEY_SHIFT;
          }
        }
      }
    } else {
      if (modifiers & (SCR_KEY_UPPER | SCR_KEY_SHIFT)) {
        if (iswalpha(character)) {
          character = towupper(character);
          modifiers &= ~SCR_KEY_SHIFT;
        }

        modifiers &= ~SCR_KEY_UPPER;
      }
    }

    if (which & SCR_KEY_CONTROL) {
      if (!(modifiers & SCR_KEY_CONTROL)) {
        if (character < 0X20) {
          character |= 0X60;
          modifiers |= SCR_KEY_CONTROL;
        }
      }
    } else {
      if (modifiers & SCR_KEY_CONTROL) {
        if (character <= 0X7F) {
          if ((character & 0X6F) == 0X2F) {
            character |= 0X50;
          } else {
            character &= 0X1F;
          }
        }

        modifiers &= ~SCR_KEY_CONTROL;
      }
    }

    ScreenKey newKey = character | modifiers;
    if (newKey != *key) {
      logMessage(LOG_CATEGORY(SCREEN_DRIVER), "transformed key: 0X%04X -> 0X%04X", *key, newKey);
      *key = newKey;
    }
  }
}

void
mapScreenKey (ScreenKey *key) {
  switch (*key & SCR_KEY_CHAR_MASK) {
    case '\n':
    case '\r':   *key = SCR_KEY_ENTER;     break;
    case '\t':   *key = SCR_KEY_TAB;       break;
    case '\b':   *key = SCR_KEY_BACKSPACE; break;
    case ASCII_ESC: *key = SCR_KEY_ESCAPE;    break;
  }
}

static const char text_BaseScreen[] = " ";

static int
currentVirtualTerminal_BaseScreen (void) {
  return 0;
}

static int
selectVirtualTerminal_BaseScreen (int vt) {
  return 0;
}

static int
switchVirtualTerminal_BaseScreen (int vt) {
  return 0;
}

static int
nextVirtualTerminal_BaseScreen (void) {
  return currentScreen->switchVirtualTerminal(currentScreen->currentVirtualTerminal() + 1);
}

static int
previousVirtualTerminal_BaseScreen (void) {
  return currentScreen->switchVirtualTerminal(currentScreen->currentVirtualTerminal() - 1);
}

static const char *
getTitle_BaseScreen (void) {
  return NULL;
}

static void
onForeground_BaseScreen (void) {
}

static void
onBackground_BaseScreen (void) {
}

static int
poll_BaseScreen (void) {
  return 0;
}

static int
refresh_BaseScreen (void) {
  return 1;
}

static void
describe_BaseScreen (ScreenDescription *description) {
  description->quality = SCQ_NONE;
  description->rows = 1;
  description->cols = strlen(text_BaseScreen);
  description->posx = 0;
  description->posy = 0;
  description->number = currentVirtualTerminal_BaseScreen();
}

static int
readCharacters_BaseScreen (const ScreenBox *box, ScreenCharacter *buffer) {
  ScreenDescription description;
  describe_BaseScreen(&description);
  if (!validateScreenBox(box, description.cols, description.rows)) return 0;
  setScreenMessage(box, buffer, text_BaseScreen);
  return 1;
}

static int
insertKey_BaseScreen (ScreenKey key) {
  return 0;
}

static ScreenPasteMode
getPasteMode_BaseScreen (void) {
  return SPM_UNKNOWN;
}

static int
routeCursor_BaseScreen (int column, int row, int screen) {
  return 0;
}

static int
highlightRegion_BaseScreen (int left, int right, int top, int bottom) {
  return 0;
}

int
unhighlightRegion_BaseScreen (void) {
  return 0;
}

static int
getPointer_BaseScreen (int *column, int *row) {
  return 0;
}

static int
clearSelection_BaseScreen (void) {
  return 0;
}

static int
setSelection_BaseScreen (int startColumn, int startRow, int endColumn, int endRow) {
  return 0;
}

static int
handleCommand_BaseScreen (int command) {
  return 0;
}

static KeyTableCommandContext
getCommandContext_BaseScreen (void) {
  return KTB_CTX_DEFAULT;
}

void
initializeBaseScreen (BaseScreen *base) {
  base->getTitle = getTitle_BaseScreen;
  base->onForeground = onForeground_BaseScreen;
  base->onBackground = onBackground_BaseScreen;

  base->poll = poll_BaseScreen;
  base->refresh = refresh_BaseScreen;
  base->describe = describe_BaseScreen;

  base->readCharacters = readCharacters_BaseScreen;
  base->insertKey = insertKey_BaseScreen;
  base->getPasteMode = getPasteMode_BaseScreen;
  base->routeCursor = routeCursor_BaseScreen;

  base->highlightRegion = highlightRegion_BaseScreen;
  base->unhighlightRegion = unhighlightRegion_BaseScreen;
  base->getPointer = getPointer_BaseScreen;

  base->clearSelection = clearSelection_BaseScreen;
  base->setSelection = setSelection_BaseScreen;

  base->currentVirtualTerminal = currentVirtualTerminal_BaseScreen;
  base->selectVirtualTerminal = selectVirtualTerminal_BaseScreen;
  base->switchVirtualTerminal = switchVirtualTerminal_BaseScreen;
  base->nextVirtualTerminal = nextVirtualTerminal_BaseScreen;
  base->previousVirtualTerminal = previousVirtualTerminal_BaseScreen;

  base->handleCommand = handleCommand_BaseScreen;
  base->getCommandContext = getCommandContext_BaseScreen;
}

void
describeScreenObject (ScreenDescription *description, BaseScreen *screen) {
  description->number = 0;
  description->hasCursor = 1;
  description->hasSelection = 0;

  description->unreadable = NULL;
  description->quality = SCQ_GOOD;

  description->cols = description->rows = 1;
  description->posx = description->posy = 0;

  screen->describe(description);

  if (description->unreadable) {
    description->hasCursor = 0;
  }
}

int
validateScreenBox (const ScreenBox *box, int columns, int rows) {
  if ((box->left >= 0))
    if ((box->width > 0))
      if (((box->left + box->width) <= columns))
        if ((box->top >= 0))
          if ((box->height > 0))
            if (((box->top + box->height) <= rows))
              return 1;

  logMessage(LOG_ERR, "invalid screen area: cols=%d left=%d width=%d rows=%d top=%d height=%d",
             columns, box->left, box->width,
             rows, box->top, box->height);
  return 0;
}

void
setScreenMessage (const ScreenBox *box, ScreenCharacter *buffer, const char *message) {
  const ScreenCharacter *end = buffer + box->width;
  unsigned int index = 0;
  size_t length = strlen(message);
  mbstate_t state;

  memset(&state, 0, sizeof(state));
  clearScreenCharacters(buffer, (box->width * box->height));

  while (length) {
    wchar_t wc;
    size_t result = mbrtowc(&wc, message, length, &state);
    if ((ssize_t)result < 1) break;

    message += result;
    length -= result;

    if (index++ >= box->left) {
      if (buffer == end) break;
      (buffer++)->text = wc;
    }
  }
}
