/*
   ****************************************************************************
   * COPYRIGHT  by Testing Solutions                                          *
   * Reuter, Christian  c.reuter@testingsolutions.de                          *
   * Von-Leoprechting-Str. 14, 86899 Landsberg / Lech Germany                 *
   * Phone: +49 8191 305202, Fax: +49 8191 305244                             *
   * Internet: www.testingsolutions.de                                        *
   ****************************************************************************
   *                                                                          *
   * All Rights Reserved.                                                     *
   *                                                                          *
   * This software is the confidential and proprietary information of the     *
   * author.                                                                  *
   *                                                                          *
   * All brand names and product names used in this software are trademarks,  *
   * registered trademarks, or trade names of their respective holders.       *
   * The authors of this software are not associated with any product or      *
   * vendor mentioned in the code or documentation.                           *
   *                                                                          *
   ****************************************************************************

   DOCUMENTATION:
     This is a low level library to split text files into tokens.
*/

#ifndef IS_FILELOADER_H

#include <stdio.h>
#include <string.h>
#include <stdlib.h>

#include "fileloader.h"

/**************************************************************************
  This is a low level library to split text files into tokens.
  Customize strings for quotes, whitespace and comments.
  Then parse one token after each other calling file_nextToken()
  or set the file cursor to a new position calling file_find(mystr)

  Documentation notes:
  - all int-functions return 0 on error/EOF, 1 on success
  - all documented functions are public, the rest is private

  Example to parse MEA-files:
    char token [256];
    file_setWhitespace (" \t\r\n-=");
    file_setQuotes ("\'{}");
    file_addComment ("$", "\n");
    file_open ("test.mea");
    while (file_find ("[MEA_"))         // search for string [MEA_
    {
      file_nextToken (token, 256);      // read the [MEA_ token
      printf ("%s\n", token);           // and print it on stdout
    }
    file_rewind ();                     // again from the beginning
    while (file_nextToken (token, 256)) // read every token
    {
      printf ("%s\n", token);           // print out every token
    }
**************************************************************************/

int file_quoteremove = 0;

FILE *file_file = NULL;         /* FILE pointer */
char file_buffer [FILE_MAXLEN+1]; /* file buffer in RAM */
int file_ptr = FILE_MAXLEN;     /* file_buffer pointer to current char = "file cursor" */
char file_whitespace [256];     /* string containing the whitespace chars */
char file_separator [256];      /* string containing the separator chars */
int file_commentnum = 0;        /* number of comment strings */
char file_quotes [256];         /* string containing the quote chars */
char file_commentstart [FILE_MAXCOMMENTS] [256]; /* strings for comment beginnings */
char file_commentend [FILE_MAXCOMMENTS] [256];   /* strings for corresponding comment ends */
int file_bufferlen = FILE_MAXLEN;                /* current length of the file buffer */
int file_line = 1;
char file_currentquotes = 0;
char file_currentcomment = 0;
XLONG file_bufferstart = 0;
int file_bufferread = 0;
char file_lastchar;
int file_reverse = 0;

/**************************************************************************
  Init data structure
  Example: file_init ();
**************************************************************************/
void file_init ()
{
  memset (file_whitespace, 0, 256);
  memset (file_separator, 0, 256);
  memset (file_quotes, 0, 256);
  memset (file_commentstart, 0, FILE_MAXCOMMENTS * 256);
  memset (file_commentend, 0, FILE_MAXCOMMENTS * 256);
  file_commentnum = 0;
  file_quoteremove = 0;
  file_currentquotes = 0;
  file_currentcomment = 0;
  file_reverse = 0;
  file_bufferread = 0;
  file_bufferstart = 0;
  file_buffer [FILE_MAXLEN] = 0;
}

void file_initComments ()
{
  memset (file_commentstart, 0, FILE_MAXCOMMENTS * 256);
  memset (file_commentend, 0, FILE_MAXCOMMENTS * 256);
  file_commentnum = 0;
}

/**************************************************************************
  Get current position of the file pointer
  Example: file_getPosition ();
**************************************************************************/
XLONG file_getPosition ()
{
  if (!file_reverse)
    return (XLONG) ftell (file_file) - FILE_MAXLEN + file_ptr;
  else
    return (XLONG) ftell (file_file) - file_ptr;
}

/**************************************************************************
  Get current line of the file
  Example: file_getLine ();
**************************************************************************/
int file_getLine ()
{
  return file_line;
}

/**************************************************************************
  Set the whitespace chars for this file
  Example: file_setWhitespace (" \n\t");
**************************************************************************/
void file_setWhitespace (char *string)
{
  strncpy (file_whitespace, string, 256);
  file_whitespace [255] = 0;
}

/**************************************************************************
  Set the separator chars for this file
  Example: file_setSeparator (",;");
**************************************************************************/
void file_setSeparator (char *string)
{
  strncpy (file_separator, string, 256);
  file_separator [255] = 0;
}

