#ifndef SASS_PARSER_H
#define SASS_PARSER_H

// sass.hpp must go before all system headers to get the
// __EXTENSIONS__ fix on Solaris.
#include "sass.hpp"

#include <string>
#include <vector>

#include "ast.hpp"
#include "position.hpp"
#include "context.hpp"
#include "position.hpp"
#include "prelexer.hpp"
#include "source.hpp"

#ifndef MAX_NESTING
// Note that this limit is not an exact science
// it depends on various factors, which some are
// not under our control (compile time or even OS
// dependent settings on the available stack size)
// It should fix most common segfault cases though.
#define MAX_NESTING 512
#endif

struct Lookahead {
  const char* found;
  const char* error;
  const char* position;
  bool parsable;
  bool has_interpolants;
  bool is_custom_property;
};

namespace Sass {

  class Parser : public SourceSpan {
  public:

    enum Scope { Root, Mixin, Function, Media, Control, Properties, Rules, AtRoot };

    Context& ctx;
    sass::vector<Block_Obj> block_stack;
    sass::vector<Scope> stack;
    SourceDataObj source;
    const char* begin;
    const char* position;
    const char* end;
    Offset before_token;
    Offset after_token;
    SourceSpan pstate;
    Backtraces traces;
    size_t indentation;
    size_t nestings;
    bool allow_parent;
    Token lexed;

    Parser(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true);

    // special static parsers to convert strings into certain selectors
    static SelectorListObj parse_selector(SourceData* source, Context& ctx, Backtraces, bool allow_parent = true);

#ifdef __clang__

    // lex and peak uses the template parameter to branch on the action, which
    // triggers clangs tautological comparison on the single-comparison
    // branches. This is not a bug, just a merging of behaviour into
    // one function

#endif


    // skip current token and next whitespace
    // moves SourceSpan right before next token
    void advanceToNextToken();

    bool peek_newline(const char* start = 0);

    // skip over spaces, tabs and line comments
    template <Prelexer::prelexer mx>
    const char* sneak(const char* start = 0)
    {
      using namespace Prelexer;

      // maybe use optional start position from arguments?
      const char* it_position = start ? start : position;

      // skip white-space?
      if (mx == spaces ||
          mx == no_spaces ||
          mx == css_comments ||
          mx == css_whitespace ||
          mx == optional_spaces ||
          mx == optional_css_comments ||
          mx == optional_css_whitespace
      ) {
        return it_position;
      }

      // skip over spaces, tabs and sass line comments
      const char* pos = optional_css_whitespace(it_position);
      // always return a valid position
      return pos ? pos : it_position;

    }

    // match will not skip over space, tabs and line comment
    // return the position where the lexer match will occur
    template <Prelexer::prelexer mx>
    const char* match(const char* start = 0)
    {
      // match the given prelexer
      return mx(position);
    }

    // peek will only skip over space, tabs and line comment
    // return the position where the lexer match will occur
    template <Prelexer::prelexer mx>
    const char* peek(const char* start = 0)
    {

      // sneak up to the actual token we want to lex
      // this should skip over white-space if desired
      const char* it_before_token = sneak < mx >(start);

      // match the given prelexer
      const char* match = mx(it_before_token);

      // check if match is in valid range
      return match <= end ? match : 0;

    }

    // white-space handling is built into the lexer
    // this way you do not need to parse it yourself
    // some matchers don't accept certain white-space
    // we do not support start arg, since we manipulate
    // sourcemap offset and we modify the position pointer!
    // lex will only skip over space, tabs and line comment
    template <Prelexer::prelexer mx>
    const char* lex(bool lazy = true, bool force = false)
    {

      if (*position == 0) return 0;

      // position considered before lexed token
      // we can skip whitespace or comments for
      // lazy developers (but we need control)
      const char* it_before_token = position;

      // sneak up to the actual token we want to lex
      // this should skip over white-space if desired
      if (lazy) it_before_token = sneak < mx >(position);

      // now call matcher to get position after token
      const char* it_after_token = mx(it_before_token);

      // check if match is in valid range
      if (it_after_token > end) return 0;

      // maybe we want to update the parser state anyway?
      if (force == false) {
        // assertion that we got a valid match
        if (it_after_token == 0) return 0;
        // assertion that we actually lexed something
        if (it_after_token == it_before_token) return 0;
      }

      // create new lexed token object (holds the parse results)
      lexed = Token(position, it_before_token, it_after_token);

      // advance position (add whitespace before current token)
      before_token = after_token.add(position, it_before_token);

      // update after_token position for current token
      after_token.add(it_before_token, it_after_token);

      // ToDo: could probably do this incremental on original object (API wants offset?)
      pstate = SourceSpan(source, before_token, after_token - before_token);

      // advance internal char iterator
      return position = it_after_token;

    }

