/* Functions that emulate UNIX catgets */

/* Copyright (C) 1999,2000,2001 Jim Hall <jhall@freedos.org> */
/* Kitten version 2003 by Tom Ehlert, heavily modified by Eric Auer 2003 */

/*
  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  Lesser General Public License for more details.

  You should have received a copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
*/

#include <stdio.h>			/* sprintf */
#include <stdlib.h>			/* getenv  */
#include <string.h>			/* strchr */
#include <fcntl.h>
#include <dos.h>
#if !defined(__TURBOC__)
#include <sys/stat.h>
#include <sys/types.h>
#endif

#include "kitten.h"

static char catcontents[6144];	/* *** Maximum size of PROGRAM.%LANG% file *** */

struct catstring{
  char key1;
  char key2;
  char *text;
};

typedef struct catstring catstring_t;

static catstring_t catpoints[128];		/* max 128 strings can be defined... */


/* Local prototypes */

int catread (char *catfile);		/* Reads a catfile into the hash */
char *processEscChars(char *line);  /* Converts c escape sequences to chars */

int get_char(int); /* not meant for external use    */
/* external use would cause consistency problems if */
/* value or related file of the file handle changes */

int mystrtoul(char *, int, int);


/* Globals */

static nl_catd _kitten_catalog = 0;	/* _kitten_catalog descriptor or 0 */
static char catfile[128];		/* full path to _kitten_catalog */

static char getlbuf[1024]; /* read buffer (better speed) -- 7/2004: 1k, not 8k */
static char *getlp;        /* current point in buffer       */
static int getlrem = -1;   /* remaining bytes in buffer     */


#if defined(__TURBOC__)
/* DOS handle based file usage */

int dos_open(char *filename, int mode)
{
	union REGS r;
        struct SREGS s;
	(void)mode;		/* mode ignored - readonly supported */
	r.h.ah = 0x3d;
	r.h.al = 0;		/* read mode only supoported now !! */
	r.x.dx = FP_OFF(filename);
        s.ds = FP_SEG(filename);
	intdosx(&r,&r,&s);
	return ( (r.x.cflag) ? -1 : r.x.ax );
}


int dos_read(int file, void *ptr, unsigned count)
{
	union REGS r;
        struct SREGS s;
	r.h.ah = 0x3f;
	r.x.bx = file;
	r.x.cx = count;
	r.x.dx = FP_OFF(ptr);
        s.ds = FP_SEG(ptr);
	intdosx(&r,&r,&s);
	return ( (r.x.cflag) ? 0 : r.x.ax );
}


int dos_write(int file, void *ptr, unsigned count)
{
	union REGS r;
	struct SREGS s;
	r.h.ah = 0x40;
	r.x.bx = file;
	r.x.cx = count;
	r.x.dx = FP_OFF(ptr);
        s.ds = FP_SEG(ptr);
	intdosx(&r,&r,&s);
	return ( (r.x.cflag) ? 0 : r.x.ax );
}


void dos_close(int file)
{
	union REGS r;
	r.h.ah = 0x3e;
	r.x.bx = file;
	intdos(&r,&r);
}
#else
#include <unistd.h>
#endif


/* Functions */

/**
 * On success, catgets() returns a pointer to an internal
 * buffer area containing the null-terminated message string.
 * On failure, catgets() returns the value 'message'.
 */

const char *kittengets(int setnum, int msgnum, const char *message)
{

  int i = 0;
  
  while ((catpoints[i].key1 != setnum) || (catpoints[i].key2 != msgnum)) {
    if ((catpoints[i].text == NULL) || (i>127)) /* at EOF */
      return message;
    i++;
  }

  if (catpoints[i].text == NULL)
    return message;
  else
    return (catpoints[i].text);
}


/**
 * Initialize kitten for program (name).
 */

