/***************************************************************************
**
**  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: 2004-10-21
**  Copyright: 2004-2019
**    Marc Wathelet
**    Marc Wathelet (ULg, Liège, Belgium)
**    Marc Wathelet (LGIT, Grenoble, France)
**
***************************************************************************/

#include "StringSection.h"
#include "MemoryChecker.h"

namespace QGpCoreTools {

  /*!
    \class StringSection StringSection.h
    \brief Section of a QString

    Usefull for parser
  */

  /*!
    Default separators inside a line: tab and space
  */
  const QString StringSection::defaultSeparators="\t ";

  /*!
    Default static null string
  */
  const StringSection StringSection::null;

  /*!
    Global Hash function to store StringSection into QHash

    This code was copied from qHash(QString key)
  */
  uint qHash(StringSection key)
  {
    TRACE;
    const QChar * p=key.data();
    int n=key.size();
    uint h=0;
    uint g;

    while(n-- ) {
      h=(h << 4) + ( *p++ ).unicode();
      if((g=(h & 0xf0000000))!=0)
        h ^= g >> 23;
      h &= ~g;
    }
    return h;
  }

  /*!
    Returns the length of the string pointed by \a str, counting characters
    until encountering a null character.
  */
  int StringSection::size(const QChar * str)
  {
    TRACE;
    int i;
    for(i=0; str[i]!=nullptr; i++) {}
    return i;
  }

  int StringSection::compare(const QChar * str1, const QChar * str2, int n)
  {
    TRACE;
    if( ! str1) { // Put invalid strings after valid ones
      if( ! str2) {
        return 0;
      } else {
        return 1;
      }
    } else if( ! str2) {
      return -1;
    }
    for(int i=0;i < n;i++ ) {
      if(str1[ i ] < str2[ i ] ) return -1;
      else if(str1[ i ] > str2[ i ] ) return 1;
    }
    return 0;
  }

  int StringSection::compare(const StringSection &o) const
  {
    TRACE;
    int r;
    if(_size < o._size) {
      r=compare(_str, o._str, _size);
      if(r==0) return -1; else return r;
    } else if(_size > o._size) {
      r=compare(_str, o._str, o._size);
      if(r==0) return 1; else return r;
    } else return compare(_str, o._str, _size);
  }

  const QChar * StringSection::find(QChar c) const
  {
    const QChar * ptr=_str;
    const QChar * ptrE=_str + _size;
    while(ptr < ptrE) {
      if( *ptr==c) break;
      ptr++;
    }
    return ptr;
  }

  qint64 StringSection::find(const StringSection target, bool reverse) const
  {
    TRACE;
    if(!isValid()) return -1;
    const QChar * ptrS=_str;
    const QChar * ptrE=_str+_size;
    int nTarget=target.size();
    qint64 i, n;
    const QChar * targetPtr=target.data();
    QChar initChar;
    if(reverse) {
      targetPtr+=nTarget-1;
      initChar=*targetPtr;
      ptrE--;
      while(ptrE>=ptrS) {
        if(initChar==*ptrE) {
          n=ptrS-ptrE;
          if(n<-nTarget) n=-nTarget;
          for(i=-1; targetPtr[i]==ptrE[i] && i>n; i--) {}
          if(targetPtr[i]==nullptr) return ptrE -_str-nTarget;
        }
        ptrE--;
      }
    } else {
      initChar=*targetPtr;
      while(ptrS<ptrE) {
        if(initChar==*ptrS) {
          n=ptrE-ptrS;
          if(n>nTarget) n=nTarget;
          for(i=1; targetPtr[i]==ptrS[i] && i<n; i++) {}
          if(i==n) return ptrS-_str;
        }
        ptrS++;
      }
    }
    return -1;
  }

  /*!
    Removes white space at the end and at the beginning.
  */
  StringSection& StringSection::trimmed()
  {
    TRACE;
    if(!isValid()) return *this;
    const QChar * ptrE=_str+_size;
    while(_str<ptrE) {
      if(!_str->isSpace()) break;
      _str++;
    }
    while(_str<ptrE) {
      ptrE--;
      if( !ptrE->isSpace()) {
        ptrE++;
        break;
      }
    }
    if(_str<ptrE) _size=ptrE-_str; else _size=0;
    return *this;
  }

