#ifndef SASS_OPERATION_H
#define SASS_OPERATION_H

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

// base classes to implement curiously recurring template pattern (CRTP)
// https://en.wikipedia.org/wiki/Curiously_recurring_template_pattern

#include <typeinfo>
#include <stdexcept>

#include "ast_fwd_decl.hpp"
#include "ast_def_macros.hpp"

namespace Sass {

  #define ATTACH_ABSTRACT_CRTP_PERFORM_METHODS()\
    virtual void perform(Operation<void>* op) = 0; \
    virtual Value* perform(Operation<Value*>* op) = 0; \
    virtual sass::string perform(Operation<sass::string>* op) = 0; \
    virtual AST_Node* perform(Operation<AST_Node*>* op) = 0; \
    virtual Selector* perform(Operation<Selector*>* op) = 0; \
    virtual Statement* perform(Operation<Statement*>* op) = 0; \
    virtual Expression* perform(Operation<Expression*>* op) = 0; \
    virtual union Sass_Value* perform(Operation<union Sass_Value*>* op) = 0; \
    virtual SupportsCondition* perform(Operation<SupportsCondition*>* op) = 0; \

  // you must add operators to every class
  // ensures `this` of actual instance type
  // we therefore call the specific operator
  // they are virtual so most specific is used
  #define ATTACH_CRTP_PERFORM_METHODS()\
    virtual void perform(Operation<void>* op) override { return (*op)(this); } \
    virtual Value* perform(Operation<Value*>* op) override { return (*op)(this); } \
    virtual sass::string perform(Operation<sass::string>* op) override { return (*op)(this); } \
    virtual AST_Node* perform(Operation<AST_Node*>* op) override { return (*op)(this); } \
    virtual Selector* perform(Operation<Selector*>* op) override { return (*op)(this); } \
    virtual Statement* perform(Operation<Statement*>* op) override { return (*op)(this); } \
    virtual Expression* perform(Operation<Expression*>* op) override { return (*op)(this); } \
    virtual union Sass_Value* perform(Operation<union Sass_Value*>* op) override { return (*op)(this); } \
    virtual SupportsCondition* perform(Operation<SupportsCondition*>* op) override { return (*op)(this); } \

