/*
 * 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.search.facet;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.solr.common.SolrException;
import org.apache.solr.common.params.CommonParams;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.RequiredSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.StrUtils;
import org.apache.solr.search.QueryParsing;
import org.apache.solr.search.SolrReturnFields;
import org.apache.solr.search.StrParser;
import org.apache.solr.search.SyntaxError;

import static org.apache.solr.common.params.CommonParams.SORT;

public class LegacyFacet {
  private SolrParams params;
  private Map<String,Object> json;
  private Map<String,Object> currentCommand = null;  // always points to the current facet command
  private Map<String,Object> currentSubs; // always points to the current facet:{} block

  String facetValue;
  String key;
  SolrParams localParams;
  SolrParams orig;
  SolrParams required;

  Map<String, List<Subfacet>> subFacets;  // only parsed once

  public LegacyFacet(SolrParams params) {
    this.params = params;
    this.orig = params;
    this.json = new LinkedHashMap<>();
    this.currentSubs = json;
  }


  Map<String,Object> getLegacy() {
    subFacets = parseSubFacets(params);
    String[] queries = params.getParams(FacetParams.FACET_QUERY);
    if (queries != null) {
      for (String q : queries) {
        addQueryFacet(q);
      }
    }
    String[] fields = params.getParams(FacetParams.FACET_FIELD);
    if (fields != null) {
      for (String field : fields) {
        addFieldFacet(field);
      }
    }
    String[] ranges = params.getParams(FacetParams.FACET_RANGE);
    if (ranges != null) {
      for (String range : ranges) {
        addRangeFacet(range);
      }
    }
    // SolrCore.log.error("###################### JSON FACET:" + json);
    return json;
  }


  protected static class Subfacet {
    public String parentKey;
    public String type; // query, range, field
    public String value;  // the actual field or the query, including possible local params
  }


  protected static Map<String, List<Subfacet>> parseSubFacets(SolrParams params) {
    Map<String,List<Subfacet>> map = new HashMap<>();
    Iterator<String> iter = params.getParameterNamesIterator();

    String SUBFACET="subfacet.";
    while (iter.hasNext()) {
      String key = iter.next();

      if (key.startsWith(SUBFACET)) {
        List<String> parts = StrUtils.splitSmart(key, '.');
        if (parts.size() != 3) {
          throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, "expected subfacet parameter name of the form subfacet.mykey.field, got:" + key);
        }
        Subfacet sub = new Subfacet();
        sub.parentKey = parts.get(1);
        sub.type = parts.get(2);
        sub.value = params.get(key);

        List<Subfacet> subs = map.get(sub.parentKey);
        if (subs == null) {
          subs = new ArrayList<>(1);
        }
        subs.add(sub);
        map.put(sub.parentKey, subs);
      }
    }

    return map;
  }


  protected void addQueryFacet(String q) {
    parseParams(FacetParams.FACET_QUERY, q);
    Map<String,Object> cmd = new HashMap<String,Object>(2);
    Map<String,Object> type = new HashMap<String,Object>(1);
    type.put("query", cmd);
    cmd.put("q", q);
    addSub(key, type);
    handleSubs(cmd);
  }

  protected void addRangeFacet(String field)  {
    parseParams(FacetParams.FACET_RANGE, field);
    Map<String,Object> cmd = new HashMap<String,Object>(5);
    Map<String,Object> type = new HashMap<String,Object>(1);
    type.put("range", cmd);

    String f = key;
    cmd.put("field", facetValue);
    cmd.put("start", required.getFieldParam(f,FacetParams.FACET_RANGE_START));
    cmd.put("end", required.getFieldParam(f,FacetParams.FACET_RANGE_END));
    cmd.put("gap", required.getFieldParam(f, FacetParams.FACET_RANGE_GAP));
    String[] p = params.getFieldParams(f, FacetParams.FACET_RANGE_OTHER);
    if (p != null) cmd.put("other", p.length==1 ? p[0] : Arrays.asList(p));
    p = params.getFieldParams(f, FacetParams.FACET_RANGE_INCLUDE);
    if (p != null) cmd.put("include", p.length==1 ? p[0] : Arrays.asList(p));

    final int mincount = params.getFieldInt(f,FacetParams.FACET_MINCOUNT, 0);
    cmd.put("mincount", mincount);

    boolean hardend = params.getFieldBool(f,FacetParams.FACET_RANGE_HARD_END,false);
    if (hardend) cmd.put("hardend", hardend);

    addSub(key, type);
    handleSubs(cmd);
  }

  protected void addFieldFacet(String field) {
    parseParams(FacetParams.FACET_FIELD, field);

    String f = key;  // the parameter to use for per-field parameters... f.key.facet.limit=10

    int offset = params.getFieldInt(f, FacetParams.FACET_OFFSET, 0);
    int limit = params.getFieldInt(f, FacetParams.FACET_LIMIT, 10);

    int mincount = params.getFieldInt(f, FacetParams.FACET_MINCOUNT, 1);

    boolean missing = params.getFieldBool(f, FacetParams.FACET_MISSING, false);

    // default to sorting if there is a limit.
    String sort = params.getFieldParam(f, FacetParams.FACET_SORT, limit>0 ? FacetParams.FACET_SORT_COUNT : FacetParams.FACET_SORT_INDEX);
    String prefix = params.getFieldParam(f, FacetParams.FACET_PREFIX);

    Map<String,Object> cmd = new HashMap<>();
    cmd.put("field", facetValue);
    if (offset != 0) cmd.put("offset", offset);
    if (limit != 10) cmd.put("limit", limit);
    if (mincount != 1) cmd.put("mincount", mincount);
    if (missing) cmd.put("missing", missing);
    if (prefix != null) cmd.put("prefix", prefix);
    if (sort.equals("count")) {
      // our default
    } else if (sort.equals("index")) {
      cmd.put(SORT, "index asc");
    } else {
      cmd.put(SORT, sort);  // can be sort by one of our stats
    }

    Map<String,Object> type = new HashMap<>(1);
    type.put("terms", cmd);

    addSub(key, type);
    handleSubs(cmd);
  }

  private void handleSubs(Map<String,Object> cmd) {
    Map<String,Object> savedCmd = currentCommand;
    Map<String,Object> savedSubs = currentSubs;
   try {
     currentCommand = cmd;
     currentSubs = null;

     // parse stats for this facet
     String[] stats = params.getFieldParams(key, "facet.stat");
     if (stats != null) {
       for (String stat : stats) {
         addStat(stat);
       }
     }

     List<Subfacet> subs = subFacets.get(key);
     if (subs != null) {
       for (Subfacet subfacet : subs) {
         if ("field".equals(subfacet.type)) {
           addFieldFacet(subfacet.value);
         } else if ("query".equals(subfacet.type)) {
           addQueryFacet(subfacet.value);
         } else if ("range".equals(subfacet.type)) {
           addQueryFacet(subfacet.value);
         }
       }
     }


   } finally {
     currentCommand = savedCmd;
     currentSubs = savedSubs;
   }
  }


  private void addStat(String val) {
    StrParser sp = new StrParser(val);
    int start = 0;
    sp.eatws();
    if (sp.pos >= sp.end) addStat(val, val);

    // try key:func() format
    String key = null;
    String funcStr = val;

    if (key == null) {
      key = SolrReturnFields.getFieldName(sp);
      if (key != null && sp.opt(':')) {
        // OK, we got the key
        funcStr = val.substring(sp.pos);
      } else {
        // an invalid key... it must not be present.
        sp.pos = start;
        key = null;
      }
    }

    if (key == null) {
      key = funcStr;  // not really ideal
    }

    addStat(key, funcStr);
  }

  private void addStat(String key, String val) {
    if ("count".equals(val) || "count()".equals(val)) return;  // we no longer have a count function, we always return the count
    getCurrentSubs().put(key, val);
  }

  private void addSub(String key, Map<String,Object> sub) {
    getCurrentSubs().put(key, sub);
  }

  private Map<String,Object> getCurrentSubs() {
    if (currentSubs == null) {
      currentSubs = new LinkedHashMap();
      currentCommand.put("facet", currentSubs);
    }
    return currentSubs;
  }



  protected void parseParams(String type, String param)  {
    facetValue = param;
    key = param;

    try {
      localParams = QueryParsing.getLocalParams(param, orig);

      if (localParams == null) {
        params = orig;
        required = new RequiredSolrParams(params);
        // setupStats();
        return;
      }

      params = SolrParams.wrapDefaults(localParams, orig);
      required = new RequiredSolrParams(params);

      // remove local params unless it's a query
      if (type != FacetParams.FACET_QUERY) {
        facetValue = localParams.get(CommonParams.VALUE);
      }

      // reset set the default key now that localParams have been removed
      key = facetValue;

      // allow explicit set of the key
      key = localParams.get(CommonParams.OUTPUT_KEY, key);

      // setupStats();
    } catch (SyntaxError e) {
      throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, e);
    }
  }


}
