/*
 * 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.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;

import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.facet.FacetHeatmap;
import org.apache.solr.search.facet.FacetMerger;
import org.apache.solr.search.facet.FacetRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/** A 2D spatial faceting summary of a rectangular region. Used by {@link org.apache.solr.handler.component.FacetComponent}
 * and {@link org.apache.solr.request.SimpleFacets}.
 * @see FacetHeatmap
 */
public class SpatialHeatmapFacets {
  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  //underneath facet_counts we put this here:
  public static final String RESPONSE_KEY = "facet_heatmaps";

  /** Called by {@link org.apache.solr.request.SimpleFacets} to compute heatmap facets. */
  public static NamedList<Object> getHeatmapForField(String fieldKey, String fieldName, ResponseBuilder rb, SolrParams params, DocSet docSet) throws IOException {
    final FacetRequest facetRequest = createHeatmapRequest(fieldKey, fieldName, rb, params);
    return (NamedList) facetRequest.process(rb.req, docSet);
  }

  private static FacetRequest createHeatmapRequest(String fieldKey, String fieldName, ResponseBuilder rb, SolrParams params) {
    Map<String, Object> jsonFacet = new HashMap<>();
    jsonFacet.put("type", "heatmap");
    jsonFacet.put("field", fieldName);
    // jsonFacets has typed values, unlike SolrParams which is all string
    jsonFacet.put(FacetHeatmap.GEOM_PARAM, params.getFieldParam(fieldKey, FacetParams.FACET_HEATMAP_GEOM));
    jsonFacet.put(FacetHeatmap.LEVEL_PARAM, params.getFieldInt(fieldKey, FacetParams.FACET_HEATMAP_LEVEL));
    jsonFacet.put(FacetHeatmap.DIST_ERR_PCT_PARAM, params.getFieldDouble(fieldKey, FacetParams.FACET_HEATMAP_DIST_ERR_PCT));
    jsonFacet.put(FacetHeatmap.DIST_ERR_PARAM, params.getFieldDouble(fieldKey, FacetParams.FACET_HEATMAP_DIST_ERR));
    jsonFacet.put(FacetHeatmap.MAX_CELLS_PARAM, params.getFieldInt(fieldKey, FacetParams.FACET_HEATMAP_MAX_CELLS));
    jsonFacet.put(FacetHeatmap.FORMAT_PARAM, params.getFieldParam(fieldKey, FacetParams.FACET_HEATMAP_FORMAT));

    return FacetRequest.parseOneFacetReq(rb.req, jsonFacet);
  }

  //
  // Distributed Support
  //

  /** Parses request to "HeatmapFacet" instances. */
  public static LinkedHashMap<String,HeatmapFacet> distribParse(SolrParams params, ResponseBuilder rb) {
    final LinkedHashMap<String, HeatmapFacet> heatmapFacets = new LinkedHashMap<>();
    final String[] heatmapFields = params.getParams(FacetParams.FACET_HEATMAP);
    if (heatmapFields != null) {
      for (String heatmapField : heatmapFields) {
        HeatmapFacet facet = new HeatmapFacet(rb, heatmapField);
        heatmapFacets.put(facet.getKey(), facet);
      }
    }
    return heatmapFacets;
  }

  /** Called by FacetComponent's impl of
   * {@link org.apache.solr.handler.component.SearchComponent#modifyRequest(ResponseBuilder, SearchComponent, ShardRequest)}. */
  public static void distribModifyRequest(ShardRequest sreq, LinkedHashMap<String, HeatmapFacet> heatmapFacets) {
    // Set the format to PNG because it's compressed and it's the only format we have code to read at the moment.
    // We re-write the facet.heatmap list with PNG format in local-params where it has highest precedence.

    //Remove existing heatmap field param vals; we will rewrite
    sreq.params.remove(FacetParams.FACET_HEATMAP);
    for (HeatmapFacet facet : heatmapFacets.values()) {
      //add heatmap field param
      ModifiableSolrParams newLocalParams = new ModifiableSolrParams();
      if (facet.localParams != null) {
        newLocalParams.add(facet.localParams);
      }
      // Set format to PNG; it's the only one we parse
      newLocalParams.set(FacetParams.FACET_HEATMAP_FORMAT, FacetHeatmap.FORMAT_PNG);
      sreq.params.add(FacetParams.FACET_HEATMAP,
          newLocalParams.toLocalParamsString() + facet.facetOn);
    }
  }

  /** Called by FacetComponent.countFacets which is in turn called by FC's impl of
   * {@link org.apache.solr.handler.component.SearchComponent#handleResponses(ResponseBuilder, ShardRequest)}. */
  @SuppressWarnings("unchecked")
  public static void distribHandleResponse(LinkedHashMap<String, HeatmapFacet> heatmapFacets, NamedList srsp_facet_counts) {
    NamedList<NamedList<Object>> facet_heatmaps = (NamedList<NamedList<Object>>) srsp_facet_counts.get(RESPONSE_KEY);
    if (facet_heatmaps == null) {
      return;
    }
    // (should the caller handle the above logic?  Arguably yes.)
    for (Map.Entry<String, NamedList<Object>> entry : facet_heatmaps) {
      String fieldKey = entry.getKey();
      NamedList<Object> shardNamedList = entry.getValue();
      final HeatmapFacet facet = heatmapFacets.get(fieldKey);
      if (facet == null) {
        log.error("received heatmap for field/key {} that we weren't expecting", fieldKey);
        continue;
      }
      facet.jsonFacetMerger.merge(shardNamedList, null);//merge context not needed (null)
    }
  }


  /** Called by FacetComponent's impl of
   * {@link org.apache.solr.handler.component.SearchComponent#finishStage(ResponseBuilder)}. */
  public static NamedList distribFinish(LinkedHashMap<String, HeatmapFacet> heatmapInfos, ResponseBuilder rb) {
    NamedList<NamedList<Object>> result = new SimpleOrderedMap<>();
    for (Map.Entry<String, HeatmapFacet> entry : heatmapInfos.entrySet()) {
      final HeatmapFacet facet = entry.getValue();
      result.add(entry.getKey(), (NamedList<Object>) facet.jsonFacetMerger.getMergedResult());
    }
    return result;
  }

  /** Goes in {@link org.apache.solr.handler.component.FacetComponent.FacetInfo#heatmapFacets}, created by
   * {@link #distribParse(org.apache.solr.common.params.SolrParams, ResponseBuilder)}. */
  public static class HeatmapFacet extends FacetComponent.FacetBase {
    //note: 'public' following-suit with FacetBase & existing subclasses... though should this really be?

    public FacetMerger jsonFacetMerger;

    public HeatmapFacet(ResponseBuilder rb, String facetStr) {
      super(rb, FacetParams.FACET_HEATMAP, facetStr);
      //note: logic in super (FacetBase) is partially redundant with SimpleFacet.parseParams :-(
      final SolrParams params = SolrParams.wrapDefaults(localParams, rb.req.getParams());
      final FacetRequest heatmapRequest = createHeatmapRequest(getKey(), facetOn, rb, params);
      jsonFacetMerger = heatmapRequest.createFacetMerger(null);
    }
  }

  // Note: originally there was a lot more code here but it migrated to the JSON Facet API as "FacetHeatmap"

}