  /*!
    Skip all continuous '\\n' and '\\r' pointed by \a ptr.
    If \a ptr is null, it is set to the beginning of this string.
    Returns false if the end of string is reached. \a sep is not null blank fields
    are also skipped.
  */
  bool StringSection::nextLine(const QChar *& ptr, const QString& sep) const
  {
    TRACE;
    if(!isValid()) return false;
    const QChar * ptrE=_str+_size;
    if(!ptr) ptr=_str;
    if(sep.isEmpty()) {
      while(ptr<ptrE) {
        if(*ptr!='\n' && *ptr!='\r') {
          return true;
        }
        ptr++;
      }
    } else {
      while(ptr<ptrE) {
        if(*ptr!='\n' && *ptr!='\r' && !sep.contains(*ptr)) {
          return true;
        }
        ptr++;
      }
    }
    return false;
  }

  /*!
    Returns the line starting at \a ptr as a StringSection excluding termination characters.
    If \a ptr is null, it is set to the beginning of this string. An invalid StringSection is
    returned if the end of this string is reached.
  */
  StringSection StringSection::readLine(const QChar *& ptr) const
  {
    TRACE;
    if(!isValid()) return StringSection::null;
    const QChar * ptrE=_str+_size;
    if(ptr==ptrE) {
      return StringSection::null;
    }
    if(!ptr) ptr=_str;
    StringSection l(ptr, 0);
    while(ptr<ptrE) {
      if(*ptr=='\n' || *ptr=='\r') {
        l.setEnd(ptr);
        // Jump ptr across empty lines
        while(ptr<ptrE) {
          if(*ptr!='\n' && *ptr!='\r') {
            break;
          }
          ptr++;
        }
        return l;
      }
      ptr++;
    }
    return l;
  }

  /*!
    Returns the next field of this string starting at \a ptr.
    If \a sep does not contain '\\n' and '\\r' and if a '\\n' or
    '\\r' is encountered, an invalid string is returned and \a ptr
    points to the first '\\n' or '\\r'. On the contrary, to jump
    over line breaks, include '\\n' and '\\r' in \a sep and switch
    \a skipEmpty to true.

    \code
        const QString sep="= \t\n\r";
        int i;
        StringSection f;
        const QChar * ptr=0;
        for(i=0;i<n;i++) {
          if(!content.nextLine(ptr)) break;
          f=content.nextField(ptr, sep);
          if(f.isValid()) x1=f.toDouble(); else break;
          f=content.nextField(ptr, sep);
          if(f.isValid()) x2=f.toDouble(); else break;
        }
        if(i<n) fprintf(stderr,"Error");
    \endcode
  */
  StringSection StringSection::nextField(const QChar *& ptr, const QString& sep, bool skipEmpty) const
  {
    TRACE;
    if(!isValid()) return StringSection();
    const QChar * ptrE=_str+_size;
    if(!ptr) ptr=_str;
    if(skipEmpty) {
      while(ptr<ptrE) {
        if(sep.contains(*ptr)) {
          ptr++;
        } else if(*ptr=='\r' || *ptr=='\n') {
          ptr++;
          return StringSection();
        } else {
          break;
        }
      }
    }
    if(ptr<ptrE) {
      StringSection field(ptr, 0);
      while(ptr<ptrE) {
        switch(ptr->unicode()) {
        case '\r':
        case '\n':
          // Reached the end of the line
          field.setEnd(ptr);
          return field;
        case '"':
          skipDoubleQuoted(ptr, ptrE);
          break;
        case '\'':
          skipSingleQuoted(ptr, ptrE);
          break;
        default:
          if(sep.contains(*ptr)) {
            field.setEnd(ptr);
            ptr++;
            return field;
          }
          break;
        }
        ptr++;
      }
      // Reached the end of the string
      field.setEnd(ptr);
      return field;
    } else return StringSection();
  }
  /*!
    \internal
    Skip doubled quoted text.
  */
  inline void StringSection::skipSingleQuoted(const QChar *&ptr, const QChar *ptrE) const
  {
    ptr++;
    while(ptr<ptrE) {
      switch(ptr->unicode()) {
      case '\r':
      case '\n':
        ptr--;
        return;
      case '\'':
        return;
      default:
        break;
      }
      ptr++;
    }
  }

