/*=========================================================================

 Program:   Visualization Toolkit
 Module:    vtkAMRResampleFilter.h

 Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 All rights reserved.
 See Copyright.txt or http://www.kitware.com/Copyright.htm 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.

 =========================================================================*/
/**
 * @class   vtkAMRResampleFilter
 *
 *
 *  This filter is a concrete instance of vtkMultiBlockDataSetAlgorithm and
 *  provides functionality for extracting portion of the AMR dataset, specified
 *  by a bounding box, in a uniform grid of the desired level of resolution.
 *  The resulting uniform grid is stored in a vtkMultiBlockDataSet where the
 *  number of blocks correspond to the number of processors utilized for the
 *  operation.
 *
 * @warning
 *  Data of the input AMR dataset is assumed to be cell-centered.
 *
 * @sa
 *  vtkOverlappingAMR, vtkUniformGrid
*/

#ifndef vtkAMRResampleFilter_h
#define vtkAMRResampleFilter_h

#include "vtkFiltersAMRModule.h" // For export macro
#include "vtkMultiBlockDataSetAlgorithm.h"
#include <vector> // For STL vector

class vtkInformation;
class vtkInformationVector;
class vtkUniformGrid;
class vtkOverlappingAMR;
class vtkMultiBlockDataSet;
class vtkMultiProcessController;
class vtkFieldData;
class vtkCellData;
class vtkPointData;
class vtkIndent;

class vtkAMRBox;
class VTKFILTERSAMR_EXPORT vtkAMRResampleFilter : public vtkMultiBlockDataSetAlgorithm
{
public:
  static vtkAMRResampleFilter *New();
  vtkTypeMacro(vtkAMRResampleFilter,vtkMultiBlockDataSetAlgorithm);
  void PrintSelf(ostream &oss, vtkIndent indent) override;

  //@{
  /**
   * Set & Get macro for the number of samples (cells) in each dimension.
   * Nominal value for the number of samples is 10x10x10.
   */
  vtkSetVector3Macro(NumberOfSamples,int);
  vtkGetVector3Macro(NumberOfSamples,int);
  //@}

  //@{
  /**
   * Set & Get macro for the TransferToNodes flag
   */
  vtkSetMacro(TransferToNodes,int);
  vtkGetMacro(TransferToNodes,int);
  //@}

  //@{
  /**
   * Set & Get macro to allow the filter to operate in both demand-driven
   * and standard modes
   */
  vtkSetMacro(DemandDrivenMode,int);
  vtkGetMacro(DemandDrivenMode,int);
  //@}

  //@{
  /**
   * Set & Get macro for the number of subdivisions
   */
  vtkSetMacro(NumberOfPartitions,int);
  vtkGetMacro(NumberOfPartitions,int);
  //@}

  //@{
  /**
   * Set and Get the min corner
   */
  vtkSetVector3Macro(Min,double);
  vtkGetVector3Macro(Min,double);
  //@}

  //@{
  /**
   * Set and Get the max corner
   */
  vtkSetVector3Macro(Max,double);
  vtkGetVector3Macro(Max,double);
  //@}

  //@{
  /**
   * Set & Get macro for the number of subdivisions
   */
  vtkSetMacro(UseBiasVector,bool);
  vtkGetMacro(UseBiasVector,bool);
  //@}

  //@{
  /**
   * Set and Get the bias vector.  If UseBiasVector is true
   * then the largest component of this vector can not have
   * the max number of samples
   */
  vtkSetVector3Macro(BiasVector,double);
  vtkGetVector3Macro(BiasVector,double);
  //@}

  //@{
  /**
   * Set & Get macro for the multi-process controller
   */
  vtkSetMacro(Controller, vtkMultiProcessController*);
  vtkGetMacro(Controller, vtkMultiProcessController*);
  //@}

  // Standard pipeline routines

  /**
   * Gets the metadata from upstream module and determines which blocks
   * should be loaded by this instance.
   */
  int RequestInformation(
      vtkInformation *rqst,
      vtkInformationVector **inputVector,
      vtkInformationVector *outputVector ) override;

  int RequestData(
       vtkInformation*,vtkInformationVector**,vtkInformationVector*) override;
  int FillInputPortInformation(int port, vtkInformation *info) override;
  int FillOutputPortInformation(int port, vtkInformation *info) override;

