// Generated by CoffeeScript 2.3.2
(function() {
  // helper module used by the multi-match-query. It takes a list of fieldnames (or a hash with
  // containing the query-time boost factors as values) and returns a function that
  // transforms an AST as generated by the query-parser into an Elasticsearch Query.
  var AstTransformer, ConfigNode, Merger, Rewrite, builtInTransform, empty, groupBy, isArray, joinConsecutiveTerms, merge,
    splice = [].splice,
    indexOf = [].indexOf;

  ({ConfigNode, Merger} = require("@l.degener/irma-config"));

  ({Rewrite} = require("@l.degener/irma-query-syntax"));

  merge = Merger();

  ({isArray} = require("util"));

  empty = function(thing) {
    if (thing == null) {
      return true;
    }
    if (isArray(thing) && thing.length === 0) {
      return true;
    }
    if (typeof thing === "object" && Object.keys(thing).length === 0) {
      return true;
    }
  };

  joinConsecutiveTerms = function(occ) {
    var operator;
    // note that we use operator: or for occ==NOT. This works because De Morgan:
    // NOT (A or B or C ...) is equivalent to NOT A and NOT B and NOT C
    operator = occ === "MUST" ? "and" : "or";
    return function(done, next) {
      var nextArgs, nextOp, prefix, prev, prevArgs, prevOp;
      [nextOp, ...nextArgs] = next;
      [...prefix] = done, [prev] = splice.call(prefix, -1);
      [prevOp, ...prevArgs] = prev != null ? prev : [];
      switch (false) {
        case !(prevOp === 'TERM' && nextOp === 'TERM'):
          return [...prefix, ['TERMS', operator, ...prevArgs, ...nextArgs]];
        case !(prevOp === 'TERMS' && nextOp === 'TERM'):
          return [...prefix, ['TERMS', ...prevArgs, ...nextArgs]];
        default:
          return [...done, next];
      }
    };
  };

  groupBy = function(crit) {
    var reducer;
    return reducer = function(groups = {}, elm) {
      var c, group;
      c = crit(elm);
      group = groups[c] != null ? groups[c] : groups[c] = [];
      group.push(elm);
      return groups;
    };
  };

  builtInTransform = function(ast, cx) {
    var actualTerms, applyQualifier, defaultOperator, fieldBoosts, groups, opc, operands, operator, pseudoTerms, qualifiedCx, totalClauses, transformChild;
    [opc, ...operands] = ast;
    ({fieldBoosts, defaultOperator, transformChild, applyQualifier} = cx);
    switch (opc) {
      case 'QLF':
        qualifiedCx = applyQualifier(operands[0], cx);
        return transformChild(qualifiedCx)(operands[1]);
      case 'TERM':
        return {
          multi_match: {
            query: operands.join(' '),
            type: 'cross_fields',
            fields: fieldBoosts,
            operator: defaultOperator
          }
        };
      case 'TERMS': // pseudo node, created for consecutive non-negative terms
        [operator, ...actualTerms] = operands;
        return {
          multi_match: {
            query: actualTerms.join(' '),
            type: 'cross_fields',
            fields: fieldBoosts,
            operator: operator
          }
        };
      case "DQUOT":
        return {
          multi_match: {
            query: operands.join(' '),
            type: 'phrase',
            fields: fieldBoosts
          }
        };
      case "SQUOT":
        return {
          multi_match: {
            query: operands.join(' '),
            type: 'phrase',
            fields: fieldBoosts
          }
        };
      case "OR":
      case "SHOULD":
        return {
          bool: {
            should: operands.map(transformChild(cx))
          }
        };
      case "AND":
      case "MUST":
        return {
          bool: {
            must: operands.map(transformChild(cx))
          }
        };
      case "NOT":
      case "MUST_NOT":
        return {
          bool: {
            must_not: operands.map(transformChild(cx))
          }
        };
      case "SEQ":
        // the children are guaranteed to be occurence annotations
        // Group children by occurence
        groups = operands.reduce(groupBy(function(t) {
          return t[0];
        }), {});
        // join consecutive terms within groups and
        // collect the resulting clauses into pseudo-terms.
        pseudoTerms = Object.keys(groups).map(function(occ) {
          operands = groups[occ].map(function(t) {
            return t[1];
          }).reduce(joinConsecutiveTerms(occ), []);
          return [occ, ...operands];
        });
        // count the resulting clauses
        totalClauses = pseudoTerms.reduce(function(sum, pseudoTerm) {
          return sum + pseudoTerm.length - 1; // (don't count the head!)
        }, 0);
        // special case:
        // if there is only one non-negative clause, we can take a short-cut.
        // In this case, we do not need the boolean query at all.
        if (totalClauses === 1 && pseudoTerms[0][0] !== "MUST_NOT") {
          return transformChild(cx)(pseudoTerms[0][1]);
        } else {
          // otherwise we recurse on the pseudo terms and merge the results
          return pseudoTerms.map(transformChild(cx)).reduce((function(body, transformedClause) {
            return merge(body, transformedClause);
          }), {});
        }
        break;
      default:
        return {
          match_all: {}
        };
    }
  };

  module.exports = AstTransformer = class AstTransformer extends ConfigNode {
    queryFields(attrs) {
      var attr, i, len, queryFields, ref, ref1, subField, subFieldBoost, subfields;
      queryFields = {};
      if (this._options.fields != null) {
        return this._options.fields;
      }
      for (i = 0, len = attrs.length; i < len; i++) {
        attr = attrs[i];
        if (!attr.query) {
          continue;
        }
        subfields = attr.includeSubfields;
        if (subfields === true) {
          subfields = {
            '*': 1
          };
        }
        if (subfields == null) {
          subfields = {};
        }
        queryFields[attr.field] = (ref = attr.boost) != null ? ref : 1;
        for (subField in subfields) {
          subFieldBoost = subfields[subField];
          queryFields[attr.field + "." + subField] = subFieldBoost * ((ref1 = attr.boost) != null ? ref1 : 1);
        }
      }
      return queryFields;
    }

    transform(ast0, attributes, query) {
      var applyQualifier, ast, body, boost, customize, customizedTransform, cx0, defaultOccurence, defaultOperator, fieldBoosts, fieldName, fieldNames, fieldQualifiers, queryFields, ref, ref1, ref2, ref3, rewrite, transformChild;
      queryFields = this.queryFields(attributes);
      if (empty(queryFields)) {
        return {};
      }
      fieldBoosts = isArray(queryFields) ? queryFields : (function() {
        var results;
        results = [];
        for (fieldName in queryFields) {
          boost = queryFields[fieldName];
          results.push(`${fieldName}^${boost}`);
        }
        return results;
      })();
      fieldNames = isArray(queryFields) ? queryFields : Object.keys(queryFields);
      defaultOperator = (ref = (ref1 = query != null ? query.qop : void 0) != null ? ref1 : (ref2 = this._options) != null ? ref2.defaultOperator : void 0) != null ? ref : "and";
      defaultOccurence = defaultOperator === "and" ? "MUST" : "SHOULD";
      rewrite = Rewrite(defaultOccurence);
      ({
        value: ast
      } = rewrite(ast0));
      customize = this._options.customize;
      fieldQualifiers = (ref3 = this._options.fieldQualifiers) != null ? ref3 : {};
      fieldName = function(s) {
        if (indexOf.call(s, '^') >= 0) {
          return s.substr(0, s.indexOf('^'));
        } else {
          return s;
        }
      };
      applyQualifier = function(qualifier, cx0) {
        var predicate;
        ({fieldBoosts} = cx0);
        predicate = fieldQualifiers[qualifier];
        fieldBoosts = (function() {
          switch (false) {
            case !(predicate == null):
              return [];
            case typeof predicate !== "function":
              return fieldBoosts.filter(predicate);
            case typeof predicate !== "string":
              return fieldBoosts.filter(function(s) {
                return fieldName(s) === predicate;
              });
            case !(Array.isArray(predicate) && predicate.every(function(p) {
                return typeof p === "string";
              })):
              return fieldBoosts.filter(function(s) {
                var ref4;
                return ref4 = fieldName(s), indexOf.call(predicate, ref4) >= 0;
              });
            default:
              throw new Error(`not a valid field qualifier: ${qualifier}`);
          }
        })();
        return merge(cx0, {fieldBoosts});
      };
      customizedTransform = function(ast, cx) {
        var customization, operands, operation, part0, ref4;
        [operation, ...operands] = ast;
        customization = (ref4 = customize != null ? customize[operation] : void 0) != null ? ref4 : {};
        part0 = builtInTransform(ast, cx);
        if (typeof customization === "function") {
          return customization(operands, cx, part0);
        } else if (typeof customization === "object") {
          return merge(part0, customization);
        }
      };
      transformChild = function(cx) {
        return function(child) {
          return customizedTransform(child, cx);
        };
      };
      cx0 = {queryFields, attributes, fieldBoosts, fieldNames, defaultOperator, transformChild, applyQualifier};
      body = ((ast != null ? ast.length : void 0) > 0 ? customizedTransform(ast, cx0) : {});
      return this.postProcess(body, cx0);
    }

    postProcess(body, options) {
      var ref;
      if (((ref = this._options) != null ? ref.postProcess : void 0) != null) {
        return this._options.postProcess(body, options);
      } else {
        return body;
      }
    }

  };

}).call(this);
