/*
 * 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.ltr.store.rest;

import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import org.apache.solr.common.SolrException;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.core.SolrCore;
import org.apache.solr.core.SolrResourceLoader;
import org.apache.solr.ltr.feature.Feature;
import org.apache.solr.ltr.store.FeatureStore;
import org.apache.solr.response.SolrQueryResponse;
import org.apache.solr.rest.BaseSolrResource;
import org.apache.solr.rest.ManagedResource;
import org.apache.solr.rest.ManagedResourceObserver;
import org.apache.solr.rest.ManagedResourceStorage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Managed resource for a storing a feature.
 */
public class ManagedFeatureStore extends ManagedResource implements ManagedResource.ChildResourceSupport {

  public static void registerManagedFeatureStore(SolrResourceLoader solrResourceLoader,
      ManagedResourceObserver managedResourceObserver) {
    solrResourceLoader.getManagedResourceRegistry().registerManagedResource(
        REST_END_POINT,
        ManagedFeatureStore.class,
        managedResourceObserver);
  }

  public static ManagedFeatureStore getManagedFeatureStore(SolrCore core) {
    return (ManagedFeatureStore) core.getRestManager()
        .getManagedResource(REST_END_POINT);
  }

  /** the feature store rest endpoint **/
  public static final String REST_END_POINT = "/schema/feature-store";

  /** name of the attribute containing the feature class **/
  static final String CLASS_KEY = "class";
  /** name of the attribute containing the feature name **/
  static final String NAME_KEY = "name";
  /** name of the attribute containing the feature params **/
  static final String PARAMS_KEY = "params";
  /** name of the attribute containing the feature store used **/
  static final String FEATURE_STORE_NAME_KEY = "store";

  private final Map<String,FeatureStore> stores = new HashMap<>();

  /**
   * Managed feature store: the name of the attribute containing all the feature
   * stores
   **/
  private static final String FEATURE_STORE_JSON_FIELD = "featureStores";

  /**
   * Managed feature store: the name of the attribute containing all the
   * features of a feature store
   **/
  private static final String FEATURES_JSON_FIELD = "features";

  private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());

  public ManagedFeatureStore(String resourceId, SolrResourceLoader loader,
      ManagedResourceStorage.StorageIO storageIO) throws SolrException {
    super(resourceId, loader, storageIO);

  }

  public synchronized FeatureStore getFeatureStore(String name) {
    if (name == null) {
      name = FeatureStore.DEFAULT_FEATURE_STORE_NAME;
    }
    if (!stores.containsKey(name)) {
      stores.put(name, new FeatureStore(name));
    }
    return stores.get(name);
  }

  @Override
  protected void onManagedDataLoadedFromStorage(NamedList<?> managedInitArgs,
      Object managedData) throws SolrException {

    stores.clear();
    log.info("------ managed feature ~ loading ------");
    if (managedData instanceof List) {
      @SuppressWarnings("unchecked")
      final List<Map<String,Object>> up = (List<Map<String,Object>>) managedData;
      for (final Map<String,Object> u : up) {
        final String featureStore = (String) u.get(FEATURE_STORE_NAME_KEY);
        addFeature(u, featureStore);
      }
    }
  }

  public synchronized void addFeature(Map<String,Object> map, String featureStore) {
    log.info("register feature based on {}", map);
    final FeatureStore fstore = getFeatureStore(featureStore);
    final Feature feature = fromFeatureMap(solrResourceLoader, map);
    fstore.add(feature);
  }

  @SuppressWarnings("unchecked")
  @Override
  public Object applyUpdatesToManagedData(Object updates) {
    if (updates instanceof List) {
      final List<Map<String,Object>> up = (List<Map<String,Object>>) updates;
      for (final Map<String,Object> u : up) {
        final String featureStore = (String) u.get(FEATURE_STORE_NAME_KEY);
        addFeature(u, featureStore);
      }
    }

    if (updates instanceof Map) {
      // a unique feature
      Map<String,Object> updatesMap = (Map<String,Object>) updates;
      final String featureStore = (String) updatesMap.get(FEATURE_STORE_NAME_KEY);
      addFeature(updatesMap, featureStore);
    }

    final List<Object> features = new ArrayList<>();
    for (final FeatureStore fs : stores.values()) {
      features.addAll(featuresAsManagedResources(fs));
    }
    return features;
  }

  @Override
  public synchronized void doDeleteChild(BaseSolrResource endpoint, String childId) {
    if (stores.containsKey(childId)) {
      stores.remove(childId);
    }
    storeManagedData(applyUpdatesToManagedData(null));
  }

  /**
   * Called to retrieve a named part (the given childId) of the resource at the
   * given endpoint. Note: since we have a unique child feature store we ignore
   * the childId.
   */
  @Override
  public void doGet(BaseSolrResource endpoint, String childId) {
    final SolrQueryResponse response = endpoint.getSolrResponse();

    // If no feature store specified, show all the feature stores available
    if (childId == null) {
      response.add(FEATURE_STORE_JSON_FIELD, stores.keySet());
    } else {
      final FeatureStore store = getFeatureStore(childId);
      if (store == null) {
        throw new SolrException(SolrException.ErrorCode.BAD_REQUEST,
            "missing feature store [" + childId + "]");
      }
      response.add(FEATURES_JSON_FIELD,
          featuresAsManagedResources(store));
    }
  }

  private static List<Object> featuresAsManagedResources(FeatureStore store) {
    final List<Feature> storedFeatures = store.getFeatures();
    final List<Object> features = new ArrayList<Object>(storedFeatures.size());
    for (final Feature f : storedFeatures) {
      final LinkedHashMap<String,Object> m = toFeatureMap(f);
      m.put(FEATURE_STORE_NAME_KEY, store.getName());
      features.add(m);
    }
    return features;
  }

  private static LinkedHashMap<String,Object> toFeatureMap(Feature feat) {
    final LinkedHashMap<String,Object> o = new LinkedHashMap<>(4, 1.0f); // 1 extra for caller to add store
    o.put(NAME_KEY, feat.getName());
    o.put(CLASS_KEY, feat.getClass().getName());
    o.put(PARAMS_KEY, feat.paramsToMap());
    return o;
  }

  private static Feature fromFeatureMap(SolrResourceLoader solrResourceLoader,
      Map<String,Object> featureMap) {
    final String className = (String) featureMap.get(CLASS_KEY);

    final String name = (String) featureMap.get(NAME_KEY);

    @SuppressWarnings("unchecked")
    final Map<String,Object> params = (Map<String,Object>) featureMap.get(PARAMS_KEY);

    return Feature.getInstance(solrResourceLoader, className, name, params);
  }
}