    // lex_css skips over space, tabs, line and block comment
    // all block comments will be consumed and thrown away
    // source-map position will point to token after the comment
    template <Prelexer::prelexer mx>
    const char* lex_css()
    {
      // copy old token
      Token prev = lexed;
      // store previous pointer
      const char* oldpos = position;
      Offset bt = before_token;
      Offset at = after_token;
      SourceSpan op = pstate;
      // throw away comments
      // update srcmap position
      lex < Prelexer::css_comments >();
      // now lex a new token
      const char* pos = lex< mx >();
      // maybe restore prev state
      if (pos == 0) {
        pstate = op;
        lexed = prev;
        position = oldpos;
        after_token = at;
        before_token = bt;
      }
      // return match
      return pos;
    }

    // all block comments will be skipped and thrown away
    template <Prelexer::prelexer mx>
    const char* peek_css(const char* start = 0)
    {
      // now peek a token (skip comments first)
      return peek< mx >(peek < Prelexer::css_comments >(start));
    }

#ifdef __clang__

#endif

    void error(sass::string msg);
    // generate message with given and expected sample
    // text before and in the middle are configurable
    void css_error(const sass::string& msg,
                   const sass::string& prefix = " after ",
                   const sass::string& middle = ", was: ",
                   const bool trim = true);
    void read_bom();

    Block_Obj parse();
    Import_Obj parse_import();
    Definition_Obj parse_definition(Definition::Type which_type);
    Parameters_Obj parse_parameters();
    Parameter_Obj parse_parameter();
    Mixin_Call_Obj parse_include_directive();
    Arguments_Obj parse_arguments();
    Argument_Obj parse_argument();
    Assignment_Obj parse_assignment();
    StyleRuleObj parse_ruleset(Lookahead lookahead);
    SelectorListObj parseSelectorList(bool chroot);
    ComplexSelectorObj parseComplexSelector(bool chroot);
    Selector_Schema_Obj parse_selector_schema(const char* end_of_selector, bool chroot);
    CompoundSelectorObj parseCompoundSelector();
    SimpleSelectorObj parse_simple_selector();
    PseudoSelectorObj parse_negated_selector2();
    Expression* parse_binominal();
    SimpleSelectorObj parse_pseudo_selector();
    AttributeSelectorObj parse_attribute_selector();
    Block_Obj parse_block(bool is_root = false);
    Block_Obj parse_css_block(bool is_root = false);
    bool parse_block_nodes(bool is_root = false);
    bool parse_block_node(bool is_root = false);

