//============================================================================
//  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.
//
//============================================================================

#include <fides/DataSetReader.h>

#include <string>
#include <unordered_map>
#include <vector>

#include <vtkm/Version.h>
#include <vtkm/cont/Algorithm.h>
#include <vtkm/cont/ArrayRangeCompute.h>

#ifdef FIDES_USE_MPI
#include <mpi.h>
#endif

int main(int argc, char** argv)
{
#ifdef FIDES_USE_MPI
  MPI_Init(&argc, &argv);
#endif

  if (argc != 3)
  {
    std::cerr << "Usage: " << argv[0] << "/path/to.json /path/to/dataroot\n";
    return 1;
  }

  int retVal = 0;
  // StreamSteps must be set to true when trying to read BP5 files in a streaming mode
  fides::io::DataSetReader reader(
    argv[1], fides::io::DataSetReader::DataModelInput::JSONFile, true /*stream steps*/);
  std::unordered_map<std::string, std::string> paths;
  paths["source"] = std::string(argv[2]) + "/composite-points.bp";

  // PrepareNextStep must be called before ReadMetaData otherwise ADIOS
  // cannot get any information at all with BP5.
  // Earlier BP versions you could get attributes or even information about
  // Arrays (e.g, dimensions of the variable) before calling BeginStep(),
  // but with BP5 you can't
  reader.PrepareNextStep(paths);
  auto metaData = reader.ReadMetaData(paths);
  auto& nBlocks = metaData.Get<fides::metadata::Size>(fides::keys::NUMBER_OF_BLOCKS());
  if (nBlocks.NumberOfItems != 1)
  {
    std::cerr << "Error: expected 1 block, got " << nBlocks.NumberOfItems << std::endl;
    retVal = 1;
  }
  using FieldInfoType = fides::metadata::Vector<fides::metadata::FieldInformation>;
  auto& fields = metaData.Get<FieldInfoType>(fides::keys::FIELDS());
  if (fields.Data.size() != 2)
  {
    std::cerr << "Error: expected 2 arrays, got " << fields.Data.size() << std::endl;
    retVal = 1;
  }

  fides::metadata::MetaData selections;
  FieldInfoType fieldSelection;
  fieldSelection.Data.push_back(
    fides::metadata::FieldInformation("point_data", vtkm::cont::Field::Association::Points));
  fieldSelection.Data.push_back(
    fides::metadata::FieldInformation("cell_data", vtkm::cont::Field::Association::Cells));
  selections.Set(fides::keys::FIELDS(), fieldSelection);

  vtkm::cont::PartitionedDataSet output = reader.ReadDataSet(paths, selections);
  if (output.GetNumberOfPartitions() != 1)
  {
    std::cerr << "Error: expected 1 output blocks, got " << output.GetNumberOfPartitions()
              << std::endl;
    retVal = 1;
  }
  vtkm::cont::DataSet ds = output.GetPartition(0);
  vtkm::IdComponent nFields = ds.GetNumberOfFields();

  // Three fields: two for the variables; another one for the coordinates.
  if (nFields != 3)
  {
    std::cerr << "Error: expected 3 output array, got " << nFields << std::endl;
    retVal = 1;
  }
  if (!ds.HasField("point_data", vtkm::cont::Field::Association::Points))
  {
    std::cerr << "Error: expected a point_data array. Did not get it." << std::endl;
    retVal = 1;
  }
  if (!ds.HasField("cell_data", vtkm::cont::Field::Association::Cells))
  {
    std::cerr << "Error: expected a cell_data array. Did not get it." << std::endl;
    retVal = 1;
  }

  const auto& pointField = ds.GetField("point_data");
  const auto& pointHandle = pointField.GetData().AsArrayHandle<vtkm::cont::ArrayHandle<double>>();
  vtkm::cont::ArrayHandle<vtkm::Range> rangeArray = vtkm::cont::ArrayRangeCompute(pointHandle);
  auto rangePortal = rangeArray.ReadPortal();
  if (rangePortal.Get(0).Min != 0.1)
  {
    std::cerr << "Unexpected point min. Got " << rangePortal.Get(0).Min << std::endl;
    retVal = 1;
  }
  if (rangePortal.Get(0).Max != 1)
  {
    std::cerr << "Unexpected point max. Got " << rangePortal.Get(0).Max << std::endl;
    retVal = 1;
  }

  const auto& cellField = ds.GetField("cell_data");
  const auto& cellHandle = cellField.GetData().AsArrayHandle<vtkm::cont::ArrayHandle<double>>();
  rangeArray = vtkm::cont::ArrayRangeCompute(cellHandle);
  rangePortal = rangeArray.ReadPortal();
  if (rangePortal.Get(0).Min != 0.1)
  {
    std::cerr << "Unexpected cell min. Got " << rangePortal.Get(0).Min << std::endl;
    retVal = 1;
  }
  if (rangePortal.Get(0).Max != 0.1)
  {
    std::cerr << "Unexpected cell max. Got " << rangePortal.Get(0).Max << std::endl;
    retVal = 1;
  }

#ifdef FIDES_USE_MPI
  MPI_Finalize();
#endif

  return retVal;
}