nl_catd kittenopen(char *name)
{
  /* catopen() returns a message _kitten_catalog descriptor  *
   * of type nl_catd on success.  On failure, it returns -1. */

  char catlang[3];			/* from LANG environment var. */
  char *nlsptr;				/* ptr to NLSPATH */
  char *lang;                           /* ptr to LANG */
  int i;
  
  /* Open the _kitten_catalog file */
  /* The value of `_kitten_catalog' will be set based on catread */

  if (_kitten_catalog) { /* Already one open */
    write(1,"cat already open\r\n",strlen("cat already open\r\n"));
    return (-1);
  }

  for (i=0; i<128; i++)
    catpoints[i].text = NULL;

  if (strchr (name, '\\')) {
    /* unusual case: 'name' is a filename */
    write(1,"found \\\r\n",9);
    _kitten_catalog = catread (name);
    if (_kitten_catalog)
      return (_kitten_catalog);
  }

  /* If the message _kitten_catalog file name does not contain a directory *
   * separator, then we need to try to locate the message _kitten_catalog. */

  /* We will need the value of LANG, and may need a 2-letter abbrev of
     LANG later on, so get it now. */

  lang = getenv ("LANG");

  if (lang == NULL) {
      /* printf("no lang= found\n"); */ /* not fatal, though */
      /* Return failure - we won't be able to locate the cat file */
      return (-1);
  }

  if ( ( strlen(lang) < 2 ) ||
       ( (strlen(lang) > 2) && (lang[2] != '-') ) ) {
      /* Return failure - we won't be able to locate the cat file */
      return (-1);
  }

  memcpy(catlang, lang, 2);
  /* we copy the full LANG value or the part before "-" if "-" found */
  catlang[2] = '\0';

  /* step through NLSPATH */

  nlsptr = getenv ("NLSPATH");

  if (nlsptr == NULL) {
      /* printf("no NLSPATH= found\n"); */ /* not fatal either */
      /* Return failure - we won't be able to locate the cat file */
      return (-1);
   }

  catfile[0] = '\0';

  while (nlsptr != NULL) {
      char *tok = strchr(nlsptr, ';');
      int toklen;

      if (tok == NULL)
        toklen = strlen(nlsptr); /* last segment */
      else
        toklen = (int)(tok - nlsptr); /* segment terminated by ';' */
      
      /* catfile = malloc(toklen+1+strlen(name)+1+strlen(lang)+1); */
      /* Try to find the _kitten_catalog file in each path from NLSPATH */

      if ((toklen+6+strlen(name)) > sizeof(catfile)) {
        write(1,"NLSPATH overflow\r\n",strlen("NLSPATH overflow\r\n"));
        return 0; /* overflow in NLSPATH, should never happen */
      }

      /* Rule #1: %NLSPATH%\%LANG%\cat */

        memcpy(catfile, nlsptr, toklen);
        strcpy(catfile+toklen,"\\");
        strcat(catfile,catlang);
        strcat(catfile,"\\");
        strcat(catfile,name);
        _kitten_catalog = catread (catfile);
        if (_kitten_catalog)
	  return (_kitten_catalog);

      /* Rule #2: %NLSPATH%\cat.%LANG% */

        /* memcpy(catfile, nlsptr, toklen); */
        strcpy(catfile+toklen,"\\");
        strcat(catfile,name);
        strcat(catfile,".");
        strcat(catfile,catlang);
        _kitten_catalog = catread (catfile);
        if (_kitten_catalog)
	  return (_kitten_catalog);

      /* Grab next tok for the next while iteration */

        nlsptr = tok;
        if (nlsptr) nlsptr++;
      
    } /* while tok */

  /* We could not find it.  Return failure. */

  return (-1);
}


/**
 * Load a message catalog into memory.
 */