  template<typename T>
  class Operation {
  public:
    virtual T operator()(AST_Node* x)               = 0;
    // statements
    virtual T operator()(Block* x)                  = 0;
    virtual T operator()(StyleRule* x)                = 0;
    virtual T operator()(Bubble* x)                 = 0;
    virtual T operator()(Trace* x)                  = 0;
    virtual T operator()(SupportsRule* x)         = 0;
    virtual T operator()(MediaRule* x) = 0;
    virtual T operator()(CssMediaRule* x) = 0;
    virtual T operator()(CssMediaQuery* x) = 0;
    virtual T operator()(AtRootRule* x)          = 0;
    virtual T operator()(AtRule* x)              = 0;
    virtual T operator()(Keyframe_Rule* x)          = 0;
    virtual T operator()(Declaration* x)            = 0;
    virtual T operator()(Assignment* x)             = 0;
    virtual T operator()(Import* x)                 = 0;
    virtual T operator()(Import_Stub* x)            = 0;
    virtual T operator()(WarningRule* x)                = 0;
    virtual T operator()(ErrorRule* x)                  = 0;
    virtual T operator()(DebugRule* x)                  = 0;
    virtual T operator()(Comment* x)                = 0;
    virtual T operator()(If* x)                     = 0;
    virtual T operator()(ForRule* x)                    = 0;
    virtual T operator()(EachRule* x)                   = 0;
    virtual T operator()(WhileRule* x)                  = 0;
    virtual T operator()(Return* x)                 = 0;
    virtual T operator()(Content* x)                = 0;
    virtual T operator()(ExtendRule* x)              = 0;
    virtual T operator()(Definition* x)             = 0;
    virtual T operator()(Mixin_Call* x)             = 0;
    // expressions
    virtual T operator()(Null* x)                   = 0;
    virtual T operator()(List* x)                   = 0;
    virtual T operator()(Map* x)                    = 0;
    virtual T operator()(Function* x)               = 0;
    virtual T operator()(Binary_Expression* x)      = 0;
    virtual T operator()(Unary_Expression* x)       = 0;
    virtual T operator()(Function_Call* x)          = 0;
    virtual T operator()(Custom_Warning* x)         = 0;
    virtual T operator()(Custom_Error* x)           = 0;
    virtual T operator()(Variable* x)               = 0;
    virtual T operator()(Number* x)                 = 0;
    virtual T operator()(Color* x)                  = 0;
    virtual T operator()(Color_RGBA* x)             = 0;
    virtual T operator()(Color_HSLA* x)             = 0;
    virtual T operator()(Boolean* x)                = 0;
    virtual T operator()(String_Schema* x)          = 0;
    virtual T operator()(String_Quoted* x)          = 0;
    virtual T operator()(String_Constant* x)        = 0;
    virtual T operator()(SupportsCondition* x)     = 0;
    virtual T operator()(SupportsOperation* x)      = 0;
    virtual T operator()(SupportsNegation* x)      = 0;
    virtual T operator()(SupportsDeclaration* x)   = 0;
    virtual T operator()(Supports_Interpolation* x) = 0;
    virtual T operator()(Media_Query* x) = 0;
    virtual T operator()(Media_Query_Expression* x) = 0;
    virtual T operator()(At_Root_Query* x)          = 0;
    virtual T operator()(Parent_Reference* x)        = 0;
    // parameters and arguments
    virtual T operator()(Parameter* x)              = 0;
    virtual T operator()(Parameters* x)             = 0;
    virtual T operator()(Argument* x)               = 0;
    virtual T operator()(Arguments* x)              = 0;
    // selectors
    virtual T operator()(Selector_Schema* x)        = 0;
    virtual T operator()(PlaceholderSelector* x)   = 0;
    virtual T operator()(TypeSelector* x)       = 0;
    virtual T operator()(ClassSelector* x)         = 0;
    virtual T operator()(IDSelector* x)            = 0;
    virtual T operator()(AttributeSelector* x)     = 0;
    virtual T operator()(PseudoSelector* x)        = 0;
    virtual T operator()(SelectorComponent* x) = 0;
    virtual T operator()(SelectorCombinator* x) = 0;
    virtual T operator()(CompoundSelector* x) = 0;
    virtual T operator()(ComplexSelector* x) = 0;
    virtual T operator()(SelectorList* x) = 0;

  };

