#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <netdb.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <termio.h>
#include <sys/ioctl.h>

#define NLS_ON		0
#define SIGSWITCH	1
#define TERMINFO	0

#if NLS_ON
	#include <libintl.h>
	#define _(String) gettext (String)
	/* i zlinkuj z libintl.a */
#else
	/* fake */
     #define _(String) (String)
     #define N_(String) (String)
     #define textdomain(Domain)
     #define bindtextdomain(Package, Directory)
#endif

/*   Version 1.2.2   Authors: kompas@uci.agh.edu.pl & rzm@torun.pdi.net
960719 1.0 - kompas
960722 małe poprawki - kompas
960725 1.1  rzm:
       konwersja na ascii z underline (-u) 
       czytanie `a jako aogonek dla -i kascii:iso (kascii bo
         ascii ma ZZ a tu trzeba XZ)
       krótsze nazwy kodów w tablicy (20 zamiast 256 B)
       gcc -Wall ....  nie szczeka
       indent -br  oprócz tabelki kodów
       komunikat "Początek ..." "Koniec ..."
960726 opcja do wyłączania sleep - rzm
960730 1.2.1 rozne dodatki, ktore jeszcze nie dzialaja (sa zakomentowane)
       - rzm
960801 Zmienna ttyname była deklarowana na 9 znaków, a wykorzystywano 11.
       I to działało! (A nie powinno :-) ). - kompas
960802 konfigurowalny znak przełączający na pl (default: `) - rzm
       kill -USR1  przelacza podkreslanie

for more info check http://www.agh.edu.pl/ogonki/ogonki.html

do zrobienia:
- mądrzej underline bo teraz jak wyłącza to wyłącza wszytkie
  reverse, underline itp. (ale może nie być takich mądrych
  własności terminala w termcapie/terminfo; konsola Linuxa
  ma troche nie publikowanych własności)
- czytac termcapa żeby znaleźć sekwencje sterujące dla nie-vt100
  (procedura dopisana, ale jak z przenaszalnością?)
- domyślne konwersje w zmiennych środowiska
- używać w jakiś sposób locale do ustawiania konwersji?
- message catalog in gettext style (to chyba przesada, nie?)
- statystyka konwersji i błędów (np. przepełnień obufora)
- przełączanie w locie przez kill -USR1
- tekst ``Początek konwersji'' powinien też być konwertowany (ogonek przy ą!)

*/


/* Configurable section */

#define PLSTDFILE "/usr/lib/plchars.dat"
#define IBUFFSIZE 1600
#define OBUFFSIZE 1600
#define SLEEP 10000l

/* End of configurable section */


struct _plstd
  {
    char name[20];
    char conv[19];
  }
plstandards[30] =

{
  { "ascii", "acelnoszzACELNOSZZ" } ,
  { "kascii", "acelnosxzACELNOSXZ" } ,
  { "mazovia", "¤˘Ś§ĽŁ Ą" } ,
  { "iso", "ąćęłńóśźżĄĆĘŁŃÓŚŹŻ" } ,
  { "cp1250", "šćęłńóżĽĆĘŁąÓŻ" } ,
  { "cp852", "ĽŠä˘Ťž¤¨ăŕ˝" } ,
  { "mac", "Ť¸Äćý˘üÁîĺű" } ,
  { "", "" }
};

char *prefix = "[2m", *postfix = "[m", keyswitch = '`';

char input_conv_tab[256];
char output_conv_tab[256];
int do_input_conv = 0, do_output_conv = 0, 
    do_underline = 0, do_keyswitch = 0, do_sleep = 1;
char *progname = NULL;
char *progargs[64];
int pid;

void read_plstandards ();
void list_plstandards ();
void print_usage ();
int set_conv_tab ();
char *get_next_word ();
int get_pty ();
int go_into_loop ();
void set_raw ();
void sigswitch();

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */


