//------------------------------------------------------------------------------
//   module fileutil.cpp  version 2.0b (english)                              //
//                                                                            //
//   utility functions to read values/strings from a stream.                  //
//                                                                            //
//    copyright (c) 1998-2003 by Lars Haendel                                 //
//    home: www.newty.de                                                      //
//                                                                            //
//    This program is free software and can be used under the terms of the    //
//    GNU licence. See header file for further information and disclaimer.    //
//                                                                            //
//------------------------------------------------------------------------------


#include <stdio>                 // due to:        sprintf()

#include "fileutil.h"            //
#include "util.h"
#include "defines.h"             //                STS (standard string length) and IsComment()

#define LEN_ILLEGAL_SEQ 10



//----------------------------------------------------------------------------------------------------------------------
// return flag (bool) as string 'true' or 'false'
const char* FlagToString(const int& flag, const bool& f_LeftAlign/*=false*/)
{
   if(flag<0 || flag>1)
      return "??";
   else
      if(flag)
         if(f_LeftAlign)
            return "true ";   // add space at the end
         else
            return "true";
      else
         return "false";
}


//----------------------------------------------------------------------------------------------------------------------
// size of a string excluding terminating '\0', i.e. szString[size] is '\0'
int SizeOfString(const char*const& szString)
{
   int size =0;
   while(szString[size]!='\0')
      size++;
   return size;
}


//----------------------------------------------------------------------------------------------------------------------
// remove a filename's extension
void RemoveExt(char*const& szFilename)
{
   int len = SizeOfString(szFilename);          // determine length

   while(len>=0)                                // position back to last '.'
   {
      if(szFilename[len]=='.')                  // if point is found
      {
         szFilename[len] = '\0';                // 'delete' rest of string
         break;                                 // and finish
      }
      len--;
   }
}


//----------------------------------------------------------------------------------------------------------------------
// check if passed string is a valid filename without(!) path and extension
bool CheckName(const char*const& szName)
{
   int i=0;
   while(szName[i]!='\0')
   {
      if(szName[i]=='\\'|| szName[i]=='/' || szName[i]==':' || szName[i]=='.') // no backslash, slash, colon or dot
         return false;
      i++;
   }
   return true; // all ok
}


//----------------------------------------------------------------------------------------------------------------------
// exchange a filename's extension
void ExchangeExt(char*const& szFilename, const char*const& szExtension)
{
   RemoveExt(szFilename);                       // remove extension
   strcat(szFilename, ".");                     // append dot
   strcat(szFilename, szExtension);             // append extension
}


//----------------------------------------------------------------------------------------------------------------------
// a path delimiter is appended if passed string is not empty and if it is not alread there
void EnsurePathDelimiter(char*const& szPath)
{
   int len = SizeOfString(szPath);              // determine length

   if(len>0)                                    // if string is not empty
      if(szPath[len-1]!=PATH_DELIMITER)         // append path delimiter if necessary
      {
         szPath[len] = PATH_DELIMITER;          // append and ...
         szPath[len+1]  = '\0';                 // ... terminate string
      }
}


//----------------------------------------------------------------------------------------------------------------------
// copy path without filename to <szPath>
void ExtractPath(char*const& szPath, const char*const& szFilename)
{
   szPath[0]='\0';                              // 'delete' path

   int i=0, pos=0;
   while(szFilename[i++]!='\0')                 // find last path delimiter, set <pos> to point to next character
      if(szFilename[i-1]==PATH_DELIMITER)       // path delimiter found
         pos=i;                                 // store position

   for(int i=0;i<pos;i++)                       // copy path (<pos> will be 0 if no delimiter was found)
      szPath[i]=szFilename[i];
   szPath[pos]='\0';
}


//----------------------------------------------------------------------------------------------------------------------
// replaces each wrong path delimiter (COMPL_PATH_DELIMITER) by the correct one (PATH_DELIMITER)
void CorrectPathDelimiter(char*const& szFilename)
{
   int i=0;
   while(szFilename[i]!='\0')                    // parse whole string
   {
      if(szFilename[i]==COMPL_PATH_DELIMITER)
         szFilename[i]=PATH_DELIMITER;           // replace
      i++;
   }
}


