//============================================================================
//  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 <vtkm/cont/MergePartitionedDataSet.h>

#include <vtkm/cont/ArrayCopy.h>
#include <vtkm/cont/ArrayHandleView.h>
#include <vtkm/cont/CoordinateSystem.h>
#include <vtkm/cont/DataSet.h>
#include <vtkm/cont/PartitionedDataSet.h>
#include <vtkm/worklet/CellDeepCopy.h>

namespace vtkm
{
namespace cont
{

struct TransferCellsFunctor
{
  template <typename T>
  VTKM_CONT void operator()(const T& cellSetIn,
                            vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
                            vtkm::cont::ArrayHandle<vtkm::Id>& numIndices,
                            vtkm::cont::ArrayHandle<vtkm::Id>& connectivity,
                            vtkm::Id pointStartIndex) const
  {
    // allocate shapes and numIndices
    vtkm::Id cellStartIndex = shapes.GetNumberOfValues();
    shapes.Allocate(cellStartIndex + cellSetIn.GetNumberOfCells(), vtkm::CopyFlag::On);
    numIndices.Allocate(cellStartIndex + cellSetIn.GetNumberOfCells(), vtkm::CopyFlag::On);

    // fill the view of numIndices
    vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::Id>> viewArrayNumIndices(
      numIndices, cellStartIndex, cellSetIn.GetNumberOfCells());
    vtkm::cont::Invoker invoke;
    invoke(vtkm::worklet::CellDeepCopy::CountCellPoints{}, cellSetIn, viewArrayNumIndices);

    // convert numIndices to offsets and derive numberOfConnectivity
    vtkm::cont::ArrayHandle<vtkm::Id> offsets;
    vtkm::Id numberOfConnectivity;
    vtkm::cont::ConvertNumComponentsToOffsets(viewArrayNumIndices, offsets, numberOfConnectivity);

    // allocate connectivity
    vtkm::Id connectivityStartIndex = connectivity.GetNumberOfValues();
    connectivity.Allocate(connectivityStartIndex + numberOfConnectivity, vtkm::CopyFlag::On);

    // fill the view of shapes and connectivity
    vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::UInt8>> viewArrayShapes(
      shapes, cellStartIndex, cellSetIn.GetNumberOfCells());
    vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<vtkm::Id>> viewArrayConnectivity(
      connectivity, connectivityStartIndex, numberOfConnectivity);
    invoke(vtkm::worklet::CellDeepCopy::PassCellStructure{},
           cellSetIn,
           viewArrayShapes,
           vtkm::cont::make_ArrayHandleGroupVecVariable(viewArrayConnectivity, offsets));
    shapes.ReleaseResourcesExecution();
    offsets.ReleaseResourcesExecution();
    connectivity.ReleaseResourcesExecution();

    // point the connectivity to the point indices of this partition
    vtkm::cont::Algorithm::Transform(
      vtkm::cont::ArrayHandleConstant<vtkm::Id>(pointStartIndex, numberOfConnectivity),
      viewArrayConnectivity,
      viewArrayConnectivity,
      vtkm::Sum());
  }
};

void TransferCells(const vtkm::cont::DynamicCellSet& cellSetIn,
                   vtkm::cont::ArrayHandle<vtkm::UInt8>& shapes,
                   vtkm::cont::ArrayHandle<vtkm::Id>& numIndices,
                   vtkm::cont::ArrayHandle<vtkm::Id>& connectivity,
                   vtkm::Id startIndex)
{
  cellSetIn.CastAndCall(TransferCellsFunctor{}, shapes, numIndices, connectivity, startIndex);
}

struct TransferArrayFunctor
{
  template <typename T, typename S>
  VTKM_CONT void operator()(const vtkm::cont::ArrayHandle<T, S>& arrayIn,
                            vtkm::cont::UnknownArrayHandle& arrayOut,
                            vtkm::Id startIndex) const
  {
    vtkm::cont::ArrayHandleView<vtkm::cont::ArrayHandle<T>> viewArrayOut(
      arrayOut.AsArrayHandle<vtkm::cont::ArrayHandle<T>>(),
      startIndex,
      arrayIn.GetNumberOfValues());
    vtkm::cont::ArrayCopy(arrayIn, viewArrayOut);
  }
};

void TransferArray(const vtkm::cont::UnknownArrayHandle& arrayIn,
                   vtkm::cont::UnknownArrayHandle& arrayOut,
                   vtkm::Id startIndex)
{
  arrayIn.CastAndCallForTypes<
    VTKM_DEFAULT_TYPE_LIST,
    vtkm::List<vtkm::cont::StorageTagBasic, vtkm::cont::StorageTagUniformPoints>>(
    TransferArrayFunctor{}, arrayOut, startIndex);
}

//-----------------------------------------------------------------------------
VTKM_CONT
vtkm::cont::DataSet MergePartitionedDataSet(
  const vtkm::cont::PartitionedDataSet& partitionedDataSet)
{
  // verify correctnees of data
  VTKM_ASSERT(partitionedDataSet.GetNumberOfPartitions() > 0);

  vtkm::cont::UnknownArrayHandle coordsOut;
  vtkm::cont::ArrayCopy(
    partitionedDataSet.GetPartition(0).GetCoordinateSystem().GetDataAsMultiplexer(), coordsOut);
  vtkm::cont::ArrayHandle<vtkm::UInt8> shapes;
  vtkm::cont::ArrayHandle<vtkm::Id> numIndices;
  vtkm::cont::ArrayHandle<vtkm::Id> connectivity;
  vtkm::Id numberOfPointsSoFar = 0;
  for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
       partitionId++)
  {
    auto partition = partitionedDataSet.GetPartition(partitionId);

    // Transfer points
    auto coordsIn = partition.GetCoordinateSystem().GetDataAsMultiplexer();
    coordsOut.Allocate(numberOfPointsSoFar + partition.GetNumberOfPoints(), vtkm::CopyFlag::On);
    TransferArray(coordsIn, coordsOut, numberOfPointsSoFar);

    // Transfer cells
    vtkm::cont::DynamicCellSet cellset;
    cellset = partition.GetCellSet();
    TransferCells(cellset, shapes, numIndices, connectivity, numberOfPointsSoFar);

    numberOfPointsSoFar += partition.GetNumberOfPoints();
  }

