#ifdef _MSC_VER
#pragma warning(disable : 4503)
#endif

#include <iostream>
#include <typeinfo>

#include "expand.hpp"
#include "bind.hpp"
#include "eval.hpp"
#include "to_string.hpp"
#include "backtrace.hpp"
#include "context.hpp"
#include "parser.hpp"

namespace Sass {

  Expand::Expand(Context& ctx, Env* env, Backtrace* bt)
  : ctx(ctx),
    eval(Eval(*this)),
    env_stack(std::vector<Env*>()),
    block_stack(std::vector<Block*>()),
    property_stack(std::vector<String*>()),
    selector_stack(std::vector<Selector_List*>()),
    backtrace_stack(std::vector<Backtrace*>()),
    in_keyframes(false)
  {
    env_stack.push_back(0);
    env_stack.push_back(env);
    block_stack.push_back(0);
    property_stack.push_back(0);
    selector_stack.push_back(0);
    backtrace_stack.push_back(0);
    backtrace_stack.push_back(bt);
  }

  Context& Expand::context()
  {
    return ctx;
  }

  Env* Expand::environment()
  {
    if (env_stack.size() > 0)
      return env_stack.back();
    return 0;
  }

  Selector_List* Expand::selector()
  {
    if (selector_stack.size() > 0)
      return selector_stack.back();
    return 0;
  }

  Backtrace* Expand::backtrace()
  {
    if (backtrace_stack.size() > 0)
      return backtrace_stack.back();
    return 0;
  }

  // blocks create new variable scopes
  Statement* Expand::operator()(Block* b)
  {
    // create new local environment
    // set the current env as parent
    Env env(environment());
    // copy the block object (add items later)
    Block* bb = SASS_MEMORY_NEW(ctx.mem, Block,
                                b->pstate(),
                                b->length(),
                                b->is_root());
    // setup block and env stack
    this->block_stack.push_back(bb);
    this->env_stack.push_back(&env);
    // operate on block
    this->append_block(b);
    // revert block and env stack
    this->block_stack.pop_back();
    this->env_stack.pop_back();
    // return copy
    return bb;
  }

  Statement* Expand::operator()(Ruleset* r)
  {
    // reset when leaving scope

    if (in_keyframes) {
      Keyframe_Rule* k = SASS_MEMORY_NEW(ctx.mem, Keyframe_Rule, r->pstate(), r->block()->perform(this)->block());
      if (r->selector()) {
        selector_stack.push_back(0);
        k->selector(static_cast<Selector_List*>(r->selector()->perform(&eval)));
        selector_stack.pop_back();
      }
      return k;
    }

    // do some special checks for the base level rules
    if (r->is_root()) {
      if (Selector_List* selector_list = dynamic_cast<Selector_List*>(r->selector())) {
        for (Complex_Selector* complex_selector : selector_list->elements()) {
          Complex_Selector* tail = complex_selector;
          while (tail) {
            if (tail->head()) for (Simple_Selector* header : tail->head()->elements()) {
              if (dynamic_cast<Parent_Selector*>(header) == NULL) continue; // skip all others
              To_String to_string(&ctx); std::string sel_str(complex_selector->perform(&to_string));
              error("Base-level rules cannot contain the parent-selector-referencing character '&'.", header->pstate(), backtrace());
            }
            tail = tail->tail();
          }
        }
      }
    }

    Expression* ex = r->selector()->perform(&eval);
    Selector_List* sel = dynamic_cast<Selector_List*>(ex);
    if (sel == 0) throw std::runtime_error("Expanded null selector");

    selector_stack.push_back(sel);
    Block* blk = r->block()->perform(this)->block();
    Ruleset* rr = SASS_MEMORY_NEW(ctx.mem, Ruleset,
                                  r->pstate(),
                                  sel,
                                  blk);
    selector_stack.pop_back();
    rr->tabs(r->tabs());

    return rr;
  }

