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

   Program:   ParaView
   Module:    vtkCFSReader.h

   Copyright (c) 2005,2006 Sandia Corporation, Kitware Inc.
   All rights reserved.

   ParaView is a free software; you can redistribute it and/or modify it
   under the terms of the ParaView license version 1.2.

   See License_v1.2.txt for the full ParaView license.
   A copy of this license can be obtained by contacting
   Kitware Inc.
   28 Corporate Drive
   Clifton Park, NY 12065
   USA

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

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

/*-------------------------------------------------------------------------
   Copyright (c) 2022 Verein zur Foerderung der Software openCFS
-------------------------------------------------------------------------*/

/**
 * @class   vtkCFSReader
 *
 * @brief   integration of reader plugin to read htd5 based openCFS output
 *
 * The file and the logic itself are within hdf5Reader.
 */

#ifndef VTKCFSREADER_H
#define VTKCFSREADER_H

#include <vector>
#include <vtkCellType.h>
#include <vtkInformationDoubleVectorKey.h>
#include <vtkInformationIntegerKey.h>
#include <vtkInformationIntegerVectorKey.h>
#include <vtkInformationStringKey.h>
#include <vtkInformationStringVectorKey.h>
#include <vtkInformationVector.h>
#include <vtkMultiBlockDataSet.h>
#include <vtkMultiBlockDataSetAlgorithm.h>
#include <vtkObjectBase.h>
#include <vtkSetGet.h>
#include <vtkUnstructuredGridAlgorithm.h>
#include <vtkWin32Header.h>

#include "hdf5Reader.h" // within the same directory

#include <vtkCFSReaderModule.h>

class vtkDoubleArray;
class vtkCell;

class VTKCFSREADER_EXPORT vtkCFSReader : public vtkMultiBlockDataSetAlgorithm
{
public:
  static vtkCFSReader* New();
  vtkTypeMacro(vtkCFSReader, vtkMultiBlockDataSetAlgorithm);
  void PrintSelf(ostream& os, vtkIndent indent) override;

  /**
   * Check, if the current file is really a CFS hdf5 file.
   * This method gets called in order to determine the "right" reader in case
   * several readers can open files with the same extension.
   */
  virtual int CanReadFile(VTK_FILEPATH const char* fname);

  /**
   * Specify the file name of the CFS data file to read.
   * Default is empty filename (nullptr)
   */
  virtual void SetFileName(VTK_FILEPATH const char* arg);

  /**
   * Obtain the CFS data file name.
   */
  virtual const char* GetFileName();

  /**
   * Close then file handle
   */
  virtual void CloseFile();

  /**
   * Get the multisequence range. Filled during RequestInformation.
   */
  vtkGetVector2Macro(MultiSequenceRange, int);

  ///@{
  /**
   * Set/Get the current multisequence step.
   * Default is step number 1.
   */
  void SetMultiSequenceStep(int step);
  vtkGetMacro(MultiSequenceStep, int);
  ///@}

  ///@{
  /**
   * Set/Get the current timestep.
   * Default is the timestep 1.
   */
  virtual void SetTimeStep(unsigned int step);
  vtkGetMacro(TimeStep, unsigned int);
  ///@}

  ///@{
  /**
   * Get current frequency / time value
   */
  virtual const char* GetTimeOrFrequencyValueStr();
  ///@}

  ///@{
  /**
   * Add dimensions to array names.
   * Default is 0 for not to extend array names.
   */
  virtual void SetAddDimensionsToArrayNames(int);
  vtkGetMacro(AddDimensionsToArrayNames, int);
  vtkBooleanMacro(AddDimensionsToArrayNames, int);
  ///@}

  ///@{
  /**
   * Form displacement data from complex values.
   * Default is 0 for not adding mode shape displacements.
   */
  virtual void SetHarmonicDataAsModeShape(int);
  vtkGetMacro(HarmonicDataAsModeShape, int);
  vtkBooleanMacro(HarmonicDataAsModeShape, int);
  ///@}

  /**
   * Return the number of time / frequency steps.
   */
  virtual unsigned int GetNumberOfSteps() { return this->NumberOfTimeSteps; }

  ///@{
  /**
   * Get the timestep range. Filled during RequestInformation.
   */
  vtkGetVector2Macro(TimeStepNumberRange, int);

