/* irfunc.c v0.4.2 (c) 1998-99 Tom Wheeley <tomw@tsys.demon.co.uk> */
/* this code is placed under the LGPL, see www.gnu.org for info    */

/*
 * irfunc.c, infrared functions (Irman specific)
 */

#ifdef HAVE_CONFIG_H
# include <config.h>
#endif

#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#endif
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <termios.h>
#include <unistd.h>

#include "irman.h"

/* generic function for ir_get_code() and ir_poll_code() */
static unsigned char *ir_read_code(unsigned long timeout);
/* converts a single hex character to an integer */
static int ir_hex_to_int(unsigned char hex);

/* flag to enable use of higher level functions */
static int ir_enabled=0;

/* output hex digits */
static char ir_hexdigit[16] = {
	'0','1','2','3','4','5','6','7','8','9','a','b','c','d','e','f'
};

int ir_init(char *filename)
{
#ifndef IR_SOFTWARE_TEST	/* normal only */
  int rdchar;
#endif				/* both */
  int fd;
  
  if (ir_enabled) {
    errno = IR_EENABLED;	/* we already have a high level ir setup */
    return -1;
  }

#ifndef IR_SOFTWARE_TEST	/* normal only */

  if ( (fd = ir_open_port(filename)) < 0) {
    return -1;
  }

  ir_clear_buffer();

  if (ir_write_char('I') < 0)
    return -1;
  
  tcdrain(fd);
  ir_usleep(IR_HANDSHAKE_GAP);
  
  if (ir_write_char('R') < 0)
    return -1;
  
  /* we'll be nice and give the box a good chance to send an 'O' */
  
  while ((rdchar = ir_read_char(IR_HANDSHAKE_TIMEOUT)) != 'O') {
    if (rdchar < 0) {	/* error or timeout */
      return -1;
    }
  }

  /* as regards the 'K', however, that really must be the next character */
  
  rdchar = ir_read_char(IR_HANDSHAKE_TIMEOUT);
  if (rdchar < 0) {
    return -1;
  }
  
  /* ENOEXEC is the closest error I could find, that would also not be
   * generated by ir_read_char().  Anyway, ENOEXEC does more or less mean
   * "I don't understand this data I've been given"
   */
  if (rdchar != 'K') {
    errno = IR_EHANDSHAKE;
    return -1;
  }
  
#else				/* software test only */
  
  fd = 1;
  
#endif /* IR_SOFTWARE_TEST */	/* both */

  /* we are now ready to roll */
  ir_enabled = 1;
  return fd;
}


/* simply a wrapper for ir_close_port() */

int ir_finish(void)
{
  if (!ir_enabled) {
    errno = IR_EDISABLED;
    return -1;
  }
  
  ir_enabled = 0;
  
#ifndef IR_SOFTWARE_TEST
  return ir_close_port();
#else
  return 0;
#endif
}


/* this function is used by both ir_get_code() and ir_poll_code(),
 * the difference being in the timeout for the first piece of data.
 * we also have a short timeout whatever for the remaining five bytes,
 * in case computer and Irman get out of sync we can just raise an error
 * and get back to normal life.
 *
 * note esp. that these functions return a pointer to statically defined
 * data.  In the forseeable usage of LIBIRMAN this seems the easiest way.
 */

#ifndef IR_SOFTWARE_TEST	/* normal behaviour */

static unsigned char *ir_read_code(unsigned long timeout)
{
  static char codebuf[2][IR_CODE_LEN];
  static struct timeval lasttv = {0, 0};
  static int curbuf = 0;
  struct timeval tv;
  int i, datum;
  
  datum = ir_read_char(timeout);
  if (datum < 0)
    return NULL;
    
  codebuf[curbuf][0] = datum;
  gettimeofday(&tv, NULL);
  
  for (i=1; i<IR_CODE_LEN; i++) {
    datum = ir_read_char(IR_POLL_TIMEOUT);
    if (datum < 0) {
      return NULL;
    } else {
      codebuf[curbuf][i] = datum;
    }
  }

  /* check for another code */

  if (lasttv.tv_sec || lasttv.tv_usec) {
    if ( ((long) (tv.tv_sec - lasttv.tv_sec)) * 1000000 +
             (tv.tv_usec - lasttv.tv_usec) < IR_DOUBLE_TIMEOUT) {
      
      /* within timeout */
      
      if (!strcmp(codebuf[0], codebuf[1])) {
        /* codes match */
        lasttv.tv_sec = 0;
        lasttv.tv_usec = 0;
        errno = IR_EDUPCODE;
        return NULL;
      }
    }
  }

  lasttv = tv;
  curbuf = curbuf ? 0 : 1;

  return (unsigned char *) codebuf[1-curbuf];
}

#else		/* following is with IR_SOFTWARE_TEST defined */

static unsigned char *ir_read_code(unsigned long timeout)
{
  static char buf[999];
  
  /* we ignore the timeout... please don't poll! */
  
  printf("                            ------------\n");
  printf("please enter 12 hex digits: ");fflush(stdout);
  fgets(buf, 998, stdin);
  
  return ir_text_to_code(buf);
}

#endif	/* IR_SOFTWARE_TEST */


unsigned char *ir_get_code(void)
{
  /* well dodgy choice of error here...! */
  if (!ir_enabled) {
    errno = IR_EDISABLED;
    return NULL;
  }
  
  return ir_read_code(IR_BLOCKING);
}


unsigned char *ir_poll_code(void)
{
  if (!ir_enabled) {
    errno = IR_EDISABLED;
    return NULL;
  }
  
  return ir_read_code(0);
}


/* checks to see if a piece of text is a valid ir code (1 = yes) */
int ir_valid_code(char *text)
{
  char *c;

  if (strlen(text) != IR_CODE_LEN * 2)
    return 0;
    
  for(c = text; *c; c++)
    if (!isxdigit(*c))
      return 0;
  
  return 1;
}


char *ir_code_to_text(unsigned char *code)
{
  static char text[2 * IR_CODE_LEN + 1];
  int i;
  char *j;
  
  j = text;
  for (i=0; i < IR_CODE_LEN; i++) {
    *j++ = ir_hexdigit[(code[i] >> 4) & 0x0f];
    *j++ = ir_hexdigit[ code[i]       & 0x0f];
  }
  *j = '\0';

  return text;
}


static int ir_hex_to_int(unsigned char hex)
{
  if (hex >= '0' && hex <= '9')
    return hex - '0';
    
  hex = tolower(hex);
  if (hex >= 'a' && hex <= 'f')
    return hex - 'a' + 10;
    
  /* error! */
  return 0;
}


unsigned char *ir_text_to_code(char *text)
{
  static char code[IR_CODE_LEN];
  int i;
  char *j;
  
  j = text;
  for (i=0; i<IR_CODE_LEN; i++) {
    if (!j[0] || !j[1]) {
      break;
    }
    code[i]  = (ir_hex_to_int(*j++) << 4) & 0xf0;
    code[i] += (ir_hex_to_int(*j++)     ) & 0x0f;
  }
  
  /* if string isn't long enough, pad with zeros. This is (marginally)
   * better than the leaving in the remains of the last conversion
   */
  for ( ; i<IR_CODE_LEN; i++)
    code[i] = '\0';
  
  return (unsigned char *)code;
}


/* this function should never be called, but maybe someone wants to manually
 * open the ir channel, then use the higher level functions.  If you use this
 * then you deserve any problems you get!  It is only here because I don't
 * believe in unneccesary restrictions.
 */
void ir_set_enabled(int val)
{
  ir_enabled = val;
}

/* end of irfunc.c */