int
main (argc, argv)
     int argc;
     char *argv[];
{
  int i, j;
  char ttyname[11] = "/dev/ttyxx";
  int tty, pty;
  struct winsize wsize;

#if NLS_ON
  setlocale (LC_ALL, "");
  bindtextdomain ("ogonki", "/usr/share/locale");
  textdomain ("ogonki");
#endif

  read_plstandards ();

  if (argc < 2) {
    print_usage (argv[0]);
    return 1;
  }

  for (i = 1; i < argc; i++) {
    if (argv[i][0] == '-')
      switch (argv[i][1]) {
      case 'l':
	list_plstandards ();
	return 0;
      case 'i':
	if (set_conv_tab (argv[++i], input_conv_tab) < 0)
	  return 1;
	else {
	  do_input_conv = 1;
	  break;
	}
      case 'o':
	if (set_conv_tab (argv[++i], output_conv_tab) < 0)
	  return 1;
	else {
	  do_output_conv = 1;
	  break;
	}
      case 'u':
	do_underline = 1;
	break;
      case 's':
        do_sleep = 0;
        break;
      case 'k':
	keyswitch = argv[++i][0];
        break;
      }
    else {
      progname = argv[i++];
      j = 0;
      progargs[j++] = progname;
      while (i < argc && j < 64)
	progargs[j++] = argv[i++];
      break;
    }
  }

  if (!do_input_conv && !do_output_conv) {
    fprintf (stderr, "No -o nor -i specified.\n");
    return 1;
  }

  if (!progname || !*progname) {
    progname = getenv ("SHELL");
    progargs[0] = progname;
    progargs[1] = NULL;
  }

  pty = get_pty (ttyname);
  if (pty < 0) {
    fprintf (stderr, "Out of pty's\n");
    exit (1);
  }

#if SIGSWITCH
	#ifdef SIGUSR1
  signal(SIGUSR1, sigswitch);
	#endif
#endif

  printf (_("Początek konwersji\n"));

  switch (pid = fork ()) {
  case -1:
    perror ("fork");
    exit (1);

  case 0:
    ioctl (0, TIOCGWINSZ, &wsize);
    setsid ();
    tty = open (ttyname, O_RDWR);
    ioctl (tty, TIOCSWINSZ, &wsize);
    dup2 (tty, 0);
    dup2 (tty, 1);
    dup2 (tty, 2);
    execvp (progname, progargs);
    perror ("exec");
    return 1;

  default:
    return go_into_loop (pty);
  }

  return 0;
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
read_plstandards ()
{
  FILE *in;
  char *p, buff[1024], *type;
  int i, plstnum;


  in = fopen (PLSTDFILE, "r");
  if (in) {
    plstnum = 0;
    while (fgets (buff, 1024, in)) {
      plstandards[plstnum].name[0] = 0;
      if (buff[0] == '#')
	continue;
      p = buff;
      strcpy (plstandards[plstnum].name, get_next_word (&p));
      type = get_next_word (&p);
      if (!*type || atoi (type) != 0)
	continue;
      for (i = 0; i < 18; i++)
	plstandards[plstnum].conv[i] = atoi (get_next_word (&p));
      plstnum++;
    }
    plstandards[plstnum].name[0] = 0;
  }
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

char *
get_next_word (p)
     char **p;
{
  static char buff[1024];
  int i;

  while (**p && (**p == ' ' || **p == '\t' || **p == '\n'))
    (*p)++;
  i = 0;
  while (**p && !(**p == ' ' || **p == '\t' || **p == '\n') && i < 1023)
    buff[i++] = *((*p)++);
  buff[i] = 0;
  return buff;
}


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
list_plstandards ()
{
  int i = 0;
  printf ("Currently known standards are:\n");
  while (plstandards[i].name[0]) {
    printf ("  %s\n", plstandards[i++].name);
  }
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
print_usage (progname)
     char *progname;
{
  char *basename = strrchr (progname, '/') + 1;
  if (basename == (char *)1)
    basename = progname;
  printf ("Usage: %s [-o from:to] [-i from:to] [-u] [-s] [prog [args]]\n", basename);
  printf (" or    %s -l\n\n", basename);
  printf ("For more info see http://www.agh.edu.pl/ogonki/ogonki.html\n");
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
set_conv_tab (args, tab)
     char *args;
     char *tab;
{
  char *p, *from, *to;
  int from_n, to_n;
  int i;

  p = strchr (args, ':');
  if (!p) {
    fprintf (stderr, "Invalid conversion definition: %s\n", args);
    return -1;
  }
  *p = 0;
  from = args;
  to = p + 1;

  if (strcasecmp (from, "kascii") == 0)
    do_keyswitch = 1;

  from_n = (-1);
  to_n = (-1);
  for (i = 0; plstandards[i].name[0]; i++) {
    if (strcasecmp (plstandards[i].name, from) == 0)
      from_n = i;
    if (strcasecmp (plstandards[i].name, to) == 0)
      to_n = i;
  }
  if (to_n < 0 || from_n < 0) {
    fprintf (stderr, "Unknown standard: %s\n", to_n < 0 ? to : from);
    return -1;
  }

  for (i = 0; i < 256; i++)
    tab[i] = i;

  for (i = 0; i < 18; i++)
    tab[plstandards[from_n].conv[i]] = plstandards[to_n].conv[i];

  return 0;

}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
get_pty (ttyname)
     char *ttyname;
{
  static char ptys[] = "/dev/ptyp0";
  char *bank, *num, *tp;
  struct stat stb;
  int master;

  bank = ptys + 8;
  num = ptys + 9;
  tp = ptys + 5;
  (*tp) = 'p';
  for (*bank = 'p'; *bank <= 'z'; (*bank)++) {
    *num = '0';
    if (stat (ptys, &stb) < 0)
      break;
    for ((*num) = '0'; (*num) <= 'f'; (*num) = ((*num) == '9' ? 'a' : (*num) + 1)) {
      master = open (ptys, O_RDWR);
      if (master >= 0) {
	/* sprawdzamy slave */
	(*tp) = 't';
	if (access (ptys, R_OK | W_OK) == 0) {
	  strcpy (ttyname, ptys);
	  fcntl (master, F_SETFL, O_NDELAY);
	  return master;
	}
	(*tp) = 'p';
	close (master);
      }
    }
  }

  return (-1);			/* Out of ptysie :-) */

}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

int
go_into_loop (pty)
     int pty;
{
  int status;
  struct termio t, t_save;
  char ibuff[IBUFFSIZE], obuff[OBUFFSIZE], *ibuffconv, *obuffconv;
  int i, j, n;
  static int endks = 0;

  if (do_input_conv)  ibuffconv=malloc(IBUFFSIZE);
  /* w zasadzie bufor wyjsciowy może się przepełnić jeżeli < 8*OBUFFSIZE 
     ale zaryzykujmy... */
#define OOBUFFSIZE	OBUFFSIZE*3
  if (do_output_conv) obuffconv=malloc(OOBUFFSIZE);
  /* aaa... jeszcze sprawdzić czy się alokowało... może kiedyś */

  ioctl (0, TCGETA, &t_save);
  ioctl (0, TCGETA, &t);
  set_raw (&t);
  ioctl (0, TCSETAW, &t);

  while (waitpid (pid, &status, WNOHANG) == 0) {
    n = read (0, ibuff, IBUFFSIZE);
    if (n > 0) {
      if (do_input_conv) {
	for (i = 0, j = 0; i < n; i++) {
	  if (do_keyswitch && endks) {
	    /* "shift" jest - convertuj; */
	    ibuffconv[j] = input_conv_tab[(short)(ibuff[i])];
	    j++;
	    endks = 0;		/* i wyłącz */
	  }
	  else if (do_keyswitch && (ibuff[i] == keyswitch)) {	/* włącz "shift" */
	    endks = 1;
	  }
	  else {
	    ibuffconv[j] = ibuff[i];
	    j++;
	  }
	}
	write (pty, ibuffconv, j);
      }
      else
	write (pty, ibuff, n);
    }
    n = read (pty, obuff, OBUFFSIZE);
    if (n > 0) {
      if (do_output_conv) {
	for (i = 0, j = 0; i < n; i++) {
          /* brzydkie, ale przynajmniej się nie wysypiemy całkiem: */
          if (j>=OOBUFFSIZE-10) j=OOBUFFSIZE-10;
	  if (do_underline && (prefix != NULL) && (obuff[i] != output_conv_tab[(short)(obuff[i])])) {
	    memcpy (obuffconv + j, prefix, strlen (prefix));
	    j += strlen (prefix);
	  }
	  obuffconv[j] = output_conv_tab[(short)(obuff[i])];
	  j++;
	  if (do_underline && (postfix != NULL) && (obuff[i] != output_conv_tab[(short)(obuff[i])])) {
	    memcpy (obuffconv + j, postfix, strlen (postfix));
	    j += strlen (postfix);
	  }
	}
	write (1, obuffconv, j);
      }
      else
	write (1, obuff, n);
    }
    if (do_sleep) usleep (SLEEP);
  }

  ioctl (0, TCSETAW, &t_save);
  printf ("Koniec konwersji\n");
  return (status & 0xff00) >> 8;

}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

void
set_raw (t)
     struct termio *t;
{
  t->c_iflag |= IGNBRK;
  t->c_iflag &= ~BRKINT;
  t->c_iflag |= IGNPAR;
  t->c_iflag &= ~PARMRK;
  t->c_iflag &= ~INPCK;
  t->c_iflag &= ~ISTRIP;
  t->c_iflag &= ~INLCR;
  t->c_iflag &= ~IGNCR;
  t->c_iflag &= ~ICRNL;
  t->c_iflag &= ~IUCLC;

  t->c_oflag |= OPOST;
  t->c_oflag |= ONLCR;

  t->c_lflag &= ~ISIG;
  t->c_lflag &= ~ICANON;
  t->c_lflag &= ~XCASE;
  t->c_lflag &= ~ECHO;
  t->c_lflag &= ~ECHOE;
  t->c_lflag &= ~ECHOK;
  t->c_lflag &= ~ECHONL;

  t->c_cc[VMIN] = 0;		/* accept input when a single character appears */
  t->c_cc[VTIME] = 0;		/* or 1 second expires (VTIME is set in 0.1s)   */
}

/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if SIGSWITCH
	#ifdef SIGUSR1

void
sigswitch(int signum) {
	switch (signum) {
		case SIGUSR1:	do_underline = 1 - do_underline; 
				signal(SIGUSR1, sigswitch);
				break;
	}
}

	#endif
#endif


/* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */

#if TERMCAPINFO

#include <curses.h>

char *tstring;

int
tputchar (int c)
{
  *tstring++ = c;
}

char *
tconv (char *tstr)
{
  char *loctstring;
  tstring = malloc (strlen (tstr) + 1);
  loctstring = tstring;
  tputs (tstr, 1, tputchar);
  *tstring = 0; /* zero termination */
  return loctstring;
}

typedef struct tcapabilities {
  char on[10], off[10], *valon, *valoff;
  int tiget;
} tcapabilities1;

struct tcapabilities capabilities[] =
{
  {"smul", "rmul", "", "", 1},
  {"us", "ue", "", "", 0},
  {"smso", "rmso", "", "", 1},
  {"so", "se", "", "", 0},
  {NULL}
};

get_onoff (char *on, char *off)
{
  int errret, i;

  if (setupterm ((char *) 0, 1, &errret) == ERR)
    if (setupterm ("vt100", 1, &errret) == ERR)
      printf ("nic\n");

  for (i = 0; capabilities[i].on[0] != 0; i++) {
    if (capabilities[i].tiget) {
      capabilities[i].valon = tigetstr (capabilities[i].on);
      capabilities[i].valoff = tigetstr (capabilities[i].off);
    } else {
      capabilities[i].valon = tgetstr (capabilities[i].on);
      capabilities[i].valoff = tgetstr (capabilities[i].off);
    }
    if (capabilities[i].valon && capabilities[i].valoff &&
	(capabilities[i].valon != -1) && (capabilities[i].valoff != -1))
      break;
  }
  if (capabilities[i].valon && capabilities[i].valoff &&
      (capabilities[i].valon != -1) && (capabilities[i].valoff != -1)) {
    printf ("Znalazłem on: %s i off: %s\n", capabilities[i].on, 
    		capabilities[i].off);
    strncpy (on,  tconv(capabilities[i].valon),  20);
    strncpy (off, tconv(capabilities[i].valoff), 20);
  } else {
    printf ("Nie mogę znaleźć odpowiednich właściwości terminala,\nspróbuję wartości domyślnych\n");
    strncpy (on, "[4m", 20);
    strncpy (off, "[m", 20);
  }

}

/*
int 
main ()
{
  char on[20], off[20];
  get_onoff (on, off);
  printf("on=<%s>, off=<%s>\n", on, off);
}
*/

#endif
