/*
 *  fntlda.c - демонстрационная программа для программирования
 *             терминала К-280 v.1.65
 *
 */

#include <stdio.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#ifndef DOS
#include <unistd.h>
#include <termio.h>
#else
#include <bios.h>
#define _COM_CHR8 3
#define _COM_STOP1 0
#define _COM_NOPARITY 0
#define _COM_9600 0xE0
int port = 0;         /* COM-port, COM1 by default    */
#endif

#define FALSE 0
#define TRUE 1

#define MAX_NAME 80
#define FNT_LEN 4096

extern int optind, opterr, optopt;
extern char *optarg;

#ifdef DOS
char usage[] = {
"\nUsage :\n\n fntlda [/q] [/r] [/p com-port] [/f font_file] [/1 kbd_file] [/2 kbd_file]\n\
\t[/F fkb_file] [/m map_file] [/M print_map_file] [/d cod]\n\
\nwhere:\n\
\tq - Quiet mode (don't show character table at the end)\n\
\tp - COM-port number. Parameter com-port may be 1 (default) or 2\n\
\tr - Restore defaults\n\
\tf - Load 8x16 VGA font from font_file\n\
\t1 - Load keyboard definition for 1st alternative set (RUS)\n\
\t    from kbddef_file\n\
\t2 - Load keyboard definition for 2nd alternative set (UKR)\n\
\t    from kbddef_file\n\
\tF - Load functional keyboard definition\n\
\tm - Load screen mapping from map_file\n\
\tM - Load local printer mapping from print_map_file\n\
\td - Set status line delimiter as character with hex code `cod`\n\
"};
#else
char usage[] = {
"\nUsage :\n\n fntlda [-qr] [-f font_file] [-1 kbd_file] \
[-2 kbd_file] [-F fkb_file]\n\t[-m map_file] [/M print_map_file] \
[-d cod]\n\
\nwhere:\n\
\tq - Quiet mode (don't show character table at the end)\n\
\tr - Restore defaults\n\
\tf - Load 8x16 VGA font from font_file\n\
\t1 - Load keyboard definition for 1st alternative set (RUS)\n\
\t    from kbddef_file\n\
\t2 - Load keyboard definition for 2nd alternative set (UKR)\n\
\t    from kbddef_file\n\
\tF - Load functional keyboard definition\n\
\tm - Load screen mapping from map_file\n\n\
\tM - Load local printer mapping from print_map_file\n\
\td - Set status line delimiter as character with hex code `cod`\n\
"};
#endif

#define RUS 1
#define UKR 2

/*****************************************************************
 **                                                             **
 **                     Main Procedure                          **
 **                                                             **
 *****************************************************************/

