/* irfunc.c v0.3 (c) 1998 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)
 */
 
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>

#include "ir.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)
{
  int rdchar;
  
  if (ir_enabled) {
    errno = EBUSY;	/* we already have a high level ir setup */
    return -1;
  }

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

  ir_clear_buffer();

  if (ir_write_char('I') < 0)
    return -1;
  
  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 = ENOEXEC;
    return -1;
  }

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


/* simply a wrapper for ir_close_port() */

int ir_finish(void)
{
  if (!ir_enabled) {
    errno = ENXIO;
    return -1;
  }
  
  ir_enabled = 0;
  
  return ir_close_port();
}


/* 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 LIBIR this seems the easiest way.
 */

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

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

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

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];
    *j++ = ir_hexdigit[code[i] & 15];
  }
  *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 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 */
