@Override
  public SortField parse(XContentParser parser, SearchContext context) throws Exception {
    String script = null;
    String scriptLang = null;
    String type = null;
    Map<String, Object> params = null;
    boolean reverse = false;
    MultiValueMode sortMode = null;
    NestedInnerQueryParseSupport nestedHelper = null;

    XContentParser.Token token;
    String currentName = parser.currentName();
    ScriptService.ScriptType scriptType = ScriptService.ScriptType.INLINE;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT) {
        if ("params".equals(currentName)) {
          params = parser.map();
        } else if ("nested_filter".equals(currentName) || "nestedFilter".equals(currentName)) {
          if (nestedHelper == null) {
            nestedHelper = new NestedInnerQueryParseSupport(parser, context);
          }
          nestedHelper.filter();
        }
      } else if (token.isValue()) {
        if ("reverse".equals(currentName)) {
          reverse = parser.booleanValue();
        } else if ("order".equals(currentName)) {
          reverse = "desc".equals(parser.text());
        } else if (ScriptService.SCRIPT_INLINE.match(currentName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.INLINE;
        } else if (ScriptService.SCRIPT_ID.match(currentName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.INDEXED;
        } else if (ScriptService.SCRIPT_FILE.match(currentName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.FILE;
        } else if (ScriptService.SCRIPT_LANG.match(currentName)) {
          scriptLang = parser.text();
        } else if ("type".equals(currentName)) {
          type = parser.text();
        } else if ("mode".equals(currentName)) {
          sortMode = MultiValueMode.fromString(parser.text());
        } else if ("nested_path".equals(currentName) || "nestedPath".equals(currentName)) {
          if (nestedHelper == null) {
            nestedHelper = new NestedInnerQueryParseSupport(parser, context);
          }
          nestedHelper.setPath(parser.text());
        }
      }
    }

    if (script == null) {
      throw new SearchParseException(
          context, "_script sorting requires setting the script to sort by");
    }
    if (type == null) {
      throw new SearchParseException(
          context, "_script sorting requires setting the type of the script");
    }
    final SearchScript searchScript =
        context
            .scriptService()
            .search(
                context.lookup(),
                scriptLang,
                script,
                scriptType,
                ScriptContext.Standard.SEARCH,
                params);

    if (STRING_SORT_TYPE.equals(type)
        && (sortMode == MultiValueMode.SUM || sortMode == MultiValueMode.AVG)) {
      throw new SearchParseException(
          context, "type [string] doesn't support mode [" + sortMode + "]");
    }

    if (sortMode == null) {
      sortMode = reverse ? MultiValueMode.MAX : MultiValueMode.MIN;
    }

    // If nested_path is specified, then wrap the `fieldComparatorSource` in a
    // `NestedFieldComparatorSource`
    final Nested nested;
    if (nestedHelper != null && nestedHelper.getPath() != null) {
      FixedBitSetFilter rootDocumentsFilter =
          context.fixedBitSetFilterCache().getFixedBitSetFilter(NonNestedDocsFilter.INSTANCE);
      FixedBitSetFilter innerDocumentsFilter;
      if (nestedHelper.filterFound()) {
        innerDocumentsFilter =
            context.fixedBitSetFilterCache().getFixedBitSetFilter(nestedHelper.getInnerFilter());
      } else {
        innerDocumentsFilter =
            context
                .fixedBitSetFilterCache()
                .getFixedBitSetFilter(nestedHelper.getNestedObjectMapper().nestedTypeFilter());
      }
      nested = new Nested(rootDocumentsFilter, innerDocumentsFilter);
    } else {
      nested = null;
    }

    final IndexFieldData.XFieldComparatorSource fieldComparatorSource;
    switch (type) {
      case STRING_SORT_TYPE:
        fieldComparatorSource =
            new BytesRefFieldComparatorSource(null, null, sortMode, nested) {
              @Override
              protected SortedBinaryDocValues getValues(AtomicReaderContext context) {
                searchScript.setNextReader(context);
                final BinaryDocValues values =
                    new BinaryDocValues() {
                      final BytesRefBuilder spare = new BytesRefBuilder();

                      @Override
                      public BytesRef get(int docID) {
                        searchScript.setNextDocId(docID);
                        spare.copyChars(searchScript.run().toString());
                        return spare.get();
                      }
                    };
                return FieldData.singleton(values, null);
              }

              @Override
              protected void setScorer(Scorer scorer) {
                searchScript.setScorer(scorer);
              }
            };
        break;
      case NUMBER_SORT_TYPE:
        // TODO: should we rather sort missing values last?
        fieldComparatorSource =
            new DoubleValuesComparatorSource(null, Double.MAX_VALUE, sortMode, nested) {
              @Override
              protected SortedNumericDoubleValues getValues(AtomicReaderContext context) {
                searchScript.setNextReader(context);
                final NumericDoubleValues values =
                    new NumericDoubleValues() {
                      @Override
                      public double get(int docID) {
                        searchScript.setNextDocId(docID);
                        return searchScript.runAsDouble();
                      }
                    };
                return FieldData.singleton(values, null);
              }

              @Override
              protected void setScorer(Scorer scorer) {
                searchScript.setScorer(scorer);
              }
            };
        break;
      default:
        throw new SearchParseException(
            context, "custom script sort type [" + type + "] not supported");
    }

    return new SortField("_script", fieldComparatorSource, reverse);
  }
  @Override
  public FacetExecutor parse(String facetName, XContentParser parser, SearchContext context)
      throws IOException {
    String field = null;
    int size = 10;
    int shardSize = -1;

    String[] fieldsNames = null;
    ImmutableSet<BytesRef> excluded = ImmutableSet.of();
    String regex = null;
    String regexFlags = null;
    TermsFacet.ComparatorType comparatorType = TermsFacet.ComparatorType.COUNT;
    String scriptLang = null;
    String script = null;
    ScriptService.ScriptType scriptType = null;
    Map<String, Object> params = null;
    boolean allTerms = false;
    String executionHint = null;

    String currentFieldName = null;
    XContentParser.Token token;
    while ((token = parser.nextToken()) != XContentParser.Token.END_OBJECT) {
      if (token == XContentParser.Token.FIELD_NAME) {
        currentFieldName = parser.currentName();
      } else if (token == XContentParser.Token.START_OBJECT) {
        if ("params".equals(currentFieldName)) {
          params = parser.map();
        } else {
          throw new ElasticsearchParseException(
              "unknown parameter ["
                  + currentFieldName
                  + "] while parsing terms facet ["
                  + facetName
                  + "]");
        }
      } else if (token == XContentParser.Token.START_ARRAY) {
        if ("exclude".equals(currentFieldName)) {
          ImmutableSet.Builder<BytesRef> builder = ImmutableSet.builder();
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            builder.add(parser.bytes());
          }
          excluded = builder.build();
        } else if ("fields".equals(currentFieldName)) {
          List<String> fields = Lists.newArrayListWithCapacity(4);
          while ((token = parser.nextToken()) != XContentParser.Token.END_ARRAY) {
            fields.add(parser.text());
          }
          fieldsNames = fields.toArray(new String[fields.size()]);
        } else {
          throw new ElasticsearchParseException(
              "unknown parameter ["
                  + currentFieldName
                  + "] while parsing terms facet ["
                  + facetName
                  + "]");
        }
      } else if (token.isValue()) {
        if ("field".equals(currentFieldName)) {
          field = parser.text();
        } else if (ScriptService.SCRIPT_INLINE.match(currentFieldName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.INLINE;
        } else if (ScriptService.SCRIPT_ID.match(currentFieldName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.INDEXED;
        } else if (ScriptService.SCRIPT_FILE.match(currentFieldName)) {
          script = parser.text();
          scriptType = ScriptService.ScriptType.FILE;
        } else if (ScriptService.SCRIPT_LANG.match(currentFieldName)) {
          scriptLang = parser.text();
        } else if ("size".equals(currentFieldName)) {
          size = parser.intValue();
        } else if ("shard_size".equals(currentFieldName) || "shardSize".equals(currentFieldName)) {
          shardSize = parser.intValue();
        } else if ("all_terms".equals(currentFieldName) || "allTerms".equals(currentFieldName)) {
          allTerms = parser.booleanValue();
        } else if ("regex".equals(currentFieldName)) {
          regex = parser.text();
        } else if ("regex_flags".equals(currentFieldName)
            || "regexFlags".equals(currentFieldName)) {
          regexFlags = parser.text();
        } else if ("order".equals(currentFieldName) || "comparator".equals(currentFieldName)) {
          comparatorType = TermsFacet.ComparatorType.fromString(parser.text());
        } else if ("execution_hint".equals(currentFieldName)
            || "executionHint".equals(currentFieldName)) {
          executionHint = parser.textOrNull();
        } else {
          throw new ElasticsearchParseException(
              "unknown parameter ["
                  + currentFieldName
                  + "] while parsing terms facet ["
                  + facetName
                  + "]");
        }
      }
    }

    if (fieldsNames != null && fieldsNames.length == 1) {
      field = fieldsNames[0];
      fieldsNames = null;
    }

    Pattern pattern = null;
    if (regex != null) {
      pattern = Regex.compile(regex, regexFlags);
    }

    SearchScript searchScript = null;
    if (script != null) {
      searchScript =
          context.scriptService().search(context.lookup(), scriptLang, script, scriptType, params);
    }

    // shard_size cannot be smaller than size as we need to at least fetch <size> entries from every
    // shards in order to return <size>
    if (shardSize < size) {
      shardSize = size;
    }

    if (fieldsNames != null) {

      // in case of multi files, we only collect the fields that are mapped and facet on them.
      ArrayList<FieldMapper> mappers = new ArrayList<>(fieldsNames.length);
      for (int i = 0; i < fieldsNames.length; i++) {
        FieldMapper mapper = context.smartNameFieldMapper(fieldsNames[i]);
        if (mapper != null) {
          mappers.add(mapper);
        }
      }
      if (mappers.isEmpty()) {
        // non of the fields is mapped
        return new UnmappedFieldExecutor(size, comparatorType);
      }
      return new FieldsTermsStringFacetExecutor(
          mappers.toArray(new FieldMapper[mappers.size()]),
          size,
          shardSize,
          comparatorType,
          allTerms,
          context,
          excluded,
          pattern,
          searchScript);
    }
    if (field == null && script != null) {
      return new ScriptTermsStringFieldFacetExecutor(
          size,
          shardSize,
          comparatorType,
          context,
          excluded,
          pattern,
          scriptLang,
          script,
          scriptType,
          params,
          context.cacheRecycler());
    }

    if (field == null) {
      throw new ElasticsearchParseException(
          "terms facet [" + facetName + "] must have a field, fields or script parameter");
    }

    FieldMapper fieldMapper = context.smartNameFieldMapper(field);
    if (fieldMapper == null) {
      return new UnmappedFieldExecutor(size, comparatorType);
    }

    IndexFieldData indexFieldData = context.fieldData().getForField(fieldMapper);
    if (indexFieldData instanceof IndexNumericFieldData) {
      IndexNumericFieldData indexNumericFieldData = (IndexNumericFieldData) indexFieldData;
      if (indexNumericFieldData.getNumericType().isFloatingPoint()) {
        return new TermsDoubleFacetExecutor(
            indexNumericFieldData,
            size,
            shardSize,
            comparatorType,
            allTerms,
            context,
            excluded,
            searchScript,
            context.cacheRecycler());
      } else {
        return new TermsLongFacetExecutor(
            indexNumericFieldData,
            size,
            shardSize,
            comparatorType,
            allTerms,
            context,
            excluded,
            searchScript,
            context.cacheRecycler());
      }
    } else {
      if (script != null || "map".equals(executionHint)) {
        return new TermsStringFacetExecutor(
            indexFieldData,
            size,
            shardSize,
            comparatorType,
            allTerms,
            context,
            excluded,
            pattern,
            searchScript);
      } else if (indexFieldData instanceof IndexOrdinalsFieldData) {
        return new TermsStringOrdinalsFacetExecutor(
            (IndexOrdinalsFieldData) indexFieldData,
            size,
            shardSize,
            comparatorType,
            allTerms,
            context,
            excluded,
            pattern,
            ordinalsCacheAbove);
      } else {
        return new TermsStringFacetExecutor(
            indexFieldData,
            size,
            shardSize,
            comparatorType,
            allTerms,
            context,
            excluded,
            pattern,
            searchScript);
      }
    }
  }