main(int argc, char **argv) {

  char buf[MAX_NAME];

  int c;
  int quiet = FALSE;
  int errflg = 0;
  int kbdtype = 0;

  errflg = init_screen(TRUE);   /* Инициализация экрана */
  /*
   *  Анализ командной строки и выполнение
   */
  if (argc < 2) errflg++;       /* Нет параметров - подскажем   */

  /* Просмотр ключей    */
#ifdef DOS
  while ((c = getopt(argc, argv, "qrf:1:2:F:m:M:d:p:")) != EOF) {
#else
  while ((c = getopt(argc, argv, "qrf:1:2:F:m:M:d:")) != EOF) {
#endif
    switch (c) {

    case 'f': /* Загрузить фонт */
              errflg += load_font(optarg);
              break;

    case '1': /* Загрузить алфавитно-цифровую клавиатуру вместо RUS  */
              errflg += load_key(optarg, 0);
              kbdtype |= RUS;
              break;

    case '2': /* Загрузить алфавитно-цифровую клавиатуру вместо UKR  */
              errflg += load_key(optarg, 1);
              kbdtype |= UKR;
              break;

    case 'F': /* Загрузить функциональную клавиатуру вместо UKR      */
              errflg += load_fkb(optarg);
              break;

    case 'm': /* Загрузить перекодировку экрана */
              errflg += load_map(optarg);
              break;

    case 'M': /* Загрузить перекодировку принтера */
              errflg += load_printmap(optarg);
              break;

    case 'q': /* Не выводить кодовую таблицу по завершению      */
              quiet = TRUE;
              break;

    case 'r': /* Восстановить значения по умолчанию              */
              errflg += restore_defaults();
              break;

    case 'd': /* Определить разделитель статусной строки         */
              errflg += define_delim(optarg);
              break;

#ifdef DOS
    case 'p': /* Номер COM-порта (1 или 2)      */
              port = atoi(optarg) - 1;
              if ((port != 0) && (port != 1)) {
                 fprintf(stderr, "ERROR: Port number may be only 1 or 2");
                 exit(1);
              }
              errflg += init_screen(TRUE);   /* Инициализация экрана */
              break;
#endif
    default:  /* Не определенный ключ - ошибка                   */
              errflg++;
              break;
    }
  }
  if (errflg) {                 /* Если была хотя бы одна ошибка */
     init_screen(FALSE);        /* Деинициализируем экран        */
     fprintf(stderr, usage);    /* Выведем подсказку             */
     exit(1);                   /* и выйдем с ошибкой            */
  }

  if (kbdtype) {                /* Была ли переопределена алф. клавиат.*/
    switch (kbdtype) {
    case RUS: c = 6;            /* да - русская                  */
              break;
    case UKR: c = 5;            /* да - украинская               */
              break;

    case UKR|RUS: c = 7;        /* да - обе                      */
                  break;
    }
    printf("\033l%d", c);       /* Установить NRAM вместо переопределенной */
  }
#ifndef DOS
  init_screen(FALSE);           /* Деинициализировать терминал   */
#endif

  if (!quiet) {
#ifdef DOS
    init_screen(2);
#endif
    show_table();     /* Если не запрещено - вывод кодовой таблицы */
  }
}

/*****************************************************************
 **                                                             **
 **             Основные подпрограммы                           **
 **                                                             **
 *****************************************************************/
/*
 * Инициализация / деинициализация терминала
 */

init_screen(int ini) {
#ifndef DOS
  static struct termio old_tio;  /* Буфер начальных параметров линии */
  static struct termio tio;
  static int sd;

  if (ini) {
     /* Инициализация   */
     sd = fileno(stdout);
     ioctl(sd, TCGETA, &old_tio); /* Прочитаем начальные параметры   */
     /* Изменить параметры во избежание преобразований вывода        */
     memcpy(&tio, &old_tio, sizeof(struct termio));
     tio.c_iflag &= 0;
     tio.c_oflag=0;
     tio.c_lflag=0;
  }
  else /* Деинициализация */
    memcpy(&tio, &old_tio, sizeof(struct termio));

  if (ioctl(sd, TCSETA, &tio)) {  /* Изменим параметры линии  */
     perror("Can't init tty");
     return(1);
  }

#else
  char buf[80];

  if (ini) {
    sprintf(buf, "COM%d", port+1);
    if (ini == 1) {
     bioscom(0, _COM_CHR8 | _COM_STOP1 | _COM_NOPARITY | _COM_9600, port);

     freopen(buf, "wb", stdout);
     close(fileno(stdout));     /* чертов Borland - надо же так изгаляться... */
     open(buf, O_WRONLY | O_BINARY);

    }
    else
      if (freopen(buf, "w", stdout)  == NULL) {
        perror(buf);
        exit(1);
      }
  }
  else
     freopen("CON", "w", stdout);
#endif

  return(0);
}

/*
 * Восстановление значений по умолчанию
 */
int restore_defaults() {
 char buf[300];
 register int i, j;

 define_delim("b3");    /* Восстановить разделитель статусной строки */
 printf("\033[:#F");    /* Восстановить фонт    */
 printf("\033l4");      /* Восстановить обе клавиатуры               */
 printf("\033l2");      /* Восстановить функциональную клавиатуру    */

 strcpy(buf, "\033l8"); /* Восстановить таблицу перекодировки экрана     */
 j = strlen(buf);
 for (i=0; i<256; i++) buf[j+i] = (char) i & 0xff;
 i += j;
#ifdef DOS
 return(fwrite(buf, i, 1, stdout) != 1);
#else
 return(write(fileno(stdout), buf, i) != i);
#endif
}

/*
 * Загрузка фонта из файла
 *
 */
int load_font
(char *fn)      /* Имя файла фонта VGA 8x16     */
{
  char fnt[FNT_LEN];

#ifdef DOS
  if (freopen(fn, "rb", stdin) == NULL) {       /* Открыть вх. файл     */
#else
  if (freopen(fn, "r", stdin) == NULL) {       /* Открыть вх. файл     */
#endif
     perror(fn);
     return(1);
  }

  read(fileno(stdin), fnt, FNT_LEN);       /*  Прочитать фонт в буфер       */
  fread(fnt, FNT_LEN, 1, stdin);
  printf("\033P");              /* Команда терминалу: принять фонт */
  fflush(stdout);

  /* Вывести фонт; ошибка если что-то не так с выводом          */
#ifdef DOS
  return(fwrite(fnt, FNT_LEN, 1, stdout) != 1);
#else
  return(write(fileno(stdout), fnt, FNT_LEN) != FNT_LEN);
#endif
}

/*
 * Загрузка алфавитно-цифровой раскладки клавиатуры
 *
 */
int load_key
(char *fn,      /* Имя входного файла описания  */
 int half)      /* Вместо какой части клавиатуры : 0 - RUS; 1 - UKR */
{
  register int i, j;
  char bufout[100], bufin[100];

  if (freopen(fn, "r", stdin) == NULL) {       /* Открыть вх. файл     */
     perror(fn);
     return(1);
  }
  sprintf(bufout, "\033l%d", half);     /* принять таб. переопределения */

  /* Допишем в конец буфера вывода таблицу переопределения      */
  j = strlen(bufout);
  for (i=0; i<94; i++) {        /* Для не более чем 94 клавиш   */
                                /* Читаем строку вх. файла описания  */
     if (fgets(bufin, 100, stdin) == NULL) break; /* EOF - закончили */
     if (!strlen(bufin)) continue; /* Пустая строка - пропускаем     */
     if (isspace(bufin[0])) continue; /* Начинается пробелом - пропускаем */
     parse_ks(bufin, &bufout[j+i], 1);/* Перевести описание в коды   */
  }
  for (;i<94;i++) bufout[j+i] = '\0'; /* Обнулим остаток буфера      */
  j += i;

  /* Вывести всю команду; ошибка если что-то не так с выводом           */
#ifdef DOS
  return(fwrite(bufout, j, 1, stdout) != 1);
#else
  return(write(fileno(stdout), bufout, j) != j);
#endif
}

/*
 * Загрузка таблицы перекодировки вывода на экран
 *
 */
int load_map
(char *fn)      /* Имя входного файла описания  */
{
  register int i, j;
  char bufout[300], bufin[100];
  int k;

  if (freopen(fn, "r", stdin) == NULL) {       /* Открыть входной файл */
     perror(fn);
     return(1);
  }
  sprintf(bufout, "\033l8");    /* принять таблицу перекодировки */
  j = strlen(bufout);
  for (i=0; i<256; i++) {/* До 256 символов             */
                         /* Читаем строку вх. файла описания     */
     if (fgets(bufin, 100, stdin) == NULL) break; /* EOF - закончили */
     if (!strlen(bufin)) continue;   /* Пустая строка - пропускаем  */
     if (isspace(bufin[0])) continue;/* Начинается пробелом - пропускаем */
     parse_ks(bufin, &bufout[j+i], 1);/* Перевести описание в коды  */
  }
  /* Остаток буфера - перекодировка один к одному       */
  for (;i<256;i++) bufout[j+i] = (char) i & 0xff;
  j += i;
#ifdef DOS
  if (fwrite(bufout, j, 1, stdout) == 1) i = j;
  else i = 0;
#else
  i = write(fileno(stdout), bufout, j); /* Вывести всю команду  */
#endif

  printf("\033!g7");    /* Character Set = NRAM                 */
  return(i != j);       /* Ошибка если что-то не так с выводом  */
}

/*
 * Загрузка таблицы перекодировки вывода на локальный принтер
 *
 */
int load_printmap
(char *fn)      /* Имя входного файла описания  */
{
  register int i, j;
  char bufout[200], bufin[100];
  int k;

  if (freopen(fn, "r", stdin) == NULL) {       /* Открыть входной файл */
     perror(fn);
     return(1);
  }
  sprintf(bufout, "\033lp");    /* принять таблицу перекодировки */
  j = strlen(bufout);
  for (i=0; i<128; i++) {/* До 128 символов             */
                         /* Читаем строку вх. файла описания     */
     if (fgets(bufin, 100, stdin) == NULL) break; /* EOF - закончили */
     if (!strlen(bufin)) continue;   /* Пустая строка - пропускаем  */
     if (isspace(bufin[0])) continue;/* Начинается пробелом - пропускаем */
     parse_ks(bufin, &bufout[j+i], 1);/* Перевести описание в коды  */
  }
  /* Остаток буфера - перекодировка один к одному       */
  for (;i<128;i++) bufout[j+i] = (char) i & 0xff;
  j += i;
#ifdef DOS
  if (fwrite(bufout, j, 1, stdout) == 1) i = j;
  else i = 0;
#else
  i = write(fileno(stdout), bufout, j); /* Вывести всю команду  */
#endif

  printf("\033lP1");    /* Mapping = Host                       */
  return(i != j);       /* Ошибка если что-то не так с выводом  */
}

/*
 * Загрузка функциональной клавиатуры
 *
 */
int load_fkb
(char *fn)      /* Имя входного файла описания  */
{
  register int i, j;
  int k, l;
  int nk;
  char id;
  unsigned char buf[1024];
  unsigned char kbuf[6], sbuf[80];

  if (freopen(fn, "r", stdin) == NULL) {       /* Открыть вх. файл */
     perror(fn);
     return(1);
  }
  sprintf(buf, "\033\060 ");    /* принять переопределение ф. клав.  */
  j = strlen(buf);
  nk = 0;                       /* Количество переопределяемых клавиш */
  id = '\037';                  /* Идентификатор клавиши              */
  for (i=0; i<90; i++) {        /* До 90 функциональных клавиш        */
     id++;                      /* Инкрементировать идентификатор     */
     /* Читаем строку вх. файла описания                              */
     if (fgets(sbuf, 80, stdin) == NULL) break; /* EOF - закончили    */
     if (!strlen(sbuf)) continue;   /* Пустая строка - пропускаем     */
     if (isspace(sbuf[0])) continue;/* Начинается пробелом - пропускаем*/
     nk++;                      /* Инкрементировать количество клавиш */
     buf[j++] = id;             /* Тек. идентификатор -> буфер вывода */
     l = parse_ks(sbuf, kbuf, 5);/* Перевести описание в коды         */
     buf[j++] = (char) ('0' + l);/* Длина последовательности -> буфер */
     for (k=0; k<l; k++) {      /* Коды - в 16-ричное представление   */
        sprintf(&buf[j],"%02X", kbuf[k]);
        j += 2;
     }
  }
  if (nk) {                     /* Если хоть одна клавиша переопределена */
     buf[2] = (char) (nk + 040);/* кол-во переопр. клавиш -> буф. вывода */

#ifdef DOS
  if (fwrite(buf, j, 1, stdout) == 1) i = j;
  else i = 0;
#else
  i = write(fileno(stdout), buf, j); /* Выведем всю команду  */
#endif

     printf("\033l97");         /* Keyboard Emulation = NRAM    */
  }
  else i = j;  /* Ни одной клавиши не переопределено - прикинемся что OK */
  return(i != j);       /* Ошибка ести что-то не так с выводом  */
}


/*
 * Определение разделителя статусной строки
 *
 */
int define_delim
(char *hc)      /* Код разделителя - строка 16-ричных цифр      */
{
 int i;
 sscanf(hc, "%x", &i);
 printf("\033l!%c", i);
 return(0);
}

/*
 * Вывод кодовой таблицы
 *
 */
show_table() {
#define cmin 0x20
#define cmax 0xff

  int i, j, c;

  printf("\n\n    ");
  for (i=cmin; i <= cmax ; i += 0x10) printf("  %2X", i);
  putchar('\n');
  printf("----");
  for (i=cmin; i<=cmax; i+= 0x10) printf("----");
  putchar('\n');
  for (i=0; i <= 0x0f; i++) {
    printf("%x  |", i);
    for (j=cmin; j<= cmax; j+=0x10) {
      if ((c=j+i) < 0x20) printf("  \033[=%dg ", c);
      else printf("  %2c", c);
    }
  printf("|  %x\n", i);
  }
  printf("\n\nDon't forget to save SetUp !\n");
}

/*****************************************************************
 **                                                             **
 **             Вспомогательные подпрограммы                    **
 **                                                             **
 *****************************************************************/
/*
 * Трансляция символьной строки на языке описания Esc-последовательностей
 * в собственно последовательность кодов

   Элементы языка описания :
   \777 - восьмеричный код
   \xAA - шестнадцатиричный код
   \d99 - десятичный код
   \n   - ^J
   \r   - ^M
   \E, \e - Esc
   ^A   - ^A
   любой алфавитно-цифровой символ = ему же
   Конец описания - пробел или достижение допустимого максимума

 */

int parse_ks    /* Возвращается длина последовательности кодов  */
(char *ks,      /* Входная строка описания                      */
 char *buf,     /* Выходной буфер кодов                         */
 int  max)      /* Максимально допустимая длина последовательности */
{
#define COD  1
#define CTRL 2
#define HEXCOD 4
#define DECCOD 8
#define isodigit(dc) ((dc >= '0') && (dc <= '7'))

   register int i, j;
   int c;
   int flag;

   i = j = flag = 0;
   while (!isspace(c = ks[i] & 0xFF)) {
      if (j >= max) break;
      switch (c) {
      case '\\':
                 if (flag & COD) goto was_backslash;
                 flag |= COD;
                 i++;
                 break;
      case '^':
                 if (flag & COD) goto was_backslash;
                 flag |= CTRL;
                 i++;
                 break;
      default:
                 if (flag) {
was_backslash:
                    if (flag & COD) {
                        if (isodigit(c)) {
                           sscanf(&ks[i], "%o", &c);
                           buf[j++] = (char) c;
                           while (c = ks[i] & 0xFF)
                              if (!isodigit(c)) break;
                              else i++;
                        }
                        else {
                           switch (c) {
                             case '\\' : buf[j++] = '\\';
                                         break;
                             case 'x'  :
                                         flag |= HEXCOD;
                                         break;
                             case 'd'  :
                                         flag |= DECCOD;
                                         break;
                             case 'n'  :
                                         buf[j++] = '\n';
                                         break;
                             case 'r'  :
                                         buf[j++] = '\r';
                                         break;
                             case 'e'   :
                             case 'E'   :
                                         buf[j++] = '\033';
                                         break;
                             default    :
                                         buf[j++] = (char) c;
                                         break;
                           }
                           i++;
                        }
                    flag &= ~(COD | CTRL);
                    }
                    else if (flag & HEXCOD) {
                       sscanf(&ks[i], "%x", &c);
                       buf[j++] = (char) c;
                       while (c = (int) ks[i] & 0xFF)
                         if (!isxdigit(c)) break;
                         else i++;
                       flag &= ~HEXCOD;
                    }
                    else if (flag & DECCOD) {
                       sscanf(&ks[i], "%d", &c);
                       buf[j++] = (char) c;
                       while (c = (int) ks[i] & 0xFF)
                         if (!isdigit(c)) break;
                         else i++;
                       flag &= ~DECCOD;
                    }
                    else if (flag & CTRL) {
                       if (c >= 0100) c &= ~(0140);
                       buf[j++] = (char) c;
                       flag &= ~CTRL;
                       i++;
                    }
                 }
                 else {
                    buf[j++] = (char) c;
                    i++;
                 }
      }
   }
   return(j);
}

