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

 Program:   Visualization Toolkit
 Module:    vtkAMRBaseReader.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   vtkAMRBaseReader
 * @brief   An abstract class that encapsulates common functionality for all AMR readers.
 */

#ifndef vtkAMRBaseReader_h
#define vtkAMRBaseReader_h

#include "vtkIOAMRModule.h" // For export macro
#include "vtkOverlappingAMRAlgorithm.h"
#include <map>     // STL map header
#include <utility> // for STL pair
#include <vector>  // STL vector header

// Forward Declarations
class vtkOverlappingAMR;
class vtkMultiProcessController;
class vtkDataArraySelection;
class vtkCallbackCommand;
class vtkIndent;
class vtkAMRDataSetCache;
class vtkUniformGrid;
class vtkDataArray;

class VTKIOAMR_EXPORT vtkAMRBaseReader : public vtkOverlappingAMRAlgorithm
{
public:
  vtkTypeMacro(vtkAMRBaseReader, vtkOverlappingAMRAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Initializes the AMR reader.
   * All concrete instances must call this method in their constructor.
   */
  void Initialize();

  ///@{
  /**
   * Set/Get Reader caching property
   */
  vtkSetMacro(EnableCaching, vtkTypeBool);
  vtkGetMacro(EnableCaching, vtkTypeBool);
  vtkBooleanMacro(EnableCaching, vtkTypeBool);
  bool IsCachingEnabled() const { return ((this->EnableCaching) ? true : false); };
  ///@}

  ///@{
  /**
   * Set/Get a multiprocess-controller for reading in parallel.
   * By default this parameter is set to nullptr by the constructor.
   */
  vtkSetMacro(Controller, vtkMultiProcessController*);
  vtkGetMacro(Controller, vtkMultiProcessController*);
  ///@}

  ///@{
  /**
   * Set the level, up to which the blocks are loaded.
   */
  vtkSetMacro(MaxLevel, int);
  ///@}

  ///@{
  /**
   * Get the data array selection tables used to configure which data
   * arrays are loaded by the reader.
   */
  vtkGetObjectMacro(CellDataArraySelection, vtkDataArraySelection);
  vtkGetObjectMacro(PointDataArraySelection, vtkDataArraySelection);
  ///@}

  ///@{
  /**
   * Get the number of point or cell arrays available in the input.
   */
  int GetNumberOfPointArrays();
  int GetNumberOfCellArrays();
  ///@}

  ///@{
  /**
   * Get the name of the point or cell array with the given index in
   * the input.
   */
  const char* GetPointArrayName(int index);
  const char* GetCellArrayName(int index);
  ///@}

  ///@{
  /**
   * Get/Set whether the point or cell array with the given name is to
   * be read.
   */
  int GetPointArrayStatus(const char* name);
  int GetCellArrayStatus(const char* name);
  void SetPointArrayStatus(const char* name, int status);
  void SetCellArrayStatus(const char* name, int status);
  ///@}

  ///@{
  /**
   * Set/Get the filename. Concrete instances of this class must implement
   * the SetFileName method accordingly.
   */
  vtkGetFilePathMacro(FileName);
  virtual void SetFileName(VTK_FILEPATH const char* fileName) = 0;
  ///@}

  /**
   * Returns the total number of blocks. Implemented by concrete instances.
   */
  virtual int GetNumberOfBlocks() = 0;

  /**
   * Returns the total number of levels. Implemented by concrete instances.
   */
  virtual int GetNumberOfLevels() = 0;

protected:
  vtkAMRBaseReader();
  ~vtkAMRBaseReader() override;

  // Desscription:
  // Checks if this reader instance is attached to a communicator
  // with more than one MPI processes.
  bool IsParallel();

  /**
   * Determines if the block is owned by this process based on the
   * the block index and total number of processes.
   */
  bool IsBlockMine(const int blockIdx);

  /**
   * Loads the AMR block corresponding to the given index. The block
   * is either loaded from the file, or, from the cache if caching is
   * enabled.
   */
  vtkUniformGrid* GetAMRBlock(const int blockIdx);

  /**
   * This method assigns blocks to processes using block-cyclic distribution.
   * It is the method that is used to load distributed AMR data by default.
   */
  void AssignAndLoadBlocks(vtkOverlappingAMR* amrds);

  /**
   * This method loads all the blocks in the BlockMap for the given process.
   * It assumes that the downstream module is doing an upstream request with
   * the flag LOAD_REQUESTED_BLOCKS which indicates that the downstream filter
   * has already assigned which blocks are needed for each process.
   */
  void LoadRequestedBlocks(vtkOverlappingAMR* amrds);

  /**
   * Loads the AMR data corresponding to the given field name.
   * NOTE: Currently, only cell-data are supported.
   */
  void GetAMRData(const int blockIdx, vtkUniformGrid* block, const char* fieldName);

  /**
   * Loads the AMR point data corresponding to the given field name.
   */
  void GetAMRPointData(const int blockIdx, vtkUniformGrid* block, const char* fieldName);

  /**
   * A wrapper that loops over point arrays and load the point
   * arrays that are enabled, i.e., selected for the given block.
   * NOTE: This method is currently not implemented.
   */
  void LoadPointData(const int blockIdx, vtkUniformGrid* block);

  /**
   * A wrapper that loops over all cell arrays and loads the cell
   * arrays that are enabled, i.e., selected for the given block.
   * The data are either loaded from the file, or, from the cache if
   * caching is enabled.
   */
  void LoadCellData(const int blockIdx, vtkUniformGrid* block);

  /**
   * Returns the block process ID for the block corresponding to the
   * given block index. If this reader instance is serial, i.e., there
   * is no controller associated, the method returns 0. Otherwise, static
   * block-cyclic-distribution is assumed and each block is assigned to
   * a process according to blockIdx%N, where N is the total number of
   * processes.
   */
  int GetBlockProcessId(const int blockIdx);

  /**
   * Initializes the request of blocks to be loaded. This method checks
   * if an upstream request has been issued from a downstream module which
   * specifies which blocks are to be loaded, otherwise, it uses the max
   * level associated with this reader instance to determine which blocks
   * are to be loaded.
   */
  void SetupBlockRequest(vtkInformation* outputInfo);

  /**
   * Reads all the metadata from the file. Implemented by concrete classes.
   */
  virtual void ReadMetaData() = 0;

  /**
   * Returns the block level for the given block
   */
  virtual int GetBlockLevel(const int blockIdx) = 0;

  /**
   * Loads all the AMR metadata & constructs the LevelIdxPair12InternalIdx
   * datastructure which maps (level,id) pairs to an internal linear index
   * used to identify the corresponding block.
   */
  virtual int FillMetaData() = 0;

  /**
   * Loads the block according to the index w.r.t. the generated BlockMap.
   */
  virtual vtkUniformGrid* GetAMRGrid(const int blockIdx) = 0;

  /**
   * Loads the block data
   */
  virtual void GetAMRGridData(const int blockIdx, vtkUniformGrid* block, const char* field) = 0;

  /**
   * Loads the block Point data
   */
  virtual void GetAMRGridPointData(
    const int blockIdx, vtkUniformGrid* block, const char* field) = 0;

  ///@{
  /**
   * Standard Pipeline methods, subclasses may override this method if needed.
   */
  int RequestData(vtkInformation* vtkNotUsed(request),
    vtkInformationVector** vtkNotUsed(inputVector), vtkInformationVector* outputVector) override;
  int RequestInformation(vtkInformation* rqst, vtkInformationVector** inputVector,
    vtkInformationVector* outputVector) override;
  int FillOutputPortInformation(int port, vtkInformation* info) override;
  ///@}

  // Array selection member variables and methods
  vtkDataArraySelection* PointDataArraySelection;
  vtkDataArraySelection* CellDataArraySelection;
  vtkCallbackCommand* SelectionObserver;

  /**
   * Initializes the array selections. If this is an initial request,
   * i.e., the first load from the file, all the arrays are deselected,
   * and the InitialRequest ivar is set to false.
   */
  void InitializeArraySelections();

  /**
   * Initializes the PointDataArraySelection & CellDataArraySelection
   */
  virtual void SetUpDataArraySelections() = 0;

  /**
   * Call-back registered with the SelectionObserver.
   */
  static void SelectionModifiedCallback(
    vtkObject* caller, unsigned long eid, void* clientdata, void* calldata);

  bool InitialRequest;
  int MaxLevel;
  char* FileName;
  vtkMultiProcessController* Controller;

  vtkTypeBool EnableCaching;
  vtkAMRDataSetCache* Cache;
  int NumBlocksFromFile;
  int NumBlocksFromCache;

  vtkOverlappingAMR* Metadata;
  bool LoadedMetaData;

  std::vector<int> BlockMap;

private:
  vtkAMRBaseReader(const vtkAMRBaseReader&) = delete;
  void operator=(const vtkAMRBaseReader&) = delete;
};

#endif /* vtkAMRBaseReader_h */