  /**
   * Performs upstream requests to the reader
   */
  int RequestUpdateExtent(
      vtkInformation*, vtkInformationVector**, vtkInformationVector* ) override;


protected:
  vtkAMRResampleFilter();
  ~vtkAMRResampleFilter() override;

  vtkOverlappingAMR *AMRMetaData;
  vtkMultiBlockDataSet *ROI; // Pointer to the region of interest.
  int NumberOfSamples[3];
  int GridNumberOfSamples[3];
  double Min[3];
  double Max[3];
  double GridMin[3];
  double GridMax[3];
  int LevelOfResolution;
  int NumberOfPartitions;
  int TransferToNodes;
  int DemandDrivenMode;
  vtkMultiProcessController *Controller;
  bool UseBiasVector;
  double BiasVector[3];

  // Debugging Stuff
  int NumberOfBlocksTestedForLevel;
  int NumberOfBlocksTested;
  int NumberOfBlocksVisSkipped;
  int NumberOfTimesFoundOnDonorLevel;
  int NumberOfTimesLevelUp;
  int NumberOfTimesLevelDown;
  int NumberOfFailedPoints;
  double AverageLevel;

  std::vector< int > BlocksToLoad; // Holds the ids of the blocks to load.

  /**
   * Checks if this filter instance is running on more than one processes
   */
  bool IsParallel();

  /**
   * Given the Region ID this function returns whether or not the region
   * belongs to this process or not.
   */
  bool IsRegionMine( const int regionIdx );

  /**
   * Given the Region ID, this method computes the corresponding process ID
   * that owns the region based on static block-cyclic distribution.
   */
  int GetRegionProcessId( const int regionIdx );

  /**
   * Given a cell index and a grid, this method computes the cell centroid.
   */
  void ComputeCellCentroid(
      vtkUniformGrid *g, const vtkIdType cellIdx, double c[3] );

  /**
   * Given the source cell data of an AMR grid, this method initializes the
   * field values, i.e., the number of arrays with the prescribed size. Note,
   * the size must correspond to the number of points if node-centered or the
   * the number of cells if cell-centered.
   */
  void InitializeFields( vtkFieldData *f, vtkIdType size, vtkCellData *src );

  /**
   * Copies the data to the target from the given source.
   */
  void CopyData( vtkFieldData *target, vtkIdType targetIdx,
                 vtkCellData *src, vtkIdType srcIdx );

  /**
   * Given a query point q and a candidate donor grid, this method checks for
   * the corresponding donor cell containing the point in the given grid.
   */
  bool FoundDonor(double q[3],vtkUniformGrid *&donorGrid,int &cellIdx);


  /**
   * Given a query point q and a target level, this method finds a suitable
   * grid at the given level that contains the point if one exists. If a grid
   * is not found, donorGrid is set to nullptr.
   */
  bool SearchForDonorGridAtLevel(
      double q[3], vtkOverlappingAMR *amrds,
      unsigned int level, unsigned int& gridId,
      int &donorCellIdx);

  /**
   * Finds the AMR grid that contains the point q. If donorGrid points to a
   * valid AMR grid in the hierarchy, the algorithm will search this grid
   * first. The method returns the ID of the cell w.r.t. the donorGrid that
   * contains the probe point q.
   */
  int ProbeGridPointInAMR(
    double q[3], unsigned int &donorLevel, unsigned int& donorGridId,
      vtkOverlappingAMR *amrds, unsigned int maxLevel, bool useCached);

  /**
   * Finds the AMR grid that contains the point q. If donorGrid points to a
   * valid AMR grid in the hierarchy, the algorithm will search this grid
   * first. The method returns the ID of the cell w.r.t. the donorGrid that
   * contains the probe point q. - Makes use of Parent/Child Info
   */
  int ProbeGridPointInAMRGraph(double q[3],
                               unsigned int &donorLevel,  unsigned int &donorGridId,
                               vtkOverlappingAMR *amrds, unsigned int maxLevel, bool useCached);

  /**
   * Transfers the solution from the AMR dataset to the cell-centers of
   * the given uniform grid.
   */
  void TransferToCellCenters(
      vtkUniformGrid *g, vtkOverlappingAMR *amrds );

