/*  ardu-term - terminal software for PC. This soft send data to Arduino/TellyMate text terminal. 
    Copyright (C) 2010 Yana Artishcheva

    To build the program use command:
    $ gcc ardu-term.c -Wall

    ardu-term is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    ardu-term is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with AdVegam Server; if not, write to the Free Software
    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA

    E-mail: yana_artis @ advegam . com (remove "anti-spam" spaces) */

#define ARDUINO_CONNECTED

#define _XOPEN_SOURCE 600
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <wait.h>
#include <stdio.h>
#include <fcntl.h>
#include <string.h>
#include <sys/ioctl.h>
#include <termios.h>
#include <signal.h>
#include <poll.h>
#include <errno.h>

#include <pty.h>

#define SCRBUFSZ 8192
#define KEYBUFSZ 8192
int i, err;

#define PPOS_PTY 0
#define PPOS_DEVKEY 1
#define PPOS_DEVSCR 2
#define POLLFDNUM 3

#ifdef ARDUINO_CONNECTED
int h = -1;
#endif
struct termios pt_saved;      // Структура исходных характеристик терминала

FILE *fLog = NULL;

// Действия при завершении процесса
static void termination(int errcode) {
  (void)tcsetattr(0, TCSANOW, &pt_saved);
#ifdef ARDUINO_CONNECTED
  if (h > 0) {
    close(h);
  }
#endif
  if (fLog) {
    fclose(fLog);
  }
  exit (errcode);
}

// Функция обработки сигнала SIGCHLD
static void chldied (int dummy) {
  // Просто кончимся
  termination (34);
}

