#pragma once

namespace nall::Markup {

inline auto ManagedNode::_evaluate(string query) const -> bool {
  if(!query) return true;

  for(auto& rule : query.split(",")) {
    enum class Comparator : u32 { ID, EQ, NE, LT, LE, GT, GE, NF };
    auto comparator = Comparator::ID;
         if(rule.match("*!=*")) comparator = Comparator::NE;
    else if(rule.match("*<=*")) comparator = Comparator::LE;
    else if(rule.match("*>=*")) comparator = Comparator::GE;
    else if(rule.match ("*=*")) comparator = Comparator::EQ;
    else if(rule.match ("*<*")) comparator = Comparator::LT;
    else if(rule.match ("*>*")) comparator = Comparator::GT;
    else if(rule.match  ("!*")) comparator = Comparator::NF;

    if(comparator == Comparator::ID) {
      if(_find(rule).size()) continue;
      return false;
    }

    if(comparator == Comparator::NF) {
      rule.trimLeft("!", 1L);
      if(_find(rule).size()) return false;
      continue;
    }

    vector<string> side;
    switch(comparator) {
    case Comparator::EQ: side = rule.split ("=", 1L); break;
    case Comparator::NE: side = rule.split("!=", 1L); break;
    case Comparator::LT: side = rule.split ("<", 1L); break;
    case Comparator::LE: side = rule.split("<=", 1L); break;
    case Comparator::GT: side = rule.split (">", 1L); break;
    case Comparator::GE: side = rule.split(">=", 1L); break;
    }

    string data = string{_value}.strip();
    if(side(0)) {
      auto result = _find(side(0));
      if(result.size() == 0) return false;
      data = result[0].text();  //strips whitespace so rules can match without requiring it
    }

    switch(comparator) {
    case Comparator::EQ: if(data.match(side(1)) ==  true)      continue; break;
    case Comparator::NE: if(data.match(side(1)) == false)      continue; break;
    case Comparator::LT: if(data.natural()  < side(1).natural()) continue; break;
    case Comparator::LE: if(data.natural() <= side(1).natural()) continue; break;
    case Comparator::GT: if(data.natural()  > side(1).natural()) continue; break;
    case Comparator::GE: if(data.natural() >= side(1).natural()) continue; break;
    }

    return false;
  }

  return true;
}

inline auto ManagedNode::_find(const string& query) const -> vector<Node> {
  vector<Node> result;

  auto path = query.split("/");
  string name = path.take(0), rule;
  u32 lo = 0u, hi = ~0u;

  if(name.match("*[*]")) {
    auto p = name.trimRight("]", 1L).split("[", 1L);
    name = p(0);
    if(p(1).find("-")) {
      p = p(1).split("-", 1L);
      lo = !p(0) ?  0u : p(0).natural();
      hi = !p(1) ? ~0u : p(1).natural();
    } else {
      lo = hi = p(1).natural();
    }
  }

  if(name.match("*(*)")) {
    auto p = name.trimRight(")", 1L).split("(", 1L);
    name = p(0);
    rule = p(1);
  }

  u32 position = 0;
  for(auto& node : _children) {
    if(!node->_name.match(name)) continue;
    if(!node->_evaluate(rule)) continue;

    bool inrange = position >= lo && position <= hi;
    position++;
    if(!inrange) continue;

    if(path.size() == 0) {
      result.append(node);
    } else for(auto& item : node->_find(path.merge("/"))) {
      result.append(item);
    }
  }

  return result;
}

//operator[](string)
inline auto ManagedNode::_lookup(const string& path) const -> Node {
  auto result = _find(path);
  return result ? result[0] : Node{};

/*//faster, but cannot search
  if(auto position = path.find("/")) {
    auto name = slice(path, 0, *position);
    for(auto& node : _children) {
      if(name == node->_name) {
        return node->_lookup(slice(path, *position + 1));
      }
    }
  } else for(auto& node : _children) {
    if(path == node->_name) return node;
  }
  return {};
*/
}

inline auto ManagedNode::_create(const string& path) -> Node {
  if(auto position = path.find("/")) {
    auto name = slice(path, 0, *position);
    for(auto& node : _children) {
      if(name == node->_name) {
        return node->_create(slice(path, *position + 1));
      }
    }
    _children.append(new ManagedNode(name));
    return _children.right()->_create(slice(path, *position + 1));
  }
  for(auto& node : _children) {
    if(path == node->_name) return node;
  }
  _children.append(new ManagedNode(path));
  return _children.right();
}

}
