/****************************************************************************
 *   This file is part of the aGrUM/pyAgrum library.                        *
 *                                                                          *
 *   Copyright (c) 2005-2026 by                                             *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *                                                                          *
 *   The aGrUM/pyAgrum library is free software; you can redistribute it    *
 *   and/or modify it under the terms of either :                           *
 *                                                                          *
 *    - the GNU Lesser General Public License as published by               *
 *      the Free Software Foundation, either version 3 of the License,      *
 *      or (at your option) any later version,                              *
 *    - the MIT license (MIT),                                              *
 *    - or both in dual license, as here.                                   *
 *                                                                          *
 *   (see https://agrum.gitlab.io/articles/dual-licenses-lgplv3mit.html)    *
 *                                                                          *
 *   This aGrUM/pyAgrum library is distributed in the hope that it will be  *
 *   useful, but WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,          *
 *   INCLUDING BUT NOT LIMITED TO THE WARRANTIES MERCHANTABILITY or FITNESS *
 *   FOR A PARTICULAR PURPOSE  AND NONINFRINGEMENT. IN NO EVENT SHALL THE   *
 *   AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER *
 *   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,        *
 *   ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR  *
 *   OTHER DEALINGS IN THE SOFTWARE.                                        *
 *                                                                          *
 *   See LICENCES for more details.                                         *
 *                                                                          *
 *   SPDX-FileCopyrightText: Copyright 2005-2026                            *
 *       - Pierre-Henri WUILLEMIN(_at_LIP6)                                 *
 *       - Christophe GONZALES(_at_AMU)                                     *
 *   SPDX-License-Identifier: LGPL-3.0-or-later OR MIT                      *
 *                                                                          *
 *   Contact  : info_at_agrum_dot_org                                       *
 *   homepage : http://agrum.gitlab.io                                      *
 *   gitlab   : https://gitlab.com/agrumery/agrum                           *
 *                                                                          *
 ****************************************************************************/


/*-------------------------------------------------------------------------
Compiler Generator Coco/R,
Copyright (c) 1990, 2004 Hanspeter Moessenboeck, University of Linz
extended by M. Loeberbauer & A. Woess, Univ. of Linz
ported to C++ by Csaba Balazs, University of Szeged
with improvements by Pat Terry, Rhodes University

This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2, or (at your option) any
later version.

This program 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 General Public License
for more details.

You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

As an exception, it is allowed to write an extension of Coco/R that is
used as a plugin in non-free software.

If not otherwise stated, any source code generated by Coco/R (other than
Coco/R itself) does not fall under the GNU General Public License.
-------------------------------------------------------------------------*/
// MappedBuffer system headers
#if defined(_WIN32)
#  ifndef NOGDI
#    define NOGDI
#    include <windows.h>
#    undef NOGDI
#  else
#    include <windows.h>
#  endif
#else
#  include <fcntl.h>
#  include <unistd.h>

#  include <sys/mman.h>
#  include <sys/stat.h>
#endif

#include <agrum/agrum.h>

#include <agrum/base/core/cocoR/common.h>

#ifndef DOXYGEN_SHOULD_SKIP_THIS

namespace gum {

  wchar_t* coco_string_create(const wchar_t* value) { return coco_string_create(value, 0); }

  wchar_t* coco_string_create(const wchar_t* value, int startIndex) {
    std::size_t valueLen = 0;
    std::size_t len      = 0;

    if (value) {
      valueLen = wcslen(value);
      len      = valueLen - startIndex;
    }

    return coco_string_create(value, startIndex, (int)len);
  }

  wchar_t* coco_string_create(const wchar_t* value, int startIndex, int length) {
    int      len = 0;
    wchar_t* data;

    if (value) { len = length; }

    data = new wchar_t[len + 1];
    wcsncpy(data, &(value[startIndex]), len);
    data[len] = 0;

    return data;
  }

  wchar_t* coco_string_create_upper(const wchar_t* data) {
    if (!data) { return nullptr; }

    std::size_t dataLen = 0;

    if (data) { dataLen = wcslen(data); }

    wchar_t* newData = new wchar_t[dataLen + 1];

    for (int i = 0; i <= int(dataLen); i++) {
      if ((L'a' <= data[i]) && (data[i] <= L'z')) {
        newData[i] = data[i] + (L'A' - L'a');
      } else {
        newData[i] = data[i];
      }
    }

    newData[dataLen] = L'\0';
    return newData;
  }

