/*
 * 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.drill.exec.store;

import java.io.IOException;
import java.util.Map;
import java.util.Set;

import org.apache.drill.common.exceptions.ExecutionSetupException;
import org.apache.drill.common.exceptions.UserException;
import org.apache.drill.common.logical.FormatPluginConfig;
import org.apache.drill.common.logical.StoragePluginConfig;
import org.apache.drill.exec.store.dfs.FormatPlugin;

import com.fasterxml.jackson.databind.ObjectMapper;

public interface StoragePluginRegistry extends Iterable<Map.Entry<String, StoragePlugin>>, AutoCloseable {
  String PSTORE_NAME = "sys.storage_plugins";

  @SuppressWarnings("serial")
  public static class PluginException extends Exception {
    public PluginException(String msg) {
      super(msg);
    }

    public PluginException(String msg, Throwable e) {
      super(msg, e);
    }

    public static PluginException systemPluginException(String operation, String name) {
      return new PluginException(String.format(
          "Cannot %s a system plugin: `%s`", operation, name));
    }
  }

  /**
   * Indicates the requested plugin was not found.
   */
  @SuppressWarnings("serial")
  public static class PluginNotFoundException extends PluginException {
    public PluginNotFoundException(String name) {
      super("No storage plugin exists with name: `" + name + "`");
    }
  }

  /**
   * Indicates an error when decoding a plugin from JSON.
   */
  @SuppressWarnings("serial")
  public static class PluginEncodingException extends PluginException {
    public PluginEncodingException(String msg, Exception e) {
      super(msg, e);
    }
  }

  /**
   * Initialize the storage plugin registry. Must be called before the registry
   * is used.
   */
  void init();

  /**
   * Store a plugin by name and configuration. If the plugin already exists,
   * update the plugin. This form directly updates persistent storage. The
   * in-memory cache is updated on the next refresh. This form will accept an
   * invalid plugin, which will be disabled upon refresh. Since Drill is
   * distributed, and plugins work with external systems, the external system
   * can become invalid at any moment (not just when pugins are updated), so the
   * model used for {@code put()} mimics normal runtime operation.
   *
   * @param name The name of the plugin
   * @param config The plugin configuration
   * @return The StoragePlugin instance.
   * @throws PluginException if plugin cannot be created
   */
  void put(String name, StoragePluginConfig config) throws PluginException;

  /**
   * Like {@link #put(String, StoragePluginConfig)}, but forces instantiation of the
   * plugin to verify that the configuration is valid at this moment in time.
   */
  void validatedPut(String name, StoragePluginConfig config) throws PluginException;

  /**
   * Set the plugin to the requested enabled state. Does nothing if the plugin
   * is already in the requested state. If a formerly enabled plugin is
   * disabled, moves the plugin from the in-memory cache to the ephemeral
   * store. If a formerly disabled plugin is enabled, verifies that the plugin
   * can be instantiated as for {@link #verifiedPut()}.
   * <p>
   * Use this method when changing state. Do not obtain the config and change
   * the state directly, doing so will make the plugin config inconsistent
   * with the internal state.
   *
   * @param name name of the plugin
   * @param enabled {@code true} to enable the plugin, {@code false} to disable
   * @throws PluginNotFoundException if the plugin is not found
   * @throws PluginException if the plugin name is not valid or
   * if enabling a plugin and the plugin is not valid
   */
  void setEnabled(String name, boolean enabled) throws PluginException;

  /**
   * Get a plugin by name. Create it based on the PStore saved definition if it doesn't exist.
   *
   * @param name The name of the plugin
   * @return The StoragePlugin instance.
   * @throws PluginException if plugin cannot be obtained
   */
  StoragePlugin getPlugin(String name) throws PluginException, UserException;

  /**
   * Get a plugin by configuration. If it doesn't exist, create it.
   *
   * @param config The configuration for the plugin.
   * @return The StoragePlugin instance.
   * @throws PluginException if plugin cannot be obtained
   */
  StoragePlugin getPluginByConfig(StoragePluginConfig config) throws PluginException;

  /**
   * @deprecated use {@link #resolve(StoragePluginConfig, Class)} which provides
   * type safety. Retained for compatibility with older plugins
   */
  @Deprecated
  StoragePlugin getPlugin(StoragePluginConfig config) throws ExecutionSetupException;

  /**
   * Return a plugin from persistent storage. Returns both enabled and
   * disabled stored plugins, but does not return system plugins.
   * Use this to obtain a plugin for editing (rather than
   * for planning or executing a query.)
   */
  StoragePluginConfig getStoredConfig(String name);

  /**
   * Return a config encoded as JSON.
   * @throws PluginException if the plugin is undefined
   */
  String encode(String name) throws PluginException;
  String encode(StoragePluginConfig config);

  /**
   * Return a config decoded from JSON.
   * @throws PluginEncodingException if the JSON is invalid
   */
  StoragePluginConfig decode(String json) throws PluginEncodingException;

  /**
   * Put a storage plugin config from JSON. Validates the JSON and the
   * resulting storage plugin. Use this form for JSON received from
   * the UI or other external source.
   *
   * @throws IOException if the JSON is invalid
   * @throws PluginException if the underlying
   * {@link #validatedPut(String, StoragePluginConfig)} fails
   */
  void putJson(String name, String json) throws PluginException;

  /**
   * Copy a stored config so that it can be modified.
   * <p>
   * <i><b>Never modify a config stored in the registry!</b></i>
   * Configs are keyed by name and value; getting a config, then
   * modifying it, will cause the value maps to become out of sync.
   *
   * @param name name of the storage plugin config to copy
   * @return a copy of the config
   * @throws PluginException if the name is undefined
   */
  StoragePluginConfig copyConfig(String name) throws PluginException;

  /**
   * Copy the given storage plugin config so it may be modified.
   *
   * @param config the storage plugin config to copy
   * @return the copy
   */
  StoragePluginConfig copyConfig(StoragePluginConfig config);

  /**
   * Retrieve an available configuration. Returns enabled stored plugins
   * and system plugins. These configs are those that can be used to
   * plan a query.
   */
  StoragePluginConfig getDefinedConfig(String name);

  /**
   * Remove a plugin by name
   *
   * @param name The name of the storage plugin to remove
   * @throws PluginException
   */
  void remove(String name) throws PluginException;

  /**
   * Returns a set of all stored plugin configurations,
   * directly from the persistent store. Note: the actual
   * configs may reside in the cache; make a copy before
   * making any changes.
   * @return map of stored plugin configurations
   */
  Map<String, StoragePluginConfig> storedConfigs();

  enum PluginFilter { ALL, ENABLED, DISABLED, TRANSLATES_USERS };

  /**
   * Return a possibly-filtered set of plugins from the persistent
   * store.
   */
  Map<String, StoragePluginConfig> storedConfigs(PluginFilter filter);

  /**
   * Returns a copy of the set of enabled stored plugin configurations.
   * The registry is refreshed against the persistent store prior
   * to building the map.
   * @return map of enabled, stored plugin configurations
   */
  Map<String, StoragePluginConfig> enabledConfigs();

  /**
   * Returns the set of available plugin names.
   * Includes system plugins and enabled stored plugins.
   */
  Set<String> availablePlugins();

  /**
   * Safe way to add or remove a format plugin config from a stored file
   * system configuration. Makes a copy of the config, adds/removes the
   * format plugin, and updates the persistent store with the copy.
   *
   * @param pluginName name of the file system storage plugin config to
   * modify
   * @param formatName name of the format plugin to modify
   * @param formatConfig if null, removes the plugin, if non-null updates
   * the format plugin config with this value
   * @throws PluginException if the storage plugin is undefined or
   * is not a file format plugin
   */
  void putFormatPlugin(String pluginName, String formatName,
      FormatPluginConfig formatConfig) throws PluginException;

  /**
   * Get the Format plugin for the FileSystemPlugin associated with the provided
   * storage config and format config.
   *
   * @param storageConfig
   *          The storage config for the associated FileSystemPlugin
   * @param formatConfig
   *          The format config for the associated FormatPlugin
   * @return A FormatPlugin instance
   * @throws PluginException
   *           if plugin cannot be obtained
   */
  FormatPlugin getFormatPluginByConfig(StoragePluginConfig storageConfig,
      FormatPluginConfig formatConfig) throws PluginException;

  /**
   * @deprecated use
   * {@link #resolveFormat(StoragePluginConfig, FormatPluginConfig, Class)}
   * which provides type safety. Retained for compatibility with older plugins
   */
  @Deprecated
  FormatPlugin getFormatPlugin(StoragePluginConfig storageConfig,
      FormatPluginConfig formatConfig) throws ExecutionSetupException;

  /**
   * Get the Schema factory associated with this storage plugin registry.
   *
   * @return A SchemaFactory that can register the schemas associated with this plugin registry.
   */
  SchemaFactory getSchemaFactory();

  /**
   * Object mapper to read/write the JSON form of a plugin.
   * config.
   */
  ObjectMapper mapper();

  <T extends StoragePlugin> T resolve(
      StoragePluginConfig storageConfig, Class<T> desired);

  /**
   * Resolve a storage plugin given a storage plugin config. Call from
   * within a file storage plugin to resolve the plugin.
   *
   * @param <T> the required type of the plugin
   * @param storageConfig storage plugin config
   * @param formatConfig format plugin config
   * @param desired desired target class
   * @return the storage plugin
   * @throws IllegalStateException if the plugin is unknown or of the wrong
   * format - errors which should never occur in normal operation
   */
  <T extends FormatPlugin> T resolveFormat(
      StoragePluginConfig storageConfig,
      FormatPluginConfig formatConfig, Class<T> desired);
}
