#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <mbari/types.h>
#include <mbari/const.h>
#include "parseUtils.h"
#include "stringUtils.h"


int getField(char *buf, int ifield, char *field)
{
  char *ptr;
  char buf2[512];
  char *token;
  int i = 0;
  
  strcpy(buf2, buf);
  ptr = buf2;
  
  while (TRUE)
  {
    if ((token = strtok(ptr, " \t")) == NULL)
      return -1;

    ptr = NULL;
    
    if (i == ifield)
    {
      strcpy(field, token);
      return 0;
    }
    i++;
  }
}


enum
{
  STARTING, INSTANCE, DONE
};


/*
Function: isIntegerBase()
Description: Determine if string represents a valid decimal, octal, or
hexadecimal integer.
Return values: base (8, 10, or 16) if integer, else 0
*/
int isIntegerBase(char *str)
{
  int i;
  int state = STARTING;
  int base = 10;
  int ndigits = 0;
  
  for (i = 0; i < strlen(str); i++)
  {
    if (!isdigit(str[i]))
    {
      switch ((int )str[i])
      {
        case '-':
        case '+':
        /* Allow leading '-' or '+' */
        if (state != STARTING)
          return 0;
        break;
        
        case ' ':
        /* Allow leading or trailing blank */
        switch (state)
        {
          case STARTING:
          continue;
          
          case INSTANCE:
          state = DONE;
          break;
        }
        break;
        
        case 'x':
        case 'X':
        if (ndigits != 1 || str[i-1] != '0')
          return 0;
  
        /* Hexadecimal */
        base = 16;
        /* Start counting digits again */
        ndigits = 0;
        break;

        case 'a':
        case 'A':
        case 'b':
        case 'B':
        case 'c':
        case 'C':
        case 'd':
        case 'D':
        case 'e':
        case 'E':
        case 'f':
        case 'F':
        if (base != 16)
          return 0;

        /* In hexadecimal, these characters count as "digits" */
        ndigits++;
        break;
        
        default:
        /* Any other non-digit is illegal */
        return 0;
      }
    }
    else
    {
      /* Got a digit */
      ndigits++;
      if (base == 8 && (int )str[i] > '7')
        return 0;
      
      switch (state)
      {
        case STARTING:
        state = INSTANCE;
        if (str[i] == '0')
        {
          /* Assume octal number unless next character is 'x' or 'X' */
          base = 8;
        }
        break;
        
        case DONE:
        return 0;

        default:
        continue;
      }
    }
  }
  if (state != INSTANCE && state != DONE)
    return 0;
  else if (ndigits <= 0)
    /* Last character must be a digit (e.g. not just 'x' or 'X') */
    return 0;
  if (base != 16 && ndigits == 1)
    /* Must be just '0'; take it to be base 10 */
    return 10;
  else
    return base;
}


/*
Function: isInteger()
Description: Determine if string represents a valid decimal integer
Return values: TRUE or FALSE
*/
MBool isInteger(char *str)
{
  int i;
  int state = STARTING;
  int ndigits = 0;
  
  for (i = 0; i < strlen(str); i++)
  {
    if (!isdigit(str[i]))
    {
      switch ((int )str[i])
      {
        case '-':
        case '+':
        /* Allow leading '-' or '+' */
        if (state != STARTING)
          return 0;
        break;
        
        case ' ':
        /* Allow leading or trailing blank */
        switch (state)
        {
          case STARTING:
          continue;
          
          case INSTANCE:
          state = DONE;
          break;
        }
        break;
        
        default:
        /* Any other non-digit is illegal */
        return FALSE;
      }
    }
    else
    {
      /* Got a digit */
      ndigits++;
      
      switch (state)
      {
        case STARTING:
        state = INSTANCE;
        break;
        
        case DONE:
        return FALSE;

        default:
        continue;
      }
    }
  }
  if (state != INSTANCE && state != DONE)
    return FALSE;
  else if (ndigits <= 0)
    return 0;
  else
    return TRUE;
}



/*
Function: isFloat()
Description: Determine if string represents a valid floating point number
Return values: TRUE if float, else FALSE
*/
MBool isFloat(char *str)
{
  int i;
  int n_dot = 0;
  MBool start_num = FALSE;
  MBool trail_blank = FALSE;
  MBool start_exp = FALSE;
  
  for (i = 0; i < strlen(str); i++)
  {
    if (trail_blank && str[i] != ' ')
      /* Already hit trailing blank(s); nothing else allowed */
      return FALSE;
    
    if (!isdigit(str[i]))
    {
      switch ((int )str[i])
      {
        case '-':
        case '+':
        /* Allow leading '-' or '+' */
        if (start_num)
          return FALSE;

        start_num = TRUE;
        break;
        
        case '.':
        /* One decimal point allowed in mantissa, not allowed in exponent */
        if (++n_dot != 1 || start_exp)
          return FALSE;

        start_num = TRUE;
        break;

	case 'e':
	case 'E':
	/* The rest of the string must be an integer */
	start_exp = TRUE;
	start_num = FALSE;
	break;
	
        case ' ':
        /* Allow leading or trailing blanks */
        if (i == 0)
          continue;
        
        if (start_num)
        {
          /* Trailing (ok) or embedded (bad) blank */
          trail_blank = TRUE;
        }
        break;
        
        default:
        return FALSE;
      }
    }
    else
      start_num = TRUE;
    
  }
  return TRUE;
}


int seq_firstchar;           /* position of first character read */
int seq_lastchar;            /* position of last character read */
int seq_cline;               /* line counter */

/*
get a line from the input. The form of the output is:
line[0] = first nonwhite char
line[n-1] = {, } or ;

NOTE: getLine differs from getline in that getLine does NOT uppercase the
line unless "case_fold" is TRUE.

(getLine skips over comments)
*/

