package org.nuxeo.ecm.restapi.server.jaxrs.firstvoices;

import ca.firstvoices.rest.data.SearchResults;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Optional;
import javax.ws.rs.DefaultValue;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MultiMatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.WildcardQueryBuilder;
import org.nuxeo.ecm.core.api.DocumentModel;
import org.nuxeo.ecm.core.api.DocumentModelList;
import org.nuxeo.ecm.core.api.IdRef;
import org.nuxeo.ecm.core.query.sql.NXQL;
import org.nuxeo.ecm.webengine.model.WebObject;

@WebObject(type = "dictionary")
@Produces(MediaType.APPLICATION_JSON)
public class DictionaryObject extends AbstractSearchlikeObject {

  @GET
  @Path("{dialect}")
  public Response getDictionary(
      @PathParam(value = "dialect") String dialect, @QueryParam(value = "q") String query,
      @QueryParam(value = "docType") @DefaultValue("WORD_AND_PHRASE") String docType,
      @QueryParam(value = "domain") @DefaultValue("both") String domain,
      @QueryParam(value = "alphabetCharacter") String alphabetCharacter,
      @QueryParam(value = "category") String category,
      @QueryParam(value = "kidsOnly") @DefaultValue("false") boolean kidsOnly,
      @QueryParam(value = "gamesOnly") @DefaultValue("false") boolean gamesOnly,
      @QueryParam(value = "sortAscending") @DefaultValue("false") boolean sortAscending,
      @QueryParam(value = "sortBy") @DefaultValue("entry") String sortBy,
      @QueryParam(value = "perPage") @DefaultValue("25") Integer perPage,
      @QueryParam(value = "page") @DefaultValue("1") Integer from) {

    if (perPage > 100) {
      perPage = 100;
    }
    final int offset = (from - 1) * perPage;
    final String trimmedQuery = query != null ? query.trim() : null;

    List<String> categoryIds = new ArrayList<>();

    try {
      DocumentModel dialectDocument = getContext().getCoreSession().getDocument(new IdRef(dialect));
      if (!"FVDialect".equals(dialectDocument.getType())) {
        throw new IllegalArgumentException("Not a dialect");
      }
      if (category != null && category.length() > 0) {
        DocumentModel categoryDocument = getContext().getCoreSession().getDocument(new IdRef(
            category));
        if (!"FVCategory".equals(categoryDocument.getType())) {
          throw new IllegalArgumentException("Not a category");
        }
        categoryIds.add(category);
        // expand child categories
        DocumentModelList categoryDOMList = getContext().getCoreSession().query(
            "Select * from FVCategory where ecm:isVersion=0 and "
                + "ecm:isTrashed=0 and ecm:parentId = " + NXQL.escapeString(category));
        for (DocumentModel subCategory : categoryDOMList) {
          categoryIds.add(subCategory.getId());
        }

      }
    } catch (Exception e) {
      throw new IllegalArgumentException();
    }

    SearchResults.DocumentTypes documentTypes = resolveDocumentTypesFromQueryString(docType);

    final SortOptions sortOptions = resolveSortOptionsFromQueryString(sortBy, sortAscending);

    SearchResults searchResults = new SearchResults();
    searchResults.setQuery(trimmedQuery);
    searchResults.setDocumentTypes(documentTypes);
    searchResults.setDomain(searchDomain);

    BoolQueryBuilder basicConstraints;

    basicConstraints = QueryBuilders.boolQuery().must(QueryBuilders.matchQuery("ecm:isVersion",
        false));

    basicConstraints = basicConstraints.must(typesQuery(documentTypes));
    Optional<WildcardQueryBuilder> ancestryContraints = ancestryQuery(dialect);
    if (ancestryContraints.isPresent()) {
      basicConstraints = basicConstraints.must(ancestryContraints.get());
    }

    if (!categoryIds.isEmpty()) {
      BoolQueryBuilder categoriesQuery = QueryBuilders.boolQuery();
      categoriesQuery.minimumShouldMatch(1);

      for (String categoryId : categoryIds) {
        categoriesQuery.should(QueryBuilders.matchQuery("category_ids", categoryId));
      }

      basicConstraints = basicConstraints.must(categoriesQuery);
    }

    BoolQueryBuilder combinedQuery = new BoolQueryBuilder().must(basicConstraints);

    if (trimmedQuery != null) {

      QueryBuilder exactMatchQuery = QueryBuilders.multiMatchQuery(trimmedQuery,
          "ecm:fulltext_dictionary_all_field",
          "dc:title").analyzer("keyword").type(MultiMatchQueryBuilder.Type.PHRASE);

      QueryBuilder englishQuery = QueryBuilders
          .fuzzyQuery("exact_matches_translations", trimmedQuery)
          .fuzziness(Fuzziness.fromEdits(1))
          .transpositions(true);

      QueryBuilder languageQuery = QueryBuilders
          .fuzzyQuery("exact_matches_language", trimmedQuery)
          .fuzziness(Fuzziness.fromEdits(1))
          .transpositions(true);

      final QueryBuilder englishPhraseQuery = QueryBuilders.matchPhraseQuery("exact_matches_translations",
          trimmedQuery).boost(1.1f).slop(3);

      final QueryBuilder languagePhraseQuery = QueryBuilders.matchPhraseQuery("exact_matches_language",
          trimmedQuery).boost(1.1f).slop(3);
      
      final SearchResults.SearchDomain searchDomain = resolveDomainFromQueryString(domain);

      BoolQueryBuilder combinedLanguageQuery = QueryBuilders.boolQuery();

      if (searchDomain == SearchResults.SearchDomain.LANGUAGE
          || searchDomain == SearchResults.SearchDomain.BOTH) {
        combinedLanguageQuery = combinedLanguageQuery.should(languageQuery).should(
            languagePhraseQuery);
      }
      if (searchDomain == SearchResults.SearchDomain.ENGLISH
          || searchDomain == SearchResults.SearchDomain.BOTH) {
        combinedLanguageQuery =
            combinedLanguageQuery.should(englishQuery).should(englishPhraseQuery);
      }


      combinedLanguageQuery = combinedLanguageQuery.should(exactMatchQuery);
      combinedLanguageQuery.minimumShouldMatch(1);

      combinedQuery.must(combinedLanguageQuery);
    }

    if (alphabetCharacter != null && alphabetCharacter.length() > 0) {
      String substitutedCharacter = null;

      DocumentModelList characterSearchResults = getContext().getCoreSession().query(

          "SELECT * " + "from FVCharacter where ecm:ancestorId = " + NXQL.escapeString(dialect)
              + " and " + "dc:title = " + NXQL.escapeString(alphabetCharacter) + " and "
              + "ecm:isVersion=0 and ecm:isTrashed=0 and ecm:isProxy=0");

      if (characterSearchResults.size() == 1) {
        DocumentModel fvchar = characterSearchResults.get(0);
        substitutedCharacter = (String) fvchar.getPropertyValue("fv:custom_order");

        if (substitutedCharacter != null) {

          BoolQueryBuilder alphabetPrefixQuery = QueryBuilders.boolQuery();
          alphabetPrefixQuery.minimumShouldMatch(1);

          alphabetPrefixQuery.should(QueryBuilders.prefixQuery("fv:custom_order",
              substitutedCharacter));

          combinedQuery.must(alphabetPrefixQuery);
        }
      }

      if (substitutedCharacter == null) {
        BoolQueryBuilder alphabetPrefixQuery = QueryBuilders.boolQuery();
        alphabetPrefixQuery.minimumShouldMatch(1);

        alphabetPrefixQuery.should(QueryBuilders.prefixQuery("dc:title",
            alphabetCharacter.toUpperCase(Locale.ROOT)));
        alphabetPrefixQuery.should(QueryBuilders.prefixQuery("dc:title",
            alphabetCharacter.toLowerCase(Locale.ROOT)));

        combinedQuery.must(alphabetPrefixQuery);
      }
    }

    combinedQuery.must(audienceQuery(kidsOnly, gamesOnly));

    runSearch(searchResults,
        combinedQuery,
        perPage,
        offset,
        sortOptions,
        d -> trimmedQuery != null ? trimmedQuery.equals(d.getTitle()) : false);

    return Response.ok(searchResults).build();

  }

}

@@ -3497,24 +3497,115 @@
 scending);%0A%0A
+    final SearchResults.SearchDomain searchDomain = resolveDomainFromQueryString(domain);%0A%0A
     SearchRe
@@ -5594,107 +5594,8 @@
 (3);
-%0A      %0A      final SearchResults.SearchDomain searchDomain = resolveDomainFromQueryString(domain);
 %0A%0A  

Update DictionaryObject.java