    Declaration_Obj parse_declaration();
    ExpressionObj parse_map();
    ExpressionObj parse_bracket_list();
    ExpressionObj parse_list(bool delayed = false);
    ExpressionObj parse_comma_list(bool delayed = false);
    ExpressionObj parse_space_list();
    ExpressionObj parse_disjunction();
    ExpressionObj parse_conjunction();
    ExpressionObj parse_relation();
    ExpressionObj parse_expression();
    ExpressionObj parse_operators();
    ExpressionObj parse_factor();
    ExpressionObj parse_value();
    Function_Call_Obj parse_calc_function();
    Function_Call_Obj parse_function_call();
    Function_Call_Obj parse_function_call_schema();
    String_Obj parse_url_function_string();
    String_Obj parse_url_function_argument();
    String_Obj parse_interpolated_chunk(Token, bool constant = false, bool css = true);
    String_Obj parse_string();
    ValueObj parse_static_value();
    String_Schema_Obj parse_css_variable_value();
    String_Obj parse_ie_property();
    String_Obj parse_ie_keyword_arg();
    String_Schema_Obj parse_value_schema(const char* stop);
    String_Obj parse_identifier_schema();
    If_Obj parse_if_directive(bool else_if = false);
    ForRuleObj parse_for_directive();
    EachRuleObj parse_each_directive();
    WhileRuleObj parse_while_directive();
    MediaRule_Obj parseMediaRule();
    sass::vector<CssMediaQuery_Obj> parseCssMediaQueries();
    sass::string parseIdentifier();
    CssMediaQuery_Obj parseCssMediaQuery();
    Return_Obj parse_return_directive();
    Content_Obj parse_content_directive();
    void parse_charset_directive();
    List_Obj parse_media_queries();
    Media_Query_Obj parse_media_query();
    Media_Query_ExpressionObj parse_media_expression();
    SupportsRuleObj parse_supports_directive();
    SupportsConditionObj parse_supports_condition(bool top_level);
    SupportsConditionObj parse_supports_negation();
    SupportsConditionObj parse_supports_operator(bool top_level);
    SupportsConditionObj parse_supports_interpolation();
    SupportsConditionObj parse_supports_declaration();
    SupportsConditionObj parse_supports_condition_in_parens(bool parens_required);
    AtRootRuleObj parse_at_root_block();
    At_Root_Query_Obj parse_at_root_query();
    String_Schema_Obj parse_almost_any_value();
    AtRuleObj parse_directive();
    WarningRuleObj parse_warning();
    ErrorRuleObj parse_error();
    DebugRuleObj parse_debug();

    Value* color_or_string(const sass::string& lexed) const;

    // be more like ruby sass
    ExpressionObj lex_almost_any_value_token();
    ExpressionObj lex_almost_any_value_chars();
    ExpressionObj lex_interp_string();
    ExpressionObj lex_interp_uri();
    ExpressionObj lex_interpolation();

    // these will throw errors
    Token lex_variable();
    Token lex_identifier();

    void parse_block_comments(bool store = true);

    Lookahead lookahead_for_value(const char* start = 0);
    Lookahead lookahead_for_selector(const char* start = 0);
    Lookahead lookahead_for_include(const char* start = 0);

    ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, Operand op);
    ExpressionObj fold_operands(ExpressionObj base, sass::vector<ExpressionObj>& operands, sass::vector<Operand>& ops, size_t i = 0);

    void throw_syntax_error(sass::string message, size_t ln = 0);
    void throw_read_error(sass::string message, size_t ln = 0);


    template <Prelexer::prelexer open, Prelexer::prelexer close>
    ExpressionObj lex_interp()
    {
      if (lex < open >(false)) {
        String_Schema_Obj schema = SASS_MEMORY_NEW(String_Schema, pstate);
        // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n";
        schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
        if (position[0] == '#' && position[1] == '{') {
          ExpressionObj itpl = lex_interpolation();
          if (!itpl.isNull()) schema->append(itpl);
          while (lex < close >(false)) {
            // std::cerr << "LEX [[" << sass::string(lexed) << "]]\n";
            schema->append(SASS_MEMORY_NEW(String_Constant, pstate, lexed));
            if (position[0] == '#' && position[1] == '{') {
              ExpressionObj itpl = lex_interpolation();
              if (!itpl.isNull()) schema->append(itpl);
            } else {
              return schema;
            }
          }
        } else {
          return SASS_MEMORY_NEW(String_Constant, pstate, lexed);
        }
      }
      return {};
    }

  public:
    static Number* lexed_number(const SourceSpan& pstate, const sass::string& parsed);
    static Number* lexed_dimension(const SourceSpan& pstate, const sass::string& parsed);
    static Number* lexed_percentage(const SourceSpan& pstate, const sass::string& parsed);
    static Value* lexed_hex_color(const SourceSpan& pstate, const sass::string& parsed);
  private:
    Number* lexed_number(const sass::string& parsed) { return lexed_number(pstate, parsed); };
    Number* lexed_dimension(const sass::string& parsed) { return lexed_dimension(pstate, parsed); };
    Number* lexed_percentage(const sass::string& parsed) { return lexed_percentage(pstate, parsed); };
    Value* lexed_hex_color(const sass::string& parsed) { return lexed_hex_color(pstate, parsed); };

    static const char* re_attr_sensitive_close(const char* src);
    static const char* re_attr_insensitive_close(const char* src);

  };

  size_t check_bom_chars(const char* src, const char *end, const unsigned char* bom, size_t len);
}

#endif