//----------------------------------------------------------------------------------------------------------------------
// prepend path to filename if necessary, i.e. if filename is relative (does not contain a ':') and if filename is
// not empty
void PrefixPath(char*const& szFilename, const char*const& szDir)
{
   if(szFilename[0]=='\0')          // return if filename is empty
      return;

   int i=0;
   while(szFilename[i++]!='\0')     // return if filename contains ':' which indicates that it is already a full ...
      if(szFilename[i-1]==':')      // ... qualified name
         return;

   char szText[STS];                // prefix filename
   strcpy(szText, szDir);
   EnsurePathDelimiter(szText);     // ensure path delimiter
   strcat(szText, szFilename);
   strcpy(szFilename, szText);
}


//----------------------------------------------------------------------------------------------------------------------
// get pointer to full qualified filename using PrefixPath()
const char* GetPrefixedPath(const char*const& szFileName, const char*const& szDir)
{
   static char szText[STS];
   strcpy(szText, szFileName);         // copy
   PrefixPath(szText, szDir);          // prepend path if necessary

   return szText;
}


//----------------------------------------------------------------------------------------------------------------------
// return pointer to filename without preceding path
const char* GetFilename(const char*const& szPath)
{
   int len = SizeOfString(szPath);                                      // get string length
   while(szPath[len]!=PATH_DELIMITER && szPath[len]!=':' && len >=0)    // position back from the end to the first ...
      len--;                                                            // ... path delimiter or ':' (path on floppy)

   return &szPath[len+1];
}


//----------------------------------------------------------------------------------------------------------------------
// search stream for string 'szKey' followed by a '=' and read following string.
bool ReadKeyString(ifstream& file, const char*const& szKey, char* szString, const int& size, const int& maxLines)
{
   szString[0]='\0';                               // 'delete' string
   file.clear();
   streampos curPos = file.tellg();                // preserve actual stream position
   try{
      SearchKey(file, szKey, maxLines, '[');       // search key
   } catch(int errNo) { goto fail; }

   if(file.get()!='=')                             // if next character is not '=' -> return false
      goto fail;

   if(skipws(file)!=0)                             // remove whitespaces
      throw ValueMissing;

   ReadString(file, szString, size);               // read string

   file.seekg(curPos);                             // restore stream position
   return true;

   fail:
   file.seekg(curPos);                             // restore stream position
   return false;
}


//----------------------------------------------------------------------------------------------------------------------
// search stream for string 'szKey' followed by a '=', read following string and compare it with
// 'true'/'false'.
int ReadKeyBool(ifstream& file, const char*const& szKey, const int defaultValue, const int& maxLines)
{
   char szString[STS];
   if(!ReadKeyString(file, szKey, szString, STS, maxLines))    // read 'szKey = string'
      return defaultValue;

   if(strcmp(szString, "true")==0)           // if key was found: compare string to 'true' and 'false'
      return true;
   else
      if(strcmp(szString, "false")==0)
         return false;
      else
         throw BoolReadFailed;
}


//----------------------------------------------------------------------------------------------------------------------
// global variables
static char szKey  [STS];
static char szErrCode[STS];      // error description
static char szIllSeq [STS];      // illegal sequence
static char illChar  ='\0';      // illegal character

char& GetIllegalCharacter() { return illChar; }
const char* GetLastKey() { return szKey; };