  virtual double* GetTimeStepRange()
  {
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): returning TimeStepRange pointer "
                  << this->TimeStepValuesRange);

    return static_cast<double*>(this->TimeStepValuesRange);
  }

  virtual void GetTimeStepRange(double& arg1, double& arg2)
  {
    arg1 = this->TimeStepValuesRange[0];
    arg2 = this->TimeStepValuesRange[1];
    vtkDebugMacro(<< this->GetClassName() << " (" << this << "): returning TimeStepRange = ("
                  << arg1 << "," << arg2 << ")");
  };

  virtual void GetTimeStepRange(double arg[2]) { this->GetTimeStepRange(arg[0], arg[1]); }
  ///@}

  ///@{
  /**
   * Type of complex result treating for adding
   * pair real/imaginary or pair amplitude/phase
   * Default is 0 for all cases (data not added)
   */
  void SetComplexReal(int flag)
  {
    this->ComplexModeReal = flag;
    // In addition trigger resetting the data value arrays
    this->ResetDataArrays = true;
    // update pipeline
    this->Modified();
  }
  vtkGetMacro(ComplexModeReal, int);

  /**
   * Add imaginary part of complex data as value set.
   * Default is 0 (off)
   */
  void SetComplexImaginary(int flag)
  {
    this->ComplexModeImaginary = flag;
    // In addition trigger resetting the data value arrays
    this->ResetDataArrays = true;
    // update pipeline
    this->Modified();
  }
  vtkGetMacro(ComplexModeImaginary, int);

  /**
   * Add normed value complex data as value set.
   * Default is 0 (off)
   */
  void SetComplexAmplitude(int flag)
  {
    this->ComplexModeAmplitude = flag;
    // In addition trigger resetting the data value arrays
    this->ResetDataArrays = true;
    // update pipeline
    this->Modified();
  }
  vtkGetMacro(ComplexModeAmplitude, int);

  /**
   * Add phase in read for value complex data as value set.
   * Default is 0 (off)
   */
  void SetComplexPhase(int flag)
  {
    this->ComplexModePhase = flag;
    // In addition trigger resetting the data value arrays
    this->ResetDataArrays = true;
    // update pipeline
    this->Modified();
  }
  vtkGetMacro(ComplexModePhase, int);
  ///@}

  ///@{
  /**
   * Switch if missing results should get filled with 0.
   * 0 = omit empty regions (only partial results available).
   * 1 = fill empty missing results with 0-valued vector.
   * Default is 0 (not filling missing regions).
   */
  void SetFillMissingResults(int);
  vtkGetMacro(FillMissingResults, int);
  vtkBooleanMacro(FillMissingResults, int);
  ///@}

  /**
   * Return spatial dimension of grid
   */
  int GetGridDimension() const { return Dimension; }

  /**
   * Return order of FEM ansatz functions within grid
   */
  int GetGridOrder() const { return GridOrder; }

  /**
   * Get the number of cell arrays available in the input.
   */
  int GetNumberOfRegionArrays() const { return static_cast<int>(RegionNames.size()); }

  ///@{
  /** Get/Set whether the cell array with the given name is to
   * be read.
   * Default is 1 (to be read)
   */
  int GetRegionArrayStatus(const char* name);
  void SetRegionArrayStatus(const char* name, int status);
  ///@}

  /**
   * Get the name of the  cell array with the given index in
   * the input.
   */
  const char* GetRegionArrayName(int index);

  /**
   * Return the total number of named node arrays within mesh
   */
  int GetNumberOfNamedNodeArrays() const { return static_cast<int>(NamedNodeNames.size()); }

  ///@{
  /**
   * Get/Set whether the named node array with the given name is to
   * be read.
   * Default is 0 (off).
   */
  int GetNamedNodeArrayStatus(const char* name);
  void SetNamedNodeArrayStatus(const char* name, int status);
  ///@}

  /**
   * Return named node array by index.
   */
  const char* GetNamedNodeArrayName(int index);

  /**
   * Return total number of named element arrays within mesh.
   */
  int GetNumberOfNamedElementArrays() const { return static_cast<int>(NamedElementNames.size()); }

  ///@{
  /**
   * Get/Set whether the named elem array with the given name is to
   * be read.
   * Default is 0 (off).
   */
  int GetNamedElementArrayStatus(const char* name);
  void SetNamedElementArrayStatus(const char* name, int status);
  ///@}

  /**
   * Return name of element array by index
   */
  const char* GetNamedElementArrayName(int index);

  /**
   * Get the coordinates of all nodes in the grid
   */
  void GetNodeCoordinates(vtkDoubleArray* coords);

  /**
   * Name of the history result
   */
  static vtkInformationStringKey* CFS_RESULT_NAME();

  /**
   * Vector of Dof names
   */
  static vtkInformationStringVectorKey* CFS_DOF_NAMES();

  /**
   * Determines on which entity the result is defined
   *  1: Node
   *  2: Edge (unused)
   *  3: Face (unused)
   *  4: Element
   *  5: Surface element
   *  6: PFEM (unused)
   *  7: Region
   *  8: Surface region
   *  9: Node group
   * 10: Coil
   * 11: Free/Unknown
   */
  static vtkInformationIntegerKey* CFS_DEFINED_ON();

  /**
   * Name of the entity on which the history result is defined
   */
  static vtkInformationStringKey* CFS_ENTITY_NAME();

  /**
   * Numerical type of the history result
   *  0: Unknown (should not occur)
   *  1: Scalar
   *  3: Vector
   *  6: Tensor
   * 32: String (currently unused)
   */
  static vtkInformationIntegerKey* CFS_ENTRY_TYPE();

  /**
   * Vector of the step numbers at which the history result was saved
   */
  static vtkInformationIntegerVectorKey* CFS_STEP_NUMS();

  /**
   * Vector of the step values at which the history result was saved
   */
  static vtkInformationDoubleVectorKey* CFS_STEP_VALUES();

  /**
   * Unit of the result quantity
   */
  static vtkInformationStringKey* CFS_UNIT();

  /**
   * Vector of the entity IDs (strings) on which the history result is defined
   */
  static vtkInformationStringVectorKey* CFS_ENTITY_IDS();

  /**
   * Index of the multisequence step
   */
  static vtkInformationIntegerKey* CFS_MULTI_SEQ_INDEX();

  /**
   * Type of the analysis performed in the multisequence step
   * 1: static
   * 2: transient
   * 3: harmonic
   * 4: eigenfrequency
   */
  static vtkInformationIntegerKey* CFS_ANALYSIS_TYPE();

