/*
 * 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-2024 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 <string.h>
#include <fcntl.h>

#include "log.h"
#include "pty_object.h"
#include "scr_types.h"

struct PtyObjectStruct {
  char *path;
  int master;

  unsigned char logLevel;
  unsigned char logInput:1;
};

PtyObject *
ptyNewObject (void) {
  PtyObject *pty;

  if ((pty = malloc(sizeof(*pty)))) {
    memset(pty, 0, sizeof(*pty));

    pty->path = NULL;
    pty->master = INVALID_FILE_DESCRIPTOR;

    pty->logLevel = LOG_DEBUG;
    pty->logInput = 0;

    if ((pty->master = posix_openpt(O_RDWR)) != -1) {
      if ((pty->path = ptsname(pty->master))) {
        if ((pty->path = strdup(pty->path))) {
          if (grantpt(pty->master) != -1) {
            if (unlockpt(pty->master) != -1) {
              return pty;
            } else {
              logSystemError("unlockpt");
            }
          } else {
            logSystemError("grantpt");
          }

          free(pty->path);
        } else {
          logMallocError();
        }
      } else {
        logSystemError("ptsname");
      }

      close(pty->master);
    } else {
      logSystemError("posix_openpt");
    }

    free(pty);
  } else {
    logMallocError();
  }

  return NULL;
}

const char *
ptyGetPath (const PtyObject *pty) {
  return pty->path;
}

int
ptyGetMaster (const PtyObject *pty) {
  return pty->master;
}

void
ptySetLogLevel (PtyObject *pty, unsigned char level) {
  pty->logLevel = level;
}

void
ptySetLogInput (PtyObject *pty, int yes) {
  pty->logInput = yes;
}

int
ptyWriteInputData (PtyObject *pty, const void *data, size_t length) {
  if (pty->logInput) {
    logBytes(pty->logLevel, "pty input", data, length);
  }

  if (write(pty->master, data, length) != -1) return 1;
  logSystemError("pty write input");
  return 0;
}

int
ptyWriteInputCharacter (PtyObject *pty, wchar_t character, int kxMode) {
  if (!isSpecialKey(character)) {
    char buffer[MB_CUR_MAX];
    int count = wctomb(buffer, character);
    if (count == -1) return 0;
    return ptyWriteInputData(pty, buffer, count);
  }

  const char *sequence = NULL;
  char buffer[0X20];

  #define KEY(key, seq) case SCR_KEY_##key: sequence = seq; break;
  switch (character) {
    KEY(ENTER       , "\r")
    KEY(TAB         , "\t")
    KEY(BACKSPACE   , "\x7F")
    KEY(ESCAPE      , "\x1B")

    KEY(CURSOR_UP   , "\x1BOA")
    KEY(CURSOR_DOWN , "\x1BOB")
    KEY(CURSOR_RIGHT, "\x1BOC")
    KEY(CURSOR_LEFT , "\x1BOD")

    KEY(HOME        , "\x1B[1~")
    KEY(INSERT      , "\x1B[2~")
    KEY(DELETE      , "\x1B[3~")
    KEY(END         , "\x1B[4~")
    KEY(PAGE_UP     , "\x1B[5~")
    KEY(PAGE_DOWN   , "\x1B[6~")

    KEY(F1          , "\x1BOP")
    KEY(F2          , "\x1BOQ")
    KEY(F3          , "\x1BOR")
    KEY(F4          , "\x1BOS")
    KEY(F5          , "\x1B[15~")
    KEY(F6          , "\x1B[17~")
    KEY(F7          , "\x1B[18~")
    KEY(F8          , "\x1B[19~")
    KEY(F9          , "\x1B[20~")
    KEY(F10         , "\x1B[21~")
    KEY(F11         , "\x1B[23~")
    KEY(F12         , "\x1B[24~")

    default:
      logMessage(LOG_WARNING, "unsupported pty screen key: %04X", character);
      break;
  }
  #undef KEY

  if (sequence) {
    switch (character) {
      case SCR_KEY_CURSOR_LEFT:
      case SCR_KEY_CURSOR_RIGHT:
      case SCR_KEY_CURSOR_UP:
      case SCR_KEY_CURSOR_DOWN:
        strcpy(buffer, sequence);
        buffer[1] = kxMode? 'O': '[';
        sequence = buffer;
        break;
    }

    if (!ptyWriteInputData(pty, sequence, strlen(sequence))) {
      return 0;
    }
  }

  return 1;
}

void
ptyCloseMaster (PtyObject *pty) {
  if (pty->master != INVALID_FILE_DESCRIPTOR) {
    close(pty->master);
    pty->master = INVALID_FILE_DESCRIPTOR;
  }
}

int
ptyOpenSlave (const PtyObject *pty, int *fileDescriptor) {
  int result = open(pty->path, O_RDWR);
  int opened = result != INVALID_FILE_DESCRIPTOR;

  if (opened) {
    *fileDescriptor = result;
  } else {
    logSystemError("pty slave open");
  }

  return opened;
}

void
ptyDestroyObject (PtyObject *pty) {
  ptyCloseMaster(pty);
  free(pty->path);
  free(pty);
}
