/***************************************************************************
**
**  This file is part of QGpCoreTools.
**
**  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 file 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., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
**
**  See http://www.geopsy.org for more information.
**
**  Created: 2011-09-20
**  Copyright: 2011-2019
**    Marc Wathelet (ISTerre, Grenoble, France)
**
***************************************************************************/

#include "Global.h"
#include "AsciiLineParser.h"
#include "CoreApplication.h"

namespace QGpCoreTools {

  /*!
    \class AsciiLineParser AsciiLineParser.h
    \brief A fast low level parser for ASCII contents

    The parser can be used in several ways:
    \li readLine() which requires the constructor to be initialized with
        the maximum number of columns expected. Remaining columns are ignored.
        Columns are separated by blanks. toInt(), toDouble() and toString() with
        one index argument to retrieve the field values.
    \li columnCount() which counts the available columns. Columns are separated by blanks.
        Values fields cannot be retrieved. This is a fast way for counting columns only.
    \li readColumn() which reads only one value per line. Nothing else than this value can
        be read. Access the field value with toInt(0), toDouble(0) and toString(0).
    \li readColumns() which reads a few values per line. Nothing else than these values can
        be read. Access the field value with toInt(), toDouble() and toString().
    \li use the toInt(), toDouble() and toString() functions with two integers as arguments
        Columns are delimited by these indexes.
  */


  /*!
    Sets buffer to be parsed. Maximum number of columns \a nColumns has to set only
    when using readLine(). For other usages, the default value is fine.

    Accepted separators can be customized in a rather rigid way, only: Tab, Space, Coma or SemiColon.
    By default, only Tab and Space are accepted.
  */
  AsciiLineParser::AsciiLineParser(char * buffer, int size, int nColumns)
  {
    _values=nullptr;
    setColumnCount(nColumns);
    reset(buffer, size);
    defaultOptions();
  }

  /*!
    Sets buffer to be parsed. Maximum number of columns \a nColumns has to set only
    when using readLine(). For other usages, the default value is fine.

    The buffer must be terminated by '\0', ensured by QByteArray. No copy of \a buffer
    is made internally, hence the \a buffer must exist all along the life of this object.

    Accepted separators can be customized in a rather rigid way, only: Tab, Space, Coma or SemiColon.
    By default, only Tab and Space are accepted.
  */
  AsciiLineParser::AsciiLineParser(QByteArray * buffer, int nColumns)
  {
    _values=nullptr;
    setColumnCount(nColumns);
    reset(buffer);
    defaultOptions();
  }

  /*!
    Description of destructor still missing
  */
  AsciiLineParser::~AsciiLineParser()
  {
    delete [] _values;
  }

  void AsciiLineParser::defaultOptions()
  {
    _separators=Tab | Space;
    _emptyValues=true;
    _quotes=false;
  }

  void AsciiLineParser::setColumnCount(int n)
  {
    ASSERT(n>0);
    _maximumValueCount=n;
    _valueCount=_maximumValueCount;
    delete [] _values;
    _values=new char *[_maximumValueCount];
  }

  /*!
    Resets the pointer to the beginning of new buffer
  */
  void AsciiLineParser::reset(char * buffer, int size)
  {
    _buffer=buffer;
    _bufferSize=size;
    _index=0;
  }

  /*!
    Resets the pointer to the beginning of new buffer

    The buffer must be terminated by '\0', ensured by QByteArray. No copy of \a buffer
    is made internally, hence the \a buffer must exist all along the life of this object.
  */
  void AsciiLineParser::reset(QByteArray * buffer)
  {
    _buffer=buffer->data();
    _bufferSize=buffer->count();
    _index=0;
  }

#define READLINE_NEWVALUE \
    if(iBuffer>iBeginValue || _emptyValues) { \
      _buffer[iBuffer]='\0'; \
      _values[iValue]=_buffer+iBeginValue; \
      iValue++; \
      if(iValue==_maximumValueCount) { \
        _valueCount=iValue; \
        iBuffer++; \
        while(iBuffer<_bufferSize && _buffer[iBuffer]!='\n') { \
          iBuffer++; \
        } \
        _index=iBuffer+1; \
        return; \
      } \
    } \
    iBeginValue=iBuffer+1;