int main(int argc, char ** argv) {
  pid_t pid;
  int pty, tty;           // Дескрипторы обеих сторон псевдотерминала
  struct termios pt;      // Структура характеристик псевдотерминала
  struct sigaction sact;
  int scrW = 38;
  int scrH = 25;

#ifdef ARDUINO_CONNECTED
  struct termios at;      // Структура характеристик Arduino-терминала
  char *sOutPort = "/dev/ttyUSB0";
#endif

  (void) tcgetattr (0, &pt_saved);

  // Откроем псевдотерминал
  if ( ((pty = posix_openpt(O_RDWR | O_NOCTTY)) < 0)
    || (unlockpt(pty) == -1)
    || (grantpt(pty) == -1)
    || ((tty = open(ptsname (pty), O_RDWR)) < 0)) {
    fprintf(stderr, "Не удалось открыть псевдотерминал\n");
    perror("POSIX_OPENPT");
    return(1);
  }

  // Установим подходящие характеристики псевдотерминала
  if (tcgetattr (pty, &pt) < 0) {
    perror ("PTY TERMIOS GET ERROR");
    return (2);
  }
  pt.c_iflag = 0;
  pt.c_oflag = ONLCR;
  pt.c_cflag = CS8 | HUPCL;
  pt.c_lflag = ISIG | ICANON | ECHO | ECHOE | ECHOK;
  pt.c_cc [VINTR] = 3;   /* CTRL+C */
  pt.c_cc [VEOF] = 4;    /* CTRL+D */
  if (tcsetattr (pty, TCSADRAIN, &pt) < 0) {
    perror ("PTY TERMIOS SET ERROR");
    return (3);
  }

  // То же – для стандартного ввода
  (void) tcgetattr (0, &pt);
  pt.c_lflag &= ~ISIG;
  pt.c_lflag &= ~ICANON;
  pt.c_lflag &= ~ECHO;
  pt.c_cc[VMIN] = 1;
  pt.c_cc[VTIME] = 0;
  if (tcsetattr (0, TCSANOW, &pt) < 0) {
    perror ("STDIN TERMIOS SET ERROR");
    return (3);
  }

  // Установим обработку сигнала о завершении потомка
  sact.sa_handler = chldied;
  (void) sigemptyset (&sact.sa_mask);
  sact.sa_flags = 0;
  (void) sigaction (SIGCHLD, &sact, (struct sigaction *) NULL);

  // Раздвоимся на процесс чтения с клавиатуры и вывода на экран и на процесс, в рамках которого запустим shell

  if ((pid = fork ()) < 0) {
    perror ("FORK1 ERROR");
    termination (-1);
  } else if (pid) {
    // Это родительский процесс, читающий с клавиатуры и выводящий на экран
    unsigned char ch;
    struct pollfd fds[POLLFDNUM];  // Массив параметров для вызова poll
    close (tty);

    fLog = fopen("ardu-term.log","wt");

#ifdef ARDUINO_CONNECTED
    if ((h = open(sOutPort, O_RDWR)) < 1) {
      perror ("Cannot open Arduino port.");
      termination(0);
    }
    if (tcgetattr (h, &at) < 0) {
      perror ("TTY TERMIOS GET ERROR");
      termination(0);
    }
    if (cfsetospeed(&at, B57600) < 0) {
      perror ("TTY cfsetospeed() ERROR");
      termination(0);
    }
    (void) tcsetattr(h, TCSANOW, &at);
#endif

    // Будем ждать ввода с клавиатуры или псевдотерминала
    fds[0].fd = 0;
    fds[0].events = POLLIN;
    fds[1].fd = pty;
    fds[1].events = POLLIN;

    while (1) {
      if (poll (fds, 2, -1) < 0) {
        fprintf(stderr, "Poll syscall error: %s", strerror(errno));
        termination (3);
      }
      if (fds[0].revents & POLLIN) {
        // Пришел символ со стандартного ввода
        do {
          err = read (0, &ch, 1);
          if (err < 1) {
            if ((err == 0) || (errno == EAGAIN)) {
              continue;
            } else {
              fprintf(stderr, "Error reading keyboard device: %s\n", strerror(errno) );
              termination (3);
            }
          } else {
            do {
              err = write (pty, &ch, 1);
              if (err < 1) {
                if (errno != EAGAIN) {
                  fprintf(stderr, "Error writing to pty: %s\n", strerror(errno) );
                  termination (3);
                }
              }
            } while (err < 1);
          }
        } while (err < 1);
      }

      if (fds[1].revents & POLLIN) {

        // Пришел символ с псевдотерминала
        do {

          err = read (pty, &ch, 1);
          if (err < 1) {
            if ((err == 0) || (errno == EAGAIN)) {
              continue;
            } else {
              fprintf(stderr, "Error reading from pty: %s\n", strerror(errno) );
              termination (3);
            }
          } else {
            // Process the incoming character
//          switch (ch) {
//          }

            do {
              err = write (1, &ch, 1);
              if (err < 1) {
                if (errno != EAGAIN) {
                  fprintf(stderr, "Error writing to stdout: %s\n", strerror(errno) );
                  termination (3);
                }
              }
            } while (err < 1);

            fprintf(fLog, "%03d - %c\n", (int)ch, ch);

#ifdef ARDUINO_CONNECTED
            do {
              err = write(h, &ch, 1);
              if (err < 1) {
                if (errno != EAGAIN) {
                  fprintf(stderr, "Error writing to Arduino: %s\n", strerror(errno) );
                  termination (3);
                }
              }
            } while (err < 1);
            usleep(100);
#endif
          }
        } while (err < 1); // do { Пришел символ с псевдотерминала
      } // if (fds[1].revents & POLLIN)
    } // while (1)
  } else {
    // Порожденный процесс – запустим в нем shell
    // Закроем все файлы, кроме псевдотерминала
    for (i = 0; i < RLIMIT_NOFILE; i++) {
      if (i != tty) {
        (void) close (i);
      }
    }

    // Сделаем процесс лидером сеанса
    (void) setsid ();

    // Свяжем стандартные ввод, вывод и протокол с псевдотерминалом
    (void) fcntl (tty, F_DUPFD, 0);
    (void) fcntl (tty, F_DUPFD, 0);
    (void) fcntl (tty, F_DUPFD, 0);
// ??? ПОЧЕМУ ВСЕ 3 СТРОКИ - ОДИНАКОВЫ???
    close (tty);
    // Поместим в окружение параметры псевдотерминала */
    {
      char lnbuf[20];
      char clbuf[20];
      char *termbuf = "TERM=vt52";
      char *termprompt = "PS2=\"\\u\\$\""; // http://stfw.ru/page.php?id=9950

      sprintf (lnbuf, "LINES=%2d", scrH);
      sprintf (clbuf, "COLUMNS=%2d", scrW);

      putenv (termbuf);
      putenv (termprompt);
      putenv (lnbuf);
      putenv (clbuf);
    }

//    if (execl ("/bin/sh", "sh", (char *) NULL) < 0) {
    if (execl ("/bin/bash", "bash", (char *) NULL) < 0) {
      perror ("EXECL ERROR");
      termination(-1);
    }
  }
  termination(0);
  return 0;
}

