// -*- c++ -*-
/*=========================================================================

  Program:   Visualization Toolkit
  Module:    vtkSLACReader.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.

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

/*-------------------------------------------------------------------------
  Copyright 2008 Sandia Corporation.
  Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
  the U.S. Government retains certain rights in this software.
-------------------------------------------------------------------------*/

/**
 * @class   vtkSLACReader
 *
 *
 *
 * A reader for a data format used by Omega3p, Tau3p, and several other tools
 * used at the Standford Linear Accelerator Center (SLAC).  The underlying
 * format uses netCDF to store arrays, but also imposes several conventions
 * to form an unstructured grid of elements.
 *
*/

#ifndef vtkSLACReader_h
#define vtkSLACReader_h

#include "vtkIONetCDFModule.h" // For export macro
#include "vtkMultiBlockDataSetAlgorithm.h"

#include "vtkSmartPointer.h"      // For internal method.

class vtkDataArraySelection;
class vtkDoubleArray;
class vtkIdTypeArray;
class vtkInformationIntegerKey;
class vtkInformationObjectBaseKey;

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

  vtkGetStringMacro(MeshFileName);
  vtkSetStringMacro(MeshFileName);

  //@{
  /**
   * There may be one mode file (usually for actual modes) or multiple mode
   * files (which usually actually represent time series).  These methods
   * set and clear the list of mode files (which can be a single mode file).
   */
  virtual void AddModeFileName(const char *fname);
  virtual void RemoveAllModeFileNames();
  virtual unsigned int GetNumberOfModeFileNames();
  virtual const char *GetModeFileName(unsigned int idx);
  //@}

  //@{
  /**
   * If on, reads the internal volume of the data set.  Set to off by default.
   */
  vtkGetMacro(ReadInternalVolume, vtkTypeBool);
  vtkSetMacro(ReadInternalVolume, vtkTypeBool);
  vtkBooleanMacro(ReadInternalVolume, vtkTypeBool);
  //@}

  //@{
  /**
   * If on, reads the external surfaces of the data set.  Set to on by default.
   */
  vtkGetMacro(ReadExternalSurface, vtkTypeBool);
  vtkSetMacro(ReadExternalSurface, vtkTypeBool);
  vtkBooleanMacro(ReadExternalSurface, vtkTypeBool);
  //@}

  //@{
  /**
   * If on, reads midpoint information for external surfaces and builds
   * quadratic surface triangles.  Set to on by default.
   */
  vtkGetMacro(ReadMidpoints, vtkTypeBool);
  vtkSetMacro(ReadMidpoints, vtkTypeBool);
  vtkBooleanMacro(ReadMidpoints, vtkTypeBool);
  //@}

  //@{
  /**
   * Variable array selection.
   */
  virtual int GetNumberOfVariableArrays();
  virtual const char *GetVariableArrayName(int idx);
  virtual int GetVariableArrayStatus(const char *name);
  virtual void SetVariableArrayStatus(const char *name, int status);
  //@}

  //@{
  /**
   * Sets the scale factor for each mode. Each scale factor is reset to 1.
   */
  virtual void ResetFrequencyScales();
  virtual void SetFrequencyScale(int index, double scale);
  //@}

  //@{
  /**
   * Sets the phase offset for each mode. Each shift is reset to 0.
   */
  virtual void ResetPhaseShifts();
  virtual void SetPhaseShift(int index, double shift);
  //@}

  //@{
  /**
   * NOTE: This is not thread-safe.
   */
  virtual vtkDoubleArray* GetFrequencyScales();
  virtual vtkDoubleArray* GetPhaseShifts();
  //@}

  /**
   * Returns true if the given file can be read by this reader.
   */
  static int CanReadFile(const char *filename);

  /**
   * This key is attached to the metadata information of all data sets in the
   * output that are part of the internal volume.
   */
  static vtkInformationIntegerKey *IS_INTERNAL_VOLUME();

  /**
   * This key is attached to the metadata information of all data sets in the
   * output that are part of the external surface.
   */
  static vtkInformationIntegerKey *IS_EXTERNAL_SURFACE();

  //@{
  /**
   * All the data sets stored in the multiblock output share the same point
   * data.  For convenience, the point coordinates (vtkPoints) and point data
   * (vtkPointData) are saved under these keys in the vtkInformation of the
   * output data set.
   */
  static vtkInformationObjectBaseKey *POINTS();
  static vtkInformationObjectBaseKey *POINT_DATA();
  //@}

  //@{
  /**
   * Simple class used internally to define an edge based on the endpoints.  The
   * endpoints are canonically identified by the lower and higher values.
   */
  class VTKIONETCDF_EXPORT EdgeEndpoints
  {
  public:
    EdgeEndpoints() : MinEndPoint(-1), MaxEndPoint(-1) {}
    EdgeEndpoints(vtkIdType endpointA, vtkIdType endpointB) {
      if (endpointA < endpointB)
      {
        this->MinEndPoint = endpointA;  this->MaxEndPoint = endpointB;
      }
      else
      {
        this->MinEndPoint = endpointB;  this->MaxEndPoint = endpointA;
      }
    }
    inline vtkIdType GetMinEndPoint() const { return this->MinEndPoint; }
    inline vtkIdType GetMaxEndPoint() const { return this->MaxEndPoint; }
    inline bool operator==(const EdgeEndpoints &other) const {
      return (   (this->GetMinEndPoint() == other.GetMinEndPoint())
              && (this->GetMaxEndPoint() == other.GetMaxEndPoint()) );
    }
  protected:
    vtkIdType MinEndPoint;
    vtkIdType MaxEndPoint;
  };
  //@}

  //@{
  /**
   * Simple class used internally for holding midpoint information.
   */
  class VTKIONETCDF_EXPORT MidpointCoordinates
  {
  public:
    MidpointCoordinates() {}
    MidpointCoordinates(const double coord[3], vtkIdType id) {
      this->Coordinate[0] = coord[0];
      this->Coordinate[1] = coord[1];
      this->Coordinate[2] = coord[2];
      this->ID = id;
    }
    double Coordinate[3];
    vtkIdType ID;
  };
  //@}

  enum {
    SURFACE_OUTPUT = 0,
    VOLUME_OUTPUT = 1,
    NUM_OUTPUTS = 2
  };