  wchar_t* coco_string_create_lower(const wchar_t* data) {
    if (!data) { return nullptr; }

    std::size_t dataLen = wcslen(data);
    return coco_string_create_lower(data, 0, int(dataLen));
  }

  wchar_t* coco_string_create_lower(const wchar_t* data, int startIndex, int dataLen) {
    if (!data) { return nullptr; }

    wchar_t* newData = new wchar_t[dataLen + 1];

    for (int i = 0; i <= dataLen; i++) {
      wchar_t ch = data[startIndex + i];

      if ((L'A' <= ch) && (ch <= L'Z')) {
        newData[i] = ch - (L'A' - L'a');
      } else {
        newData[i] = ch;
      }
    }

    newData[dataLen] = L'\0';
    return newData;
  }

  wchar_t* coco_string_create_append(const wchar_t* data1, const wchar_t* data2) {
    wchar_t*    data;
    std::size_t data1Len = 0;
    std::size_t data2Len = 0;

    if (data1) { data1Len = wcslen(data1); }

    if (data2) { data2Len = wcslen(data2); }

    data = new wchar_t[data1Len + data2Len + 1];

    if (data1) { wcscpy(data, data1); }

    if (data2) { wcscpy(data + data1Len, data2); }

    data[data1Len + data2Len] = 0;

    return data;
  }

  wchar_t* coco_string_create_append(const wchar_t* target, const wchar_t appendix) {
    int      targetLen = coco_string_length(target);
    wchar_t* data      = new wchar_t[targetLen + 2];
    wcsncpy(data, target, targetLen);
    data[targetLen]     = appendix;
    data[targetLen + 1] = 0;
    return data;
  }

  void coco_string_delete(wchar_t*& data) {
    delete[] data;
    data = nullptr;
  }

  int coco_string_length(const wchar_t* data) {
    if (data) { return (int)wcslen(data); }

    return 0;
  }

  bool coco_string_endswith(const wchar_t* data, const wchar_t* end) {
    std::size_t dataLen = wcslen(data);
    std::size_t endLen  = wcslen(end);
    return (endLen <= dataLen) && (wcscmp(data + dataLen - endLen, end) == 0);
  }

  int coco_string_indexof(const wchar_t* data, const wchar_t value) {
    const wchar_t* chr = wcschr(data, value);

    if (chr) { return (int)(chr - data); }

    return -1;
  }

  int coco_string_lastindexof(const wchar_t* data, const wchar_t value) {
    const wchar_t* chr = wcsrchr(data, value);

    if (chr) { return (int)(chr - data); }

    return -1;
  }

  void coco_string_merge(wchar_t*& target, const wchar_t* appendix) {
    if (!appendix) { return; }

    wchar_t* data = coco_string_create_append(target, appendix);
    delete[] target;
    target = data;
  }

  bool coco_string_equal(const wchar_t* data1, const wchar_t* data2) {
    return wcscmp(data1, data2) == 0;
  }

  int coco_string_compareto(const wchar_t* data1, const wchar_t* data2) {
    return wcscmp(data1, data2);
  }

  unsigned int coco_string_hash(const wchar_t* data) {
    unsigned int h = 0;

    if (!data) { return 0; }

    while (*data != 0) {
      h = (h * 7) ^ *data;
      ++data;
    }

    return h;
  }

  // string handling, ascii character
  wchar_t* coco_string_create(const char* value) {
    std::size_t len = 0;

    if (value) { len = strlen(value); }

    wchar_t* data = new wchar_t[len + 1];

    for (std::size_t i = 0; i < len; ++i) {
      data[i] = (wchar_t)value[i];
    }

    data[len] = 0;
    return data;
  }

  char* coco_string_create_char(const wchar_t* value) {
    int   len = coco_string_length(value);
    char* res = new char[len + 1];

    for (int i = 0; i < len; ++i) {
      res[i] = (char)value[i];
    }

    res[len] = 0;
    return res;
  }

  void coco_string_delete(char*& data) {
    delete[] data;
    data = nullptr;
  }

  // ==========================================================================
  // Token - Implementation
  // ==========================================================================

