/*
 * 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-2021 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 <errno.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <hurd/console.h>

#include "log.h"
#include "brl_cmds.h"
#include "utf8.h"

#include "scr_driver.h"
#include "screen.h"
#include "unicode.h"

static char *
vtPath (const char *base, unsigned char vt) {
  size_t length = strlen(base);
  char buffer[length+1];
  snprintf(buffer, length, base, vt);
  buffer[length] = 0;
  return strdup(buffer);
}

static int
currentVt(void) {
  char link[]=HURD_VCSDIR "/00";
  char *c,*d;
  int size,ret;
  if ((size = readlink(HURD_CURVCSPATH,link,sizeof(link))) == -1) {
    /* console may not be started yet or in X mode, don't flood */
    if (errno != ENOENT && errno != ENOTDIR && errno != ENODEV)
      logSystemError("reading " HURD_CURVCSPATH " link");
    return 1;
  }
  link[size]='\0';
  if (!(c = strrchr(link,'/')))
    c = link;
  else
    c++;
  if (!*c)
    /* bad number */
    return 1;
  ret = strtol(c,&d,10);
  if (*d)
    /* bad number */
    return 1;
  return ret;
}

static int
openDevice (const char *path, const char *description, int flags) {
  int file;
  logMessage(LOG_DEBUG, "Opening %s device: %s", description, path);
  if ((file = open(path, flags)) == -1) {
    logMessage(LOG_ERR, "Cannot open %s device: %s: %s",
               description, path, strerror(errno));
  }
  return file;
}

static const char *const consolePath = HURD_INPUTPATH;
static int consoleDescriptor;

static void
closeConsole (void) {
  if (consoleDescriptor != -1) {
    if (close(consoleDescriptor) == -1) {
      logSystemError("Console close");
    }
    logMessage(LOG_DEBUG, "Console closed: fd=%d", consoleDescriptor);
    consoleDescriptor = -1;
  }
}

static int
openConsole (unsigned char vt) {
  char *path = vtPath(consolePath, vt?vt:currentVt());
  if (path) {
    int console = openDevice(path, "console", O_RDWR|O_NOCTTY);
    if (console != -1) {
      closeConsole();
      consoleDescriptor = console;
      logMessage(LOG_DEBUG, "Console opened: %s: fd=%d", path, consoleDescriptor);
      free(path);
      return 1;
    }
    logSystemError("Console open");
    free(path);
  }
  return 0;
}

static const char *const screenPath = HURD_DISPLAYPATH;
static int screenDescriptor;
static const struct cons_display *screenMap;
static size_t screenMapSize;
#define screenDisplay ((conchar_t *)((wchar_t *) screenMap + screenMap->screen.matrix))
static unsigned char virtualTerminal; /* currently shown (0 means system's) */
static unsigned char lastReadVt; /* last shown */

static void
closeScreen (void) {
  if (screenDescriptor != -1) {
    if (munmap((void *) screenMap, screenMapSize) == -1) {
      logSystemError("Screen unmap");
    }
    if (close(screenDescriptor) == -1) {
      logSystemError("Screen close");
    }
    logMessage(LOG_DEBUG, "Screen closed: fd=%d mmap=%p", screenDescriptor, screenMap);
    screenDescriptor = -1;
    screenMap = NULL;
  }
}

static int
openScreen (unsigned char vt) {
  char *path = vtPath(screenPath, vt?vt:currentVt());
  if (path) {
    int screen = openDevice(path, "screen", O_RDONLY);
    if (screen != -1) {
      struct stat st;
      if (fstat(screen, &st) != -1) {
        const struct cons_display *map = mmap(NULL, st.st_size, PROT_READ, MAP_SHARED, screen, 0);
        if (map != MAP_FAILED) {
          if (map->magic == CONS_MAGIC && map->version >> CONS_VERSION_MAJ_SHIFT ==0 && openConsole(vt)) {
            closeScreen();
            screenDescriptor = screen;
            screenMap = map;
            screenMapSize = st.st_size;
            logMessage(LOG_DEBUG, "Screen opened: %s: fd=%d map=%p", path, screenDescriptor, screenMap);
            free(path);
            return 1;
          }
          munmap((void *) map, st.st_size);
        } else logSystemError("Screen map");
      } else logSystemError("Getting size of Screen");
      close(screen);
    }
    free(path);
  }
  return 0;
}

static int
processParameters_HurdScreen (char **parameters) {
  return 1;
}

static int
construct_HurdScreen (void) {
  screenDescriptor = -1;
  consoleDescriptor = -1;
  return openScreen(0);
}