  // create dataset
  vtkm::cont::CellSetExplicit<> cellSet;
  vtkm::Id nPts = static_cast<vtkm::Id>(coordsOut.GetNumberOfValues());
  vtkm::cont::ArrayHandle<vtkm::Id> offsets;
  vtkm::cont::Algorithm::ScanExtended(numIndices, offsets);
  cellSet.Fill(nPts, shapes, connectivity, offsets);
  vtkm::cont::DataSet derivedDataSet;
  derivedDataSet.AddCoordinateSystem(vtkm::cont::CoordinateSystem(
    partitionedDataSet.GetPartition(0).GetCoordinateSystem().GetName(), coordsOut));
  derivedDataSet.SetCellSet(cellSet);

  // Transfer fields
  for (vtkm::IdComponent f = 0; f < partitionedDataSet.GetPartition(0).GetNumberOfFields(); f++)
  {
    std::string name = partitionedDataSet.GetPartition(0).GetField(f).GetName();
    vtkm::cont::UnknownArrayHandle outFieldHandle;
    vtkm::cont::ArrayCopy(partitionedDataSet.GetPartition(0).GetField(name).GetData(),
                          outFieldHandle);

    if (partitionedDataSet.GetPartition(0).GetField(name).IsFieldCell())
    {
      outFieldHandle.Allocate(derivedDataSet.GetNumberOfCells());
      vtkm::Id numberOfCellValuesSoFar = 0;
      for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
           partitionId++)
      {
        try
        {
          auto cellField = partitionedDataSet.GetPartition(partitionId).GetField(name).GetData();
          TransferArray(cellField, outFieldHandle, numberOfCellValuesSoFar);
        }
        catch (const vtkm::cont::Error& error)
        {
          std::cout << "Partition 0 contains an array that partition " << partitionId
                    << " does not contain. The merged Dataset will have random values where values "
                       "were missing."
                    << std::endl;
          std::cout << error.GetMessage() << std::endl;
        }
        numberOfCellValuesSoFar += partitionedDataSet.GetPartition(partitionId).GetNumberOfCells();
      }
      derivedDataSet.AddCellField(name, outFieldHandle);
    }
    else
    {
      outFieldHandle.Allocate(derivedDataSet.GetNumberOfPoints());
      vtkm::Id numberOfPointValuesSoFar = 0;
      for (vtkm::Id partitionId = 0; partitionId < partitionedDataSet.GetNumberOfPartitions();
           partitionId++)
      {
        try
        {
          auto pointField = partitionedDataSet.GetPartition(partitionId).GetField(name).GetData();
          TransferArray(pointField, outFieldHandle, numberOfPointValuesSoFar);
        }
        //        catch (vtkm::cont::ErrorBadValue& error)
        catch (const vtkm::cont::Error& error)
        {
          std::cout << "Partition 0 contains an array that partition " << partitionId
                    << " does not contain. The merged Dataset will have random values where values "
                       "were missing."
                    << std::endl;
          std::cout << error.GetMessage() << std::endl;
        }
        numberOfPointValuesSoFar +=
          partitionedDataSet.GetPartition(partitionId).GetNumberOfPoints();
      }
      derivedDataSet.AddPointField(name, outFieldHandle);
    }
  }

  return derivedDataSet;
}

}
}
