#include "names.h"

#include "utils.h"

inline void VariableContainer::CreateFrame() {
  StackScopes.push(std::unordered_map<std::string, std::any>());
}
void VariableContainer::DestroyFrame() {
  if (StackScopes.empty()) throw FatalError("DestroyFrame: empty stack");
  StackScopes.pop();
}
std::any VariableContainer::ReadVariable(const std::string &name) {
  if (StackScopes.size()) {
    auto &top = StackScopes.top();
    if (top.find(name) != top.end()) return top[name];
  }
  if (GlobalScope.find(name) != GlobalScope.end()) return GlobalScope[name];
  throw InterpretException(("ReadVariable: not found"));
}
void VariableContainer::WriteVariable(const std::string &name,
                                      const std::any &value, bool cover) {
  if (StackScopes.empty()) {
    GlobalScope[name] = value;
    return;
  }
  auto &top = StackScopes.top();
  if (top.find(name) != top.end()) {
    top[name] = value;
    return;
  }
  if (cover) {
    top[name] = value;
    return;
  }
  if (GlobalScope.find(name) != GlobalScope.end()) {
    GlobalScope[name] = value;
    return;
  }
  top[name] = value;
}
void FucntionContainer::AddFunction(const std::string &name,
                                    const FunctionItem &item) {
  if (FunctionIndex.find(name) != FunctionIndex.end()) {
    throw InterpretException("AddFunction: function already exists");
  }
  FunctionIndex[name] = item;
}

std::any FucntionContainer::CallFunction(
    const std::string &name, const std::vector<ParaArguItemType> &args,
    VariableContainer &Variables) {
  if (name == "print") {
    bool is_first = true;
    for (int i = 0; i < args.size(); i++) {
      if (!is_first) std::cout << ' ';
      is_first = false;
      std::string buf = Any2String(args[i].value);
      std::cout << buf;
    }
    std::cout << '\n';
    return nullptr;
  } else if (name == "str") {
    if (args.size() != 1)
      throw InterpretException(
          "CallFunction: str() should take exactly one argument");
    return Any2String(args[0].value);
  } else if (name == "int") {
    if (args.size() == 1) {
      return Any2Int(args[0].value);
    } else {
      throw InterpretException(
          "CallFunction: int() should take exactly one argument");
    }
  } else if (name == "float") {
    if (args.size() == 1) {
      return Any2Float(args[0].value);
    } else {
      throw InterpretException(
          "CallFunction: float() should take exactly one argument");
    }
  } else if (name == "bool") {
    if (args.size() == 1) {
      return Any2Bool(args[0].value);
    } else {
      throw InterpretException(
          "CallFunction: bool() should take exactly one argument");
    }
  }
  if (FunctionIndex.find(name) == FunctionIndex.end()) {
    throw InterpretException("CallFunction: function not found");
  }
  const FunctionItem &func = FunctionIndex[name];
  std::unordered_set<std::string> args_with_data;
  Variables.CreateFrame();
  for (int i = 0; i < args.size(); i++)
    if (args[i].name != "") {
      Variables.WriteVariable(args[i].name, args[i].value, true);
      args_with_data.insert(args[i].name);
    }
  int p_src = 0;
  for (int i = 0; i < func.para_list.size(); i++) {
    while (p_src < args.size() &&
           args_with_data.find(args[p_src].name) != args_with_data.end())
      p_src++;
    if (p_src >= args.size()) break;
    while (i < func.para_list.size() &&
           args_with_data.find(func.para_list[i].name) != args_with_data.end())
      i++;
    if (i >= func.para_list.size()) break;
    Variables.WriteVariable(func.para_list[i].name, args[p_src].value, true);
    args_with_data.insert(func.para_list[i].name);
    p_src++;
  }
  for (int i = 0; i < func.para_list.size(); i++) {
    while (i < func.para_list.size() &&
           args_with_data.find(func.para_list[i].name) != args_with_data.end())
      i++;
    if (i >= func.para_list.size()) break;
    if (!func.para_list[i].value.has_value())
      throw InterpretException("CallFunction: function parameter not found");
    Variables.WriteVariable(func.para_list[i].name, func.para_list[i].value,
                            true);
    args_with_data.insert(func.para_list[i].name);
  }
  EvalVisitor vis;
  std::any res = vis.visit(func.code_address);
  Variables.DestroyFrame();
  // std::cerr << "function called " << std::endl;
  FlowType *flow = std::any_cast<FlowType>(&res);
  if (flow) {
    if (flow->ReturnValueLists.size() == 0)
      return NoneType();
    else if (flow->ReturnValueLists.size() == 1)
      return flow->ReturnValueLists[0];
    else
      return flow->ReturnValueLists;
  }
  // std::cerr<<"Return value isn't FlowType"<<std::endl;
  return NoneType();
}