  Token::Token() {
    kind    = 0;
    pos     = 0;
    col     = 0;
    line    = 0;
    val     = nullptr;
    next    = nullptr;
    charPos = 0;
  }

  Token::~Token() { coco_string_delete(val); }

  namespace {
    // ==========================================================================
    // MappedBuffer - For large files (>= COCO_MMAP_FILE_THRESHOLD)
    // ==========================================================================
    // Uses memory-mapped I/O for efficient access to very large files.

    class MappedBuffer: public Buffer {
      private:
      unsigned char* mappedData_;
#  if defined(_WIN32)
      HANDLE fileHandle_;
      HANDLE mappingHandle_;
#  else
      int fd_;
#  endif

      public:
      explicit MappedBuffer(const char* fileName);
      ~MappedBuffer() override;

      void Close() override;

      int Read() override { return (bufPos_ < fileLen_) ? mappedData_[bufPos_++] : EoF; }

      int Peek() override { return (bufPos_ < fileLen_) ? mappedData_[bufPos_] : EoF; }

      wchar_t* GetString(int beg, int end) override;

      int GetPos() override { return bufPos_; }

      void SetPos(int value) override {
        bufPos_ = (value >= 0 && value <= fileLen_) ? value : bufPos_;
      }
    };

    // ==========================================================================
    // InMemoryBuffer - For small files (< COCO_SMALL_FILE_THRESHOLD)
    // ==========================================================================
    // Loads entire file into memory at once. Fastest for small files.
    class InMemoryBuffer: public Buffer {
      private:
      unsigned char* buf_;
      bool           ownsData_;

      public:
      InMemoryBuffer(const unsigned char* data, int len, bool copy = true);
      InMemoryBuffer(FILE* s);
      ~InMemoryBuffer() override;

      void Close() override {}

      int Read() override { return (bufPos_ < fileLen_) ? buf_[bufPos_++] : EoF; }

      int Peek() override { return (bufPos_ < fileLen_) ? buf_[bufPos_] : EoF; }

      wchar_t* GetString(int beg, int end) override;

      int GetPos() override { return bufPos_; }

      void SetPos(int value) override {
        bufPos_ = (value >= 0 && value <= fileLen_) ? value : bufPos_;
      }
    };

    // ==========================================================================
    // StreamBuffer - For medium files (COCO_SMALL_FILE_THRESHOLD to COCO_MMAP_FILE_THRESHOLD)
    // ==========================================================================
    // Uses a large buffer with chunked reading from file stream.
    class StreamBuffer: public Buffer {
      private:
      unsigned char* buf_;
      int            bufCapacity_;
      int            bufStart_;   // position of first byte in buffer relative to input stream
      int            bufLen_;     // length of valid data in buffer
      FILE*          stream_;
      bool           isUserStream_;

      int  ReadNextStreamChunk();
      bool CanSeek();

      public:
      explicit StreamBuffer(FILE* s, bool isUserStream);
      ~StreamBuffer() override;

      void     Close() override;
      int      Read() override;
      int      Peek() override;
      wchar_t* GetString(int beg, int end) override;

      int GetPos() override { return bufPos_ + bufStart_; }

      void SetPos(int value) override;
    };

  }   // anonymous namespace

  // ==========================================================================
  // Buffer - Factory methods for hybrid I/O strategy
  // ==========================================================================

  Buffer* Buffer::CreateOptimal(const char* fileName) {
    FILE* f = fopen(fileName, "rb");
    if (f == nullptr) return nullptr;

    // Get file size
    fseek(f, 0, SEEK_END);
    long fileSize = ftell(f);
    fseek(f, 0, SEEK_SET);

    // Choose strategy based on file size
    if (fileSize < 0) {
      // Non-seekable stream, use StreamBuffer
      return new StreamBuffer(f, false);
    } else if (fileSize < COCO_SMALL_FILE_THRESHOLD) {
      // Small file: load entirely into memory
      Buffer* buf = new InMemoryBuffer(f);
      fclose(f);
      return buf;
    } else if (fileSize < COCO_MMAP_FILE_THRESHOLD) {
      // Medium file: use large buffer with chunked reading
      return new StreamBuffer(f, false);
    } else {
      // Large file: use memory-mapped I/O
      fclose(f);
      return new MappedBuffer(fileName);
    }
  }