  Statement* Expand::operator()(Propset* p)
  {
    property_stack.push_back(p->property_fragment());
    Block* expanded_block = p->block()->perform(this)->block();

    for (size_t i = 0, L = expanded_block->length(); i < L; ++i) {
      Statement* stm = (*expanded_block)[i];
      if (Declaration* dec = static_cast<Declaration*>(stm)) {
        String_Schema* combined_prop = SASS_MEMORY_NEW(ctx.mem, String_Schema, p->pstate());
        if (!property_stack.empty()) {
          *combined_prop << property_stack.back()->perform(&eval)
                         << SASS_MEMORY_NEW(ctx.mem, String_Quoted,
             p->pstate(), "-")
             << dec->property(); // TODO: eval the prop into a string constant
        }
        else {
          *combined_prop << dec->property();
        }
        dec->property(combined_prop);
        *block_stack.back() << dec;
      }
      else if (typeid(*stm) == typeid(Comment)) {
        // drop comments in propsets
      }
      else {
        error("contents of namespaced properties must result in style declarations only", stm->pstate(), backtrace());
      }
    }

    property_stack.pop_back();

    return 0;
  }

  Statement* Expand::operator()(Supports_Block* f)
  {
    Expression* condition = f->condition()->perform(&eval);
    Supports_Block* ff = SASS_MEMORY_NEW(ctx.mem, Supports_Block,
                                       f->pstate(),
                                       static_cast<Supports_Condition*>(condition),
                                       f->block()->perform(this)->block());
    return ff;
  }

  Statement* Expand::operator()(Media_Block* m)
  {
    To_String to_string(&ctx);
    Expression* mq = m->media_queries()->perform(&eval);
    mq = Parser::from_c_str(mq->perform(&to_string).c_str(), ctx, mq->pstate()).parse_media_queries();
    Media_Block* mm = SASS_MEMORY_NEW(ctx.mem, Media_Block,
                                      m->pstate(),
                                      static_cast<List*>(mq),
                                      m->block()->perform(this)->block(),
                                      0);
    mm->tabs(m->tabs());
    return mm;
  }

  Statement* Expand::operator()(At_Root_Block* a)
  {
    Block* ab = a->block();
    // if (ab) ab->is_root(true);
    Expression* ae = a->expression();
    if (ae) ae = ae->perform(&eval);
    else ae = SASS_MEMORY_NEW(ctx.mem, At_Root_Expression, a->pstate());
    Block* bb = ab ? ab->perform(this)->block() : 0;
    At_Root_Block* aa = SASS_MEMORY_NEW(ctx.mem, At_Root_Block,
                                        a->pstate(),
                                        bb,
                                        static_cast<At_Root_Expression*>(ae));
    // aa->block()->is_root(true);
    return aa;
  }

  Statement* Expand::operator()(At_Rule* a)
  {
    LOCAL_FLAG(in_keyframes, a->is_keyframes());
    Block* ab = a->block();
    Selector* as = a->selector();
    Expression* av = a->value();
    selector_stack.push_back(0);
    if (av) av = av->perform(&eval);
    if (as) as = dynamic_cast<Selector*>(as->perform(&eval));
    selector_stack.pop_back();
    Block* bb = ab ? ab->perform(this)->block() : 0;
    At_Rule* aa = SASS_MEMORY_NEW(ctx.mem, At_Rule,
                                  a->pstate(),
                                  a->keyword(),
                                  as,
                                  bb,
                                  av);
    return aa;
  }

  Statement* Expand::operator()(Declaration* d)
  {
    String* old_p = d->property();
    String* new_p = static_cast<String*>(old_p->perform(&eval));
    Expression* value = d->value()->perform(&eval);
    if (!value || (value->is_invisible() && !d->is_important())) return 0;
    Declaration* decl = SASS_MEMORY_NEW(ctx.mem, Declaration,
                                        d->pstate(),
                                        new_p,
                                        value,
                                        d->is_important());
    decl->tabs(d->tabs());
    return decl;
  }

  Statement* Expand::operator()(Assignment* a)
  {
    Env* env = environment();
    std::string var(a->variable());
    if (a->is_global()) {
      if (a->is_default()) {
        if (env->has_global(var)) {
          Expression* e = dynamic_cast<Expression*>(env->get_global(var));
          if (!e || e->concrete_type() == Expression::NULL_VAL) {
            env->set_global(var, a->value()->perform(&eval));
          }
        }
        else {
          env->set_global(var, a->value()->perform(&eval));
        }
      }
      else {
        env->set_global(var, a->value()->perform(&eval));
      }
    }
    else if (a->is_default()) {
      if (env->has_lexical(var)) {
        auto cur = env;
        while (cur && cur->is_lexical()) {
          if (cur->has_local(var)) {
            if (AST_Node* node = cur->get_local(var)) {
              Expression* e = dynamic_cast<Expression*>(node);
              if (!e || e->concrete_type() == Expression::NULL_VAL) {
                cur->set_local(var, a->value()->perform(&eval));
              }
            }
            else {
              throw std::runtime_error("Env not in sync");
            }
            return 0;
          }
          cur = cur->parent();
        }
        throw std::runtime_error("Env not in sync");
      }
      else if (env->has_global(var)) {
        if (AST_Node* node = env->get_global(var)) {
          Expression* e = dynamic_cast<Expression*>(node);
          if (!e || e->concrete_type() == Expression::NULL_VAL) {
            env->set_global(var, a->value()->perform(&eval));
          }
        }
      }
      else if (env->is_lexical()) {
        env->set_local(var, a->value()->perform(&eval));
      }
      else {
        env->set_local(var, a->value()->perform(&eval));
      }
    }
    else {
      env->set_lexical(var, a->value()->perform(&eval));
    }
    return 0;
  }