  /*!
    Reads one line. toInt() and toDouble() are then ready to be used.
  */
  void AsciiLineParser::readLine()
  {
    int iValue=0;
    int iBuffer=_index;
    int iBeginValue=_index;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\n':
        _buffer[iBuffer]='\0';
#if __clang__
      [[clang::fallthrough]];
#endif
      case '\0':
        if(iBuffer>iBeginValue || _emptyValues) {
          _values[iValue]=_buffer+iBeginValue;
          _valueCount=iValue+1;
        } else {
          _valueCount=iValue;
        }
        _index=iBuffer+1;
        return;
      case '\'':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipSingleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case '"':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipDoubleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case ',':
        if(_separators & Coma) {READLINE_NEWVALUE}
        break;
      case ';':
        if(_separators & Semicolon) {READLINE_NEWVALUE}
        break;
      case ' ':
        if(_separators & Space) {READLINE_NEWVALUE}
        break;
      case '\t':
        if(_separators & Tab) {READLINE_NEWVALUE}
        break;
      case '\r': // Ignored carriage return (not included in field)
        READLINE_NEWVALUE
        break;
      default:
        break;
      }
      iBuffer++;
    }
    if(iBuffer>iBeginValue || _emptyValues) {
      _values[iValue]=_buffer+iBeginValue;
      _valueCount=iValue+1;
    } else {
      _valueCount=iValue;
    }
    _index=iBuffer+1;
  }

#define READVALUE_NEWVALUE \
    if(iBuffer>iBeginValue || _emptyValues) { \
      _buffer[iBuffer]='\0'; \
      _values[valueIndex]=_buffer+iBeginValue; \
      iBuffer++; \
      if(nextLine) { \
        while(iBuffer<_bufferSize && _buffer[iBuffer]!='\n') { \
          iBuffer++; \
        } \
        iBuffer++; \
      } \
      _index=iBuffer; \
      return true; \
    } \
    iBeginValue=iBuffer+1;

  /*!
    \internal
    Reads one value at the current pointer in buffer and moves the general pointer
    to the end of the line if \a nextLine is true. the value is stored at \a valueIndex.
    \a endOfLine is set to true if end of line is encountered.
  */
  inline bool AsciiLineParser::readValue(int valueIndex, bool nextLine, bool& endOfLine)
  {
    int iBuffer=_index;
    int iBeginValue=iBuffer;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        endOfLine=true;
        if(iBuffer>iBeginValue || _emptyValues) {
          _buffer[iBuffer]='\0';
          _values[valueIndex]=_buffer+iBeginValue;
          _index=iBuffer+1;
          return true;
        } else {
          _valueCount=0;
          _index=iBuffer+1;
          return false;
        }
      case '\'':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipSingleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case '"':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipDoubleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case ',':
        if(_separators & Coma) {READVALUE_NEWVALUE}
        break;
      case ';':
        if(_separators & Semicolon) {READVALUE_NEWVALUE}
        break;
      case ' ':
        if(_separators & Space) {READVALUE_NEWVALUE}
        break;
      case '\t':
        if(_separators & Tab) {READVALUE_NEWVALUE}
        break;
      case '\r': // Ignored carriage return (not included in field)
        READVALUE_NEWVALUE
        break;
      default:
        break;
      }
      iBuffer++;
    }
    endOfLine=true;
    if(iBuffer>iBeginValue || _emptyValues) {
      _values[valueIndex]=_buffer+iBeginValue;
      _index=iBuffer+1;
      return true;
    } else {
      _valueCount=0;
      _index=iBuffer+1;
      return false;
    }
  }

