/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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 org.apache.solr.handler.component;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.solr.client.solrj.SolrQuery;
import org.apache.solr.client.solrj.request.CollectionAdminRequest;
import org.apache.solr.client.solrj.request.QueryRequest;
import org.apache.solr.client.solrj.request.UpdateRequest;
import org.apache.solr.client.solrj.response.QueryResponse;
import org.apache.solr.cloud.AbstractDistribZkTestBase;
import org.apache.solr.cloud.ConfigRequest;
import org.apache.solr.cloud.SolrCloudTestCase;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.highlight.SolrFragmentsBuilder;
import org.junit.BeforeClass;
import org.junit.Test;

public class CustomHighlightComponentTest extends SolrCloudTestCase {

  public static class CustomHighlightComponent extends HighlightComponent {

    protected String id_key = "id";
    protected String snippets_key = "snippets";

    @Override
    protected String highlightingResponseField() {
      return "custom_highlighting";
    }

    @Override
    protected Object convertHighlights(NamedList hl) {
      final ArrayList<SimpleOrderedMap> hlMaps = new ArrayList<>();
      for (int i=0; i<hl.size(); ++i) {
          SimpleOrderedMap hlMap = new SimpleOrderedMap<Object>();
          hlMap.add(id_key, hl.getName(i));
          hlMap.add(snippets_key, hl.getVal(i));
          hlMaps.add(hlMap);
      }
      return hlMaps;
    }

    @Override
    protected Object[] newHighlightsArray(int size) {
      return new SimpleOrderedMap[size];
    }

    @Override
    protected void addHighlights(Object[] objArr, Object obj, Map<Object, ShardDoc> resultIds) {
      SimpleOrderedMap[] mapArr = (SimpleOrderedMap[])objArr;
      final ArrayList<SimpleOrderedMap> hlMaps = (ArrayList<SimpleOrderedMap>)obj;
      for (SimpleOrderedMap hlMap : hlMaps) {
        String id = (String)hlMap.get(id_key);
        ShardDoc sdoc = resultIds.get(id);
        int idx = sdoc.positionInResponse;
        mapArr[idx] = hlMap;
      }
    }

    @Override
    protected Object getAllHighlights(Object[] objArr) {
      final SimpleOrderedMap[] mapArr = (SimpleOrderedMap[])objArr;
      // remove nulls in case not all docs were able to be retrieved
      ArrayList<SimpleOrderedMap> mapList = new ArrayList<>();
      for (SimpleOrderedMap map : mapArr) {
        if (map != null) {
          mapList.add((SimpleOrderedMap)map);
        }
      }
      return mapList;
    }

  }

  protected String customHighlightComponentClassName() {
    return CustomHighlightComponent.class.getName();
  }

  protected String id_key = "id";
  protected String snippets_key = "snippets";

  private static String COLLECTION;

  @BeforeClass
  public static void setupCluster() throws Exception {

    // decide collection name ...
    COLLECTION = "collection"+(1+random().nextInt(100)) ;
    // ... and shard/replica/node numbers
    final int numShards = 3;
    final int numReplicas = 2;
    final int maxShardsPerNode = 2;
    final int nodeCount = (numShards*numReplicas + (maxShardsPerNode-1))/maxShardsPerNode;

    // create and configure cluster
    configureCluster(nodeCount)
        .addConfig("conf", configset("cloud-dynamic"))
        .configure();

    // create an empty collection
    CollectionAdminRequest
    .createCollection(COLLECTION, "conf", numShards, numReplicas)
    .setMaxShardsPerNode(maxShardsPerNode)
    .processAndWait(cluster.getSolrClient(), DEFAULT_TIMEOUT);
    AbstractDistribZkTestBase.waitForRecoveriesToFinish(COLLECTION, cluster.getSolrClient().getZkStateReader(), false, true, DEFAULT_TIMEOUT);
  }

