// Generated by CoffeeScript 2.5.1
(function() {
  var VAR, VARS, _matchAll, _matchSome, applySubst, bottomup, compileRule, flatmapReducer, flatmapReducerWithCx, isTerm, match, matchAll, matchSome, merge, replace, ruleBased, sigmatch, topdown;

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

  sigmatch = require("sigmatch");

  ({match} = require("./ast-matcher"));

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

  replace = function(ast, [i, ...tail], replacement) {
    var newAst;
    if (i == null) {
      return replacement(ast);
    } else {
      newAst = ast.slice();
      newAst[i + 1] = replace(ast[i + 1], tail, replacement);
      return newAst;
    }
  };

  flatmapReducer = function(f) {
    return function(prevArgs, arg, i) {
      var processedArg;
      processedArg = f(arg, i);
      if (Array.isArray(processedArg) && !isTerm(processedArg)) {
        return prevArgs.concat(processedArg);
      } else {
        return prevArgs.concat([processedArg]);
      }
    };
  };

  flatmapReducerWithCx = function(f) {
    return function({
        cx: prevCx,
        value: prevArgs
      }, arg, i) {
      var newArg, newCx;
      ({
        cx: newCx,
        value: newArg
      } = f(arg, i, prevCx));
      if (Array.isArray(newArg) && !isTerm(newArg)) {
        return {
          cx: newCx,
          value: prevArgs.concat(newArg)
        };
      } else {
        return {
          cx: newCx,
          value: prevArgs.concat([newArg])
        };
      }
    };
  };

  applySubst = function(s, term0) {
    var recurse, recurseArgs;
    recurseArgs = function(args) {
      return args.reduce(flatmapReducer(recurse), []);
    };
    recurse = function(term) {
      var args, head, newArgs;
      if (!isTerm(term)) {
        return term;
      }
      [head, ...args] = term;
      if (head === VAR) {
        return recurse(s[args[0]]);
      } else if (head === VARS) {
        return recurseArgs(s[args[0]]);
      } else {
        newArgs = recurseArgs(args);
        return [head, ...newArgs];
      }
    };
    return recurse(term0);
  };

  topdown = function(rewrite) {
    var recurse, recurseArgs;
    recurseArgs = function(args, cx, path) {
      var reducer;
      reducer = flatmapReducerWithCx(function(v, i, cx) {
        return recurse(v, cx, [...path, i]);
      });
      return args.reduce(reducer, {
        cx: cx,
        value: []
      });
    };
    return recurse = function(t0, cx0 = {}, path = []) {
      var cx, head, newArgs, newCx, oldArgs, value;
      if (!isTerm(t0)) {
        return {
          // rewrite strategy is only applied on propper terms
          cx: cx0,
          value: t0
        };
      } else {
        // we are going top-down, so we first have to pass the parent node to the
        // rewrite strategy
        ({value, cx = {}} = rewrite(t0, cx0, path));
        // the value produced by the strategy may be anything, so we have to
        // check here:

        // anything non-array-ish is passed on unchanged. There is no need
        // for recursion because there are no children.
        if (!Array.isArray(value)) {
          return {cx, value};
        // we may get an array that is not a propper term.
        // this can happen when e.g. a single argument is expanded to a list of arguments
        // In this case we will process the terms like an argument list, but we will not
        // construct a term from it.
        } else if (!isTerm(value)) {
          return recurseArgs(value, cx, path);
        } else {
          // a regular term with a head and zero or more arguments.
          // process the arguments recursively
          [head, ...oldArgs] = value;
          ({
            value: newArgs,
            cx: newCx
          } = recurseArgs(oldArgs, cx, path));
          return {
            value: [head, ...newArgs],
            cx: newCx
          };
        }
      }
    };
  };

  bottomup = function(rewrite) {
    var recurse, recurseArgs;
    recurseArgs = function(args, cx, path) {
      var reducer;
      reducer = flatmapReducerWithCx(function(v, i, cx) {
        return recurse(v, cx, [...path, i]);
      });
      return args.reduce(reducer, {
        cx: cx,
        value: []
      });
    };
    return recurse = function(t0, cx0 = {}, path = []) {
      var head, newArgs, newCx, oldArgs;
      // short-circuit for non-array stuff
      if (!Array.isArray(t0)) {
        return {
          cx: cx0,
          value: t0
        };
      // if it is an array, but not a propper term, we assume an array of terms
      } else if (!isTerm(t0)) {
        // NOTE: this should happen during normal recursion.
        // It *may* happen if our toplevel is not a propper term to begin with.
        return recurseArgs(t0, cx0, path);
      } else {
        // first we hae to process the argument terms
        [head, ...oldArgs] = t0;
        ({
          value: newArgs,
          cx: newCx
        } = recurseArgs(oldArgs, cx0, path));
        // now that we processed the arguments, construct a new
        // term and pass it to the rewrite strategy
        return rewrite([head, ...newArgs], newCx, path);
      }
    };
  };

  compileRule = function(rule) {
    var identity, ruleReducer;
    identity = function(x) {
      return x;
    };
    ruleReducer = function(prevf, f0) {
      var f, g;
      if (typeof f0 === "string") {
        prevf.displayName = f0;
        return prevf;
      } else {
        f = (function() {
          switch (false) {
            case !isTerm(f0):
              return function(input) {
                switch (false) {
                  case !isTerm(input):
                    return match(f0, input);
                  case typeof input !== "object":
                    return applySubst(input, f0);
                  default:
                    throw new Error("cannot process this: " + input);
                }
              };
            case typeof f0 !== "function":
              return f0;
            default:
              throw new Error("not a valid rule component: " + f0);
          }
        })();
        g = function(v0, path) {
          var v;
          v = prevf(v0, path);
          if (v != null) {
            return f(v, path);
          }
        };
        g.displayName = prevf.displayName;
        return g;
      }
    };
    if (typeof rule === "function") {
      return rule;
    }
    if (!Array.isArray(rule)) {
      throw new Error("not a rule: " + rule);
    }
    
    // composes the functions within a rule into one function
    return rule.reduce(ruleReducer, identity);
  };

  ruleBased = function(opts) {
    var maxIterations, report, rules, rules0;
    if (Array.isArray(opts)) {
      opts = {
        rules: opts
      };
    }
    ({
      rules: rules0,
      maxIterations = 25,
      report
    } = opts);
    rules = rules0.map(compileRule);
    return function(value, cx, path) {
      var R, dirty, i, j, len, newValue, rule;
      dirty = true;
      i = 0;
      while (dirty) {
        if (!(i < maxIterations)) {
          throw new Error(`no fixpoint in ${maxIterations} iterations`);
        }
        i++;
        dirty = false;
        for (R = j = 0, len = rules.length; j < len; R = ++j) {
          rule = rules[R];
          newValue = rule(value, path);
          if (newValue != null) {
            if (typeof report === "function") {
              report(rule, R, path, value, newValue);
            }
            value = newValue;
            dirty = true;
          }
        }
      }
      return {cx, value};
    };
  };

  _matchAll = function(InVar, Rule, OutVar) {
    var rule;
    rule = compileRule(Rule);
    return function(input, path) {
      var instances, orig, subst, terms, transformed;
      subst = InVar != null ? input : {};
      terms = InVar != null ? input[InVar] : input;
      if (!Array.isArray(terms)) {
        return;
      }
      if (isTerm(terms)) {
        return;
      }
      instances = (function() {
        var j, len, results;
        results = [];
        for (j = 0, len = terms.length; j < len; j++) {
          orig = terms[j];
          transformed = rule(orig, path);
          if (transformed) {
            results.push(transformed);
          } else {
            break;
          }
        }
        return results;
      })();
      if (instances.length !== terms.length) {
        return null;
      }
      if (OutVar != null) {
        return merge(subst, {
          [`${OutVar}`]: instances
        });
      } else {
        return instances;
      }
    };
  };

  matchAll = sigmatch(function(m) {
    m("s,.,s?", _matchAll);
    return m(".,s?", function(rule, OutVar) {
      return _matchAll(null, rule, OutVar);
    });
  });

  _matchSome = function(InVar, Rule, OutVar) {
    var rule;
    rule = compileRule(Rule);
    return function(input, path) {
      var instances, orig, subst, success, terms, transformed;
      subst = InVar != null ? input : {};
      terms = InVar != null ? input[InVar] : input;
      if (!Array.isArray(terms)) {
        return;
      }
      if (isTerm(terms)) {
        return;
      }
      success = false;
      instances = (function() {
        var j, len, results;
        results = [];
        for (j = 0, len = terms.length; j < len; j++) {
          orig = terms[j];
          transformed = rule(orig, path);
          if (transformed) {
            success = true;
            results.push(transformed);
          } else {
            results.push(orig);
          }
        }
        return results;
      })();
      if (!success) {
        return null;
      }
      if (OutVar != null) {
        return merge(subst, {
          [`${OutVar}`]: instances
        });
      } else {
        return instances;
      }
    };
  };

  matchSome = sigmatch(function(m) {
    m("s,.,s?", _matchSome);
    return m(".,s?", function(rule, OutVar) {
      return _matchSome(null, rule, OutVar);
    });
  });

  module.exports = {replace, topdown, bottomup, ruleBased, applySubst, matchAll, matchSome, compileRule};

}).call(this);
