#ifndef REGEXIMPL_H_62B23520_7C8E_11DE_8A39_0800200C9A66
#define REGEXIMPL_H_62B23520_7C8E_11DE_8A39_0800200C9A66

#if defined(_MSC_VER) ||                                            \
    (defined(__GNUC__) && (__GNUC__ == 3 && __GNUC_MINOR__ >= 4) || \
     (__GNUC__ >= 4))  // GCC supports "pragma once" correctly since 3.4
#pragma once
#endif

#include "stream.h"
#include "streamcharsource.h"
#include "stringsource.h"

namespace YAML {
// query matches
inline bool RegEx::Matches(char ch) const {
  std::string str;
  str += ch;
  return Matches(str);
}

inline bool RegEx::Matches(const std::string& str) const {
  return Match(str) >= 0;
}

inline bool RegEx::Matches(const Stream& in) const { return Match(in) >= 0; }

template <typename Source>
inline bool RegEx::Matches(const Source& source) const {
#if ((defined(_MSVC_LANG) && _MSVC_LANG >= 201103L) || __cplusplus >= 201103L)
  static_assert(!std::is_same<Source, const char*>::value,
#endif
    "Must use StringCharSource instead of plain C-string");
  return Match(source) >= 0;
}

// Match
// . Matches the given string against this regular expression.
// . Returns the number of characters matched.
// . Returns -1 if no characters were matched (the reason for
//   not returning zero is that we may have an empty regex
//   which is ALWAYS successful at matching zero characters).
// . REMEMBER that we only match from the start of the buffer!
inline int RegEx::Match(const std::string& str) const {
  StringCharSource source(str.c_str(), str.size());
  return Match(source);
}

inline int RegEx::Match(const Stream& in) const {
  StreamCharSource source(in);
  return Match(source);
}

template <typename Source>
inline bool RegEx::IsValidSource(const Source& source) const {
  return source;
}

template <>
inline bool RegEx::IsValidSource<StringCharSource>(
    const StringCharSource& source) const {
  switch (m_op) {
    case REGEX_MATCH:
    case REGEX_RANGE:
      return source;
    default:
      return true;
  }
}

template <typename Source>
inline int RegEx::Match(const Source& source) const {
  return IsValidSource(source) ? MatchUnchecked(source) : -1;
}

template <typename Source>
inline int RegEx::MatchUnchecked(const Source& source) const {
  switch (m_op) {
    case REGEX_EMPTY:
      return MatchOpEmpty(source);
    case REGEX_MATCH:
      return MatchOpMatch(source);
    case REGEX_RANGE:
      return MatchOpRange(source);
    case REGEX_OR:
      return MatchOpOr(source);
    case REGEX_AND:
      return MatchOpAnd(source);
    case REGEX_NOT:
      return MatchOpNot(source);
    case REGEX_SEQ:
      return MatchOpSeq(source);
  }

  return -1;
}

//////////////////////////////////////////////////////////////////////////////
// Operators
// Note: the convention MatchOp*<Source> is that we can assume
// IsSourceValid(source).
//       So we do all our checks *before* we call these functions

// EmptyOperator
template <typename Source>
inline int RegEx::MatchOpEmpty(const Source& source) const {
  return source[0] == Stream::eof() ? 0 : -1;
}

template <>
inline int RegEx::MatchOpEmpty<StringCharSource>(
    const StringCharSource& source) const {
  return !source ? 0 : -1;  // the empty regex only is successful on the empty
                            // string
}

// MatchOperator
template <typename Source>
inline int RegEx::MatchOpMatch(const Source& source) const {
  if (source[0] != m_a)
    return -1;
  return 1;
}

// RangeOperator
template <typename Source>
inline int RegEx::MatchOpRange(const Source& source) const {
  if (m_a > source[0] || m_z < source[0])
    return -1;
  return 1;
}

// OrOperator
template <typename Source>
inline int RegEx::MatchOpOr(const Source& source) const {
  for (const RegEx& param : m_params) {
    int n = param.MatchUnchecked(source);
    if (n >= 0)
      return n;
  }
  return -1;
}

// AndOperator
// Note: 'AND' is a little funny, since we may be required to match things
//       of different lengths. If we find a match, we return the length of
//       the FIRST entry on the list.
template <typename Source>
inline int RegEx::MatchOpAnd(const Source& source) const {
  int first = -1;
  for (std::size_t i = 0; i < m_params.size(); i++) {
    int n = m_params[i].MatchUnchecked(source);
    if (n == -1)
      return -1;
    if (i == 0)
      first = n;
  }
  return first;
}

// NotOperator
template <typename Source>
inline int RegEx::MatchOpNot(const Source& source) const {
  if (m_params.empty())
    return -1;
  if (m_params[0].MatchUnchecked(source) >= 0)
    return -1;
  return 1;
}

// SeqOperator
template <typename Source>
inline int RegEx::MatchOpSeq(const Source& source) const {
  int offset = 0;
  for (const RegEx& param : m_params) {
    int n = param.Match(source + offset);  // note Match, not
                                           // MatchUnchecked because we
                                           // need to check validity after
                                           // the offset
    if (n == -1)
      return -1;
    offset += n;
  }

  return offset;
}
}  // namespace YAML

#endif  // REGEXIMPL_H_62B23520_7C8E_11DE_8A39_0800200C9A66