  @Test
  // commented out on: 24-Dec-2018   @BadApple(bugUrl="https://issues.apache.org/jira/browse/SOLR-12028") // 14-Oct-2018
  public void test() throws Exception {

    // determine custom search handler name (the exact name should not matter)
    final String customSearchHandlerName = "/custom_select"+random().nextInt();

    final String defaultHighlightComponentName = HighlightComponent.COMPONENT_NAME;
    final String highlightComponentName;

    // add custom component (if needed) and handler
    {
      if (random().nextBoolean()) {
        // default component
        highlightComponentName = defaultHighlightComponentName;
      } else {
        // custom component
        highlightComponentName = "customhighlight"+random().nextInt();
        cluster.getSolrClient().request(
            new ConfigRequest(
                "{\n" +
                "  'add-searchcomponent': {\n" +
                "    'name': '"+highlightComponentName+"',\n" +
                "    'class': '"+customHighlightComponentClassName()+"'\n" +
                "  }\n" +
                "}"),
            COLLECTION);
      }
      // handler
      cluster.getSolrClient().request(
          new ConfigRequest(
              "{\n" +
              "  'add-requesthandler': {\n" +
              "    'name' : '"+customSearchHandlerName+"',\n" +
              "    'class' : 'org.apache.solr.handler.component.SearchHandler',\n" +
              "    'components' : [ '"+QueryComponent.COMPONENT_NAME+"', '"+highlightComponentName+"' ]\n" +
              "  }\n" +
              "}"),
          COLLECTION);
    }

    // add some documents
    final String id = "id";
    final String t1 = "a_t";
    final String t2 = "b_t";
    {
      new UpdateRequest()
          .add(sdoc(id, 1, t1, "bumble bee", t2, "bumble bee"))
          .add(sdoc(id, 2, t1, "honey bee", t2, "honey bee"))
          .add(sdoc(id, 3, t1, "solitary bee", t2, "solitary bee"))
          .commit(cluster.getSolrClient(), COLLECTION);
    }

    // search for the documents
    {
      // compose the query
      final SolrQuery solrQuery =  new SolrQuery(t1+":bee");
      solrQuery.setRequestHandler(customSearchHandlerName);
      solrQuery.setHighlight(true);
      final boolean t1Highlights = random().nextBoolean();
      if (t1Highlights) {
        solrQuery.addHighlightField(t1);
      }
      final boolean t2Highlights = random().nextBoolean();
      if (t2Highlights) {
        solrQuery.addHighlightField(t2);
      }

      // make the query
      final QueryResponse queryResponse = new QueryRequest(solrQuery)
          .process(cluster.getSolrClient(), COLLECTION);

      // analyse the response
      final Map<String, Map<String, List<String>>> highlighting = queryResponse.getHighlighting();
      final ArrayList<SimpleOrderedMap<Object>> custom_highlighting =
          (ArrayList<SimpleOrderedMap<Object>>)queryResponse.getResponse().get("custom_highlighting");

      if (defaultHighlightComponentName.equals(highlightComponentName)) {
        // regular 'highlighting' ...
        if (t1Highlights) {
          checkHighlightingResponseMap(highlighting, t1);
        }
        if (t2Highlights) {
          checkHighlightingResponseMap(highlighting, t2);
        }
        if (!t1Highlights && !t2Highlights) {
          checkHighlightingResponseMap(highlighting, null);
        }
        // ... and no 'custom_highlighting'
        assertNull(custom_highlighting);
      } else {
        // no regular 'highlighting' ...
        assertNull(highlighting);
        // ... but 'custom_highlighting'
        assertNotNull(custom_highlighting);
        if (t1Highlights) {
          checkHighlightingResponseList(custom_highlighting, t1);
        }
        if (t2Highlights) {
          checkHighlightingResponseList(custom_highlighting, t2);
        }
        if (!t1Highlights && !t2Highlights) {
          checkHighlightingResponseList(custom_highlighting, null);
        }
      }
    }
  }

  protected void checkHighlightingResponseMap(Map<String, Map<String, List<String>>> highlightingMap,
      String highlightedField) throws Exception {
    assertEquals("too few or too many keys: "+highlightingMap.keySet(),
        3, highlightingMap.size());
    checkHighlightingResponseMapElement(highlightingMap.get("1"), highlightedField, "bumble ", "bee");
    checkHighlightingResponseMapElement(highlightingMap.get("2"), highlightedField, "honey ", "bee");
    checkHighlightingResponseMapElement(highlightingMap.get("3"), highlightedField, "solitary ", "bee");
  }

  protected void checkHighlightingResponseMapElement(Map<String, List<String>> docHighlights,
      String highlightedField, String preHighlightText, String highlightedText) throws Exception {
    if (highlightedField == null) {
      assertEquals(0, docHighlights.size());
    } else {
      List<String> docHighlightsList = docHighlights.get(highlightedField);
      assertEquals(1, docHighlightsList.size());
      assertEquals(preHighlightText
          + SolrFragmentsBuilder.DEFAULT_PRE_TAGS
          + highlightedText
          + SolrFragmentsBuilder.DEFAULT_POST_TAGS, docHighlightsList.get(0));
    }
  }

  protected void checkHighlightingResponseList(ArrayList<SimpleOrderedMap<Object>> highlightingList,
      String highlightedField) throws Exception {
    assertEquals("too few or too many elements: "+highlightingList.size(),
        3, highlightingList.size());
    final Set<String> seenDocIds = new HashSet<>();
    for (SimpleOrderedMap<Object> highlightingListElementMap : highlightingList) {
      final String expectedHighlightText;
      final String actualHighlightText;
      // two elements in total: id and snippets
      assertEquals(highlightingList.toString(), 2, highlightingListElementMap.size());
      // id element
      {
        final String docId = (String)highlightingListElementMap.get(id_key);
        seenDocIds.add(docId);
        final String preHighlightText;
        final String highlightedText = "bee";
        if ("1".equals(docId)) {
          preHighlightText = "bumble ";
        } else if ("2".equals(docId)) {
          preHighlightText = "honey ";
        } else if ("3".equals(docId)) {
          preHighlightText = "solitary ";
        } else  {
          preHighlightText = null;
          fail("unknown docId "+docId);
        }
        expectedHighlightText = preHighlightText
            + SolrFragmentsBuilder.DEFAULT_PRE_TAGS
            + highlightedText
            + SolrFragmentsBuilder.DEFAULT_POST_TAGS;
      }
      // snippets element
      {
        SimpleOrderedMap<Object> snippets = (SimpleOrderedMap<Object>)highlightingListElementMap.get(snippets_key);
        if (highlightedField == null) {
          assertEquals(0, snippets.size());
        } else {
          ArrayList<String> docHighlights = (ArrayList<String>)(snippets).get(highlightedField);
          assertEquals(1, docHighlights.size());
          actualHighlightText = (String)docHighlights.get(0);
          assertEquals(expectedHighlightText, actualHighlightText);
        }
      }
    }
    assertEquals(3, seenDocIds.size());
  }

}