  Buffer* Buffer::CreateOptimal(FILE* s, bool isUserStream) {
    // For user-provided streams, we can't close/reopen, so use StreamBuffer
    return new StreamBuffer(s, isUserStream);
  }

  Buffer* Buffer::CreateFromMemory(const unsigned char* data, int len) {
    return new InMemoryBuffer(data, len, true);
  }

  // ==========================================================================
  // InMemoryBuffer - Implementation
  // ==========================================================================

  namespace {

    InMemoryBuffer::InMemoryBuffer(const unsigned char* data, int len, bool copy) {
      fileLen_  = len;
      bufPos_   = 0;
      ownsData_ = copy;
      if (copy) {
        buf_ = new unsigned char[len];
        memcpy(buf_, data, len * sizeof(unsigned char));
      } else {
        buf_ = const_cast< unsigned char* >(data);
      }
    }

    InMemoryBuffer::InMemoryBuffer(FILE* s) {
      // ensure binary read on windows
#  if _MSC_VER >= 1300
      _setmode(_fileno(s), _O_BINARY);
#  endif

      fseek(s, 0, SEEK_END);
      fileLen_ = (int)ftell(s);
      fseek(s, 0, SEEK_SET);

      buf_        = new unsigned char[fileLen_];
      size_t read = fread(buf_, 1, fileLen_, s);
      if ((int)read != fileLen_) {
        fileLen_ = (int)read;   // Adjust if read less than expected
      }
      bufPos_   = 0;
      ownsData_ = true;
    }

    InMemoryBuffer::~InMemoryBuffer() {
      if (ownsData_ && buf_ != nullptr) {
        delete[] buf_;
        buf_ = nullptr;
      }
    }

    wchar_t* InMemoryBuffer::GetString(int beg, int end) {
      int      len    = end - beg;
      wchar_t* result = new wchar_t[len + 1];
      for (int i = 0; i < len && (beg + i) < fileLen_; ++i) {
        result[i] = (wchar_t)buf_[beg + i];
      }
      wchar_t* res = coco_string_create(result, 0, len);
      delete[] result;
      return res;
    }

    // ==========================================================================
    // StreamBuffer - Implementation (for medium files and non-seekable streams)
    // ==========================================================================

    StreamBuffer::StreamBuffer(FILE* s, bool isUserStream) {
      stream_       = s;
      isUserStream_ = isUserStream;

      // ensure binary read on windows
#  if _MSC_VER >= 1300
      _setmode(_fileno(s), _O_BINARY);
#  endif

      if (CanSeek()) {
        fseek(s, 0, SEEK_END);
        fileLen_ = (int)ftell(s);
        fseek(s, 0, SEEK_SET);
        if (fileLen_ < 0) {
          // ftell failed despite CanSeek (e.g. directory fd on Linux): treat as non-seekable
          fileLen_ = bufLen_ = bufStart_ = 0;
        } else {
          bufLen_   = (fileLen_ < COCO_LARGE_BUFFER_SIZE) ? fileLen_ : COCO_LARGE_BUFFER_SIZE;
          bufStart_ = INT_MAX;   // nothing in the buffer so far
        }
      } else {
        fileLen_ = bufLen_ = bufStart_ = 0;
      }

      bufCapacity_ = (bufLen_ > 0) ? bufLen_ : MIN_BUFFER_LENGTH;
      buf_         = new unsigned char[bufCapacity_];

      if (fileLen_ > 0) SetPos(0);
      else bufPos_ = 0;

      // If entire file fits in buffer, close the file handle
      if (bufLen_ == fileLen_ && CanSeek()) Close();
    }

    StreamBuffer::~StreamBuffer() {
      Close();
      if (buf_ != nullptr) {
        delete[] buf_;
        buf_ = nullptr;
      }
    }

    void StreamBuffer::Close() {
      if (!isUserStream_ && stream_ != nullptr) {
        fclose(stream_);
        stream_ = nullptr;
      }
    }

    int StreamBuffer::Read() {
      if (bufPos_ < bufLen_) {
        return buf_[bufPos_++];
      } else if (GetPos() < fileLen_) {
        SetPos(GetPos());   // shift buffer start to Pos
        return buf_[bufPos_++];
      } else if ((stream_ != nullptr) && !CanSeek() && (ReadNextStreamChunk() > 0)) {
        return buf_[bufPos_++];
      } else {
        return EoF;
      }
    }

