/*  This file is part of the OpenLB library
 *
 *  Copyright (C) 2007, 2014 Mathias J. Krause
 *  E-mail contact: info@openlb.net
 *  The most recent release of OpenLB can be downloaded at
 *  
 *
 *  This program is free software; you can redistribute it and/or
 *  modify it under the terms of the GNU General Public License
 *  as published by the Free Software Foundation; either version 2
 *  of the License, or (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public
 *  License along with this program; if not, write to the Free
 *  Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 *  Boston, MA  02110-1301, USA.
*/
/** \file
 * The description of a vector of 3D cuboid -- header file.
 */
#ifndef CUBOID_GEOMETRY_3D_H
#define CUBOID_GEOMETRY_3D_H
#include 
#include 
#include "core/singleton.h"
#include "core/serializer.h"
#include "io/ostreamManager.h"
#include "io/xmlReader.h"
#include "core/vector.h"
#include "geometry/cuboid3D.h"
/// All OpenLB code is contained in this namespace.
namespace olb {
template< typename T> class Cuboid3D;
template  class IndicatorF3D;
template  class LoadBalancer;
/// A cuboid geometry represents a voxel mesh
/** A cuboid geometry is given by a number of cuboids. To represent
 * a connected domain it is required that the distance between two
 * neighbouring cuboids is less than the smallest delta of them.
 *
 * By the class, access is provided to the cuboids. Core methods of
 * the class are transforming lattice to physical positions in the
 * corresponding unit systems.
 *
 * WARNING:
 * At the moment there are only cuboids with a constant delta possible
 * and the distance between two neighbouring cuboids must be delta
 * since an interpolation operator in time and space is missing in
 * cuboidNeighbourhood and superLattice.
 *
 * This class is not intended to be derived from.
 */
template
class CuboidGeometry3D : public BufferSerializable {
private:
  /// Vector of the cuboids
  std::vector > _cuboids;
  /// Cuboid which contains all other cuboids
  Cuboid3D _motherCuboid;
  /// Periodicity flag
  Vector _periodicityOn;
  /// class specific ostream
  mutable OstreamManager clout;
public:
  /// Constructs empty Geometry
  CuboidGeometry3D();
  /// Constructs a cuboid geometry with a cubic shape of size nX times nY times nZ with origin originR=(originX, originY, originZ) that consists of nC cuboids
  CuboidGeometry3D(T originX, T originY, T originZ, T deltaR, int nX, int nY, int nZ, int nC=1);
  /// Constructor with vectors
  CuboidGeometry3D(std::vector origin, T deltaR, std::vector extent, int nC=1);
  /// Constructs a cuboid structure with a uniform spacing of voxelSize which consists of  nC cuboids, the cuboids not needed are removed and too big ones are shrinked
  CuboidGeometry3D(IndicatorF3D& indicatorF, T voxelSize, int nC=1);
  CuboidGeometry3D(std::shared_ptr> indicator_sharedPtrF, T voxelSize, int nC=1);
  /// Constructs a cuboid structure with a uniform spacing of voxelSize which consists of nC cuboids, the cuboids not needed are removed and too big ones are shrinked. Uses an iterative method: Halves the largest child cuboid until the set number of cuboids is reached. Largest cuboid is determined by either volume or weight as choosen in minimizeBy.
  CuboidGeometry3D(IndicatorF3D& indicatorF, T voxelSize, int nC, std::string minimizeBy);
  CuboidGeometry3D(std::shared_ptr> indicator_sharedPtrF, T voxelSize, int nC, std::string minimizeBy);
  /// Destructs CuboidGeometry
  virtual ~CuboidGeometry3D();
  /// Read and write access to a single cuboid
  Cuboid3D& get(int iC);
  /// Read access to a single cuboid
  Cuboid3D const& get(int iC) const;
  /// Returns the smallest cuboid that includes all cuboids of the structure
  Cuboid3D getMotherCuboid();
  Cuboid3D const& getMotherCuboid() const;
  /// Set flag to enable/disable periodicity depending of direction. Be aware that not all directions are true to ensure boundary conditions like for velocity are not disturbed.
  void setPeriodicity(bool periodicityX, bool periodicityY, bool periodicityZ);
  /// Gives for a given point (globX/globY/globZ) the related cuboidID
  /// and _p if the point is not in any of the cuboid _childrenQ
  int get_iC(T globX, T globY, T globZ, int offset = 0) const; //TODO old ones
  int get_iC(Vector, int offset = 0) const;
  /// This function checks if the points (globX/globY/globZ) and
  /// (globX + orientationX/delta/globY + orientationY/delta/
  /// globZ + orientationZ/delta) is in a cuboid.
  /// It gives the related cuboidID and _p if the points are
  /// not in any of the cuboids.
  /// abs(orientationX) = abs(orientationY) = abs(orientationY) = 1
  /// must be satisfied
  int get_iC(T globX, T globY, T globZ, int orientationX, int orientationY, int orientationZ) const; //TODO old ones
  /// Returns true and the cuboid number of the nearest lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getC(T physR[3], int& iC) const;
  /// Returns true and the cuboid number of the nearest lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getC(std::vector physR, int& iC) const; //TODO new one
  /// Returns true and the cuboid number of the nearest lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getC(const Vector& physR, int& iC) const;
  /// Returns true and the nearest lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getLatticeR(int latticeR[4], const T physR[3]) const;
  /// Returns true and the floor lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getFloorLatticeR(const std::vector& physR, std::vector& latticeR) const;
  /// Returns true and the floor lattice position to the given physical position if the physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  bool getFloorLatticeR(const Vector& physR, Vector& latticeR) const;
  /// Returns the physical position to the given lattice position respecting periodicity for the overlap nodes which are not in the mother cuboid for the case the flag periodicityOn[iDim]=true if the   physical position is within any of the cuboids with an overlap of 1/2*delta belonging to the cuboid geometry
  void getPhysR(T physR[3], const int& iCglob,  const int& iX, const int& iY, const int& iZ) const;
  /// Returns the physical position to the given lattice position respecting periodicity for the overlap nodes which are not in the mother cuboid for the case the flag periodicityOn[iDim]=true
  void getPhysR(T physR[3], const int latticeR[4]) const;
  /// Stores the iC of the neighbouring cuboids in a vector;
  void getNeighbourhood(int cuboid, std::vector& neighbours, int offset = 0);
  /// Returns the number of cuboids in the structure
  int getNc() const;
  /// Returns the minimum of the ratio nX/NY in the structure
  T getMinRatio() const;
  /// Returns the maximum of the ratio nX/NY in the structure
  T getMaxRatio() const;
  /// Returns the minimum coordinate in the structure
  Vector getMinPhysR() const;
  /// Returns the maximum coordinate in the structure
  Vector getMaxPhysR() const;
  /// Returns the minimum volume in the structure
  T getMinPhysVolume() const;
  /// Returns the maximum volume in the structure
  T getMaxPhysVolume() const;
  /// Returns the minimum number of nodes in the structure
  size_t getMinLatticeVolume() const;
  /// Returns the maximum number of nodes in the structure
  size_t getMaxLatticeVolume() const;
  /// Returns the minimum number of nodes in the structure inside the indicator
  size_t getMinLatticeWeight() const;
  /// Returns the maximum number of nodes in the structure inside the indicator
  size_t getMaxLatticeWeight() const;
  /// Returns the minimum delta in the structure
  T getMinDeltaR() const;
  /// Returns the maximum delta in the structure
  T getMaxDeltaR() const;
  /// Compares two CuboidGeometries
  bool operator==(CuboidGeometry3D& rhs);
  /// Swaps data from input into object
  void swap(CuboidGeometry3D& rhs);
  /// Swaps the vector of cuboids
  void swapCuboids(std::vector< Cuboid3D >& cuboids);
  /// Replace the vector of cuboids
  void replaceCuboids(std::vector< Cuboid3D >& cuboids);
  /// Sets the number of full cells of each cuboid
  void setWeights(IndicatorF3D& indicatorF);
  /// Resets the cuboid array
  void clearCuboids()
  {
    _cuboids.clear();
  }
  /// Adds a cuboid
  void add(Cuboid3D cuboid);
  /// Splits cuboid iC, removes it and adds p cuboids of same volume
  void split(int iC, int p);
  /// Splits cuboid iC, removes it and adds p cuboids of same weight
  void splitByWeight(int iC, int p, IndicatorF3D& indicatorF);
  /// Removes the cuboid iC
  void remove(int iC);
  /// Removes all cuboids where indicatorF = 0
  void remove(IndicatorF3D& indicatorF);
  /// Shrink cuboid iC so that no empty planes are left
  void shrink(int iC, IndicatorF3D& indicatorF);
  /// Shrink all cuboids so that no empty planes are left
  void shrink(IndicatorF3D& indicatorF);
  /// Number of data blocks for the serializer interface
  size_t getNblock() const override;
  /// Binary size for the serializer interface
  size_t getSerializableSize() const override;
  /// Return a pointer to the memory of the current block and its size for the serializable interface
  bool* getBlock(std::size_t iBlock, std::size_t& sizeBlock, bool loadingMode) override;
  /// Prints cuboid geometry details
  void print() const;
  /// Prints cuboid geometry details plus details of all cuboids
  void printExtended();
  /// Save CuboidGeometry into an existing XML File
  void writeToExistingFile(std::string completeFileName, LoadBalancer& loadBalancer);
  /// Save CuboidGeometry into XML File
  void writeToFile(std::string fileName, LoadBalancer& loadBalancer);
private:
  /// Helper Function to create cuboid parameters for XML tag
  std::string _cuboidParameters(Cuboid3D const& cub);
};
/// Helper Function to retrieve nData-dimensional std::vector of type S from space separated tag
template
std::vector getDataFromTag(XMLreader const& reader, std::string attrName, int nData)
{
  std::vector values(nData, S());
  std::stringstream extstr(reader.getAttribute(attrName));
  for (auto& valueI: values) {
    extstr >> valueI;
  }
  return values;
}
/// Load CuboidGeometry from XML File
template
CuboidGeometry3D* createCuboidGeometry(std::string fileName)
{
  OstreamManager clout("saveCuboidGeometry3D");
  std::string fname = singleton::directories().getLogOutDir() + fileName + ".xml";
  XMLreader reader(fname);
  std::vector origin = getDataFromTag(reader["CuboidGeometry"], "origin", 3);
  std::vector extent = getDataFromTag(reader["CuboidGeometry"], "extent", 3);
  T deltaR = getDataFromTag(reader["CuboidGeometry"], "deltaR", 1)[0];
  size_t weight = getDataFromTag(reader["CuboidGeometry"], "weight", 1)[0];
  int refinementLevel = getDataFromTag(reader["CuboidGeometry"], "refinementLevel", 1)[0];
  CuboidGeometry3D* cGeo = new CuboidGeometry3D (origin, deltaR, extent);
  cGeo->getMotherCuboid().setRefinementLevel(refinementLevel);
  cGeo->getMotherCuboid().setWeight(weight);
  cGeo->clearCuboids();
  for ( XMLreader* cub: reader["CuboidGeometry"] ) {
    origin = getDataFromTag(*cub, "origin", 3);
    extent = getDataFromTag(*cub, "extent", 3);
    deltaR = getDataFromTag(*cub, "deltaR", 1)[0];
    weight = getDataFromTag(*cub, "weight", 1)[0];
    refinementLevel = getDataFromTag(*cub, "refinementLevel", 1)[0];
    cGeo->add( Cuboid3D(origin, deltaR, extent, refinementLevel) );
    cGeo->get(cGeo->getNc() - 1).setWeight(weight);
  }
  return cGeo;
}
}  // namespace olb
#endif