int catread (char *catfile)
{
  int   file;				/* pointer to the catfile */
  int   i;
  char  *where;
  char  *tok;

  /* Get the whole catfile into a buffer and parse it */
  
  file = open (catfile, O_RDONLY);
  if (file < 0) 
      /* Cannot open the file.  Return failure */
      return 0;

  for (i=0; i<128; i++)
    catpoints[i].text = NULL;

  for (i=0; i<sizeof(catcontents); i++)
    catcontents[i] = '\0';

  /* Read the file into memory */
  i = read (file, catcontents, sizeof(catcontents)-1);

  if ((i == sizeof(catcontents)-1) || (i < 1))
      return 0; /* file was too big or too small */

  where = catcontents;
  i = 0; /* catpoints entry */

  do {
    char *msg;
    char *key;
    int key1 = 0;
    int key2 = 0;

    tok = strchr(where, '\n');

    if (tok == NULL) { /* done? */
      close(file);
      return 1; /* success */
    }

    tok[0] = '\0'; /* terminate here */ 
    tok--; /* guess: \r before \n */
    if (tok[0] != '\r')
      tok++; /* if not, go back */
    else {
      tok[0] = '\0'; /* terminate here already */
      tok++;
    }
    tok++; /* this is where the next line starts */

    if ( (where[0] >= '0') && (where[0] <= '9') &&
         ( (msg = strchr(where,':')) != NULL) ) {
      /* Skip everything which starts with # or with no digit */
      /* Entries look like "1.2:This is a message" */

      msg[0] = '\0'; /* remove : */
      msg++; /* go past the : */

      if ((key = strchr (where, '.')) != NULL)	{
        key[0] = '\0'; /* turn . into terminator */
        key++; /* go past the . */
        key1 = mystrtoul(where, 10, strlen(where));
        key2 = mystrtoul(key, 10, strlen(key));

        if ((key1 >= 0) && (key2 >= 0)) {
          catpoints[i].key1 = key1;
          catpoints[i].key2 = key2;
          catpoints[i].text = processEscChars(msg);
          if (catpoints[i].text == NULL) /* ESC parse error */
	    catpoints[i].text = msg;
          i++; /* next entry! */
        } /* valid keys */

      } /* . found */

    } /* : and digit found */

    where = tok; /* go to next line */

  } while (1);
}


void kittenclose (void)
{
  /* close a message _kitten_catalog */
  _kitten_catalog = 0;
}


/**
 * Parse a string that represents an unsigned integer.
 * Returns -1 if an error is found. The first size
 * chars of the string are parsed.
 */

int mystrtoul(char *src, int base, int size)
{
	int ret = 0;
	
	for (; size > 0; size--)  {
	  int digit;
	  int ch = *src;
          src++;

	  if (ch >= '0' && ch <= '9')
	    digit = ch - '0';
	  else if (ch >= 'A' && ch <= 'Z')
            digit = ch - 'A' + 10;
	  else if (ch >= 'a' && ch <= 'z')
            digit = ch - 'a' + 10;
	  else
	    return -1;

          if (digit >= base)
  	    return -1;
	  
	  ret = ret * base + digit;
	} /* for */

	return ret;
}		  	  	
	  	

/**
 * Process strings, converting \n, \t, \v, \b, \r, \f, \\,
 * \ddd, \xdd and \x0dd to actual chars.
 * (Note: \x is an extension to support hexadecimal)
 * This is used to allow the messages to use c escape sequences.
 * Modifies the line in-place (always same size or shorter).
 * Returns a pointer to input string.
 */