    int StreamBuffer::Peek() {
      int curPos = GetPos();
      int ch     = Read();
      SetPos(curPos);
      return ch;
    }

    wchar_t* StreamBuffer::GetString(int beg, int end) {
      int      len    = 0;
      wchar_t* wbuf   = new wchar_t[end - beg];
      int      oldPos = GetPos();
      SetPos(beg);

      while (GetPos() < end)
        wbuf[len++] = (wchar_t)Read();

      SetPos(oldPos);
      wchar_t* res = coco_string_create(wbuf, 0, len);
      delete[] wbuf;
      return res;
    }

    void StreamBuffer::SetPos(int value) {
      if ((value >= fileLen_) && (stream_ != nullptr) && !CanSeek()) {
        while ((value >= fileLen_) && (ReadNextStreamChunk() > 0))
          ;
      }

      if ((value < 0) || (value > fileLen_)) {
        wprintf(L"--- buffer out of bounds access, position: %d\n", value);
        exit(1);
      }

      if ((value >= bufStart_) && (value < (bufStart_ + bufLen_))) {
        bufPos_ = value - bufStart_;
      } else if (stream_ != nullptr) {
        fseek(stream_, value, SEEK_SET);
        bufLen_   = (int)fread(buf_, sizeof(unsigned char), bufCapacity_, stream_);
        bufStart_ = value;
        bufPos_   = 0;
      } else {
        bufPos_ = fileLen_ - bufStart_;
      }
    }

    int StreamBuffer::ReadNextStreamChunk() {
      int free = bufCapacity_ - bufLen_;

      if (free == 0) {
        bufCapacity_          = bufLen_ * 2;
        unsigned char* newBuf = new unsigned char[bufCapacity_];
        memcpy(newBuf, buf_, bufLen_ * sizeof(unsigned char));
        delete[] buf_;
        buf_ = newBuf;
        free = bufLen_;
      }

      int bytesRead = (int)fread(buf_ + bufLen_, sizeof(unsigned char), free, stream_);

      if (bytesRead > 0) {
        fileLen_ = bufLen_ = (bufLen_ + bytesRead);
        return bytesRead;
      }

      return 0;
    }

    bool StreamBuffer::CanSeek() { return (stream_ != nullptr) && (ftell(stream_) != -1); }

    // ==========================================================================
    // MappedBuffer - Implementation (for large files using memory-mapped I/O)
    // ==========================================================================

#  if defined(_WIN32)

    MappedBuffer::MappedBuffer(const char* fileName) {
      mappedData_    = nullptr;
      fileHandle_    = INVALID_HANDLE_VALUE;
      mappingHandle_ = nullptr;
      fileLen_       = 0;
      bufPos_        = 0;

      fileHandle_ = CreateFileA(fileName,
                                GENERIC_READ,
                                FILE_SHARE_READ,
                                nullptr,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                nullptr);
      if (fileHandle_ == INVALID_HANDLE_VALUE) return;

      LARGE_INTEGER size;
      if (!GetFileSizeEx(fileHandle_, &size)) {
        CloseHandle(fileHandle_);
        fileHandle_ = INVALID_HANDLE_VALUE;
        return;
      }
      fileLen_ = (int)size.QuadPart;

      mappingHandle_ = CreateFileMappingA(fileHandle_, nullptr, PAGE_READONLY, 0, 0, nullptr);
      if (mappingHandle_ == nullptr) {
        CloseHandle(fileHandle_);
        fileHandle_ = INVALID_HANDLE_VALUE;
        return;
      }

      mappedData_ = (unsigned char*)MapViewOfFile(mappingHandle_, FILE_MAP_READ, 0, 0, 0);
      if (mappedData_ == nullptr) {
        CloseHandle(mappingHandle_);
        CloseHandle(fileHandle_);
        mappingHandle_ = nullptr;
        fileHandle_    = INVALID_HANDLE_VALUE;
      }
    }

    MappedBuffer::~MappedBuffer() { Close(); }

    void MappedBuffer::Close() {
      if (mappedData_ != nullptr) {
        UnmapViewOfFile(mappedData_);
        mappedData_ = nullptr;
      }
      if (mappingHandle_ != nullptr) {
        CloseHandle(mappingHandle_);
        mappingHandle_ = nullptr;
      }
      if (fileHandle_ != INVALID_HANDLE_VALUE) {
        CloseHandle(fileHandle_);
        fileHandle_ = INVALID_HANDLE_VALUE;
      }
    }

#  else    // POSIX (Linux, macOS, etc.)