//----------------------------------------------------------------------------------------------------------------------
// function to get a description of the last error.
const char* GetLastError(const int& errNo)
{
   switch(errNo)
   {
      case ErrReadingBits   : sprintf(szErrCode, "Encountered '%c' when reading bits! Only '0' and '1' are valid characters!"
                                       , illChar); break;
      case UnterminatedBits : sprintf(szErrCode, "Encountered '%c' where bitstring should be terminated by a whitespace!"
                                       , illChar); break;
      case BoolReadFailed   : sprintf(szErrCode, "Key '%s' must be either 'true' or 'false'!", szKey); break;
      case ValueMissing     : sprintf(szErrCode, "Value missing for key '%s'!", szKey); break;
      case NoWhitespace     :
         switch(illChar)
         {
            case '.'    : strcpy(szErrCode,"I found a point directly after the value! \nMaybe you've specified a float\
value where an integer was expected ?"); break;
            case '+'    :
            case '-'    : strcpy(szErrCode,"'+' or '-' directly follows preceeding number!"); break;
            default     : sprintf(szErrCode,"Encountered illegal character '%c'!", illChar);
         }  break;

      case KeyNotFound      :    sprintf(szErrCode,"Key '%s' not found!", szKey); break;
      case UnexpectedEndl   :    strcpy(szErrCode,"Unexpected end of line!"); break;
      case UnexpectedEOF    :    strcpy(szErrCode,"Unexpected end of file!"); break;
      case IllegalCharacter :
         if(illChar=='+' || illChar=='-')
            sprintf(szErrCode,"Displaced character '%c' - maybe a blank to much?", illChar);
         else
            sprintf(szErrCode,"Encountered illegal character '%c'!", illChar);
         break;

      case NoEndlFound :
         szIllSeq[LEN_ILLEGAL_SEQ]='\0';                                                  // terminate the string
         sprintf(szErrCode, "Line should be ended but I found '%s'!", szIllSeq); break;
      default          : strcpy(szErrCode,"Unknown reason!"); break;
   }

   return szErrCode;
}


//----------------------------------------------------------------------------------------------------------------------
// wrapper which removes all whitespaces from a stream. an exception is thrown if no endl is encountered
int skipwsEndl(ifstream& file)
{
   int lines = skipws(file);              // remove all whitespaces

   if (lines==0)
   {
      file.get(szIllSeq, STS);
      throw NoEndlFound;                  // throw exception if no endl occured
   }

   return lines;
}


//----------------------------------------------------------------------------------------------------------------------
// like skipwsEndl() but the function skipwsEx(), which addionally removes comments, is used
int skipwsExEndl(ifstream& file)
{
   int lines = skipwsEx(file);         // remove all whitespaces and(!) comments

   if (lines==0)
   {
      file.get(szIllSeq, STS);
      throw NoEndlFound;               // throw exception if no endl occured
   }

   return lines;
}


//----------------------------------------------------------------------------------------------------------------------
// wrapper which removes all whitespaces from a stream. an exception is thrown if an endl is encountered
void skipwsNoEndl(ifstream& file)
{
   int lines = skipws(file);           // remove all whitespaces

   if (lines>0)
      throw UnexpectedEndl;            // throw exception if an endl occured
}


//----------------------------------------------------------------------------------------------------------------------
// like skipwsNoEndl() but the function skipwsEx(), which addionally removes comments, is used
void skipwsExNoEndl(ifstream& file)
{
   int lines = skipwsEx(file);         // remove all whitespaces and(!) comments

   if (lines>0)
      throw UnexpectedEndl;            // throw exception if an endl occured
}


//----------------------------------------------------------------------------------------------------------------------
// functions reads all whitespaces from an ifstream returning the number of encountered endl's
int skipws(ifstream& file)
{
   file.clear();                 // reset stream state

   int line =0;
   int c;

   // remove whitespaces counting endl's
   do
   {
      c=file.get();
      if(!isspace(c))
         break;

      if(c =='\n')               // if it's an endl: increment counter
         line++;

   } while (file.fail()==0);     // proceed while there are ws in the stream


   // finish
   file.clear();                 // reset stream state
   file.putback((char) c);       // put the last character back in the stream cause it's no ws

   return line;
}


//----------------------------------------------------------------------------------------------------------------------
// functions reads all whitespaces and comments from an ifstream returning the number of encountered endl's.
int skipwsEx(ifstream& file)
{
   file.clear();                    // reset stream state

   int line =0;
   int c;

   // while there are whitespaces or comments ...
   do
   {
      c =file.get();                         // read char and ...
      if(IsComment((char) c))                // ... if it's a comment ->  ...
      {
         file.ignore(INT_MAX, '\n');         // ... skip the rest of the line
         line++;                             // increment line-counter
      }
      else
         if (c =='\n')                       // else if it's an endl
            line++;                          // increment line-counter
         else
            if(!isspace(c))                  // else if it's neither ws nor comment -> break
               break;

   }
   while (file.fail()==0);

   // finish
   file.clear();              // reset stream state
   file.putback((char) c);    // put the last character back in the stream

   return line;
}