  // example: Operation_CRTP<Expression*, Eval>
  // T is the base return type of all visitors
  // D is the class derived visitor class
  // normally you want to implement all operators
  template <typename T, typename D>
  class Operation_CRTP : public Operation<T> {
  public:
    T operator()(AST_Node* x)               { return static_cast<D*>(this)->fallback(x); }
    // statements
    T operator()(Block* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(StyleRule* x)                { return static_cast<D*>(this)->fallback(x); }
    T operator()(Bubble* x)                 { return static_cast<D*>(this)->fallback(x); }
    T operator()(Trace* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(SupportsRule* x)         { return static_cast<D*>(this)->fallback(x); }
    T operator()(MediaRule* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(CssMediaRule* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(CssMediaQuery* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(AtRootRule* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(AtRule* x)              { return static_cast<D*>(this)->fallback(x); }
    T operator()(Keyframe_Rule* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(Declaration* x)            { return static_cast<D*>(this)->fallback(x); }
    T operator()(Assignment* x)             { return static_cast<D*>(this)->fallback(x); }
    T operator()(Import* x)                 { return static_cast<D*>(this)->fallback(x); }
    T operator()(Import_Stub* x)            { return static_cast<D*>(this)->fallback(x); }
    T operator()(WarningRule* x)                { return static_cast<D*>(this)->fallback(x); }
    T operator()(ErrorRule* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(DebugRule* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(Comment* x)                { return static_cast<D*>(this)->fallback(x); }
    T operator()(If* x)                     { return static_cast<D*>(this)->fallback(x); }
    T operator()(ForRule* x)                    { return static_cast<D*>(this)->fallback(x); }
    T operator()(EachRule* x)                   { return static_cast<D*>(this)->fallback(x); }
    T operator()(WhileRule* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(Return* x)                 { return static_cast<D*>(this)->fallback(x); }
    T operator()(Content* x)                { return static_cast<D*>(this)->fallback(x); }
    T operator()(ExtendRule* x)              { return static_cast<D*>(this)->fallback(x); }
    T operator()(Definition* x)             { return static_cast<D*>(this)->fallback(x); }
    T operator()(Mixin_Call* x)             { return static_cast<D*>(this)->fallback(x); }
    // expressions
    T operator()(Null* x)                   { return static_cast<D*>(this)->fallback(x); }
    T operator()(List* x)                   { return static_cast<D*>(this)->fallback(x); }
    T operator()(Map* x)                    { return static_cast<D*>(this)->fallback(x); }
    T operator()(Function* x)               { return static_cast<D*>(this)->fallback(x); }
    T operator()(Binary_Expression* x)      { return static_cast<D*>(this)->fallback(x); }
    T operator()(Unary_Expression* x)       { return static_cast<D*>(this)->fallback(x); }
    T operator()(Function_Call* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(Custom_Warning* x)         { return static_cast<D*>(this)->fallback(x); }
    T operator()(Custom_Error* x)           { return static_cast<D*>(this)->fallback(x); }
    T operator()(Variable* x)               { return static_cast<D*>(this)->fallback(x); }
    T operator()(Number* x)                 { return static_cast<D*>(this)->fallback(x); }
    T operator()(Color* x)                  { return static_cast<D*>(this)->fallback(x); }
    T operator()(Color_RGBA* x)             { return static_cast<D*>(this)->fallback(x); }
    T operator()(Color_HSLA* x)             { return static_cast<D*>(this)->fallback(x); }
    T operator()(Boolean* x)                { return static_cast<D*>(this)->fallback(x); }
    T operator()(String_Schema* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(String_Constant* x)        { return static_cast<D*>(this)->fallback(x); }
    T operator()(String_Quoted* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(SupportsCondition* x)     { return static_cast<D*>(this)->fallback(x); }
    T operator()(SupportsOperation* x)      { return static_cast<D*>(this)->fallback(x); }
    T operator()(SupportsNegation* x)      { return static_cast<D*>(this)->fallback(x); }
    T operator()(SupportsDeclaration* x)   { return static_cast<D*>(this)->fallback(x); }
    T operator()(Supports_Interpolation* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(Media_Query* x)            { return static_cast<D*>(this)->fallback(x); }
    T operator()(Media_Query_Expression* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(At_Root_Query* x)          { return static_cast<D*>(this)->fallback(x); }
    T operator()(Parent_Reference* x)        { return static_cast<D*>(this)->fallback(x); }
    // parameters and arguments
    T operator()(Parameter* x)              { return static_cast<D*>(this)->fallback(x); }
    T operator()(Parameters* x)             { return static_cast<D*>(this)->fallback(x); }
    T operator()(Argument* x)               { return static_cast<D*>(this)->fallback(x); }
    T operator()(Arguments* x)              { return static_cast<D*>(this)->fallback(x); }
    // selectors
    T operator()(Selector_Schema* x)        { return static_cast<D*>(this)->fallback(x); }
    T operator()(PlaceholderSelector* x)   { return static_cast<D*>(this)->fallback(x); }
    T operator()(TypeSelector* x)       { return static_cast<D*>(this)->fallback(x); }
    T operator()(ClassSelector* x)         { return static_cast<D*>(this)->fallback(x); }
    T operator()(IDSelector* x)            { return static_cast<D*>(this)->fallback(x); }
    T operator()(AttributeSelector* x)     { return static_cast<D*>(this)->fallback(x); }
    T operator()(PseudoSelector* x)        { return static_cast<D*>(this)->fallback(x); }
    T operator()(SelectorComponent* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(SelectorCombinator* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(CompoundSelector* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(ComplexSelector* x) { return static_cast<D*>(this)->fallback(x); }
    T operator()(SelectorList* x) { return static_cast<D*>(this)->fallback(x); }

    // fallback with specific type U
    // will be called if not overloaded
    template <typename U> inline T fallback(U x)
    {
      throw std::runtime_error(
        std::string(typeid(*this).name()) + ": CRTP not implemented for " + typeid(x).name());
    }

  };

}

#endif