#ifndef INC_UnicodeCharScanner_hpp__
#define INC_UnicodeCharScanner_hpp__

#include <cctype>
#include <cstdio>
#include <map>

#include <antlr/BitSet.hpp>
#include <antlr/CommonToken.hpp>
#include <antlr/InputBuffer.hpp>
#include <antlr/LexerSharedInputState.hpp>
#include <antlr/RecognitionException.hpp>
#include <antlr/SemanticException.hpp>
#include <antlr/TokenStream.hpp>
#include <antlr/config.hpp>

#include "MismatchedUnicodeCharException.hpp"

/** Superclass of generated lexers
 */
class UnicodeCharScanner : public antlr::TokenStream {
protected:
  typedef antlr::RefToken (*factory_type)();

public:
  typedef unsigned int char_type;
  typedef std::map<std::string, int> string_map;

  UnicodeCharScanner(antlr::InputBuffer &cb, bool case_sensitive)
      : saveConsumedInput(true), caseSensitive(case_sensitive), literals(),
        inputState(new antlr::LexerInputState(cb)), commitToPath(false),
        tabsize(8), traceDepth(0) {
    setTokenObjectFactory(&antlr::CommonToken::factory);
  }
  UnicodeCharScanner(antlr::InputBuffer *cb, bool case_sensitive)
      : saveConsumedInput(true), caseSensitive(case_sensitive), literals(),
        inputState(new antlr::LexerInputState(cb)), commitToPath(false),
        tabsize(8), traceDepth(0) {
    setTokenObjectFactory(&antlr::CommonToken::factory);
  }
  UnicodeCharScanner(const antlr::LexerSharedInputState &state,
                     bool case_sensitive)
      : saveConsumedInput(true), caseSensitive(case_sensitive), literals(),
        inputState(state), commitToPath(false), tabsize(8), traceDepth(0) {
    setTokenObjectFactory(&antlr::CommonToken::factory);
  }

  virtual ~UnicodeCharScanner() {}

  virtual char_type LA(char_type i) {
    char_type c = inputState->getInput().LA(i);
    return c;
  }

  virtual void append(char_type c) {
    if (saveConsumedInput) {
      size_t len = text.length();

      if ((len % 256) == 0)
        text.reserve(len + 256);

      // This is how UTF8 is encoded
      // +---------------------------+----------+----------+----------+----------+
      // | Unicode scalar            | 1st      | 2nd      | 3th      | 4th |
      // +---------------------------+----------+----------+----------+----------+
      // |00000000 0xxxxxxx          | 0xxxxxxx |          |          | |
      // |00000yyy yyxxxxxx          | 110yyyyy | 10xxxxxx |          | |
      // |zzzzyyyy yyxxxxxx          | 1110zzzz | 10yyyyyy | 10xxxxxx | |
      // |000uuuuu zzzzyyyy yyxxxxxx | 11110uuu | 10uuzzzz | 10yyyyyy | 10xxxxxx
      // |
      // +---------------------------+----------+----------+----------+----------+

      if (c < 0x80) {
        text += c;
        return;
      } else if (c < 0x800) {
        text += ((c >> 6) | 0xC0);
        text += (c & 0x3F | 0x80);
      } else if (c < 0x10000) {
        text += ((c >> 12) | 0xE0);
        text += (((c >> 6) & 0x3F) | 0x80);
        text += ((c & 0x3F) | 0x80);
      } else if (c < 0x200000) {
        text += ((c >> 18) | 0xF0); // first 3 bits
        text += ((((c >> 16) & 0x3) << 4) | ((c >> 12) & 0xF) | 0x80);
        text += (((c >> 6) & 0x3F) | 0x80);
        text += ((c & 0x3F) | 0x80);
      } else
        assert(0);
    }
  }

  virtual void append(const std::string &s) {
    assert(0);
    if (saveConsumedInput)
      text += s;
  }

  virtual void commit() { inputState->getInput().commit(); }

  virtual void consume() {
    if (inputState->guessing == 0) {
      char_type c = LA(1);
      append(c);
      inputState->column++;
    }
    inputState->getInput().consume();
  }

  /** Consume chars until one matches the given char */
  virtual void consumeUntil(char_type c) {
    for (;;) {
      char_type la_1 = LA(1);
      if (static_cast<char_type>(EOF_CHAR) == la_1 || la_1 == c)
        break;
      consume();
    }
  }

  /** Consume chars until one matches the given set */
  virtual void consumeUntil(const antlr::BitSet &set) {
    for (;;) {
      char_type la_1 = LA(1);
      if (static_cast<char_type>(EOF_CHAR) == la_1 || set.member(la_1))
        break;
      consume();
    }
  }

  /// Mark the current position and return a id for it
  virtual unsigned int mark() { return inputState->getInput().mark(); }

