/* ---------------------------------------------------------- */
/* | Nombre: stermp.c ( Simple Terminal Play)               | */
/* | Autor:  Gaspar Fernández (blakeyes@totaki.com)         | */
/* | Fecha:  05 Enero 2009                                  | */
/* | Web:    http://www.totaki.com/poesiabinaria/           | */
/* ---------------------------------------------------------- */
/* | Descripción:                                           | */
/* |   Manejo de colores y posicionamiento de texto en un   | */
/* |  terminal para nostálgicos de conio.h                  | */
/* ---------------------------------------------------------- */
/*   Este código se distribuye libremente, tal cual, sin garantías. */
/*   Pero si utilizas este código, y lo distribuyes, me gustaría que */
/*   citaras la fuente, y ya de paso, un enlace al blog no vendría mal :) */

/* Changelog: */
/* 20100629: Varias actualizaciones: */
/*    - Creación de la función error() */
/*    - Creación de la función term_init() que inicializa las variables globales */
/*    - Creación del reemplazo de getch() */
/*    - Creación de term_info() */
/*    - Creación de save_term_info() */
/*    - Creación de restore_term() */
/*    - Creación de kbhit(), kbhit2(), __kbhit() y kbhit_pre() */
/*    - Creación de macros para agilizar kbhit() */
/*    - kbhit_usleep para no congestionar la CPU */
/*    - termnoecho() es más corta */
/*    - restore_terminal() ahora restaura flags de stdin */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include "stermp.h"
#include <errno.h>
#include <fcntl.h>


#define KBHIT_USLEEP() { if (kbh_usleep) usleep(kbh_usleep); }
#define KBHIT_PRE() { termnoecho(1); fcntl(STDIN_FILENO, F_SETFL, fdflags | O_NONBLOCK); /* Activamos el flag NON_BLOCK para que no se pare la ejecución */ KBHIT_USLEEP(); }
/* Equivalencias de colores */
static const char ansicolors[16] = {30, 34, 32, 36, 31, 35, 33, 37, 0, 34, 32, 36, 31, 35, 33, 37};

struct termios  orig_tty;	/* Atributos de terminal iniciales */
struct termios	save_tty;	/* Copia de seguridad de atributos de terminal */
int             orig_fdf;	/* Valor de las banderas inicial */
int             fdflags; 	/* Valor de las banderas */

int             kbh_usleep=100000;	/* Para no congestionar la CPU con muchos kbhits seguidos, hacemos un usleep tras cada uno */
int             __init=0;	/* ¿ Se ha inicializado ? */

void clrscr()			/* Borrar la pantalla */
{
  printf("\033[2J");   // Borra pantalla
  printf("\033[1;1H"); // Posicionamos el cursor en la fila 1 columna 1
}

void gotoxy(int x, int y)
{
  printf("\033[%d;%dH", y, x); 	/* Posicionamos el cursor en X, Y */
}

void textcolor(int color)
{
  int atrval=0;
  if (color & UNDERLINE)	/* Si el flag subrayado está activo... */
    atrval=UNDERLINE_ATTR;
  else if (color & BLINK)	/* Si el flag intermitente está actvo */
    atrval=BLINK_ATTR;
  else if (color>=BRILLO_MIN)	/* Si el color es brillante */
    atrval=BRILLO_ATTR;

  color=ansicolors[color & 15];		/* Eliminamos los atributos */
  
  printf("\033[%d;%dm", atrval, color);
}

void textbackground(int color)
{
  if (color<BRILLO_MIN)
    {				    /* Sólo ciertos colores tienen fondo */
      color=ansicolors[color & 15]+10;		/* Eliminamos los atributos */
  
      printf("\033[%dm", color);
    }
}

void restore_color()
{
  printf("\033[0m");
}

int wherex()
{
  coord_t coord;

  coord=wherexy(0);
  return coord.x;
}

int wherey()
{
  coord_t coords;

  coords=wherexy(0);
  return coords.y;
}

int screenwidth()
{
  coord_t coords;

  coords=wherexy(1);
  return coords.x;
}