  /*!
    \internal
    Skip doubled quoted text.
  */
  inline void StringSection::skipDoubleQuoted(const QChar *&ptr, const QChar *ptrE) const
  {
    ptr++;
    while(ptr<ptrE) {
      switch(ptr->unicode()) {
      case '\r':
      case '\n':
        ptr--;
        return;
      case '"':
        return;
      default:
        break;
      }
      ptr++;
    }
  }

  bool StringSection::nextNumber(int& n, const QChar *& ptr, const QString& sep, bool skipEmpty) const
  {
    TRACE;
    StringSection s=nextField(ptr, sep, skipEmpty);
    if(s.isValid()) {
      n=s.toInt();
    } else return false;
    return true;
  }

  bool StringSection::nextNumber(double& n, const QChar *& ptr, const QString& sep, bool skipEmpty) const
  {
    TRACE;
    StringSection s=nextField(ptr, sep, skipEmpty);
    if(s.isValid()) {
      n=s.toDouble();
    } else return false;
    return true;
  }

  bool StringSection::nextNumber(Complex& n, const QChar *& ptr, const QString& sep, bool skipEmpty) const
  {
    TRACE;
    StringSection s;
    s=nextField(ptr, sep, skipEmpty);
    if(s.isValid()) {
      n.setRe(s.toDouble());
    } else return false;
    s=nextField(ptr, sep, skipEmpty);
    if(s.isValid()) {
      n.setIm(s.toDouble());
    } else return false;
    return true;
  }

  /*!
    Converts string to boolean. \a ok is set to false
    if string is not 'false' or 'true'. \a ok is untouched if
    if no error occur.
  */
  bool StringSection::toBool(bool * ok) const
  {
    if(_size>=1) {
      switch(_str[0].unicode()) {
      case 't':
        if(*this=="true") {
          return true;
        }
        break;
      case 'f':
        if(*this=="false") {
          return false;
        }
        break;
      default:
        break;
      }
    }
    if(ok) *ok=false;
    return true;
  }

  /*!
    Converts string to integer. \a ok is set to false
    if string is not an integer. \a ok is untouched if
    if no error occur.
  */
  int StringSection::toInt(bool * ok, int base) const
  {
    if(ok && *ok) {
      return toString().toInt(ok, base);
    } else {
      return toString().toInt(nullptr, base);
    }
  }

  /*!
    Converts string to unsinged integer. \a ok is set to false
    if string is not an unsinged integer. \a ok is untouched if
    if no error occur.
  */
  uint StringSection::toUInt(bool * ok, int base) const
  {
    if(ok && *ok) {
      return toString().toUInt(ok, base);
    } else {
      return toString().toUInt(nullptr, base);
    }
  }

  /*!
    Converts string to long long. \a ok is set to false
    if string is not a long long. \a ok is untouched if
    if no error occur.
  */
  qlonglong StringSection::toLongLong(bool * ok, int base) const
  {
    if(ok && *ok) {
      return toString().toLongLong(ok, base);
    } else {
      return toString().toLongLong(nullptr, base);
    }
  }

  /*!
    Converts string to a double. \a ok is set to false
    if string is not a double. \a ok is untouched if
    if no error occur.
  */
  double StringSection::toDouble(bool * ok) const
  {
    if(ok && *ok) {
      return toString().toDouble(ok);
    } else {
      return toString().toDouble(nullptr);
    }
  }

  DateTime StringSection::toTime(const QString& format, bool * ok) const
  {
    DateTime t;
    if(ok && *ok) {
      *ok=t.fromString(toString(), format);
      return t;
    } else {
      t.fromString(toString(), format);
      return t;
    }
  }

} // namespace QGpCoreTools