static void
destruct_HurdScreen (void) {
  closeConsole();
  closeScreen();
}

static void
getScreenDescription (ScreenDescription *description) {
  description->rows = screenMap->screen.height;
  description->cols = screenMap->screen.width;
  description->posx = screenMap->cursor.col;
  description->posy = screenMap->cursor.row;
}

static void
getConsoleDescription (ScreenDescription *description) {
  description->number = virtualTerminal ? virtualTerminal : currentVt();
}

static void
describe_HurdScreen (ScreenDescription *description) {
  getScreenDescription(description);
  getConsoleDescription(description);
}

#ifndef offsetof
#define offsetof(type,field) ((size_t) &((type *) 0)->field)
#endif
static int
readCharacters_HurdScreen (const ScreenBox *box, ScreenCharacter *buffer) {
  uint32_t lines, start, row, col;
  ScreenDescription description;
  describe_HurdScreen(&description);
  if (lastReadVt != description.number) {
    openScreen(description.number);
    lastReadVt = description.number;
  }
  if (!validateScreenBox(box, description.cols, description.rows)) return 0;

  lines = screenMap->screen.lines;
  start = screenMap->screen.cur_line;
  for (row=start+box->top; row<start+box->top+box->height; ++row)
    for (col=box->left; col<box->left+box->width; ++col) {
      wchar_t text;
      conchar_attr_t attr;
      text = screenDisplay[(row%lines)*description.cols+col].chr;
#ifdef CONS_WCHAR_MASK
      text &= CONS_WCHAR_MASK;
#endif
#ifdef CONS_WCHAR_CONTINUED
      if (text & CONS_WCHAR_CONTINUED)
	text = UNICODE_ZERO_WIDTH_SPACE;
#endif
      buffer->text = text;
      attr = screenDisplay[(row%lines)*description.cols+col].attr;
      buffer->attributes = attr.fgcol | (attr.bgcol << 4)
	| (attr.intensity == CONS_ATTR_INTENSITY_BOLD?SCR_ATTR_FG_BRIGHT:0)
	| (attr.blinking?SCR_ATTR_BLINK:0);
      buffer++;
    }
  return 1;
}

static int
insertByte (unsigned char byte) {
  if (write(consoleDescriptor, &byte, 1) != -1)
    return 1;
  logSystemError("Console write");
  return 0;
}