int screenheight()
{
  coord_t coords;

  coords=wherexy(1);
  return coords.y;
}

coord_t wherexy(int abs)
{
  coord_t coords;
  char buf[12];
  char *p;
  termnoecho(1);

  fflush(stdout);		/* Actualizamos stdout porque a lo mejor  */

  if (abs)			/* Si queremos las coordenadas de la pantalla... */
    {
	fprintf(stderr, "\033[s");	  /* Salva la posición actual del cursor */
	fprintf(stderr, "\033[r");	  /* Elimina el scroll */
	fprintf(stderr, "\033[255;255H"); /* Nos vamos a la esquina inferior derecha */
    }

  fprintf(stderr, "\033[6n");

  while (read(STDIN_FILENO, buf, sizeof(buf))==0);
  
  sscanf(buf, "\033[%d;%dR", &coords.y, &coords.x);

  if (abs)
    {
	fprintf(stderr, "\033[u"); /* Restauramos la posición del cursor */
    }

  restore_terminal();		/* Restauramos los atributos del terminal */

  return coords;
}

/* canon activa o desactiva el modo canónico */
/* básicamente, no tenemos que pulsar INTRO. */
void termnoecho(int canon)
{
  struct termios   tty;

  if (__init)
    save_term_info();

  tty = save_tty;
  
  /* Desactivamos atributos del terminal */
  if (canon)
    tty.c_lflag &= ~(ICANON);	
  tty.c_lflag &= ~(ECHO|ECHOE|ECHOK|ECHONL);
  tty.c_cc[VMIN]  = 1;
  tty.c_cc[VTIME] = 1;
  
  if ( tcsetattr(STDIN_FILENO, TCSANOW, &tty) == -1 )
    error ("ERROR: No puedo definir atributos al terminal.");
}

int getch()
{
  int key;

  termnoecho(1);
  key=getch();

  restore_terminal();
}

int getche()
{
  int key;

  termnoecho(0);
  key=getch();

  restore_terminal();
}

void kbhit_pre()
{
  KBHIT_PRE();
}

int __kbhit()
{
  int key;

  key=getchar();

  if (key!=EOF)
      return key;
}

int kbhit2()
{
  int key;

  KBHIT_PRE();
  key=getchar();
  restore_terminal();
  if (key!=EOF)
      return key;

  return 0;
}

int kbhit()
{
  int key;

  KBHIT_PRE();
  key=getchar();
  restore_terminal();
  if (key!=EOF)
    {
      ungetc(key, stdin);
      return 1;
    }
  return 0;
}

void restore_terminal()
{
  restore_term(&save_tty, &fdflags);
}

void restore_term(struct termios *tty, int *stdinflags)
{
  if ( tcsetattr(STDIN_FILENO, TCSANOW, tty) == -1 )
    error ("ERROR: No puedo definir atributos al terminal.");

  if ( fcntl(STDIN_FILENO, F_SETFL, stdinflags) == -1 )
    error ("ERROR: No puedo definir los flags de stdin.");
}

void error(char *err)
{
  fprintf(stderr, "%s errno=%d (%s)\n", err, errno, strerror(errno));
  exit (EXIT_CODE);
}

void term_info(struct termios *tty, int *stdinflags)
{
  if ( tcgetattr(STDIN_FILENO, tty) == -1 )
    error ("ERROR: No puedo obtener atributos del terminal.");

  *stdinflags = fcntl(STDIN_FILENO, F_GETFL, 0);
}

void save_term_info()
{
  term_info (&save_tty, &fdflags); /* Guardamos flags de terminal en variables */
		  		   /* temporales */
}

void term_init()
{
  /* Salvamos los valores iniciales */
  term_info (&orig_tty, &orig_fdf);
  /* Hacemos una copia como valores temporalmente salvados */
  /* Estos son los que podremos modificar en nuestros programas */
  save_tty=orig_tty;
  fdflags=orig_fdf;

  __init=1;
}

void term_defaults()
{
  restore_term(&orig_tty, &orig_fdf);
}