    MappedBuffer::MappedBuffer(const char* fileName) {
      mappedData_ = nullptr;
      fd_         = -1;
      fileLen_    = 0;
      bufPos_     = 0;

      fd_ = open(fileName, O_RDONLY);
      if (fd_ < 0) return;

      struct stat sb;
      if (fstat(fd_, &sb) < 0) {
        close(fd_);
        fd_ = -1;
        return;
      }
      fileLen_ = (int)sb.st_size;

      mappedData_ = (unsigned char*)mmap(nullptr, fileLen_, PROT_READ, MAP_PRIVATE, fd_, 0);
      if (mappedData_ == MAP_FAILED) {
        mappedData_ = nullptr;
        close(fd_);
        fd_ = -1;
      }
    }

    MappedBuffer::~MappedBuffer() { Close(); }

    void MappedBuffer::Close() {
      if (mappedData_ != nullptr) {
        munmap(mappedData_, fileLen_);
        mappedData_ = nullptr;
      }
      if (fd_ >= 0) {
        close(fd_);
        fd_ = -1;
      }
    }

#  endif   // _WIN32

    wchar_t* MappedBuffer::GetString(int beg, int end) {
      int      len    = end - beg;
      wchar_t* result = new wchar_t[len + 1];
      for (int i = 0; i < len && (beg + i) < fileLen_; ++i) {
        result[i] = (wchar_t)mappedData_[beg + i];
      }
      wchar_t* res = coco_string_create(result, 0, len);
      delete[] result;
      return res;
    }

  }   // anonymous namespace

  // ==========================================================================
  // UTF8Buffer - Implementation (wrapper for UTF-8 decoding)
  // ==========================================================================

  UTF8Buffer::UTF8Buffer(Buffer* b, bool takeOwnership) {
    innerBuffer_ = b;
    ownsInner_   = takeOwnership;
    fileLen_     = b->GetFileLen();
    bufPos_      = 0;
  }

  UTF8Buffer::~UTF8Buffer() {
    if (ownsInner_ && innerBuffer_ != nullptr) {
      delete innerBuffer_;
      innerBuffer_ = nullptr;
    }
  }

  int UTF8Buffer::Read() {
    int ch;

    do {
      ch = innerBuffer_->Read();
      // until we find a utf8 start (0xxxxxxx or 11xxxxxx)
    } while ((ch >= 128) && ((ch & 0xC0) != 0xC0) && (ch != EoF));

    if (ch < 128 || ch == EoF) {
      // nothing to do, first 127 chars are the same in ascii and utf8
      // 0xxxxxxx or end of file character
    } else if ((ch & 0xF0) == 0xF0) {
      // 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
      int c1 = ch & 0x07;
      ch     = innerBuffer_->Read();
      int c2 = ch & 0x3F;
      ch     = innerBuffer_->Read();
      int c3 = ch & 0x3F;
      ch     = innerBuffer_->Read();
      int c4 = ch & 0x3F;
      ch     = ((((((c1 << 6) | c2) << 6) | c3) << 6) | c4);
    } else if ((ch & 0xE0) == 0xE0) {
      // 1110xxxx 10xxxxxx 10xxxxxx
      int c1 = ch & 0x0F;
      ch     = innerBuffer_->Read();
      int c2 = ch & 0x3F;
      ch     = innerBuffer_->Read();
      int c3 = ch & 0x3F;
      ch     = (((c1 << 6) | c2) << 6) | c3;
    } else if ((ch & 0xC0) == 0xC0) {
      // 110xxxxx 10xxxxxx
      int c1 = ch & 0x1F;
      ch     = innerBuffer_->Read();
      int c2 = ch & 0x3F;
      ch     = (c1 << 6) | c2;
    }

    return ch;
  }

  int UTF8Buffer::Peek() {
    int oldPos = innerBuffer_->GetPos();
    int ch     = Read();
    innerBuffer_->SetPos(oldPos);
    return ch;
  }

}   // namespace gum

#endif   // DOXYGEN_SHOULD_SKIP_THIS