static int
insertMapped (ScreenKey key, int (*insertCharacter)(wchar_t character)) {
  wchar_t buffer[2];
  wchar_t *sequence;
  wchar_t *end;

  setScreenKeyModifiers(&key, 0);

  if (isSpecialKey(key)) {
    switch (key & SCR_KEY_CHAR_MASK) {
      case SCR_KEY_ENTER:
        sequence = WS_C("\r");
        break;
      case SCR_KEY_TAB:
        sequence = WS_C("\t");
        break;
      case SCR_KEY_BACKSPACE:
        sequence = WS_C("\x7f");
        break;
      case SCR_KEY_ESCAPE:
        sequence = WS_C("\x1b");
        break;
      case SCR_KEY_CURSOR_LEFT:
        sequence = WS_C("\x1b[D");
        break;
      case SCR_KEY_CURSOR_RIGHT:
        sequence = WS_C("\x1b[C");
        break;
      case SCR_KEY_CURSOR_UP:
        sequence = WS_C("\x1b[A");
        break;
      case SCR_KEY_CURSOR_DOWN:
        sequence = WS_C("\x1b[B");
        break;
      case SCR_KEY_PAGE_UP:
        sequence = WS_C("\x1b[5~");
        break;
      case SCR_KEY_PAGE_DOWN:
        sequence = WS_C("\x1b[6~");
        break;
      case SCR_KEY_HOME:
        sequence = WS_C("\x1b[1~");
        break;
      case SCR_KEY_END:
        sequence = WS_C("\x1b[4~");
        break;
      case SCR_KEY_INSERT:
        sequence = WS_C("\x1b[2~");
        break;
      case SCR_KEY_DELETE:
        sequence = WS_C("\x1b[3~");
        break;
      case SCR_KEY_FUNCTION + 0:
        sequence = WS_C("\x1bOP");
        break;
      case SCR_KEY_FUNCTION + 1:
        sequence = WS_C("\x1bOQ");
        break;
      case SCR_KEY_FUNCTION + 2:
        sequence = WS_C("\x1bOR");
        break;
      case SCR_KEY_FUNCTION + 3:
        sequence = WS_C("\x1bOS");
        break;
      case SCR_KEY_FUNCTION + 4:
        sequence = WS_C("\x1b[15~");
        break;
      case SCR_KEY_FUNCTION + 5:
        sequence = WS_C("\x1b[17~");
        break;
      case SCR_KEY_FUNCTION + 6:
        sequence = WS_C("\x1b[18~");
        break;
      case SCR_KEY_FUNCTION + 7:
        sequence = WS_C("\x1b[19~");
        break;
      case SCR_KEY_FUNCTION + 8:
        sequence = WS_C("\x1b[20~");
        break;
      case SCR_KEY_FUNCTION + 9:
        sequence = WS_C("\x1b[21~");
        break;
      case SCR_KEY_FUNCTION + 10:
        sequence = WS_C("\x1b[23~");
        break;
      case SCR_KEY_FUNCTION + 11:
        sequence = WS_C("\x1b[24~");
        break;
      case SCR_KEY_FUNCTION + 12:
        sequence = WS_C("\x1b[25~");
        break;
      case SCR_KEY_FUNCTION + 13:
        sequence = WS_C("\x1b[26~");
        break;
      case SCR_KEY_FUNCTION + 14:
        sequence = WS_C("\x1b[28~");
        break;
      case SCR_KEY_FUNCTION + 15:
        sequence = WS_C("\x1b[29~");
        break;
      case SCR_KEY_FUNCTION + 16:
        sequence = WS_C("\x1b[31~");
        break;
      case SCR_KEY_FUNCTION + 17:
        sequence = WS_C("\x1b[32~");
        break;
      case SCR_KEY_FUNCTION + 18:
        sequence = WS_C("\x1b[33~");
        break;
      case SCR_KEY_FUNCTION + 19:
        sequence = WS_C("\x1b[34~");
        break;
      default:
        logMessage(LOG_WARNING, "Key %04X not suported in ANSI mode.", key);
        return 0;
    }
    end = sequence + wcslen(sequence);
  } else {
    sequence = end = buffer + ARRAY_COUNT(buffer);
    *--sequence = key & SCR_KEY_CHAR_MASK;

    if (key & SCR_KEY_ALT_LEFT)
      *--sequence = 0X1B;
  }

  while (sequence != end)
    if (!insertCharacter(*sequence++))
      return 0;
  return 1;
}

static int
insertUtf8 (wchar_t character) {
  Utf8Buffer utf8;
  size_t utfs = convertWcharToUtf8(character, utf8);
  int i;
  for (i=0; i<utfs; ++i)
    if (!insertByte(utf8[i]))
      return 0;
  return 1;
}

static int
insertKey_HurdScreen (ScreenKey key) {
  logMessage(LOG_DEBUG, "Insert key: %4.4X", key);
  return insertMapped(key, insertUtf8); 
}

static int
validateVt (int vt) {
  if ((vt >= 1) && (vt <= 99)) return 1;
  logMessage(LOG_DEBUG, "Virtual terminal %d is out of range.", vt);
  return 0;
}

static int
selectVirtualTerminal_HurdScreen (int vt) {
  if (vt == virtualTerminal) return 1;
  if (vt && !validateVt(vt)) return 0;
  return openScreen(vt);
}

static int
switchVirtualTerminal_HurdScreen (int vt) {
  if (validateVt(vt)) {
    char link[]=HURD_VCSDIR "/00";
    snprintf(link, sizeof(link), HURD_VCSDIR "/%u", vt);
    if (symlink(link, HURD_CURVCSPATH) != -1) {
      logMessage(LOG_DEBUG, "Switched to virtual terminal %d.", vt);
      return 1;
    } else {
      logSystemError("symlinking to switch vt");
    }
  }
  return 0;
}

static int
currentVirtualTerminal_HurdScreen (void) {
  ScreenDescription description;
  getConsoleDescription(&description);
  return description.number;
}

static void
scr_initialize (MainScreen *main) {
  initializeRealScreen(main);
  main->base.describe = describe_HurdScreen;
  main->base.readCharacters = readCharacters_HurdScreen;
  main->base.insertKey = insertKey_HurdScreen;
  main->base.selectVirtualTerminal = selectVirtualTerminal_HurdScreen;
  main->base.switchVirtualTerminal = switchVirtualTerminal_HurdScreen;
  main->base.currentVirtualTerminal = currentVirtualTerminal_HurdScreen;
  main->processParameters = processParameters_HurdScreen;
  main->construct = construct_HurdScreen;
  main->destruct = destruct_HurdScreen;
}
