// Generated by CoffeeScript 2.5.1
(function() {
  var VAR, VARS, collapseVarargs, expandVarargs, find, isTerm, match, merge;

  merge = require("./shallow-merge");

  ({VAR, VARS, isTerm} = require("./ast-helper"));

  collapseVarargs = function(subst, name) {
    var _, j, len, newSubst, subvarName, vars;
    if (!((name != null) && (subst != null))) {
      return subst;
    }
    vars = subst[name];
    newSubst = merge(subst, {
      [`${name}`]: vars.map(function([_, subvarName]) {
        return subst[subvarName];
      })
    });
    for (j = 0, len = vars.length; j < len; j++) {
      [_, subvarName] = vars[j];
      delete newSubst[subvarName];
    }
    return newSubst;
  };

  expandVarargs = function(totalLength, origArgs, subst) {
    var reducer;
    reducer = function({
        args: prevArgs,
        subst: prevSubst,
        varargName: prevName
      }, arg) {
      var N, i, name, newVars, values;
      if (isTerm(arg) && arg[0] === "VARS") {
        name = arg[1];
        values = subst[name];
        if (values != null) {
          newVars = values;
        } else {
          N = totalLength - origArgs.length + 1;
          newVars = (function() {
            var j, ref, results;
            results = [];
            for (i = j = 0, ref = N; (0 <= ref ? j < ref : j > ref); i = 0 <= ref ? ++j : --j) {
              results.push([VAR, `${name}.${i}`]);
            }
            return results;
          })();
        }
        return {
          args: [...prevArgs, ...newVars],
          subst: merge(prevSubst, {
            [`${name}`]: newVars
          }),
          varargName: name
        };
      } else {
        return {
          args: [...prevArgs, arg],
          subst: prevSubst,
          varargName: prevName
        };
      }
    };
    return origArgs.reduce(reducer, {
      args: [],
      subst: subst
    });
  };

  match = function(pattern, ast, subst = {}) {
    var aTail, childSubst, nextSubst, pHead, pTail, reducer, varName, varValue, varargName;
    
    // short-circuit if subst is null. This
    // happens if a check on a preceeding sibling node failed.
    if (subst == null) {
      return null;
    }
    // short-circuit if pattern is not a term
    if (!isTerm(pattern)) {
      if (pattern === ast) {
        return subst;
      } else {
        return null;
      }
    }
    [pHead, ...pTail] = pattern;
    // check if pattern is a variable
    if (pHead === VAR) {
      [varName] = pTail;
      varValue = subst[varName];
      // is it bound?
      if (varValue != null) {
        // then match against its value
        return match(varValue, ast, subst);
      } else {
        // return the extended substitution
        return merge(subst, {
          [`${varName}`]: ast
        });
      }
    } else {
      // short-circuit if ast is not a term
      // if pattern is no variable, compare its signature with that of the ast
      if (!isTerm(ast)) {
        if (pattern === ast) {
          return subst;
        } else {
          return null;
        }
      }
      // expand any varargs
      aTail = ast.slice(1);
      ({
        args: pTail,
        subst: childSubst,
        varargName
      } = expandVarargs(aTail.length, pTail, subst));
      if (pHead !== ast[0] || pTail.length !== aTail.length) {
        // no match means: we are out.
        return null;
      } else if (ast.length === 1) {
        // if signatures match and there are no children, we are done.
        return subst;
      } else {
        // if signatures match, but there are children, check them recursively
        reducer = function(prevSubst, pChild, i) {
          var aChild;
          aChild = aTail[i];
          return match(pChild, aChild, prevSubst);
        };
        nextSubst = pTail.reduce(reducer, childSubst);
        
        // if a vararg was expanded, we need to re-substitute
        return collapseVarargs(nextSubst, varargName);
      }
    }
  };

  find = function(pattern, ast, prefix = []) {
    var localMatches, reducer, subst;
    subst = match(pattern, ast);
    localMatches = subst != null ? [
      {
        path: prefix,
        subst
      }
    ] : [];
    if (isTerm(ast) && ast.length > 1) {
      reducer = function(matches, child, i) {
        var childMatches;
        childMatches = find(pattern, child, [...prefix, i]);
        return matches.concat(childMatches);
      };
      return ast.slice(1).reduce(reducer, localMatches);
    } else {
      return localMatches;
    }
  };

  module.exports = {match, find};

}).call(this);
