// Copyright (C) 2009 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package com.google.gwtexpui.safehtml.client;

import com.google.gwt.user.client.ui.SuggestOracle;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

/**
 * A suggestion oracle that tries to highlight the matched text.
 * <p>
 * Suggestions supplied by the implementation of
 * {@link #onRequestSuggestions(Request, Callback)} are modified to wrap all
 * occurrences of the
 * {@link com.google.gwt.user.client.ui.SuggestOracle.Request#getQuery()}
 * substring in HTML {@code &lt;strong&gt;} tags, so they can be emphasized to
 * the user.
 */
public abstract class HighlightSuggestOracle extends SuggestOracle {
  private static String escape(String ds) {
    return new SafeHtmlBuilder().append(ds).asString();
  }

  @Override
  public final boolean isDisplayStringHTML() {
    return true;
  }

  @Override
  public final void requestSuggestions(final Request request, final Callback cb) {
    onRequestSuggestions(request, new Callback() {
      @Override
      public void onSuggestionsReady(final Request request,
          final Response response) {
        final String qpat = getQueryPattern(request.getQuery());
        final boolean html = isHTML();
        final ArrayList<Suggestion> r = new ArrayList<>();
        for (final Suggestion s : response.getSuggestions()) {
          r.add(new BoldSuggestion(qpat, s, html));
        }
        cb.onSuggestionsReady(request, new Response(r));
      }
    });
  }

  protected String getQueryPattern(final String query) {
    return query;
  }

  /**
   * @return true if
   *         {@link com.google.gwt.user.client.ui.SuggestOracle.Suggestion#getDisplayString()}
   *         returns HTML; false if the text must be escaped before evaluating
   *         in an HTML like context.
   */
  protected boolean isHTML() {
    return false;
  }

  /** Compute the suggestions and return them for display. */
  protected abstract void onRequestSuggestions(Request request, Callback done);

  private static class BoldSuggestion implements Suggestion {
    private final Suggestion suggestion;
    private final String displayString;

    BoldSuggestion(final String qstr, final Suggestion s, final boolean html) {
      suggestion = s;

      String ds = s.getDisplayString();
      if (!html) {
        ds = escape(ds);
      }

      for (String qterm : splitQuery(qstr)) {
        qterm = "(" + escape(qterm) + ")";
        // We now surround qstr by <strong>. But the chosen approach is not too
        // smooth, if qstr is small (e.g.: "t") and this small qstr may occur in
        // escapes (e.g.: "Tim &lt;email@example.org&gt;"). Those escapes will
        // get <strong>-ed as well (e.g.: "&lt;" -> "&<strong>l</strong>t;"). But
        // as repairing those mangled escapes is easier than not mangling them in
        // the first place, we repair them afterwards.
        ds = sgi(ds, qterm, "<strong>$1</strong>");
      }

      // Repairing <strong>-ed escapes.
      ds = sgi(ds, "(&[a-z]*)<strong>([a-z]*)</strong>([a-z]*;)", "$1$2$3");

      displayString = ds;
    }

    /**
     * Split the query by whitespace and filter out query terms which are
     * substrings of other query terms.
     */
    private static List<String> splitQuery(String query) {
      List<String> queryTerms = Arrays.asList(query.split("\\s+"));
      Collections.sort(queryTerms, new Comparator<String>() {
        @Override
        public int compare(String s1, String s2) {
          return Integer.compare(s2.length(), s1.length());
        }
      });

      List<String> result = new ArrayList<>();
      for (String s : queryTerms) {
        boolean add = true;
        for (String queryTerm : result) {
          if (queryTerm.toLowerCase().contains(s.toLowerCase())) {
            add = false;
            break;
          }
        }
        if (add) {
          result.add(s);
        }
      }
      return result;
    }

    private static native String sgi(String inString, String pat, String newHtml)
    /*-{ return inString.replace(RegExp(pat, 'gi'), newHtml); }-*/;

    @Override
    public String getDisplayString() {
      return displayString;
    }

    @Override
    public String getReplacementString() {
      return suggestion.getReplacementString();
    }
  }
}

@@ -2969,24 +2969,75 @@
 );%0A      %7D%0A%0A
+      StringBuilder pattern = new StringBuilder();%0A
       for (S
@@ -3089,14 +3089,8 @@
 rm =
- %22(%22 +
  esc
@@ -3103,14 +3103,8 @@
 erm)
- + %22)%22
 ;%0A  
@@ -3564,34 +3564,175 @@
 ds.%0A
-        ds = sgi(ds, qterm
+%0A        if (pattern.length() %3E 0) %7B%0A          pattern.append(%22%7C%22);%0A        %7D%0A        pattern.append(qterm);%0A      %7D%0A%0A      ds = sgi(ds, %22(%22 + pattern.toString() + %22)%22
 , %22%3C
@@ -3752,24 +3752,16 @@
 rong%3E%22);
-%0A      %7D
 %0A%0A      

Fix malformed account suggestions