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

#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;
  fides::io::DataSetReader reader(argv[1]);
  std::unordered_map<std::string, std::string> paths;
  paths["source"] = std::string(argv[2]) + "/tris-blocks-time.bp";

  auto metaData = reader.ReadMetaData(paths);
  fides::metadata::Vector<size_t> blockSelection;
  blockSelection.Data.push_back(1);
  fides::metadata::MetaData selections;
  selections.Set(fides::keys::BLOCK_SELECTION(), blockSelection);

  const auto nSteps =
    metaData.Get<fides::metadata::Size>(fides::keys::NUMBER_OF_STEPS()).NumberOfItems;
  for (size_t step = 0; step < nSteps; ++step)
  {
    std::cout << "\n=== Step " << step << '/' << nSteps << " ===" << std::endl;
    reader.PrepareNextStep(paths); // puts dataset reader into streaming mode.

    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;
    }

    const auto& ds = output.GetPartition(0);
    auto coords = ds.GetCoordinateSystem();
    auto coordsHandle =
      coords.GetData().AsArrayHandle<vtkm::cont::ArrayHandle<vtkm::Vec<float, 3>>>();
    // Don't verify range with ArrayRangeCompute, because that computes range with arrays from execution environment.
    // Instead, sync and compute range of array in control.
    coordsHandle.SyncControlArray();
    // Grab ownership of control memory.
    auto bufferInfo = coordsHandle.GetBuffers()[0].GetHostBufferInfo();
    auto transfer = bufferInfo.TransferOwnership();
    std::vector<float> xs, ys, zs;
    for (vtkm::BufferSizeType i = 0; i < transfer.Size / vtkm::BufferSizeType(sizeof(float));)
    {
      float x = reinterpret_cast<float*>(transfer.Memory)[i++];
      xs.emplace_back(x);
      float y = reinterpret_cast<float*>(transfer.Memory)[i++];
      ys.emplace_back(y);
      float z = reinterpret_cast<float*>(transfer.Memory)[i++];
      zs.emplace_back(z);
    }
    auto xminmax = std::minmax_element(xs.begin(), xs.end());
    auto yminmax = std::minmax_element(ys.begin(), ys.end());
    auto zminmax = std::minmax_element(zs.begin(), zs.end());
    // log the ranges
    for (auto& minmax : { xminmax, yminmax, zminmax })
    {
      std::cout << '[' << *minmax.first << ',' << *minmax.second << ']' << std::endl;
    }
    // verify range
    if (*xminmax.first > -0.487463 || *xminmax.first < -0.487465)
    {
      std::cerr << "Unexpected minimum for x, " << *xminmax.first << std::endl;
      retVal = 1;
    }
    if (*xminmax.second != 0.0)
    {
      std::cerr << "Unexpected maximum for x, " << *xminmax.second << std::endl;
      retVal = 1;
    }
    if (*yminmax.first > -4.26154e-08 || *yminmax.first < -4.26156e-08)
    {
      std::cerr << "Unexpected minimum for y, " << *yminmax.first << std::endl;
      retVal = 1;
    }
    if (*yminmax.second > 0.487464 || *yminmax.second < 0.487463)
    {
      std::cerr << "Unexpected maximum for y, " << *yminmax.second << std::endl;
      retVal = 1;
    }
    if (*zminmax.first != -0.5)
    {
      std::cerr << "Unexpected minimum for z, " << *zminmax.first << std::endl;
      retVal = 1;
    }
    if (*zminmax.second != 0.5)
    {
      std::cerr << "Unexpected maximum for z, " << *zminmax.second << std::endl;
      retVal = 1;
    }
    // Now try to delete control memory.
    // If the DataModel didn't cache correctly, i.e, returned an array handle which owns the data,
    // Two things happen:
    //  1. Since ownership was transferred above, we will be able to delete it here.
    //  2. All the range checks in subsequent steps will fail.
    transfer.Delete(transfer.Container);
  }

#ifdef FIDES_USE_MPI
  MPI_Finalize();
#endif

  return retVal;
}