  /**
   * Transfer the solution from the AMR dataset to the nodes of the
   * given uniform grid.
   */
  void TransferToGridNodes(
      vtkUniformGrid *g, vtkOverlappingAMR *amrds );

  /**
   * Transfers the solution
   */
  void TransferSolution(
      vtkUniformGrid *g, vtkOverlappingAMR *amrds);

  /**
   * Extract the region (as a multiblock) from the given AMR dataset.
   */
  void ExtractRegion(
      vtkOverlappingAMR *amrds, vtkMultiBlockDataSet *mbds,
      vtkOverlappingAMR *metadata );

  /**
   * Checks if the AMR block, described by a uniform grid, is within the
   * bounds of the ROI perscribed by the user.
   */
  bool IsBlockWithinBounds( double *grd );

  /**
   * Given a user-supplied region of interest and the metadata by a module
   * upstream, this method generates the list of linear AMR block indices
   * that need to be loaded.
   */
  void ComputeAMRBlocksToLoad( vtkOverlappingAMR *metadata );

  /**
   * Computes the region parameters
   */
  void ComputeRegionParameters(
      vtkOverlappingAMR *amrds,
      int N[3], double min[3], double max[3], double h[3] );

  /**
   * This method accesses the domain boundaries
   */
  void GetDomainParameters(
      vtkOverlappingAMR *amr,
      double domainMin[3], double domainMax[3], double h[3],
      int dims[3], double &rf );

  /**
   * Checks if the domain and requested region intersect.
   */
  bool RegionIntersectsWithAMR(
      double domainMin[3], double domainMax[3],
      double regionMin[3], double regionMax[3] );

  /**
   * This method adjust the numbers of samples in the region, N, if the
   * requested region falls outside, but, intersects the domain.
   */
  void AdjustNumberOfSamplesInRegion(const double Rh[3],
      const bool outside[6], int N[3] );

  /**
   * This method computes the level of resolution based on the number of
   * samples requested, N, the root level spacing h0, the length of the box,
   * L (actual length after snapping) and the refinement ratio.
   */
  void ComputeLevelOfResolution(
      const int N[3], const double h0[3], const double L[3], const double rf);

  /**
   * This method snaps the bounds s.t. they are within the interior of the
   * domain described the root level uniform grid with h0, domainMin and
   * domain Max. The method computes and returns the new min/max bounds and
   * the corresponding ijkmin/ijkmax coordinates w.r.t. the root level.
   */
  void SnapBounds(
    const double h0[3], const double domainMin[3], const double domainMax[3],
    const int dims[3], bool outside[6] );

  /**
   * This method computes and adjusts the region parameters s.t. the requested
   * region always fall within the AMR region and the number of samples is
   * adjusted if the region of interest moves outsided the domain.
   */
  void ComputeAndAdjustRegionParameters(
      vtkOverlappingAMR *amrds, double h[3] );

  /**
   * This method gets the region of interest as perscribed by the user.
   */
  void GetRegion( double h[3] );

  /**
   * Checks if two uniform grids intersect.
   */
  bool GridsIntersect( double *g1, double *g2 );

  /**
   * Returns a reference grid from the amrdataset.
   */
  vtkUniformGrid* GetReferenceGrid( vtkOverlappingAMR *amrds );

  /**
   * Writes a uniform grid to a file. Used for debugging purposes.
   * void WriteUniformGrid( vtkUniformGrid *g, std::string prefix );
   * void WriteUniformGrid(
   * double origin[3], int dims[3], double h[3],
   * std::string prefix );
   */

  /**
   * Find a descendant of the specified grid that contains the point.
   * If none is found then the original grid information is returned.
   * The search is limited to levels < maxLevel
   */
  void SearchGridDecendants(double q[3],
                            vtkOverlappingAMR *amrds,
                            unsigned int maxLevel,
                            unsigned int &level,
                            unsigned int &gridId,
                            int &id);

  /**
   * Find an ancestor of the specified grid that contains the point.
   * If none is found then the original grid information is returned
   */
  bool SearchGridAncestors(double q[3],
                           vtkOverlappingAMR *amrds,
                           unsigned int &level,
                           unsigned int &gridId,
                           int &id);


private:
  vtkAMRResampleFilter(const vtkAMRResampleFilter&) = delete;
  void operator=(const vtkAMRResampleFilter&) = delete;

};

#endif /* vtkAMRResampleFilter_h */