/**************************************************************************
  Add comment strings for this file
  Example language C++:
    file_addComment ("//", "\n");
**************************************************************************/
void file_addComment (char *startstring, char *endstring)
{
  if (file_commentnum >= FILE_MAXCOMMENTS) return;
  strncpy (file_commentstart [file_commentnum], startstring, 256);
  file_commentstart [file_commentnum] [255] = 0;
  strncpy (file_commentend [file_commentnum], endstring, 256);
  file_commentend [file_commentnum] [255] = 0;
  file_commentnum ++;
}

/**************************************************************************
  Set the quote chars for string in this file
  Example: file_setQuotes ("\'\"");
**************************************************************************/
void file_setQuotes (char *string)
{
  strncpy (file_quotes, string, 256);
  file_quotes [255] = 0;
}

void file_revertBuffer (int start, int end)
{
  int i;
  char c;
  for (i = 0; i <= (end - start) / 2; i ++)
  {
    c = file_buffer [start + i];
    file_buffer [start + i] = file_buffer [end - i];
    file_buffer [end - i] = c;
  }
}

int file_fillBuffer ()
{
  int bytesread = 0;
  int n = FILE_MAXLEN - file_ptr;

  int bytestoread = file_ptr;

  if (file_ptr < FILE_MAXLEN)
    if (file_buffer [file_ptr] == 0)
      return 0;

  if (file_ptr <= FILE_MAXLEN / 2) return 1;

  memcpy (file_buffer, &file_buffer [file_ptr], n);

  if (file_reverse)
  {
    if (file_bufferstart - bytestoread - file_bufferread >= 0)
    {
      fseek (file_file, -bytestoread - file_bufferread, SEEK_CUR);
      file_bufferstart += -bytestoread - file_bufferread;
    }
    else
    {
      bytestoread = (int) file_bufferstart - file_bufferread;
      fseek (file_file, 0, SEEK_SET);
      file_bufferstart = 0;
      memset (&file_buffer [n + bytestoread], 0, FILE_MAXLEN - (n + bytestoread));
    }
  }

  bytesread = fread (&file_buffer [n], 1, bytestoread, file_file);
  file_bufferlen = file_bufferlen + bytesread - file_ptr;
  file_bufferread = bytesread;
  
  file_bufferstart += bytesread;
  if (file_reverse)
  {
    if (bytesread > 0)
      file_revertBuffer (n, n + bytesread - 1);
  }

  if (bytesread != file_ptr)
  {
    if (file_bufferlen >= 0 && file_bufferlen < FILE_MAXLEN)
      file_buffer [file_bufferlen] = 0;
  }
  file_ptr = 0;
  return 1;
}

/**************************************************************************
  Revert the file read order (forward/backward)
  Example: file_revert ();
**************************************************************************/
void file_revert ()
{
  if (file_reverse)
  {
    fseek (file_file, -file_bufferread - file_ptr + file_bufferlen, SEEK_CUR);
    file_bufferstart += -file_bufferread - file_ptr + file_bufferlen;
  }
  file_bufferread = file_bufferlen - file_ptr;
  file_reverse = !file_reverse;
  file_ptr = FILE_MAXLEN;
  file_bufferlen = FILE_MAXLEN;
  file_fillBuffer ();
}

/**************************************************************************
  Set current position of the file pointer
  Example: file_setPosition ();
**************************************************************************/
int file_setPosition (XLONG pointer, int whence)
{
  if (fseek (file_file, 0, whence))
    return 0;

  if (whence == 1)
  {
    if (!file_reverse)
      pointer += -FILE_MAXLEN + file_ptr;
    else
      pointer += -file_ptr;
  }

  while (pointer > 0x77777777)
  {
    if (fseek (file_file, 0x77777777, 1))
      return 0;
    pointer -= 0x77777777;
  }
  while (pointer < -0x77777777)
  {
    if (fseek (file_file, -0x77777777, 1))
      return 0;
    pointer += 0x77777777;
  }
  if (!fseek (file_file, (long) pointer, 1))
  {
    file_bufferstart = ftell (file_file);
    file_bufferread = 0;
    file_ptr = FILE_MAXLEN;
    file_bufferlen = FILE_MAXLEN;
    file_fillBuffer ();
    return 1;
  }
  return 0;
}

/**************************************************************************
  Rewind file to an initial state to restart parsing
**************************************************************************/
void file_rewind ()
{
  fseek (file_file, 0, SEEK_SET);
  file_reverse = 0;
  file_ptr = FILE_MAXLEN;
  file_bufferlen = FILE_MAXLEN;
  file_bufferstart = 0;
  file_fillBuffer ();
  file_line = 1;
}

/**************************************************************************
  Open file for parsing
**************************************************************************/
int file_open (char *filename)
{
  file_file = fopen (filename, "rb");
  if (!file_file) return 0;
  file_rewind ();
  return 1;
}

/**************************************************************************
  Close file
**************************************************************************/
int file_close ()
{
  if (file_file)
    return !fclose (file_file);
  else
    return 1;
}

int file_isChar (char *string)
{
  int i = 0;
  while (string [i] != 0 && i < 256)
  {
    if (file_buffer [file_ptr] == string [i])
      return 1;
    i ++;
  }
  return 0;
}