  /// Rewind the scanner to a previously marked position
  virtual void rewind(unsigned int pos) { inputState->getInput().rewind(pos); }

  /// See if input contains character 'c' throw MismatchedUnicodeCharException
  /// if not
  virtual void match(char_type c) {
    char_type la_1 = LA(1);
    if (la_1 != c)
      throw MismatchedUnicodeCharException(la_1, c, false, this);
    consume();
  }

  /** See if input contains element from bitset b
   * throw MismatchedUnicodeCharException if not
   */
  virtual void match(const antlr::BitSet &b) {
    char_type la_1 = LA(1);

    if (!b.member(la_1))
      throw MismatchedUnicodeCharException(la_1, b, false, this);
    consume();
  }

  /** See if input contains string 's' throw MismatchedUnicodeCharException if
   * not
   * @note the string cannot match EOF
   */
  virtual void match(const char *s) {
    while (*s != '\0') {
      // the & 0xFF is here to prevent sign extension lateron
      char_type la_1 = LA(1), c = (*s++ & 0xFF);

      if (la_1 != c)
        throw MismatchedUnicodeCharException(la_1, c, false, this);

      consume();
    }
  }
  /** See if input contains string 's' throw MismatchedUnicodeCharException if
   * not
   * @note the string cannot match EOF
   */
  virtual void match(const std::string &s) {
    size_t len = s.length();

    for (size_t i = 0; i < len; i++) {
      // the & 0xFF is here to prevent sign extension lateron
      char_type la_1 = LA(1), c = (s[i] & 0xFF);

      if (la_1 != c)
        throw MismatchedUnicodeCharException(la_1, c, false, this);

      consume();
    }
  }
  /** See if input does not contain character 'c'
   * throw MismatchedUnicodeCharException if not
   */
  virtual void matchNot(char_type c) {
    char_type la_1 = LA(1);

    if (la_1 == c)
      throw MismatchedUnicodeCharException(la_1, c, true, this);

    consume();
  }
  /** See if input contains character in range c1-c2
   * throw MismatchedUnicodeCharException if not
   */
  virtual void matchRange(char_type c1, char_type c2) {
    char_type la_1 = LA(1);

    if (la_1 < c1 || la_1 > c2)
      throw MismatchedUnicodeCharException(la_1, c1, c2, false, this);

    consume();
  }

  /// Get the line the scanner currently is in (starts at 1)
  virtual int getLine() const { return inputState->line; }

  /// set the line number
  virtual void setLine(int l) { inputState->line = l; }

  /// Get the column the scanner currently is in (starts at 1)
  virtual int getColumn() const { return inputState->column; }
  /// set the column number
  virtual void setColumn(int c) { inputState->column = c; }

  /// get the filename for the file currently used
  virtual const std::string &getFilename() const {
    return inputState->filename;
  }
  /// Set the filename the scanner is using (used in error messages)
  virtual void setFilename(const std::string &f) { inputState->filename = f; }

  virtual bool getCommitToPath() const { return commitToPath; }

  virtual void setCommitToPath(bool commit) { commitToPath = commit; }

  /** return a copy of the current text buffer */
  virtual const std::string &getText() const { return text; }

  virtual void setText(const std::string &s) { text = s; }

  virtual void resetText() {
    text = "";
    inputState->tokenStartColumn = inputState->column;
    inputState->tokenStartLine = inputState->line;
  }

  virtual antlr::RefToken getTokenObject() const { return _returnToken; }

  ///{ These need different handling in unicode case

  virtual bool getCaseSensitiveLiterals() const = 0;

  virtual bool getCaseSensitive() const { return caseSensitive; }

  virtual void setCaseSensitive(bool t) { caseSensitive = t; }

  /** Override this method to get more specific case handling
   * @note some platforms probably require setting the right locale for
   * correct functioning.
   */
  virtual char_type toLower(char_type c) const { return std::tolower(c); }

  /** Used to keep track of line breaks, needs to be called from
   * within generated lexers when a \n \r is encountered.
   */
  virtual void newline() {
    ++inputState->line;
    inputState->column = 1;
  }

  /** Advance the current column number by an appropriate amount according
   * to the tabsize. This method needs to be explicitly called from the
   * lexer rules encountering tabs.
   */
  virtual void tab() {
    int c = getColumn();
    int nc = (((c - 1) / tabsize) + 1) * tabsize + 1; // calculate tab stop
    setColumn(nc);
  }
  /// set the tabsize. Returns the old tabsize
  int setTabsize(int size) {
    int oldsize = tabsize;
    tabsize = size;
    return oldsize;
  }
  /// Return the tabsize used by the scanner
  int getTabSize() const { return tabsize; }
  ///}

  /** Report exception errors caught in nextToken() */
  virtual void reportError(const antlr::RecognitionException &ex) {
    std::cerr << ex.toString().c_str() << std::endl;
  }

