/*  This file is part of the OpenLB library
 *
 *  Copyright (C) 2012 Jonas Kratzke, 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
 * A helper for initialising 3D boundaries -- generic implementation.
 */
#ifndef SUPER_OFF_BOUNDARY_CONDITION_3D_HH
#define SUPER_OFF_BOUNDARY_CONDITION_3D_HH
#include 
#include 
#include "offBoundaryCondition3D.h"
#include "superOffBoundaryCondition3D.h"
#include "core/superLattice3D.h"
#include "core/util.h"
#include "functors/analytical/analyticalF.h"
#include "functors/lattice/indicator/superIndicatorF3D.h"
namespace olb {
///////// class superOffBoundaryCondition3D ///////////////////////////////
template
sOffLatticeBoundaryCondition3D::sOffLatticeBoundaryCondition3D(
  SuperLattice3D& sLattice, T epsFraction )
  : clout(std::cout,"sOffLatticeBoundaryCondition3D"),
    _sLattice(sLattice),
    _epsFraction(epsFraction),
    _output(false)
{}
template
sOffLatticeBoundaryCondition3D::sOffLatticeBoundaryCondition3D(
  sOffLatticeBoundaryCondition3D const& rhs)
  : clout(std::cout,"sOffLatticeBoundaryCondition3D"),
    _sLattice(rhs._sLattice),
    _epsFraction(rhs._epsFraction),
    _output(false)
{
  _blockBCs = rhs._blockBCs;
  _overlap = rhs._overlap;
}
template
sOffLatticeBoundaryCondition3D sOffLatticeBoundaryCondition3D::operator=(
  sOffLatticeBoundaryCondition3D rhs)
{
  sOffLatticeBoundaryCondition3D tmp(rhs);
  return tmp;
}
template
sOffLatticeBoundaryCondition3D::~sOffLatticeBoundaryCondition3D()
{
  for (unsigned iC=0; iC<_blockBCs.size(); iC++) {
    delete _blockBCs[iC];
  }
}
template
void sOffLatticeBoundaryCondition3D::addVelocityBoundary(
  FunctorPtr>&& boundaryIndicator,
  FunctorPtr>&& bulkIndicator,
  IndicatorF3D&                   geometryIndicator)
{
  if (_output) {
    clout << "epsFraction=" << _epsFraction << std::endl;
    clout.setMultiOutput(true);
  }
  for (int iCloc = 0; iCloc < _sLattice.getLoadBalancer().size(); ++iCloc) {
    if (_output) {
      clout << "Cuboid globiC " << _sLattice.getLoadBalancer().glob(iCloc)
            << " starts to read distances for Velocity Boundary..." << std::endl;
    }
    _blockBCs[iCloc]->addVelocityBoundary(
      boundaryIndicator->getExtendedBlockIndicatorF(iCloc),
      bulkIndicator->getExtendedBlockIndicatorF(iCloc),
      geometryIndicator);
    if (_output) {
      clout << "Cuboid globiC " << _sLattice.getLoadBalancer().glob(iCloc)
            << " finished reading distances for Velocity Boundary." << std::endl;
    }
  }
  if (_output) {
    clout.setMultiOutput(false);
  }
  addPoints2CommBC(std::forward(boundaryIndicator));
}
template
void sOffLatticeBoundaryCondition3D::addVelocityBoundary(
  SuperGeometry3D& superGeometry, int material,
  IndicatorF3D& geometryIndicator, std::vector bulkMaterials)
{
  addVelocityBoundary(superGeometry.getMaterialIndicator(material),
                      superGeometry.getMaterialIndicator(std::move(bulkMaterials)),
                      geometryIndicator);
}
template
void sOffLatticeBoundaryCondition3D::addZeroVelocityBoundary(
  FunctorPtr>&& boundaryIndicator,
  FunctorPtr>&& bulkIndicator,
  IndicatorF3D&                   geometryIndicator)
{
  if (_output) {
    clout << "epsFraction=" << _epsFraction << std::endl;
    clout.setMultiOutput(true);
  }
  for (int iCloc = 0; iCloc < _sLattice.getLoadBalancer().size(); ++iCloc) {
    if (_output) {
      clout << "Cuboid globiC " << _sLattice.getLoadBalancer().glob(iCloc)
            << " starts to read distances for ZeroVelocity Boundary..." << std::endl;
    }
    _blockBCs[iCloc]->addZeroVelocityBoundary(
      boundaryIndicator->getExtendedBlockIndicatorF(iCloc),
      bulkIndicator->getExtendedBlockIndicatorF(iCloc),
      geometryIndicator);
    if (_output) {
      clout << "Cuboid globiC " << _sLattice.getLoadBalancer().glob(iCloc)
            << " finished reading distances for ZeroVelocity Boundary." << std::endl;
    }
  }
  if (_output) {
    clout.setMultiOutput(false);
  }
  addPoints2CommBC(std::forward(boundaryIndicator));
}
template
void sOffLatticeBoundaryCondition3D::addZeroVelocityBoundary(
  FunctorPtr>&& boundaryIndicator,
  IndicatorF3D& geometryIndicator, std::vector bulkMaterials)
{
  SuperGeometry3D& superGeometry = boundaryIndicator->getSuperGeometry();
  addZeroVelocityBoundary(
    std::forward(boundaryIndicator),
    superGeometry.getMaterialIndicator(std::move(bulkMaterials)),
    geometryIndicator);
}
template
void sOffLatticeBoundaryCondition3D::addZeroVelocityBoundary(
  SuperGeometry3D& superGeometry, int material,
  IndicatorF3D& geometryIndicator, std::vector bulkMaterials)
{
  addZeroVelocityBoundary(superGeometry.getMaterialIndicator(material),
                          geometryIndicator,
                          bulkMaterials);
}
template
void sOffLatticeBoundaryCondition3D::defineU(
  FunctorPtr>&& boundaryIndicator,
  FunctorPtr>&& bulkIndicator,
  AnalyticalF3D& u)
{
  for (int iCloc = 0; iCloc < _sLattice.getLoadBalancer().size(); ++iCloc) {
    _blockBCs[iCloc]->defineU(boundaryIndicator->getExtendedBlockIndicatorF(iCloc),
                              bulkIndicator->getExtendedBlockIndicatorF(iCloc),
                              u);
  }
}
template
void sOffLatticeBoundaryCondition3D::defineU(
  FunctorPtr>&& boundaryIndicator,
  AnalyticalF3D& u, std::vector bulkMaterials)
{
  defineU(std::forward(boundaryIndicator),
          boundaryIndicator->getSuperGeometry().getMaterialIndicator(std::move(bulkMaterials)),
          u);
}
template
void sOffLatticeBoundaryCondition3D::defineU(
  SuperGeometry3D& superGeometry, int material,
  AnalyticalF3D& u, std::vector bulkMaterials)
{
  defineU(superGeometry.getMaterialIndicator(material), u, bulkMaterials);
}
template
void sOffLatticeBoundaryCondition3D::addPoints2CommBC(
  FunctorPtr>&& indicator)
{
  if (_overlap == 0) {
    return;
  }
  SuperGeometry3D& superGeometry = indicator->getSuperGeometry();
  for (int iCloc = 0; iCloc < _sLattice.getLoadBalancer().size(); ++iCloc) {
    const int nX = superGeometry.getBlockGeometry(iCloc).getNx();
    const int nY = superGeometry.getBlockGeometry(iCloc).getNy();
    const int nZ = superGeometry.getBlockGeometry(iCloc).getNz();
    for (int iX=-_overlap; iX nX - 1 ||
              iY < 0 || iY > nY - 1 ||
              iZ < 0 || iZ > nZ - 1 ) { // is inside boundary
            int found = false;
            if (superGeometry.getBlockGeometry(iCloc).getMaterial(iX,iY,iZ) != 0) {
              for (int iXo=-_overlap; iXo<=_overlap && !found; ++iXo) {
                for (int iYo=-_overlap; iYo<=_overlap && !found; ++iYo) {
                  for (int iZo=-_overlap; iZo<=_overlap && !found; ++iZo) {
                    const int nextX = iXo + iX;
                    const int nextY = iYo + iY;
                    const int nextZ = iZo + iZ;
                    if (indicator->getBlockIndicatorF(iCloc)(nextX, nextY, nextZ)) {
                      _sLattice.get_commBC().add_cell(iCloc, iX, iY, iZ);
                      found = true;
                    }
                  }
                }
              }
            }
          }
        }
      }
    }
  }
}
template
void sOffLatticeBoundaryCondition3D::addPoints2CommBC(
  SuperGeometry3D& superGeometry, int material)
{
  addPoints2CommBC(superGeometry.getMaterialIndicator(material));
}
template
SuperLattice3D& sOffLatticeBoundaryCondition3D::getSuperLattice()
{
  return _sLattice;
}
template
std::vector* >& sOffLatticeBoundaryCondition3D::getBlockBCs()
{
  return _blockBCs;
}
template
int sOffLatticeBoundaryCondition3D::getOverlap()
{
  return _overlap;
}
template
void sOffLatticeBoundaryCondition3D::setOverlap(int overlap)
{
  _overlap = overlap;
}
template
void sOffLatticeBoundaryCondition3D::outputOn()
{
  _output = true;
  int nC = _sLattice.getLoadBalancer().size();
  for (int iCloc = 0; iCloc < nC; iCloc++) {
    _blockBCs[iCloc]->outputOn();
  }
}
template
void sOffLatticeBoundaryCondition3D::outputOff()
{
  _output = false;
  int nC = _sLattice.getLoadBalancer().size();
  for (int iCloc = 0; iCloc < nC; iCloc++) {
    _blockBCs[iCloc]->outputOff();
  }
}
////////////////// Factory functions //////////////////////////////////
template
void createBouzidiBoundaryCondition3D(sOffLatticeBoundaryCondition3D& sBC)
{
  int nC = sBC.getSuperLattice().getLoadBalancer().size();
  sBC.setOverlap(1);
  for (int iC=0; iC* blockBC
      = createBouzidiBoundaryCondition3D(sBC.getSuperLattice().getExtendedBlockLattice(iC));
    sBC.getBlockBCs().push_back(blockBC);
  }
}
}  // namespace olb
#endif