#define SKIPVALUE_NEWVALUE \
  if(iBuffer>iBeginValue || _emptyValues) { \
    _index=iBuffer; \
    return; \
  } \
  iBeginValue=iBuffer+1;

  /*!
    \internal
    Skip one value.
  */
  inline void AsciiLineParser::skipValue(bool& endOfLine)
  {
    int iBuffer=_index;
    int iBeginValue=iBuffer;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        endOfLine=true;
        _index=iBuffer+1;
        return;
      case '\'':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipSingleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case '"':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipDoubleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case ',':
        if(_separators & Coma) {SKIPVALUE_NEWVALUE}
        break;
      case ';':
        if(_separators & Semicolon) {SKIPVALUE_NEWVALUE}
        break;
      case ' ':
        if(_separators & Space) {SKIPVALUE_NEWVALUE}
        break;
      case '\t':
        if(_separators & Tab) {SKIPVALUE_NEWVALUE}
        break;
      case '\r': // Ignored carriage return (not included in field)
        SKIPVALUE_NEWVALUE
        break;
      default:
        break;
      }
      iBuffer++;
    }
    endOfLine=true;
    _index=iBuffer+1;
  }

  /*!
    \internal
    Skip doubled quoted text.
  */
  inline int AsciiLineParser::skipSingleQuoted(int iBeginBuffer)
  {
    int iBuffer=iBeginBuffer;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        App::log(tr("Unmatched single quote at index %1.\n").arg(iBuffer));
        return iBuffer;
      case '\'':
        return iBuffer;
      default:
        break;
      }
      iBuffer++;
    }
    return iBuffer;
  }

  /*!
    \internal
    Skip doubled quoted text.
  */
  inline int AsciiLineParser::skipDoubleQuoted(int iBeginBuffer)
  {
    int iBuffer=iBeginBuffer;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        App::log(tr("Unmatched double quote at index %1.\n").arg(iBuffer) );
        return iBuffer;
      case '"':
        return iBuffer;
      default:
        break;
      }
      iBuffer++;
    }
    return iBuffer;
  }

#define READCOLUMN_NEWVALUE \
  if(iBuffer>iBeginValue || _emptyValues) { \
    iValue++; \
    if(iValue==index) { \
      _index=iBuffer+1; \
      return readValue(0, true, endOfLine); \
    } \
  } \
  iBeginValue=iBuffer+1;

  /*!
    Reads one column in current line. toInt() and toDouble() are then ready to be used with index 0 only.
    count() will return 1 or 0.
  */
  bool AsciiLineParser::readColumn(int index)
  {
    _valueCount=1;
    bool endOfLine=false;
    if(index==0) {
      return readValue(0, true, endOfLine);
    }
    int iValue=0;
    int iBuffer=_index;
    int iBeginValue=iBuffer;
    // Read all columns before index
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        _index=iBuffer+1;
        return false;
      case '\'':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipSingleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case '"':
        if(_quotes) {
          iBeginValue=iBuffer+1;
          iBuffer=skipDoubleQuoted(iBeginValue);
          _buffer[iBuffer]='\0';
        }
        break;
      case ',':
        if(_separators & Coma) {READCOLUMN_NEWVALUE}
        break;
      case ';':
        if(_separators & Semicolon) {READCOLUMN_NEWVALUE}
        break;
      case ' ':
        if(_separators & Space) {READCOLUMN_NEWVALUE}
        break;
      case '\t':
        if(_separators & Tab) {READCOLUMN_NEWVALUE}
        break;
      case '\r': // Ignored carriage return (not included in field)
        READCOLUMN_NEWVALUE
        break;
      default:
        break;
      }
      iBuffer++;
    }
    _index=iBuffer+1;
    return false;
  }

  /*!
    Reads \a count columns in current line with indexes stored in \a columns. toInt() and toDouble() are then ready to be
    used with index 0 to \a count-1. count() will return \a count or 0. Constructor must be initialized with at least
    \a count columns. Index of \a columns must be sort ascendingly .
  */
  bool AsciiLineParser::readColumns(int count, int * columns)
  {
    _valueCount=0;
    if(count<=0) {
      return false;
    }
    bool endOfLine=false;
    int lastColIndex=count-1;
    int colIndex=0;
    int iValue=0;
    while(colIndex<count) {
      if(columns[colIndex]==iValue) {
        if(colIndex==lastColIndex) {
          if(!readValue(colIndex, true, endOfLine)) {
            return false;
          }
          _valueCount++;
          return true;
        } else {
          if(!readValue(colIndex, false, endOfLine)) {
            return false;
          }
          _valueCount++;
          colIndex++;
          if(endOfLine) {
            return false;
          }
        }
      } else {
        skipValue(endOfLine);
        if(endOfLine) {
          return false;
        }
      }
      iValue++;
    }
    return false;
  }