char *processEscChars(char *line)
{
  char *src = line;
  char *dst = line; /* possible as dst is shorter than src */

  /* used when converting \xdd and \ddd (hex or octal) characters */
  char ch;

  if (line == NULL)
    return line;

  /* cycle through copying characters, except when a \ is encountered. */
  while (*src != '\0') {
    ch = *src;
    src++;
    
    if (ch == '\\') {
      ch = *src; /* what follows slash? */
      src++;

      switch (ch) {
        case '\\': /* a single slash */
	  *dst = '\\';
	  dst++;
	  break;
        case 'n': /* a newline (linefeed) */
	  *dst = '\n';
	  dst++;
	  break;
        case 'r': /* a carriage return */
	  *dst = '\r';
	  dst++;
	  break;
        case 't': /* a horizontal tab */
	  *dst = '\t';
	  dst++;
	  break;
        case 'v': /* a vertical tab */
          *dst = '\v';
          dst++;
          break;
        case 'b': /* a backspace */
          *dst = '\b';
          dst++;
          break;
        case 'a': /* alert */
          *dst = '\a';
          dst++;
          break;
        case 'f': /* formfeed */
          *dst = '\f';
          dst++;
          break;
        case 'x': /* extension supporting hex numbers \xdd or \x0dd */
          {
            int chx = mystrtoul(src, 16, 2); /* get value */
            if (chx >= 0) { /* store character */
	       *dst = chx;
	       dst++;
               src += 2;
            } else /* error so just store x (loose slash) */
            {
              *dst = *src;
              dst++;
            }
          }
          break;
        default: /* just store letter (loose slash) or handle octal */
	  {
            int chx = mystrtoul(src, 8, 3); /* get value */
            if (chx >= 0) {/* store character */
	       *dst = chx;
	       dst++;
               src += 3;
            } else
            {
               *dst = *src;
               dst++;
            }
	  }
	  break;
      } /* switch */
    } /* if backslash */
      else
    {
      *dst = ch;
      dst++;
    }
  } /* while */

  /* ensure '\0' terminated */
  *dst = '\0';

  return line;
}


int get_char(int file) {
  int rval = -1;

  if (getlrem <= 0) { /* (re)init buffer */
    getlrem = read(file, getlbuf, sizeof(getlbuf));
    if (getlrem <= 0)
      return -1; /* fail: read error / EOF */
    getlp = getlbuf; /* init pointer */
  }

  if (getlrem > 0) { /* consume byte from buffer */
    rval = getlp[0];
    getlp++;
    getlrem--;
  }

  return rval;
}


/**
 * Read a line of text from file. You must call this with
 * a null buffer or null size to flush buffers when you are
 * done with a file before using it on the next file. Cannot
 * be used for 2 files at the same time.
 */

int get_line (int file, char *orig_str, int size)
{
  int ch;
  char *str = orig_str;

  if ((size == 0) || (str == NULL)) { /* re-init get_line buffers */
    getlp = getlbuf;
    getlrem = -1;
    return 0;
  }

  str[0] = '\0';

  while (size > 0) {
    ch = get_char (file);
    if (ch < 0) {
      /* encountered EOF / read error, so no more content*/
      str[0] = '\0';

      /* test if the pointers are equal, there is not content in the buffer */
      if(str == orig_str)
        return 0;  /* fail, no more content */
      
      return 1; /* success */
    }

    if (ch == '\r')
      ch = get_char (file); /* ignore \r */

    str[0] = ch;

    if ( (ch == '\n') || (ch == '\r') || (ch < 0) ) { /* done? */
      str[0] = '\0';
      return 1; /* success */
    }

    str++;
    size--;

  } /* while */

  str[0] = '\0'; /* terminate buffer */

  return 0; /* fail, because the line is longer than the buffer? */

}

/*
 * input
 *   s          : input string to be parsed
 *   delimiters : start, intermediate and end
 *   responses  : output buffer (has to be long enough for num chars)
 *   num        : required number of responses
 *
 * return
 *   0 on failure
 *   1 on success
 */
int kitten_extract_response(const char *s, const char *delimiters,
                            char *responses, int num) {
  int slen = strlen(s);
  const char *p, *q;
  int i;

  /* Response parsing requires substring usually of the form '(%c/%c)' */
  p = strchr(s, delimiters[0]);
  if (!p)
    return 0;

  /* Each response requires letter followed by a delimiter e.g. 'Y/' or 'N)' */
  if (p + 1 - s > slen - 2 * num)
    return 0;

  for (i = 0; i < num; i++) {
    q = p + 2 + i * 2;
    if (*q != delimiters[1] && *q != delimiters[2])
      return 0;
    responses[i] = *(p + 1 + i * 2);
  }

  return 1;
}