//----------------------------------------------------------------------------------------------------------------------
// utility-function for template readExpNoEndl() - is called to examine a stream failstate.
void ReadFailed(ifstream& file)
{
   file.clear();                    // reset stream state

   int c;

   if (EOF == (char) file.peek())   // peek and compare with EOF (end of file)
      throw UnexpectedEOF;


  // else: inspect if the character is a ws. if so it cannot be the one which caused the error. so go back
  // in the stream until you find a none ws. this is the one the user wants to know;-)
  while(isspace(c = file.get()) && file.fail()==0)
      file.seekg(-2,ios::cur);

   illChar = (char) c;

   throw IllegalCharacter;    // throw exception
}


//----------------------------------------------------------------------------------------------------------------------
// function reads a string from an ifstream. All whitespaces, comments and the delimiter
// terminate the string. The delimiter is left in the stream. The delimiter is returned.
// note: remind that a string can be empty.
char ReadString(ifstream& file, char* szString, const int& size, const char& delimiter)
{
   file.clear();                       // reset stream state

   int i=0, c;

   do
   {
      c = file.get();                  // read character

      if(file.fail()!=0)               // if the stream gets into a fail state
      {
         file.clear();                 // just for principle: reset stream state
         throw UnexpectedEOF;          // throw an exception. note: EOF is the only possible reason
      }

      // if character is whitespace, comment, delimiter or EOF -> stop reading, i.e. terminate the string and return
      if(isspace(c) || IsComment((char) c) || c==delimiter || EOF == (char) c)
      {
         file.putback((char) c);
         break;
      }
      szString[i++]= (char) c;         // else: copy character to string and continue

   } while(i<size-1);

   szString[i]='\0';    // terminate string

   return (char) c;     // return delimiter
}


//----------------------------------------------------------------------------------------------------------------------
// function searches stream for string 'szKey'. The key must be at the begin of a line.
// if key is found, stream position is moved to first following non-whitespace
//
// exceptions are thrown if:
//    a) key not found
//    b) key found but first following non-whitespace is EOF
//
// return value: # encountered endl
int SearchKey(ifstream& file, const char*const& _szKey, const int& maxLines/*=INT_MAX*/, const char& delimiter/*=EOF*/)
{
   if(maxLines<1)
      throw 1;

   file.clear();

   int line =0;
   char buffer[STS+5];        // note: don't know anymore if this is necessary
   buffer[0] = '\0';

   strcpy(szKey, _szKey);     // store in global

   // read linewise until match is found or EOF is encountered
   while(strcmp(buffer, szKey)!=0 /* && file.fail()==0 */)
   {
      line += skipwsEx(file);                                              // skip all whitespaces and comments
      if(file.peek()=='=')                                                 // skip '=' cause ReadString() won't do it
         file.get();

      // key wasn't found if EOF is reached
      if(file.peek()==delimiter || line>maxLines || ReadString(file, buffer, STS, '=')==EOF)
         throw KeyNotFound;
   }

   line += skipwsEx(file);    // position to next non-whitespace

   if(file.fail()!=0)         // note: only way to get into a fail-state is EOF
      throw UnexpectedEOF;

   return line;
}


//----------------------------------------------------------------------------------------------------------------------
// read 'nBits' bits from ifstream
void ReadBits(ifstream& file, int& value, const int& nBits/*=32*/)
{
   file.clear();              // reset stream state

   ClearAllBits(value);       // clear all bits

   int c;
   for(int i=0;i<min(MaxBits, nBits);i++)
   {
      c=file.get();                 // read character
      if(c=='1')
         SetBit(value,i);           // set bit
      else if(c!='0')
      {
         illChar = (char) c;        // store illegal character
         throw ErrReadingBits;      // throw error if character is neither '0' nor '1'
      }
   }

   // ensure following whitespace
   if(!isspace(file.peek()))
   {
      illChar = (char) c;              // store illegal character
      throw UnterminatedBits;
   }
}