  Statement* Expand::operator()(Import* imp)
  {
    Import* result = SASS_MEMORY_NEW(ctx.mem, Import, imp->pstate());
    if (imp->media_queries()) {
      Expression* ex = imp->media_queries()->perform(&eval);
      result->media_queries(dynamic_cast<List*>(ex));
    }
    for ( size_t i = 0, S = imp->urls().size(); i < S; ++i) {
      result->urls().push_back(imp->urls()[i]->perform(&eval));
    }
    return result;
  }

  Statement* Expand::operator()(Import_Stub* i)
  {
    append_block(ctx.style_sheets[i->file_name()]);
    return 0;
  }

  Statement* Expand::operator()(Warning* w)
  {
    // eval handles this too, because warnings may occur in functions
    w->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(Error* e)
  {
    // eval handles this too, because errors may occur in functions
    e->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(Debug* d)
  {
    // eval handles this too, because warnings may occur in functions
    d->perform(&eval);
    return 0;
  }

  Statement* Expand::operator()(Comment* c)
  {
    // TODO: eval the text, once we're parsing/storing it as a String_Schema
    return SASS_MEMORY_NEW(ctx.mem, Comment, c->pstate(), static_cast<String*>(c->text()->perform(&eval)), c->is_important());
  }

  Statement* Expand::operator()(If* i)
  {
    if (*i->predicate()->perform(&eval)) {
      append_block(i->block());
    }
    else {
      Block* alt = i->alternative();
      if (alt) append_block(alt);
    }
    return 0;
  }

  // For does not create a new env scope
  // But iteration vars are reset afterwards
  Statement* Expand::operator()(For* f)
  {
    std::string variable(f->variable());
    Expression* low = f->lower_bound()->perform(&eval);
    if (low->concrete_type() != Expression::NUMBER) {
      error("lower bound of `@for` directive must be numeric", low->pstate(), backtrace());
    }
    Expression* high = f->upper_bound()->perform(&eval);
    if (high->concrete_type() != Expression::NUMBER) {
      error("upper bound of `@for` directive must be numeric", high->pstate(), backtrace());
    }
    Number* sass_start = static_cast<Number*>(low);
    Number* sass_end = static_cast<Number*>(high);
    // check if units are valid for sequence
    if (sass_start->unit() != sass_end->unit()) {
      std::stringstream msg; msg << "Incompatible units: '"
        << sass_start->unit() << "' and '"
        << sass_end->unit() << "'.";
      error(msg.str(), low->pstate(), backtrace());
    }
    double start = sass_start->value();
    double end = sass_end->value();
    // only create iterator once in this environment
    Env* env = environment();
    Number* it = SASS_MEMORY_NEW(env->mem, Number, low->pstate(), start, sass_end->unit());
    AST_Node* old_var = env->has_local(variable) ? env->get_local(variable) : 0;
    env->set_local(variable, it);
    Block* body = f->block();
    if (start < end) {
      if (f->is_inclusive()) ++end;
      for (double i = start;
           i < end;
           ++i) {
        it->value(i);
        env->set_local(variable, it);
        append_block(body);
      }
    } else {
      if (f->is_inclusive()) --end;
      for (double i = start;
           i > end;
           --i) {
        it->value(i);
        env->set_local(variable, it);
        append_block(body);
      }
    }
    // restore original environment
    if (!old_var) env->del_local(variable);
    else env->set_local(variable, old_var);
    return 0;
  }

  // Eval does not create a new env scope
  // But iteration vars are reset afterwards
  Statement* Expand::operator()(Each* e)
  {
    std::vector<std::string> variables(e->variables());
    Expression* expr = e->list()->perform(&eval);
    List* list = 0;
    Map* map = 0;
    if (expr->concrete_type() == Expression::MAP) {
      map = static_cast<Map*>(expr);
    }
    else if (expr->concrete_type() != Expression::LIST) {
      list = SASS_MEMORY_NEW(ctx.mem, List, expr->pstate(), 1, SASS_COMMA);
      *list << expr;
    }
    else {
      list = static_cast<List*>(expr);
    }
    // remember variables and then reset them
    Env* env = environment();
    std::vector<AST_Node*> old_vars(variables.size());
    for (size_t i = 0, L = variables.size(); i < L; ++i) {
      old_vars[i] = env->has_local(variables[i]) ? env->get_local(variables[i]) : 0;
      env->set_local(variables[i], 0);
    }
    Block* body = e->block();

    if (map) {
      for (auto key : map->keys()) {
        Expression* k = key->perform(&eval);
        Expression* v = map->at(key)->perform(&eval);

        if (variables.size() == 1) {
          List* variable = SASS_MEMORY_NEW(ctx.mem, List, map->pstate(), 2, SASS_SPACE);
          *variable << k;
          *variable << v;
          env->set_local(variables[0], variable);
        } else {
          env->set_local(variables[0], k);
          env->set_local(variables[1], v);
        }
        append_block(body);
      }
    }
    else {
      bool arglist = list->is_arglist();
      for (size_t i = 0, L = list->length(); i < L; ++i) {
        Expression* e = (*list)[i];
        // unwrap value if the expression is an argument
        if (Argument* arg = dynamic_cast<Argument*>(e)) e = arg->value();
        // check if we got passed a list of args (investigate)
        if (List* scalars = dynamic_cast<List*>(e)) {
          if (variables.size() == 1) {
            Expression* var = scalars;
            if (arglist) var = (*scalars)[0];
            env->set_local(variables[0], var);
          } else {
            for (size_t j = 0, K = variables.size(); j < K; ++j) {
              Expression* res = j >= scalars->length()
                ? SASS_MEMORY_NEW(ctx.mem, Null, expr->pstate())
                : (*scalars)[j]->perform(&eval);
              env->set_local(variables[j], res);
            }
          }
        } else {
          if (variables.size() > 0) {
            env->set_local(variables[0], e);
            for (size_t j = 1, K = variables.size(); j < K; ++j) {
              Expression* res = SASS_MEMORY_NEW(ctx.mem, Null, expr->pstate());
              env->set_local(variables[j], res);
            }
          }
        }
        append_block(body);
      }
    }
    // restore original environment
    for (size_t j = 0, K = variables.size(); j < K; ++j) {
      if(!old_vars[j]) env->del_local(variables[j]);
      else env->set_local(variables[j], old_vars[j]);
    }
    return 0;
  }

  Statement* Expand::operator()(While* w)
  {
    Expression* pred = w->predicate();
    Block* body = w->block();
    while (*pred->perform(&eval)) {
      append_block(body);
    }
    return 0;
  }

  Statement* Expand::operator()(Return* r)
  {
    error("@return may only be used within a function", r->pstate(), backtrace());
    return 0;
  }

  Statement* Expand::operator()(Extension* e)
  {
    To_String to_string(&ctx);
    Selector_List* extender = dynamic_cast<Selector_List*>(selector());
    if (!extender) return 0;
    selector_stack.push_back(0);

    if (Selector_List* selector_list = dynamic_cast<Selector_List*>(e->selector())) {
      for (Complex_Selector* complex_selector : selector_list->elements()) {
        Complex_Selector* tail = complex_selector;
        while (tail) {
          if (tail->head()) for (Simple_Selector* header : tail->head()->elements()) {
            if (dynamic_cast<Parent_Selector*>(header) == NULL) continue; // skip all others
            To_String to_string(&ctx); std::string sel_str(complex_selector->perform(&to_string));
            error("Can't extend " + sel_str + ": can't extend parent selectors", header->pstate(), backtrace());
          }
          tail = tail->tail();
        }
      }
    }

    Selector_List* contextualized = dynamic_cast<Selector_List*>(e->selector()->perform(&eval));
    if (contextualized == NULL) return 0;
    for (auto complex_sel : contextualized->elements()) {
      Complex_Selector* c = complex_sel;
      if (!c->head() || c->tail()) {
        To_String to_string(&ctx); std::string sel_str(contextualized->perform(&to_string));
        error("Can't extend " + sel_str + ": can't extend nested selectors", c->pstate(), backtrace());
      }
      Compound_Selector* placeholder = c->head();
      placeholder->is_optional(e->selector()->is_optional());
      for (size_t i = 0, L = extender->length(); i < L; ++i) {
        Complex_Selector* sel = (*extender)[i];
        if (!(sel->head() && sel->head()->length() > 0 &&
            dynamic_cast<Parent_Selector*>((*sel->head())[0])))
        {
          Compound_Selector* hh = SASS_MEMORY_NEW(ctx.mem, Compound_Selector, (*extender)[i]->pstate());
          Complex_Selector* ssel = SASS_MEMORY_NEW(ctx.mem, Complex_Selector, (*extender)[i]->pstate());
          if (sel->has_line_feed()) ssel->has_line_feed(true);
          *hh << SASS_MEMORY_NEW(ctx.mem, Parent_Selector, (*extender)[i]->pstate());
          ssel->tail(sel);
          ssel->head(hh);
          sel = ssel;
        }
        // if (c->has_line_feed()) sel->has_line_feed(true);
        ctx.subset_map.put(placeholder->to_str_vec(), std::make_pair(sel, placeholder));
      }
    }

    selector_stack.pop_back();

    return 0;
  }

  Statement* Expand::operator()(Definition* d)
  {
    Env* env = environment();
    Definition* dd = SASS_MEMORY_NEW(ctx.mem, Definition, *d);
    env->local_frame()[d->name() +
                        (d->type() == Definition::MIXIN ? "[m]" : "[f]")] = dd;
    // set the static link so we can have lexical scoping
    dd->environment(env);
    return 0;
  }

  Statement* Expand::operator()(Mixin_Call* c)
  {
    Env* env = environment();
    std::string full_name(c->name() + "[m]");
    if (!env->has(full_name)) {
      error("no mixin named " + c->name(), c->pstate(), backtrace());
    }
    Definition* def = static_cast<Definition*>((*env)[full_name]);
    Block* body = def->block();
    Parameters* params = def->parameters();

    if (c->block() && c->name() != "@content" && !body->has_content()) {
      error("Mixin \"" + c->name() + "\" does not accept a content block.", c->pstate(), backtrace());
    }
    Arguments* args = static_cast<Arguments*>(c->arguments()
                                               ->perform(&eval));
    Backtrace new_bt(backtrace(), c->pstate(), ", in mixin `" + c->name() + "`");
    backtrace_stack.push_back(&new_bt);
    Env new_env(def->environment());
    env_stack.push_back(&new_env);
    if (c->block()) {
      // represent mixin content blocks as thunks/closures
      Definition* thunk = SASS_MEMORY_NEW(ctx.mem, Definition,
                                          c->pstate(),
                                          "@content",
                                          SASS_MEMORY_NEW(ctx.mem, Parameters, c->pstate()),
                                          c->block(),
                                          Definition::MIXIN);
      thunk->environment(env);
      new_env.local_frame()["@content[m]"] = thunk;
    }
    bind("mixin " + c->name(), params, args, ctx, &new_env, &eval);
    append_block(body);
    backtrace_stack.pop_back();
    env_stack.pop_back();
    return 0;
  }

  Statement* Expand::operator()(Content* c)
  {
    Env* env = environment();
    // convert @content directives into mixin calls to the underlying thunk
    if (!env->has("@content[m]")) return 0;
    Mixin_Call* call = SASS_MEMORY_NEW(ctx.mem, Mixin_Call,
                                       c->pstate(),
                                       "@content",
                                       SASS_MEMORY_NEW(ctx.mem, Arguments, c->pstate()));
    return call->perform(this);
  }

  // produce an error if something is not implemented
  inline Statement* Expand::fallback_impl(AST_Node* n)
  {
    std::string err =std:: string("`Expand` doesn't handle ") + typeid(*n).name();
    String_Quoted* msg = SASS_MEMORY_NEW(ctx.mem, String_Quoted, ParserState("[WARN]"), err);
    error("unknown internal error; please contact the LibSass maintainers", n->pstate(), backtrace());
    return SASS_MEMORY_NEW(ctx.mem, Warning, ParserState("[WARN]"), msg);
  }

  // process and add to last block on stack
  inline void Expand::append_block(Block* b)
  {
    for (size_t i = 0, L = b->length(); i < L; ++i) {
      Statement* ith = (*b)[i]->perform(this);
      if (ith) *block_stack.back() << ith;
    }
  }

}
