/******************************************************************************

  This source file is part of the Avogadro project.

  Copyright 2013 Kitware, Inc.

  This source code is released under the New BSD License, (the "License").

  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.

******************************************************************************/

#ifndef AVOGADRO_IO_FILEFORMATMANAGER_H
#define AVOGADRO_IO_FILEFORMATMANAGER_H

#include "avogadroioexport.h"

#include "fileformat.h" // For FileFormat::Operation enum.

#include <map>
#include <string>
#include <vector>

namespace Avogadro {
namespace Core {
class Molecule;
}
namespace Io {

/**
 * @class FileFormatManager fileformatmanager.h
 * <avogadro/io/fileformatmanager.h>
 * @brief Class to manage registration, searching and creation of file formats.
 * @author Marcus D. Hanwell
 *
 * The file format manager is a singleton class that handles the runtime
 * registration, search, creation and eventual destruction of file formats. It
 * can be used to gain a listing of available formats, register new formats and
 * retrieve the correct format to facilitate file IO.
 *
 * All files IO can take place independent of this manager, but for automated
 * registration and look up this is the preferred API. It is possible to use
 * the convenience API without ever dealing directly with a format class.
 */

class AVOGADROIO_EXPORT FileFormatManager
{
public:
  /**
   * Get the singleton instance of the file format manager. This instance should
   * not be deleted.
   */
  static FileFormatManager& instance();

  /**
   * Load @p molecule with the @p fileName contents supplied, inferring the
   * @p fileExtension if it is empty. The @p options can be used to modify
   * the behavior of the file format.
   * @return True on success, false on failure.
   */
  bool readFile(Core::Molecule& molecule, const std::string& fileName,
                const std::string& fileExtension = std::string(),
                const std::string& options = std::string()) const;

  /**
   * Write @p molecule to the @p fileName supplied, inferring the
   * @p fileExtension if it is empty. The @p options can be used to modify
   * the behavior of the file format.
   * @return True on success, false on failure.
   */
  bool writeFile(const Core::Molecule& molecule, const std::string& fileName,
                 const std::string& fileExtension = std::string(),
                 const std::string& options = std::string()) const;

  /**
   * Load @p molecule with the contents of @p string, using the supplied
   * @p fileExtension to determine the format. The @p options can be used to
   * modify the behavior of the file format.
   * @return True on success, false on failure.
   */
  bool readString(Core::Molecule& molecule, const std::string& string,
                  const std::string& fileExtension,
                  const std::string& options = std::string()) const;

  /**
   * Write @p molecule to the @p string, using the supplied @p fileExtension
   * to determine the format. The @p options can be used to modify the behavior
   * of the file format.
   * @return True on success, false on failure.
   */
  bool writeString(const Core::Molecule& molecule, std::string& string,
                   const std::string& fileExtension,
                   const std::string& options = std::string()) const;

  /**
   * @brief Register a new file format with the format manager.
   * @param format An instance of the format to manage, the manager assumes
   * ownership of the object passed in.
   * @return True on success, false on failure.
   */
  static bool registerFormat(FileFormat* format);

  /**
   * @brief Unregister a file format from the format manager.
   * @param identifier The identifier for the format to remove.
   * @return True on success, false on failure.
   */
  static bool unregisterFormat(const std::string& identifier);

  /**
   * Add the supplied @p format to the manager, registering its ID, MIME type,
   * file extension and other relevant data for later lookup. The manager
   * assumes ownership of the supplied object.
   * @return True on success, false on failure.
   */
  bool addFormat(FileFormat* format);

  /**
   * Remove the format with the identifier @a identifier from the manager.
   * @return True on success, false on failure.
   */
  bool removeFormat(const std::string& identifier);