protected:
  vtkSLACReader();
  ~vtkSLACReader() override;

  class vtkInternal;
  vtkInternal *Internal;

  // Friend so vtkInternal can access MidpointIdMap
  // (so Sun CC compiler doesn't complain).
  friend class vtkInternal;

  char *MeshFileName;

  vtkTypeBool ReadInternalVolume;
  vtkTypeBool ReadExternalSurface;
  vtkTypeBool ReadMidpoints;

  /**
   * True if reading from a proper mode file.  Set in RequestInformation.
   */
  bool ReadModeData;

  /**
   * True if "mode" files are a sequence of time steps.
   */
  bool TimeStepModes;

  /**
   * True if mode files describe vibrating fields.
   */
  bool FrequencyModes;

  int RequestInformation(vtkInformation *request,
                                 vtkInformationVector **inputVector,
                                 vtkInformationVector *outputVector) override;

  int RequestData(vtkInformation *request,
                          vtkInformationVector **inputVector,
                          vtkInformationVector *outputVector) override;

  /**
   * Callback registered with the VariableArraySelection.
   */
  static void SelectionModifiedCallback(vtkObject *caller, unsigned long eid,
                                        void *clientdata, void *calldata);

  /**
   * Convenience function that checks the dimensions of a 2D netCDF array that
   * is supposed to be a set of tuples.  It makes sure that the number of
   * dimensions is expected and that the number of components in each tuple
   * agree with what is expected.  It then returns the number of tuples.  An
   * error is emitted and 0 is returned if the checks fail.
   */
  virtual vtkIdType GetNumTuplesInVariable(int ncFD, int varId,
                                           int expectedNumComponents);

  /**
   * Checks the winding of the tetrahedra in the mesh file.  Returns 1 if
   * the winding conforms to VTK, 0 if the winding needs to be corrected.
   */
  virtual int CheckTetrahedraWinding(int meshFD);

  /**
   * Read the connectivity information from the mesh file.  Returns 1 on
   * success, 0 on failure.
   */
  virtual int ReadConnectivity(int meshFD, vtkMultiBlockDataSet *surfaceOutput,
                               vtkMultiBlockDataSet *volumeOutput);

  //@{
  /**
   * Reads tetrahedron connectivity arrays.  Called by ReadConnectivity.
   */
  virtual int ReadTetrahedronInteriorArray(int meshFD,
                                           vtkIdTypeArray *connectivity);
  virtual int ReadTetrahedronExteriorArray(int meshFD,
                                           vtkIdTypeArray *connectivity);
  //@}

  /**
   * Reads point data arrays.  Called by ReadCoordinates and ReadFieldData.
   */
  virtual vtkSmartPointer<vtkDataArray> ReadPointDataArray(int ncFD, int varId);

  /**
   * Helpful constants equal to the amount of identifiers per tet.
   */
  enum {
    NumPerTetInt = 5,
    NumPerTetExt = 9
  };

  //@{
  /**
   * Manages a map from edges to midpoint coordinates.
   */
  class VTKIONETCDF_EXPORT MidpointCoordinateMap
  {
  public:
    MidpointCoordinateMap();
    ~MidpointCoordinateMap();
  //@}

    void AddMidpoint(const EdgeEndpoints &edge,
                     const MidpointCoordinates &midpoint);
    void RemoveMidpoint(const EdgeEndpoints &edge);
    void RemoveAllMidpoints();
    vtkIdType GetNumberOfMidpoints() const;

    /**
     * Finds the coordinates for the given edge or returns nullptr if it
     * does not exist.
     */
    MidpointCoordinates *FindMidpoint(const EdgeEndpoints &edge);

  protected:
    class vtkInternal;
    vtkInternal *Internal;

  private:
    // Too lazy to implement these.
    MidpointCoordinateMap(const MidpointCoordinateMap &);
    void operator=(const MidpointCoordinateMap &);
  };

  //@{
  /**
   * Manages a map from edges to the point id of the midpoint.
   */
  class VTKIONETCDF_EXPORT MidpointIdMap
  {
  public:
    MidpointIdMap();
    ~MidpointIdMap();
  //@}

    void AddMidpoint(const EdgeEndpoints &edge, vtkIdType midpoint);
    void RemoveMidpoint(const EdgeEndpoints &edge);
    void RemoveAllMidpoints();
    vtkIdType GetNumberOfMidpoints() const;

    /**
     * Finds the id for the given edge or returns nullptr if it does not exist.
     */
    vtkIdType *FindMidpoint(const EdgeEndpoints &edge);

    /**
     * Initialize iteration.  The iteration can occur in any order.
     */
    void InitTraversal();
    /**
     * Get the next midpoint in the iteration.  Return 0 if the end is reached.
     */
    bool GetNextMidpoint(EdgeEndpoints &edge, vtkIdType &midpoint);

  protected:
    class vtkInternal;
    vtkInternal *Internal;

  private:
    // Too lazy to implement these.
    MidpointIdMap(const MidpointIdMap &);
    void operator=(const MidpointIdMap &);
  };

  /**
   * Read in the point coordinate data from the mesh file.  Returns 1 on
   * success, 0 on failure.
   */
  virtual int ReadCoordinates(int meshFD, vtkMultiBlockDataSet *output);

  /**
   * Reads in the midpoint coordinate data from the mesh file and returns a map
   * from edges to midpoints.  This method is called by ReadMidpointData.
   * Returns 1 on success, 0 on failure.
   */
  virtual int ReadMidpointCoordinates(int meshFD, vtkMultiBlockDataSet *output,
                                      MidpointCoordinateMap &map);

  /**
   * Read in the midpoint data from the mesh file.  Returns 1 on success,
   * 0 on failure.  Also fills a midpoint id map that will be passed into
   * InterpolateMidpointFieldData.
   */
  virtual int ReadMidpointData(int meshFD, vtkMultiBlockDataSet *output,
                               MidpointIdMap &map);

  /**
   * Instead of reading data from the mesh file, restore the data from the
   * previous mesh file read.
   */
  virtual int RestoreMeshCache(vtkMultiBlockDataSet *surfaceOutput,
                               vtkMultiBlockDataSet *volumeOutput,
                               vtkMultiBlockDataSet *compositeOutput);

  /**
   * Read in the field data from the mode file.  Returns 1 on success, 0
   * on failure.
   */
  virtual int ReadFieldData(const int *modeFDArray,
                            int numModeFDs,
                            vtkMultiBlockDataSet *output);

  /**
   * Takes the data read on the fields and interpolates data for the midpoints.
   * map is the same map that was created in ReadMidpointData.
   */
  virtual int InterpolateMidpointData(vtkMultiBlockDataSet *output,
                                      MidpointIdMap &map);

  /**
   * A time stamp for the last time the mesh file was read.  This is used to
   * determine whether the mesh needs to be read in again or if we just need
   * to read the mode data.
   */
  vtkTimeStamp MeshReadTime;

  /**
   * Returns 1 if the mesh is up to date, 0 if the mesh needs to be read from
   * disk.
   */
  virtual int MeshUpToDate();

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

#endif //vtkSLACReader_h
