#include "attr-path.hh"

#include "eval-inline.hh"
#include "util.hh"

namespace nix {

static Strings parseAttrPath(const string& s) {
  Strings res;
  string cur;
  string::const_iterator i = s.begin();
  while (i != s.end()) {
    if (*i == '.') {
      res.push_back(cur);
      cur.clear();
    } else if (*i == '"') {
      ++i;
      while (true) {
        if (i == s.end()) {
          throw Error(format("missing closing quote in selection path '%1%'") %
                      s);
        }
        if (*i == '"') {
          break;
        }
        cur.push_back(*i++);
      }
    } else {
      cur.push_back(*i);
    }
    ++i;
  }
  if (!cur.empty()) {
    res.push_back(cur);
  }
  return res;
}

Value* findAlongAttrPath(EvalState& state, const string& attrPath,
                         Bindings& autoArgs, Value& vIn) {
  Strings tokens = parseAttrPath(attrPath);

  Error attrError =
      Error(format("attribute selection path '%1%' does not match expression") %
            attrPath);

  Value* v = &vIn;

  for (auto& attr : tokens) {
    /* Is i an index (integer) or a normal attribute name? */
    enum { apAttr, apIndex } apType = apAttr;
    unsigned int attrIndex;
    if (string2Int(attr, attrIndex)) {
      apType = apIndex;
    }

    /* Evaluate the expression. */
    Value* vNew = state.allocValue();
    state.autoCallFunction(autoArgs, *v, *vNew);
    v = vNew;
    state.forceValue(*v);

    /* It should evaluate to either a set or an expression,
       according to what is specified in the attrPath. */

    if (apType == apAttr) {
      if (v->type != tAttrs) {
        throw TypeError(format("the expression selected by the selection path "
                               "'%1%' should be a set but is %2%") %
                        attrPath % showType(*v));
      }

      if (attr.empty()) {
        throw Error(format("empty attribute name in selection path '%1%'") %
                    attrPath);
      }

      Bindings::iterator a = v->attrs->find(state.symbols.Create(attr));
      if (a == v->attrs->end()) {
        throw Error(
            format("attribute '%1%' in selection path '%2%' not found") % attr %
            attrPath);
      }
      v = &*a->value;
    }

    else if (apType == apIndex) {
      if (!v->isList()) {
        throw TypeError(format("the expression selected by the selection path "
                               "'%1%' should be a list but is %2%") %
                        attrPath % showType(*v));
      }

      if (attrIndex >= v->listSize()) {
        throw Error(
            format("list index %1% in selection path '%2%' is out of range") %
            attrIndex % attrPath);
      }

      v = v->listElems()[attrIndex];
    }
  }

  return v;
}

}  // namespace nix