#define COUNTCOLUMNS_NEWVALUE \
  if(iBuffer>iBeginValue || _emptyValues) { \
    iValue++; \
  } \
  iBeginValue=iBuffer+1;

  /*!
    Counts the number of columns and moves the pointer to the next line.
    count() returns the number of columns. Maximum number of columns set
    in constructor is ignored here.
  */
  void AsciiLineParser::countColumns()
  {
    int iValue=0;
    int iBuffer=_index;
    int iBeginValue=_index;
    while(iBuffer<_bufferSize) {
      switch(_buffer[iBuffer]) {
      case '\0':
      case '\n':
        if(iBuffer>iBeginValue || _emptyValues) {
          _valueCount=iValue+1;
        } else {
          _valueCount=iValue;
        }
        _index=iBuffer+1;
        return;
      case '\'':
        if(_quotes) {
          iBuffer=skipSingleQuoted(iBuffer+1);
        }
        break;
      case '"':
        if(_quotes) {
          iBuffer=skipDoubleQuoted(iBuffer+1);
        }
        break;
      case ',':
        if(_separators & Coma) {COUNTCOLUMNS_NEWVALUE}
        break;
      case ';':
        if(_separators & Semicolon) {COUNTCOLUMNS_NEWVALUE}
        break;
      case ' ':
        if(_separators & Space) {COUNTCOLUMNS_NEWVALUE}
        break;
      case '\t':
        if(_separators & Tab) {COUNTCOLUMNS_NEWVALUE}
        break;
      case '\r': // Ignored carriage return (not included in field)
        COUNTCOLUMNS_NEWVALUE
        break;
      default:
        break;
      }
      iBuffer++;
    }
    if(iBuffer>iBeginValue || _emptyValues) {
      _valueCount=iValue+1;
    } else {
      _valueCount=iValue;
    }
    _index=iBuffer+1;
  }

  int AsciiLineParser::toInt(int begin, int count)
  {
    ASSERT(begin+count<_bufferSize);
    char c=_buffer[begin+count];
    _buffer[begin+count]='\0';
    int v=atoi(_buffer+begin);
    _buffer[begin+count]=c;
    return v;
  }

  double AsciiLineParser::toDouble(int begin, int count)
  {
    ASSERT(begin+count<_bufferSize);
    char c=_buffer[begin+count];
    _buffer[begin+count]='\0';
    double v=atof(_buffer+begin);
    _buffer[begin+count]=c;
    return v;
  }

  AsciiLineParser::Separators AsciiLineParser::stringToSeparators(const QString& s)
  {
    Separators sep=None;
    if(s.contains("\t")) {
      sep|=Tab;
    }
    if(s.contains(" ")) {
      sep|=Space;
    }
    if(s.contains(",")) {
      sep|=Coma;
    }
    if(s.contains(";")) {
      sep|=Semicolon;
    }
    return sep;
  }

  QString AsciiLineParser::separatorsToString(Separators s)
  {
    QString sep;
    if(s & Tab) {
      sep+="\t";
    }
    if(s & Space) {
      sep+=" ";
    }
    if(s & Coma) {
      sep+=",";
    }
    if(s & Semicolon) {
      sep+=";";
    }
    return sep;
  }

  QString AsciiLineParser::separatorsToUserString(Separators s)
  {
    QString sep;
    if(s & Tab) {
      sep+=tr("TAB");
    }
    if(s & Space) {
      if(!sep.isEmpty()) sep+=",";
      sep+=tr("Space");
    }
    if(s & Coma) {
      if(!sep.isEmpty()) sep+=",";
      sep+=tr("Coma");
    }
    if(s & Semicolon) {
      if(!sep.isEmpty()) sep+=",";
      sep+=tr("Semicolon");
    }
    return sep;
  }

  AsciiLineParser::Separators AsciiLineParser::userStringToSeparators(const QString& s)
  {
    Separators sep=None;
    if(s.contains(tr("TAB"))) {
      sep|=Tab;
    }
    if(s.contains(tr("Space"))) {
      sep|=Space;
    }
    if(s.contains(tr("Coma"))) {
      sep|=Coma;
    }
    if(s.contains(tr("Semicolon"))) {
      sep|=Semicolon;
    }
    return sep;
  }

} // namespace QGpCoreTools