int getLine(FILE *fp, char *line, MBool case_fold, int *stmnt_pos)
{
  int n;         /* number of chars in buffer */
  int c;         /* current char */
  int first_white;      /* flag for getting first non white char */
  int c_mode;         /* comment mode */
  int firstchar;
  int maxchar;
  MBool done;

  *stmnt_pos = -1;
  
#define NOTINC 0      /* not in a comment */
#define HAFINC 1      /* half in comment (found /) */
#define WHOINC 2      /* inside comment */
#define SINGLE_LINE_COMMENT 3

  n = 0;
  first_white = 1;
  firstchar = 1;
  maxchar = MAX_LINE_LEN;
  c_mode = NOTINC;
  
  line[0] = '\0';
  for(;;) {
    c = getc(fp);
    if (firstchar)
    {
      seq_firstchar = ftell(fp) - 1;
      seq_lastchar = seq_firstchar;
    }
    seq_lastchar++;
    firstchar = 0;
    /*    printf("c[%d] %c\n",n,c); */

    switch(c) {

      case EOF:
      return(EOF);
      break;

      case NULL:
      break;

      case '\f':
      case '\r':
      case '\n':
      seq_cline++;
      break;


      case '#':
      done = FALSE;
      while(!done)
      {
	c = getc(fp);
	seq_lastchar++;

	switch(c) 
	{
	  case EOF:
	  return EOF;
	  break;
	    
	  case '\f':
	  case '\r':
	  case '\n':
	  seq_cline++;
	  done = TRUE;
	  break;
	}
      }
      break;

      
      case '/':
      switch(c_mode) {

	case NOTINC:
	c_mode = HAFINC;
	break;

	case HAFINC:
	storeChar(c,line,maxchar,&n,&first_white);
	break;
      }
      /*      printf("/ char: mode = %d\n",c_mode); */
      break;

      case '*':
      /*      printf("* char: mode = %d\n",c_mode); */
      switch(c_mode) {

	case NOTINC:
	storeChar(c,line,maxchar,&n,&first_white);
	break;

	case HAFINC:
	/*   printf("found start of comment\n"); */
	c_mode = WHOINC;

	while(c_mode != NOTINC) {
	  c = getc(fp);
	  seq_lastchar++;
	  /*     printf("char = %c [0x%04x], mode = %d\n",c,c,c_mode);  */
	  if(c == EOF)
	    return(EOF);
	  switch(c) {

	    case '*':
	    c_mode = HAFINC;
	    /*       printf("* char = %c, mode = %d\n",c,c_mode); */
	    break;

	    case '/':
	    if(c_mode == HAFINC)
	      c_mode = NOTINC;
	    /*       printf("/ char = %c, mode = %d\n",c,c_mode); */
	    break;

	    case '\f':
	    case '\r':
	    case '\n':
	    seq_cline++;

	    default:
	    c_mode = WHOINC;
	  }
	}
      }
      /*      printf("out of comment\n"); */
      break;

      case ' ':
      if(first_white)
	break;

      case '\t':
      if(first_white)
	break;
      c = ' ';

      default:
      /*      printf("  gldefault: "); */
      if (c_mode == HAFINC)
      {
        /* Previous '/' did NOT start a comment, so store it */
        storeChar('/', line, maxchar, &n, &first_white);
        c_mode = NOTINC;
      }      
      storeChar(c,line,maxchar,&n,&first_white);
      if (*stmnt_pos == -1)
        /* Start of line */
        *stmnt_pos = ftell(fp) - 1;
      
      if(c == '{'  ||  c == ';' || c == '}') {
	/* came to end of line, clean it up a little */
        if (case_fold)
          upperCase(line);
	if(n>1) 
	  cleanLine(line);
	return(n);
      }
    }
  }
}


void resetLineNo()
{
  seq_cline = 1;
}

int getLineNo()
{
  return seq_cline;
}


void setParseError(char *buf, char *outputBuf)
{
  sprintf(outputBuf, "Error line %d:\n%s", getLineNo(), buf);
}


/* clean up the line: get rid of extra spaces and replace
   commas with spaces
   */
void cleanLine(char *line)
{
  int i, n;
  int iws;         /* in white space flag */
  char c;
  char buff[MAX_LINE_LEN];
  
  buff[0] = '\0';
  n = strlen(line);
  /* convert commas to blanks */
  for(i=0;i<n;i++)
    if(line[i] == ',' || line[i] == '\t')
      line[i] = ' ';
  /* get rid of extra white space */
  iws = 0;
  for(i=0;i<n;i++)
    switch(line[i]) 
    {
      case ' ':
      if (!iws && i != 0)
        strncat(buff,&line[i],1);
      iws = 1;
      break;

      default:
      iws = 0;
      strncat(buff,&line[i],1);
      break;
    }
  strcpy(line,buff);
  /* if nec, put a blank in before the ending character */
  n = strlen(line);
  if(line[n-2]!=' ') {
    c = line[n-1];
    line[n-1] = ' ';
    line[n] = c;
    line[n+1] = 0;
  }
}


/* store a char in a string buffer */
int storeChar(int c, char *line, int maxchar, int *n, int *fw)
{
  /*  printf("sc[%d] = %c in %s\n",*n,c,line); */
  if (*n < maxchar)
  {
    line[*n] = c;
    *n += 1;
    line[*n]   = 0;
    *fw = 0;
    return 0;
  }
  else
  {
    fprintf(stderr,"n = %d\n",*n);
    fprintf(stderr,"Line length exceeds %d characters : %s\n",maxchar,line);
    return -1;
  }

  return 0;
}