int file_isCommentStart ()
{
  int i, i2;
  for (i = 0; i < file_commentnum; i ++)
  {
    for (i2 = 0; i2 < (int) strlen (file_commentstart [i]); i2 ++)
      if (file_buffer [file_ptr + i2] != file_commentstart [i] [i2])
        break;
    if (i2 == (int) strlen (file_commentstart [i]))
    {
      file_currentcomment = i;
      return 1;
    }
  }
  return 0;
}

int file_isCommentEnd ()
{
  int i, i2;
  i = file_currentcomment;
  for (i2 = 0; i2 < (int) strlen (file_commentend [i]); i2 ++)
    if (file_buffer [file_ptr + i2] != file_commentend [i] [i2])
      break;
  if (i2 == (int) strlen (file_commentend [i]))
  {
    for (i = 0; i < i2; i ++)
      (void) file_nextChar ();
    return 1;
  }
  return 0;
}

/**************************************************************************
  Find the first occurance of string from the cursor and set cursor
  to the new position, this is quite fast!
**************************************************************************/
int file_find (char *string)
{
  char *result = NULL;
  int stringlen = strlen (string);

  if (stringlen >= FILE_MAXLEN / 2)
    return 0;

  while (result == NULL)
  {
    result = strstr (&file_buffer [file_ptr], string);
    if (result == NULL)
    {
      file_ptr = file_bufferlen - stringlen;
      if (file_bufferlen < FILE_MAXLEN)
        return 0;
      if (!file_fillBuffer ())
        return 0;
    }
    else
    {
      file_ptr += (int) (result - (&file_buffer [file_ptr]));
      file_fillBuffer ();
      return 1;
    }
  }
  return 1;
}

int file_nextChar ()
{
  if (file_buffer [file_ptr] == '\n' && file_lastchar != 92)
    file_line ++;
  file_lastchar = file_buffer [file_ptr];
  file_ptr ++;
  return file_fillBuffer ();
}

int file_tokenpos = 0;

int file_fillToken (char *token, int tokenlen)
{
  if (tokenlen == 0)
    return 0;
  if (file_tokenpos >= tokenlen - 1)
  {
    token [tokenlen - 1] = 0;
    return 0;
  }
  token [file_tokenpos ++] = file_buffer [file_ptr];
  token [file_tokenpos] = 0;
  return 1;
}

/**************************************************************************
  Find the next token from the current cursor position and fill the
  token string. Use tokenlen to define a maximum length for token.
**************************************************************************/
int file_nextToken (char *token, int tokenlen)
{
  char last = 0;
  file_tokenpos = 0;
  if (tokenlen <= 0) return 1;
  token [0] = '\0';

  if (file_buffer [file_ptr] == 0)
  {
    return 0;
  }

  while (1)
  {
    if (file_isChar (file_whitespace))
    {
      if (!file_nextChar ())
        return 0;
    }
    else if (file_isChar (file_separator))
    {
      if (!file_fillToken (token, tokenlen))
        return 1;
      if (!file_nextChar ())
        return 0;
      return 1;
    }
    else if (file_isCommentStart ())
    {
      do
      {
        if (!file_nextChar ())
          return 0;
      } while (!file_isCommentEnd ());
    }
    else if (file_isChar (file_quotes))
    {
      file_currentquotes = file_buffer [file_ptr];
      if (!file_quoteremove)
        if (!file_fillToken (token, tokenlen)) /* opening quotes */
          return 1;
      last = 0;
      while (1)
      {
        last = file_buffer [file_ptr];
        if (!file_nextChar ())
          return 0;
        if (file_buffer [file_ptr] == file_currentquotes && last != '\\')
        {
          if (!file_quoteremove)
            if (!file_fillToken (token, tokenlen)) /* closing quotes */
              return 1;
          if (!file_nextChar ())
            return 0;
          return 1;
        }
        if (file_buffer [file_ptr] != '\\')
        {
          if (last != '\\')
          {
            if (!file_fillToken (token, tokenlen))
              return 1;
          }
          else
          {
            if (file_buffer [file_ptr] == 'n') file_buffer [file_ptr] = '\n';
            else if (file_buffer [file_ptr] == 't') file_buffer [file_ptr] = '\t';
            else if (file_buffer [file_ptr] == 'r') file_buffer [file_ptr] = '\r';
            else if (file_buffer [file_ptr] == 'a') file_buffer [file_ptr] = '\a';
            else if (file_buffer [file_ptr] == '\\') file_buffer [file_ptr] = '\\';
            if (!file_fillToken (token, tokenlen))
              return 1;
          }
        }
      }
    }
    else
    {
      while (1)
      {
        if (!file_fillToken (token, tokenlen))
          return 1;
        if (!file_nextChar ())
          return 0;
        if (file_isChar (file_whitespace))
          return 1;
        if (file_isChar (file_separator))
          return 1;
        if (file_isCommentStart ())
          return 1;
        if (file_isChar (file_quotes))
          return 1;
      }
    }
  }
  return 1;
}

#endif
