//============================================================================
//  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.
//
//  Copyright 2015 Sandia Corporation.
//  Copyright 2015 UT-Battelle, LLC.
//  Copyright 2015 Los Alamos National Security.
//
//  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
//  the U.S. Government retains certain rights in this software.
//
//  Under the terms of Contract DE-AC52-06NA25396 with Los Alamos National
//  Laboratory (LANL), the U.S. Government retains certain rights in
//  this software.
//============================================================================
#include "vtkmlib/Storage.h"
#include "vtkmCellSetSingleType.h"
#include "vtkmConnectivityExec.h"

#include <vtkm/cont/internal/ReverseConnectivityBuilder.h>
#include <vtkm/worklet/WorkletMapField.h>
#include <vtkm/worklet/DispatcherMapField.h>

namespace
{


// Converts [0, rconnSize) to [0, connSize), skipping cell length entries.
struct SingleTypeRConnToConn
{
  vtkm::Id PointsPerCell;

  VTKM_EXEC
  vtkm::Id operator()(vtkm::Id rconnIdx) const
  {
    return rconnIdx + 1 + (rconnIdx / this->PointsPerCell);
  }
};

// Converts a connectivity index to a cell id:
struct SingleTypeCellIdCalc
{
  vtkm::Id EncodedCellSize;

  VTKM_EXEC
  vtkm::Id operator()(vtkm::Id connIdx) const
  {
    return connIdx / this->EncodedCellSize;
  }
};

} // end anon namespace

namespace vtkm {
namespace cont {

//------------------------------------------------------------------------------
vtkm::IdComponent vtkmCellSetSingleType::DetermineNumberOfPoints() const
{
  vtkm::IdComponent numberOfPointsPerCell = -1;
  switch (this->CellTypeAsId)
  {
    vtkmGenericCellShapeMacro(this->DetermineNumberOfPoints(
        CellShapeTag(), vtkm::CellTraits<CellShapeTag>::IsSizeFixed(),
        numberOfPointsPerCell));
  default:
    throw vtkm::cont::ErrorBadValue(
        "CellSetSingleType unable to determine the cell type");
  }
  return numberOfPointsPerCell;
}

//------------------------------------------------------------------------------
void vtkmCellSetSingleType::Fill(
    vtkm::Id numberOfPoints,
    const vtkm::cont::ArrayHandle<vtkm::Id, tovtkm::vtkCellArrayContainerTag>&
        connectivity)
{
  const vtkm::IdComponent numberOfPointsPerCell =
      this->DetermineNumberOfPoints();
  this->NumberOfCells =
      connectivity.GetNumberOfValues() / (numberOfPointsPerCell + 1);
  this->Connectivity = connectivity;
  this->NumberOfPoints = numberOfPoints;
}

//------------------------------------------------------------------------------
template <typename Device>
typename vtkm::exec::ConnectivityVTKSingleType<Device>
    vtkmCellSetSingleType::PrepareForInput(Device,
                                           vtkm::TopologyElementTagCell,
                                           vtkm::TopologyElementTagPoint) const
{
  const vtkm::IdComponent numberOfPointsPerCell =
      this->DetermineNumberOfPoints();
  const vtkm::UInt8 shapeTypeValue =
      static_cast<vtkm::UInt8>(this->CellTypeAsId);

  return vtkm::exec::ConnectivityVTKSingleType<Device>(
      this->Connectivity.PrepareForInput(Device()), this->NumberOfCells,
      numberOfPointsPerCell, shapeTypeValue);
}

//------------------------------------------------------------------------------
template <typename Device>
typename vtkm::exec::ReverseConnectivityVTK<Device>
    vtkmCellSetSingleType::PrepareForInput(Device,
                                           vtkm::TopologyElementTagPoint,
                                           vtkm::TopologyElementTagCell) const
{
  if(!this->ReverseConnectivityBuilt)
  {
    const vtkm::Id numberOfPoints = this->GetNumberOfPoints();
    const vtkm::Id numberOfCells = this->GetNumberOfCells();
    const vtkm::Id numberOfPointsPerCell = this->DetermineNumberOfPoints();
    const vtkm::Id rconnSize = numberOfCells*numberOfPointsPerCell;

    const SingleTypeRConnToConn rconnToConnCalc{numberOfPointsPerCell};
    const SingleTypeCellIdCalc cellIdCalc{numberOfPointsPerCell + 1}; // +1 for cell length entries

    vtkm::cont::internal::ReverseConnectivityBuilder builder;

    builder.Run(this->Connectivity,
                this->RConn,
                this->RNumIndices,
                this->RIndexOffsets,
                rconnToConnCalc,
                cellIdCalc,
                numberOfPoints,
                rconnSize,
                Device{});

    this->NumberOfPoints = this->RIndexOffsets.GetNumberOfValues();
    this->ReverseConnectivityBuilt = true;
  } // End if !RConnBuilt

  //no need to have a reverse shapes array, as everything has the shape type
  //of vertex
  return vtkm::exec::ReverseConnectivityVTK<Device>(
      this->RConn.PrepareForInput(Device()),
      this->RNumIndices.PrepareForInput(Device()),
      this->RIndexOffsets.PrepareForInput(Device()));
}

//------------------------------------------------------------------------------
void vtkmCellSetSingleType::PrintSummary(std::ostream& out) const
{
  out << "   vtkmCellSetSingleType: " << std::endl;
  out << "   NumberOfCells: " << this->NumberOfCells << std::endl;
  out << "   CellTypeAsId: " << this->CellTypeAsId << std::endl;
  out << "   Connectivity: " << std::endl;
  vtkm::cont::printSummary_ArrayHandle(this->Connectivity, out);
}

// template methods we want to compile only once
template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ConnectivityVTKSingleType<vtkm::cont::DeviceAdapterTagSerial>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagSerial,
      vtkm::TopologyElementTagCell, vtkm::TopologyElementTagPoint) const;

template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ReverseConnectivityVTK<vtkm::cont::DeviceAdapterTagSerial>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagSerial,
      vtkm::TopologyElementTagPoint, vtkm::TopologyElementTagCell) const;

