//============================================================================
//  Copyright (c) Kitware, Inc.
//  All rights reserved.
//  See LICENSE.txt for details.
//  This software is distributed WITHOUT ANY WARRANTY; without even
//  the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
//  PURPOSE.  See the above copyright notice for more information.
//
//============================================================================

#ifndef fides_io_DataSetReader_h
#define fides_io_DataSetReader_h

#include <fides/FidesTypes.h>
#include <fides/FieldDataManager.h>
#include <fides/MetaData.h>

#include <viskores/cont/DataSet.h>
#include <viskores/cont/PartitionedDataSet.h>

#include <memory>
#include <string>
#include <unordered_map>

#include "fides_export.h"

namespace fides
{
namespace io
{

/// \brief General purpose reader for data described by an Fides data model.
///
/// \c fides::io::DataSetReader reads data described by an Fides data model
/// and creates Viskores datasets. See the Fides schema definition for the
/// supported data model. \c DataSetReader also supports reading meta-data.
///
class FIDES_EXPORT DataSetReader
{
public:
  /*! Input types when setting up the DataSetReader */
  enum class DataModelInput
  {
    JSONFile,   /*!< Brief input is path to a JSON file with the data model */
    JSONString, /*!< Brief input is JSON containing the data model stored in a string */
    BPFile      /*!< Brief input is a BP file that contains attributes that provide details
                      for the predefined data model to be generated by Fides */
  };

  DataSetReader();

  /// Constructor to set up the Fides reader
  /// \param dataModel the value should be 1) a path
  /// to a JSON file describing the data model to be used by the reader,
  /// 2) a string containing valid JSON, 3) a path to a BP file containing attributes
  /// that Fides can use to generate a data model, or 4) a path to a BP file that
  /// contains a fides/schema attribute that contains the full JSON for the data model.
  /// \param inputType specifies what is stored in the dataModel arg. Optional
  /// \param params a map of ADIOS engine parameters to be
  /// used for each data source. Optional
  /// \sa DataModelInput
  DataSetReader(const std::string& dataModel,
                DataModelInput inputType = DataModelInput::JSONFile,
                const Params& params = Params(),
                bool createSharedPoints = false);

  DataSetReader(const std::string& dataModel,
                DataModelInput inputType,
                bool streamSteps,
                const Params& params = Params(),
                bool createSharedPoints = false);

  ~DataSetReader();

  /// Checks a bp file for an attribute containing information that Fides can
  /// use to generate the data model. Static so that it doesn't require setting
  /// up the DataSetReader first. Useful for applications like ParaView, where
  /// it wants to check if it can use Fides to read a file without needing to
  /// configure Fides first.
  /// \param filename Name of file to check
  /// \param attrName Name of attribute to look for
  static bool CheckForDataModelAttribute(const std::string& filename,
                                         const std::string& attrName = "Fides_Data_Model");

  /// Sets the parameters for a given data source.
  /// Currently, only the inline engine requires this to be called, which
  /// must happen before attempting to read data.
  /// \param source name of the \c DataSource, which should match a data_sources
  /// name given in the data model JSON.
  /// \param params a map of parameters and their values
  void SetDataSourceParameters(const std::string& source, const DataSourceParams& params);

  /// Set the IO for a given \c source. This call should only be used when
  /// using the inline engine and must be called before attempting to read data or metadata.
  /// \param source name of the \c DataSource, which should match a data_sources
  /// name given in the data model JSON.
  /// \param io pointer to the ADIOS IO object
  void SetDataSourceIO(const std::string& source, void* io);

  /// Set the IO for a given \c source. This call should only be used when
  /// using the inline engine and must be called before attempting to read data or metadata.
  /// \param source name of the \c DataSource, which should match a data_sources
  /// name given in the data model JSON.
  /// \param io the address to an ADIOS IO object, stored in a string
  void SetDataSourceIO(const std::string& source, const std::string& io);

  /// Read and return meta-data. This includes information such as the
  /// number of blocks, available fields etc.
  /// \param paths a map that provides
  /// the paths (filenames usually) corresponding to each data source.
  /// \param groupName looks for metadata on variables inside the given group.
  fides::metadata::MetaData ReadMetaData(
    const std::unordered_map<std::string, std::string>& paths = {},
    const std::string& groupName = "");

  /// Read and return heavy-data.
  /// \param paths a map that provides
  /// the paths (filenames usually) corresponding to each data source.
  /// \param selections provides support for reading a subset of
  /// the data by providing choices for things such as time and blocks.
  viskores::cont::PartitionedDataSet ReadDataSet(
    const std::unordered_map<std::string, std::string>& paths,
    const fides::metadata::MetaData& selections);

  /// Read and return heavy-data.
  /// \param selections provides support for reading a subset of
  /// the data by providing choices for things such as time and blocks.
  viskores::cont::PartitionedDataSet ReadDataSet(const fides::metadata::MetaData& selections);

  /// When reading in streaming mode, this method has to be called before
  /// reading any meta-data or heavy data. It will also move the reader
  /// to the next step. Fides will loop on a data source while ADIOS
  /// reports that it is NotReady, but the user should also check the return
  /// which could return fides::StepStatus::OK or fides::StepStatus::EndOfStream.
  /// If EndOfStream, all steps have been read.
  /// \param paths a map that provides
  /// the paths (filenames usually) corresponding to each data source.
  StepStatus PrepareNextStep(const std::unordered_map<std::string, std::string>& paths = {});

  /// Same as \c ReadDataSet except that it works in streaming mode and
  /// needs to be preceeded by PrepareStep.
  /// \param paths a map that provides
  /// the paths (filenames usually) corresponding to each data source.
  /// \param selections provides support for reading a subset of
  /// the data by providing choices for things such as time and blocks.
  FIDES_DEPRECATED_SUPPRESS_BEGIN
  FIDES_DEPRECATED(1.2, "ReadDataSet() will now handle both streaming and random access modes.")
  viskores::cont::PartitionedDataSet ReadStep(
    const std::unordered_map<std::string, std::string>& paths,
    const fides::metadata::MetaData& selections);
  FIDES_DEPRECATED_SUPPRESS_END

  /// Get a pointer to the field data manager
  /// \sa FieldDataManager, FieldData
  FIDES_DEPRECATED_SUPPRESS_BEGIN
  FIDES_DEPRECATED(1.1, "FieldData is no longer used. All data is stored in Viskores DataSet.")
  std::shared_ptr<fides::datamodel::FieldDataManager> GetFieldData();
  FIDES_DEPRECATED_SUPPRESS_END

  /// Get std::vector of DataSource names.
  std::vector<std::string> GetDataSourceNames();
  /// Get all available group names.
  std::set<std::string> GetGroupNames(const std::unordered_map<std::string, std::string>& paths);

private:
  class DataSetReaderImpl;
  std::unique_ptr<DataSetReaderImpl> Impl;

  std::vector<viskores::cont::DataSet> ReadDataSetInternal(
    const std::unordered_map<std::string, std::string>& paths,
    const fides::metadata::MetaData& selections);
};

} // end namespace io
} // end namespace fides

#endif // fides_io_DataSetReader_h
