// vi:set ft=cpp: -*- Mode: C++ -*-
/*
 * (c) 2008-2009 Alexander Warg <warg@os.inf.tu-dresden.de>
 *     economic rights: Technische Universität Dresden (Germany)
 *
 * This file is part of TUD:OS and distributed under the terms of the
 * GNU General Public License 2.
 * Please see the COPYING-GPL-2 file for details.
 *
 * As a special exception, you may use this file as part of a free software
 * library without restriction.  Specifically, if other files instantiate
 * templates or use macros or inline functions from this file, or you compile
 * this file and link it with other files to produce an executable, this
 * file does not by itself cause the resulting executable to be covered by
 * the GNU General Public License.  This exception does not however
 * invalidate any other reasons why the executable file might be covered by
 * the GNU General Public License.
 */

/**
 * Strings.
 */
#pragma once

#include <l4/cxx/minmax>
#include <l4/cxx/basic_ostream>


namespace cxx {

/**
 * Allocation free string class with explicit length field.
 *
 * This class is used to group characters of a string which belong
 * to one syntactical token types number, identifier, string,
 * whitespace or another single character.
 *
 * Stings in this class can contain null bytes and may denote parts of
 * other strings.
 */
class String
{
public:

  /// Character index type.
  typedef char const *Index;

  /// Initialize from a zero-terminated string.
  String(char const *s) throw() : _start(s), _len(__builtin_strlen(s)) {}
  /// Initialize from a pointer to first character and a length.
  String(char const *s, unsigned long len) throw() : _start(s), _len(len) {}

  /**
   * Initialize with start and end pointer.
   *
   * \param s  first character of the string
   * \param e  pointer to first byte behind the string
   */
  String(char const *s, char const *e) throw() : _start(s), _len(e - s) {}

  /// Zero-initialize. Create an invalid string.
  String() : _start(0), _len(0) {}

  /// Pointer to first character.
  Index start() const { return _start; }
  /// Pointer to first byte behind the string.
  Index end() const { return _start + _len; }
  /// Length.
  int len() const { return _len; }

  /// Set start.
  void start(char const *s) { _start = s; }
  /// Set length.
  void len(unsigned long len) { _len = len; }
  /// Check if the string has length zero.
  bool empty() const { return !_len; }

  /// Return prefix up to index.
  String head(Index end) const
  {
    if (end < _start)
      return String();

    if (eof(end))
      return *this;

    return String(_start, end - _start);
  }

  /// Prefix of length `end`.
  String head(unsigned long end) const
  { return head(start() + end); }

  /// Substring of length `len` starting at `idx`.
  String substr(unsigned long idx, unsigned long len = ~0UL) const
  {
    if (idx >= _len)
      return String(end(), 0UL);

    return String(_start + idx, cxx::min(len, _len - idx));
  }

  /// Substring of length `len` starting at `start`.
  String substr(char const *start, unsigned long len = 0) const
  {
    if (start >= _start && !eof(start))
      {
	unsigned long nlen = _start + _len - start;
	if (len != 0)
	  nlen = cxx::min(nlen, len);
	return String(start, nlen);
      }

    return String(end(), 0UL);
  }

  /// Find matching character. `match` should be a function such as `isspace`.
  template< typename F >
  char const *find_match(F &&match) const
  {
    String::Index s = _start;
    while (1)
      {
	if (eof(s))
	  return s;

	if (match(*s))
	  return s;

	++s;
      }
  }

  /// Find character. Return end() if not found.
  char const *find(char const *c) const
  { return find(c, start());  }

  /// Find character. Return end() if not found.
  char const *find(int c) const
  { return find(c, start());  }

  /// Find right-most character. Return end() if not found.
  char const *rfind(char const *c) const
  {
    if (!_len)
      return end();

    char const *p = end();
    --p;
    while (p >= _start)
      {
	if (*p == *c)
	  return p;
	--p;
      }
    return end();

  }

  /**
   * Check if `c` is a prefix of string.
   *
   * \return 0 if `c` is not a prefix, if it is a prefix, return
   *         first position not in `c` (which might be end()).
   */
  Index starts_with(cxx::String const &c) const
  {
    unsigned long i;
    for (i = 0; i < c._len && i < _len; ++i)
      if (_start[i] != c[i])
	return 0;
    return i == c._len ? start() + i : 0;
  }

  /// Find character `c` starting at position `s`. Return end() if not found.
  char const *find(int c, char const *s) const
  {
    if (s < _start)
      return end();

    while (1)
      {
	if (eof(s))
	  return s;

	if (*s == c)
	  return s;

	++s;
      }
  }

  /**
   * Find character set at position.
   *
   * \param c  zero-terminated string of characters to search for
   * \param s  start position of search in string
   *
   * \retval end()     if no char in `c` is contained in string at or behind `s`.
   * \retval position  in string of some character in `c`.
   */
  char const *find(char const *c, char const *s) const
  {
    if (s < _start)
      return end();

    while (1)
      {
	if (eof(s))
	  return s;

	for (char const *x = c; *x; ++x)
	  if (*s == *x)
	    return s;

	++s;
      }
  }

  /// Get character at `idx`.
  char const &operator [] (unsigned long idx) const { return _start[idx]; }
  /// Get character at `idx`.
  char const &operator [] (int idx) const { return _start[idx]; }
  /// Get character at `idx`.
  char const &operator [] (Index idx) const { return *idx; }

  /// Check if pointer `s` points behind string.
  bool eof(char const *s) const { return s >= _start + _len || !*s; }

  /**
   * Convert decimal string to integer.
   *
   * \tparam     INT  result integer type
   * \param[out] v    conversion result
   *
   * \return position of first character not converted.
   */
  template<typename INT>
  int from_dec(INT *v) const
  {
    *v = 0;
    Index c;
    for (c = start(); !eof(c); ++c)
      {
	unsigned char n;
	if (*c >= '0' && *c <= '9')
	  n = *c - '0';
	else
	  return c - start();

        *v *= 10;
	*v += n;
      }
    return c - start();
  }

  /**
   * Convert hex string to integer.
   *
   * \tparam     INT  result integer type
   * \param[out] v    conversion result
   *
   * \retval -1        if the maximal amount of digits fitting into `INT` have
   *                   been read,
   * \retval position  of first character not converted otherwise.
   */
  template<typename INT>
  int from_hex(INT *v) const
  {
    *v = 0;
    unsigned shift = 0;
    Index c;
    for (c = start(); !eof(c); ++c)
      {
	shift += 4;
	if (shift > sizeof(INT) * 8)
	  return -1;
	unsigned char n;
	if (*c >= '0' && *c <= '9')
	  n = *c - '0';
	else if (*c >= 'A' && *c <= 'F')
	  n = *c - 'A' + 10;
	else if (*c >= 'a' && *c <= 'f')
	  n = *c - 'a' + 10;
	else
	  return c - start();

        *v <<= 4;
	*v |= n;
      }
    return c - start();
  }

  /// Equality.
  bool operator == (String const &o) const
  {
    if (len() != o.len())
      return false;

    for (unsigned long i = 0; i < _len; ++i)
      if (_start[i] != o._start[i])
	return false;

    return true;
  }

  /// Inequality.
  bool operator != (String const &o) const
  { return ! (operator == (o)); }

private:
  char const *_start;
  unsigned long _len;
};

}

/// Write `str` on `s`.
inline
L4::BasicOStream &operator << (L4::BasicOStream &s, cxx::String const &str)
{
  s.write(str.start(), str.len());
  return s;
}