#ifdef VTKM_ENABLE_TBB
template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ConnectivityVTKSingleType<vtkm::cont::DeviceAdapterTagTBB>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagTBB,
      vtkm::TopologyElementTagCell, vtkm::TopologyElementTagPoint) const;

template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ReverseConnectivityVTK<vtkm::cont::DeviceAdapterTagTBB>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagTBB,
      vtkm::TopologyElementTagPoint, vtkm::TopologyElementTagCell) const;
#endif

#ifdef VTKM_ENABLE_OPENMP
template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ConnectivityVTKSingleType<vtkm::cont::DeviceAdapterTagOpenMP>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagOpenMP,
      vtkm::TopologyElementTagCell, vtkm::TopologyElementTagPoint) const;

template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ReverseConnectivityVTK<vtkm::cont::DeviceAdapterTagOpenMP>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagOpenMP,
      vtkm::TopologyElementTagPoint, vtkm::TopologyElementTagCell) const;
#endif

#ifdef VTKM_ENABLE_CUDA
template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ConnectivityVTKSingleType<vtkm::cont::DeviceAdapterTagCuda>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagCuda,
      vtkm::TopologyElementTagCell, vtkm::TopologyElementTagPoint) const;

template VTKACCELERATORSVTKM_EXPORT
  vtkm::exec::ReverseConnectivityVTK<vtkm::cont::DeviceAdapterTagCuda>
    vtkmCellSetSingleType::PrepareForInput(vtkm::cont::DeviceAdapterTagCuda,
      vtkm::TopologyElementTagPoint, vtkm::TopologyElementTagCell) const;
#endif
}
}