  /**
   * New instance of the format for the specified @p identifier. Ownership
   * is passed to the caller.
   * @param identifier The unique identifier of the format.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @return Instance of the format, nullptr if not found. Ownership passes to
   * the
   * caller.
   */
  FileFormat* newFormatFromIdentifier(
    const std::string& identifier,
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * New instance of the format for the specified @p mimeType. Ownership
   * is passed to the caller.
   * @param mimeType The MIME type (in lower case).
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @return Instance of the format, nullptr if not found. Ownership passes to
   * the
   * caller.
   */
  FileFormat* newFormatFromMimeType(
    const std::string& mimeType,
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * New instance of the format for the specified file @p extension. Ownership
   * is passed to the caller.
   * @param extension The file extension (in lower case).
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @return Instance of the format, nullptr if not found. Ownership passes to
   * the
   * caller.
   */
  FileFormat* newFormatFromFileExtension(
    const std::string& extension,
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of all loaded identifiers, optionally matching the specified
   * filter.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<std::string> identifiers(
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of all loaded MIME types, optionally matching the specified
   * filter.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<std::string> mimeTypes(
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of the file extensions supported, optionally matching the
   * specified filter.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<std::string> fileExtensions(
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of known FileFormat objects, optionally matching the
   * specified filter.
   * @warning The objects in the returned list are owned by the
   * FileFormatManager and cannot be modified. Use FileFormat::newInstance()
   * to create mutable copies.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<const FileFormat*> fileFormats(
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of known FileFormat objects that handle the specified MIME type,
   * optionally matching a filter.
   * @warning The objects in the returned list are owned by the
   * FileFormatManager and cannot be modified. Use FileFormat::newInstance()
   * to create mutable copies.
   * @param mimeType MIME type.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<const FileFormat*> fileFormatsFromMimeType(
    const std::string& mimeType,
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get a list of known FileFormat objects that handle the specified file
   * extension, optionally matching a filter.
   * @warning The objects in the returned list are owned by the
   * FileFormatManager and cannot be modified. Use FileFormat::newInstance()
   * to create mutable copies.
   * @param extension File extension.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   */
  std::vector<const FileFormat*> fileFormatsFromFileExtension(
    const std::string& extension,
    FileFormat::Operations filter = FileFormat::None) const;

  /**
   * Get any errors that have been logged when loading formats.
   */
  std::string error() const;

private:
  typedef std::vector<size_t> FormatIdVector;
  typedef std::map<std::string, FormatIdVector> FormatIdMap;

  FileFormatManager();
  ~FileFormatManager();

  FileFormatManager(const FileFormatManager&);            // Not implemented.
  FileFormatManager& operator=(const FileFormatManager&); // Not implemented.

  /**
   * @brief Return keys from a map that have formats matching the supplied
   * operation filter.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @param fmap The FormatMap to operate on.
   */
  std::vector<std::string> filteredKeysFromFormatMap(
    FileFormat::Operations filter, const FormatIdMap& fmap) const;

  /**
   * @brief Return formats from a map that match the supplied key and operation
   * filter.
   * @note Ownership of the format filter(s) remains with the FileFormatManager.
   * Use FileFormat::newInstance to clone each format before use.
   * @param key The map key.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @param fmap The FormatIdMap to operate on.
   */
  std::vector<FileFormat*> filteredFormatsFromFormatMap(
    const std::string& key, FileFormat::Operations filter,
    const FormatIdMap& fmap) const;

  /**
   * @brief Return a format from a map that matches the supplied key and
   * operation filter.
   * @note Ownership of the format filter(s) remains with the FileFormatManager.
   * Use FileFormat::newInstance to clone each format before use.
   * @param key The map key.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @param fmap The FormatIdMap to operate on.
   */
  FileFormat* filteredFormatFromFormatMap(const std::string& key,
                                          FileFormat::Operations filter,
                                          const FormatIdMap& fmap) const;

  /**
   * @brief Return formats from a vector that match the supplied operation
   * filter.
   * @note Ownership of the format filter(s) remains with the FileFormatManager.
   * Use FileFormat::newInstance to clone each format before use.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @param fvec The FormatIdVector to operate on.
   */
  std::vector<FileFormat*> filteredFormatsFromFormatVector(
    FileFormat::Operations filter, const FormatIdVector& fvec) const;

  /**
   * @brief Return the first format from a vector that matches the supplied
   * operation filter.
   * @note Ownership of the format filter(s) remains with the FileFormatManager.
   * Use FileFormat::newInstance to clone each format before use.
   * @param filter Bitwise combination of FileFormat::Operation values that
   * represents the minimum required capabilities.
   * @param fmap The FormatIdVector to operate on.
   */
  FileFormat* filteredFormatFromFormatVector(FileFormat::Operations filter,
                                             const FormatIdVector& fvec) const;

  /**
   * @brief Append warnings/errors to the error message string.
   * @param errorMessage The error message to append.
   */
  void appendError(const std::string& errorMessage);

  std::vector<FileFormat*> m_formats;

  FormatIdMap m_identifiers;
  FormatIdMap m_mimeTypes;
  FormatIdMap m_fileExtensions;

  std::string m_error;
};

} // end Io namespace
} // end Avogadro namespace

#endif // AVOGADRO_IO_FILEFORMATMANAGER_H