  /** Parser error-reporting function can be overridden in subclass */
  virtual void reportError(const std::string &s) {
    if (getFilename() == "")
      std::cerr << "error: " << s.c_str() << std::endl;
    else
      std::cerr << getFilename().c_str() << ": error: " << s.c_str()
                << std::endl;
  }

  /** Parser warning-reporting function can be overridden in subclass */
  virtual void reportWarning(const std::string &s) {
    if (getFilename() == "")
      std::cerr << "warning: " << s.c_str() << std::endl;
    else
      std::cerr << getFilename().c_str() << ": warning: " << s.c_str()
                << std::endl;
  }

  virtual antlr::InputBuffer &getInputBuffer() {
    return inputState->getInput();
  }

  virtual antlr::LexerSharedInputState getInputState() { return inputState; }

  /** set the input state for the lexer.
   * @note state is a reference counted object, hence no reference */
  virtual void setInputState(antlr::LexerSharedInputState state) {
    inputState = state;
  }

  /// Set the factory for created tokens
  virtual void setTokenObjectFactory(factory_type factory) {
    tokenFactory = factory;
  }

  /** Test the token text against the literals table
   * Override this method to perform a different literals test
   */
  virtual int testLiteralsTable(int ttype) const {
    string_map::const_iterator i = literals.find(text);
    if (i != literals.end())
      ttype = (*i).second;
    return ttype;
  }

  /** Test the text passed in against the literals table
   * Override this method to perform a different literals test
   * This is used primarily when you want to test a portion of
   * a token
   */
  virtual int testLiteralsTable(const std::string &text, int ttype) const {
    string_map::const_iterator i = literals.find(text);
    if (i != literals.end())
      ttype = (*i).second;
    return ttype;
  }

  /** This method is called by YourLexer::nextToken() when the lexer has
   *  hit EOF condition.  EOF is NOT a character.
   *  This method is not called if EOF is reached during
   *  syntactic predicate evaluation or during evaluation
   *  of normal lexical rules, which presumably would be
   *  an IOException.  This traps the "normal" EOF condition.
   *
   *  uponEOF() is called after the complete evaluation of
   *  the previous token and only if your parser asks
   *  for another token beyond that last non-EOF token.
   *
   *  You might want to throw token or char stream exceptions
   *  like: "Heh, premature eof" or a retry stream exception
   *  ("I found the end of this file, go back to referencing file").
   */
  virtual void uponEOF() {}

  /// Methods used to change tracing behavior
  void traceIndent() {
    for (int i = 0; i < traceDepth; i++)
      std::cout << " ";
  }

  void traceIn(const char *rname) {
    traceDepth++;
    traceIndent();
    std::cout << "> lexer " << rname << "; c==" << LA(1) << std::endl;
  }

  void traceOut(const char *rname) {
    traceIndent();
    std::cout << "< lexer " << rname << "; c==" << LA(1) << std::endl;
    traceDepth--;
  }

#ifndef NO_STATIC_CONSTS
  static const int EOF_CHAR = EOF;
#else
  enum { EOF_CHAR = EOF };
#endif
protected:
  std::string text; ///< Text of current token
                    /// flag indicating wether consume saves characters
  bool saveConsumedInput;
  factory_type tokenFactory; ///< Factory for tokens
  bool caseSensitive;        ///< Is this lexer case sensitive
  string_map literals;       // set by subclass

  antlr::RefToken _returnToken; ///< used to return tokens w/o using return val

  /// Input state, gives access to input stream, shared among different lexers
  antlr::LexerSharedInputState inputState;

  /** Used during filter mode to indicate that path is desired.
   * A subsequent scan error will report an error as usual
   * if acceptPath=true;
   */
  bool commitToPath;

  unsigned int tabsize; ///< tab size the scanner uses.

  /// Create a new RefToken of type t
  virtual antlr::RefToken makeToken(int t) {
    antlr::RefToken tok = tokenFactory();
    // actually at this point you want to convert the stored lexeme text
    // into the format you want to have it in in the backend...
    tok->setType(t);
    tok->setColumn(inputState->tokenStartColumn);
    tok->setLine(inputState->tokenStartLine);
    return tok;
  }

  /** Tracer class, used when -traceLexer is passed to antlr
   */
  class Tracer {
  private:
    UnicodeCharScanner *parser;
    const char *text;

    Tracer(const Tracer &other);            // undefined
    Tracer &operator=(const Tracer &other); // undefined
  public:
    Tracer(UnicodeCharScanner *p, const char *t) : parser(p), text(t) {
      parser->traceIn(text);
    }
    ~Tracer() { parser->traceOut(text); }
  };

  int traceDepth;

private:
  UnicodeCharScanner(const UnicodeCharScanner &other);            // undefined
  UnicodeCharScanner &operator=(const UnicodeCharScanner &other); // undefined
};

#endif // INC_UnicodeCharScanner_hpp__