protected:
  vtkCFSReader();
  ~vtkCFSReader() override;
  int RequestInformation(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;
  int RequestData(vtkInformation*, vtkInformationVector**, vtkInformationVector*) override;

private:
  /**
   * This object encapsulates the openCFS hf5 file with some service functions
   */
  H5CFS::Hdf5Reader Reader;

  /**
   * name of current file or empty
   */
  std::string FileName;

  /**
   * map cfs/hdf5 element type to vtk native ones
   */
  static VTKCellType GetCellIdType(H5CFS::ElemType type);

  static vtkDoubleArray* SaveToArray(const std::vector<double>& vals,
    const std::vector<std::string>& dofNames, unsigned int numEntities, const std::string& name);

  /**
   * dimension of the grid (2 or 3)
   */
  int Dimension = 0;

  /**
   * order of grid (linear case 1)
   */
  int GridOrder = 0;

  /**
   * The region names within mesh
   */
  std::vector<std::string> RegionNames;

  /**
   * The groups of named elements within mesh
   */
  std::vector<std::string> NamedElementNames;

  /**
   * The groups of named nodes within mesh
   */
  std::vector<std::string> NamedNodeNames;

  /**
   * map (region, globalNodeNumber)->(regionLocalNodeNumber)
   */
  std::vector<std::vector<unsigned int>> NodeMap;

  /**
   * time steps of current multisequence step
   */
  std::vector<double> StepVals;

  /**
   * vector with step values having a result
   */
  std::vector<unsigned int> StepNumbers;

  /**
   * multiblock dataset, which contains for each
   * region an initialized grid
   */
  vtkMultiBlockDataSet* MBDataSet = nullptr;

  /**
   * reduced multiblock dataset, which contains
   * only blocks for currently active regions
   */
  vtkMultiBlockDataSet* MBActiveDataSet = nullptr;

  /**
   * flag array indicating active regions
   */
  std::map<std::string, int> RegionSwitch;

  /**
   * flag array indicating named nodes to read
   */
  std::map<std::string, int> NamedNodeSwitch;

  /**
   * flag array indicating named elems to read
   */
  std::map<std::string, int> NamedElementSwitch;

  /**
   * flag indicating change of multisequence step
   */
  bool MSStepChanged = false;

  /**
   * Analysis type of multi-sequence steps with mesh results
   */
  std::map<unsigned int, H5CFS::AnalysisType> ResAnalysisType;

  /**
   * ResultInfo of mesh results per multi-sequence step
   */
  std::map<unsigned int, std::vector<std::shared_ptr<H5CFS::ResultInfo>>> MeshResultInfos;

  /**
   * Analysis type of multi-sequence step with history results
   */
  std::map<unsigned int, H5CFS::AnalysisType> HistAnalysisType;

  /**
   * ResultInfo of history results per multi-sequence step
   */
  std::map<unsigned int, std::vector<std::shared_ptr<H5CFS::ResultInfo>>> HistResultInfos;

  /**
   * Global steps per multisequence where ANY result is available
   */
  std::map<unsigned int, std::set<std::pair<unsigned int, double>>> GlobalResultSteps;

  /**
   * Current multisequence step, exposed by vtk macro
   */
  unsigned int MultiSequenceStep = 1;

  /**
   * Analysis type of current multisequence step
   */
  H5CFS::AnalysisType AnalysisType = H5CFS::NO_ANALYSIS_TYPE;

  /**
   * Current time step (for discrete time steps), exposed by vtk macro
   */
  unsigned int TimeStep = 1;

  /**
   * Current time step / frequency value pretty printed
   */
  double TimeOrFrequencyValue = 0.0;

  /**
   * pretty formatted time value / frequency value
   */
  std::string TimeOrFrequencyValueStr = "0.0";

  /**
   * The time value which the pipeline requests
   */
  double RequestedTimeValue = 0.0;

  ///@{
  /**
   * The complex mode comes in real/imaginary or amplitude/phase pairs.
   *
   * Exposed by vtk macros.
   */
  unsigned int ComplexModeReal = 0;
  unsigned int ComplexModeImaginary = 0;
  unsigned int ComplexModeAmplitude = 0;
  unsigned int ComplexModePhase = 0;
  ///@}

  /**
   * Switch if missing results should get filled with 0
   * 0 = omit empty regions (only partial results available)
   * 1 = fill empty missing results with 0-valued vector
   * Exposed by vtk macro
   */
  unsigned int FillMissingResults = 0;

  /**
   * Add dimensions to array names. Exposed by vtk macro
   */
  int AddDimensionsToArrayNames = 0;

  /**
   * Interpret harmonic data as transient mode shape.
   * Exposed by vtk macro
   */
  int HarmonicDataAsModeShape = 0;

  /**
   * Are we dealing with harmonic data?
   */
  bool HarmonicData = false;

  /**
   * number of time/freq steps in current ms step
   */
  unsigned int NumberOfTimeSteps = 0;

  /**
   * array with first/last time/freq step value
   */
  int TimeStepNumberRange[2] = { 1,
    1 }; // as this is used in vtk macros, a std::array<> does not work

  /**
   * array with first/last time/freq step value
   */
  double TimeStepValuesRange[2] = { 0.0, 1.0 };

  /**
   *  array with first/last ms step number
   */
  int MultiSequenceRange[2] = { 1, 1 };

  /**
   * flag if hdf5 file is already read in
   */
  bool IsInitialized = false;

  /**
   * flag if hdf5 infos (region names, named node/elems) have been read in
   */
  bool Hdf5InfoRead = false;

  /**
   * flag, if region settings were modified
   */
  bool ActiveRegionsChanged = false;

  /**
   * flag, to reset the data value arrays (e.g. after
   * switching the complex mode etc.)
   */
  bool ResetDataArrays = false;

  /**
   * Actually opens the file and reads region and group names
   */
  void ReadHdf5Informations();

  void ReadFile(vtkMultiBlockDataSet* output);
  void ReadNodes(vtkMultiBlockDataSet* output);
  void ReadCells(vtkMultiBlockDataSet* output);

  /**
   * Create single grid from elements of one region / group
   */
  void AddElements(vtkUnstructuredGrid* actGrid, unsigned int blockIndex,
    const std::vector<unsigned int>& elems, std::vector<H5CFS::ElemType>& types,
    std::vector<std::vector<unsigned int>>& connect);

  void ReadNodeCellData(vtkMultiBlockDataSet* output, bool isNode);

  /**
   * Update multiblock dataset according to current region/block settings
   */
  void UpdateActiveRegions();

  vtkCFSReader(const vtkCFSReader&) = delete;   // Not implemented.
  void operator=(const vtkCFSReader&) = delete; // Not implemented.
};

#endif // VTKCFSREADER_H
