/*
   For more information, please see: http://software.sci.utah.edu

   The MIT License

   Copyright (c) 2015 Scientific Computing and Imaging Institute,
   University of Utah.

   Permission is hereby granted, free of charge, to any person obtaining a
   copy of this software and associated documentation files (the "Software"),
   to deal in the Software without restriction, including without limitation
   the rights to use, copy, modify, merge, publish, distribute, sublicense,
   and/or sell copies of the Software, and to permit persons to whom the
   Software is furnished to do so, subject to the following conditions:

   The above copyright notice and this permission notice shall be included
   in all copies or substantial portions of the Software.

   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
   OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
   FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
   THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
   LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
   DEALINGS IN THE SOFTWARE.
*/

#include <Core/Algorithms/Legacy/Fields/Mapping/MapFieldDataFromSourceToDestination.h>
#include <Core/Algorithms/Base/AlgorithmVariableNames.h>
#include <Core/Algorithms/Base/AlgorithmPreconditions.h>
#include <Core/Thread/Parallel.h>
#include <Core/Thread/Barrier.h>

#include <Core/Datatypes/Legacy/Field/Field.h>
#include <Core/Datatypes/Legacy/Field/VField.h>
#include <Core/Datatypes/Legacy/Field/VMesh.h>
#include <Core/Datatypes/Matrix.h>
#include <Core/Datatypes/SparseRowMatrix.h>
#include <Core/Datatypes/Legacy/Field/FieldInformation.h>

#include <boost/scoped_ptr.hpp>

using namespace SCIRun::Core::Algorithms::Fields;
using namespace SCIRun::Core::Geometry;
using namespace SCIRun::Core::Datatypes;
using namespace SCIRun::Core::Utility;
using namespace SCIRun::Core::Algorithms;
using namespace SCIRun::Core::Logging;
using namespace SCIRun::Core::Thread;
using namespace SCIRun;

ALGORITHM_PARAMETER_DEF(Fields, DefaultValue);
ALGORITHM_PARAMETER_DEF(Fields, MappingMethod);

const AlgorithmOutputName MapFieldDataFromSourceToDestinationAlgo::Remapped_Destination("Remapped_Destination");

MapFieldDataFromSourceToDestinationAlgo::MapFieldDataFromSourceToDestinationAlgo()
{
  using namespace Parameters;
  addParameter(DefaultValue, 0.0);
  addParameter(MaxDistance, -1.0);
  addOption(MappingMethod, "interpolateddata", "interpolateddata|closestdata|singledestination");
}

namespace detail
{
  class MapFieldDataFromSourceToDestinationPAlgoBase : public Interruptible
  {
  public:
    explicit MapFieldDataFromSourceToDestinationPAlgoBase(const std::string& name, int nproc) :
      sfield_(0), dfield_(0), smesh_(0), dmesh_(0), maxdist_(0), algo_(0),
      barrier_(name, nproc), nproc_(nproc) {}

    virtual void parallel(int proc) = 0;

    VField* sfield_;
    VField* dfield_;
    VMesh*  smesh_;
    VMesh*  dmesh_;

    double  maxdist_;
    const AlgorithmBase* algo_;

  protected:
    Barrier barrier_;
    int nproc_;
  };

//------------------------------------------------------------
// Algorithm - each destination has its closest source

class MapFieldDataFromSourceToDestinationClosestDataPAlgo : public MapFieldDataFromSourceToDestinationPAlgoBase
{
  public:
    explicit MapFieldDataFromSourceToDestinationClosestDataPAlgo(int nproc) :
      MapFieldDataFromSourceToDestinationPAlgoBase(" MapFieldDataFromSourceToDestinationClosestDataPAlgo Barrier", nproc) {}

    virtual void parallel(int proc) override;
};

void
MapFieldDataFromSourceToDestinationClosestDataPAlgo::parallel(int proc)
{
  // Determine which ones to run
  VField::size_type num_values = dfield_->num_values();
  VField::size_type localsize = num_values/nproc_;
  VField::index_type start = localsize*proc;
  VField::index_type end = localsize*(proc+1);
  if (proc == nproc_-1) end = num_values;

  barrier_.wait();

  int cnt = 0;

  if (dfield_->basis_order() == 0 && sfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::Elem::index_type didx;

    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_elem(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 1 && sfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::Elem::index_type didx;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_elem(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 0 && sfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::Node::index_type didx;
    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_node(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 1 && sfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::Node::index_type didx;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_node(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }

  barrier_.wait();
}

//------------------------------------------------------------
// Algorithm - each source will map to one destination

class MapFieldDataFromSourceToDestinationSingleDestinationPAlgo : public MapFieldDataFromSourceToDestinationPAlgoBase
{
  public:
    explicit MapFieldDataFromSourceToDestinationSingleDestinationPAlgo(int nproc) :
      MapFieldDataFromSourceToDestinationPAlgoBase(" MapFieldDataFromSourceToDestinationSingleDestinationPAlgo Barrier", nproc) {}

    virtual void parallel(int proc) override;

    std::vector<index_type> tcc_;
    std::vector<index_type> cc_;
};

void
MapFieldDataFromSourceToDestinationSingleDestinationPAlgo::parallel(int proc)
{
  // Determine which ones to run
  VField::size_type num_values = sfield_->num_values();
  VField::size_type localsize = num_values/nproc_;
  VField::index_type start = localsize*proc;
  VField::index_type end = localsize*(proc+1);
  if (proc == nproc_-1) end = num_values;

  if (proc == 0)
  {
    tcc_.resize(dfield_->num_values(),-1);
    cc_.resize(sfield_->num_values(),-1);
  }

  barrier_.wait();
  int cnt = 0;

  if (sfield_->basis_order() == 0 && dfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::coords_type coords;
    VMesh::Elem::index_type didx;
    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      smesh_->get_center(p,idx);
      double dist;
      if(dmesh_->find_closest_elem(dist,r,coords,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          cc_[idx] = didx;
        }
        else cc_[idx] = -1;
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (sfield_->basis_order() == 1 && dfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::coords_type coords;
    VMesh::Elem::index_type didx;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      smesh_->get_center(p,idx);
      double dist;
      if(dmesh_->find_closest_elem(dist,r,coords,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          cc_[idx] = didx;
        }
        else cc_[idx] = -1;
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (sfield_->basis_order() == 0 && dfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::Node::index_type didx;
    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      smesh_->get_center(p,idx);
      double dist;
      if(dmesh_->find_closest_node(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          cc_[idx] = didx;
        }
        else cc_[idx] = -1;
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (sfield_->basis_order() == 1 && dfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::Node::index_type didx;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      smesh_->get_center(p,idx);
      double dist;
      if(dmesh_->find_closest_node(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          cc_[idx] = didx;
        }
        else cc_[idx] = -1;
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }

  barrier_.wait();

  // Copy the data thread safe
  if (proc == 0)
  {
    VField::size_type num_dvalues = dfield_->num_values();

    for (VMesh::index_type idx=0; idx<num_values;idx++)
    {
      if (cc_[idx] >= 0) tcc_[cc_[idx]] = idx;
    }

    for (VMesh::index_type idx=0; idx<num_dvalues;idx++)
    {
      if (tcc_[idx] >= 0)
      {
        dfield_->copy_value(sfield_,tcc_[idx],idx);
      }
    }
  }
}

//------------------------------------------------------------
// Algorithm - get interpolated data

class MapFieldDataFromSourceToDestinationInterpolatedDataPAlgo : public MapFieldDataFromSourceToDestinationPAlgoBase
{
  public:
    explicit MapFieldDataFromSourceToDestinationInterpolatedDataPAlgo(int nproc) :
      MapFieldDataFromSourceToDestinationPAlgoBase(" MapFieldDataFromSourceToDestinationInterpolatedDataPAlgo Barrier", nproc) {}

    virtual void parallel(int proc) override;
};

void
MapFieldDataFromSourceToDestinationInterpolatedDataPAlgo::parallel(int proc)
{
  // Determine which ones to run
  VField::size_type num_values = dfield_->num_values();
  VField::size_type localsize = num_values/nproc_;
  VField::index_type start = localsize*proc;
  VField::index_type end = localsize*(proc+1);
  if (proc == nproc_-1) end = num_values;

  barrier_.wait();

  int cnt = 0;
  if (dfield_->basis_order() == 0 && sfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::Elem::index_type didx;

    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);

      double dist;
      if(smesh_->find_closest_elem(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 1 && sfield_->basis_order() == 0)
  {
    Point p, r;
    VMesh::Elem::index_type didx;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_elem(dist,r,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          dfield_->copy_value(sfield_,didx,idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 0 && sfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::coords_type coords;
    VMesh::Elem::index_type didx;
    VMesh::ElemInterpolate interp;
    for (VMesh::Elem::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_elem(dist,r,coords,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          smesh_->get_interpolate_weights(coords,didx,interp,1);
          dfield_->copy_weighted_value(sfield_,&(interp.node_index[0]),
              &(interp.weights[0]),interp.node_index.size(),idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }
  else if (dfield_->basis_order() == 1 && sfield_->basis_order() == 1)
  {
    Point p, r;
    VMesh::coords_type coords;
    VMesh::Elem::index_type didx;
    VMesh::ElemInterpolate interp;
    for (VMesh::Node::index_type idx=start; idx<end;idx++)
    {
      checkForInterruption();
      dmesh_->get_center(p,idx);
      double dist;
      if(smesh_->find_closest_elem(dist,r,coords,didx,p))
      {
        if (maxdist_ < 0.0 || dist < maxdist_)
        {
          smesh_->get_interpolate_weights(coords,didx,interp,1);
          dfield_->copy_weighted_value(sfield_,&(interp.node_index[0]),
              &(interp.weights[0]),interp.node_index.size(),idx);
        }
      }
      if (proc == 0) { cnt++; if (cnt == 200) {cnt = 0; algo_->update_progress_max(idx,end); } }
    }
  }

  barrier_.wait();
}

}

bool
MapFieldDataFromSourceToDestinationAlgo::runImpl(FieldHandle source, FieldHandle destination, FieldHandle& output) const
{
  ScopedAlgorithmStatusReporter asr(this, "MapFieldDataFromSourceToDestination");
  using namespace Parameters;

  if (!source)
  {
    error("No source field");
    return (false);
  }

  if (!destination)
  {
    error("No destination field");
    return (false);
  }

  FieldInformation fis(source);
  FieldInformation fid(destination);
  fid.set_data_type(fis.get_data_type());

  if (fid.is_nodata()) fid.make_lineardata();
  output = CreateField(fid,destination->mesh());

  if (!output)
  {
    error("Could not allocate output field");
    return (false);
  }

  // Determine output type

  VMesh* smesh = source->vmesh();
  VMesh* dmesh = output->vmesh();
  VField* sfield = source->vfield();
  VField* dfield = output->vfield();

  // Make sure output field is all empty and of right size
  dfield->resize_values();
  dfield->clear_all_values();
  dfield->set_all_values(get(DefaultValue).toDouble());

  std::string method = getOption(MappingMethod);
  int sbasis_order = sfield->basis_order();
  int dbasis_order = dfield->basis_order();

  if (sbasis_order < 0)
  {
    error("Source field basis order needs to constant or linear");
    return (false);
  }

  if (dbasis_order < 0)
  {
    error("Destination field basis order needs to constant or linear");
    return (false);
  }

  if (method == "closestdata")
  {
    if (sbasis_order == 0) smesh->synchronize(Mesh::FIND_CLOSEST_ELEM_E);
    else smesh->synchronize(Mesh::FIND_CLOSEST_NODE_E);
  }
  else if(method == "singledestination")
  {
    if (dbasis_order == 0) dmesh->synchronize(Mesh::FIND_CLOSEST_ELEM_E);
    else dmesh->synchronize(Mesh::FIND_CLOSEST_NODE_E);
  }
  else if (method == "interpolateddata")
  {
    if (smesh->num_elems() > 0)
    {
      smesh->synchronize(Mesh::FIND_CLOSEST_ELEM_E);
    }
    else
    {
      error("Source does not have any elements, hence one cannot interpolate data in this field");
      error("Use a closestdata interpolation scheme for this data");
      return (false);
    }
  }

  double maxdist = get(MaxDistance).toDouble();

  boost::scoped_ptr<detail::MapFieldDataFromSourceToDestinationPAlgoBase> algoP;
  int np = Parallel::NumCores();
  if (method == "closestdata")
  {
    algoP.reset(new detail::MapFieldDataFromSourceToDestinationClosestDataPAlgo(np));
  }
  else if(method == "singledestination")
  {
    np = 1; //TODO: ???
    algoP.reset(new detail::MapFieldDataFromSourceToDestinationSingleDestinationPAlgo(np));
  }
  else if (method == "interpolateddata")
  {
    algoP.reset(new detail::MapFieldDataFromSourceToDestinationInterpolatedDataPAlgo(np));
  }

  if (!algoP)
    THROW_ALGORITHM_INPUT_ERROR("Invalid mapping method");

  algoP->sfield_ = sfield;
  algoP->dfield_ = dfield;
  algoP->smesh_ = smesh;
  algoP->dmesh_ = dmesh;
  algoP->maxdist_ = maxdist;
  algoP->algo_ = this;

  auto task_i = [&algoP,this](int i) { algoP->parallel(i); };
  Parallel::RunTasks(task_i, np);

  CopyProperties(*destination, *output);

  return (true);
}

AlgorithmOutput MapFieldDataFromSourceToDestinationAlgo::run(const AlgorithmInput& input) const
{
  auto source = input.get<Field>(Variables::Source);
  auto destination = input.get<Field>(Variables::Destination);

  FieldHandle output_field;
  if (!runImpl(source, destination, output_field))
    THROW_ALGORITHM_PROCESSING_ERROR("False returned from legacy run call");

  AlgorithmOutput output;
  output[Remapped_Destination] = output_field;

  return output;
}


/*
    This file is part of Nori, a simple educational ray tracer

    Copyright (c) 2015 by Wenzel Jakob

    Nori is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License Version 3
    as published by the Free Software Foundation.

    Nori 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, see <http://www.gnu.org/licenses/>.
*/

#include <nori/accel.h>
#include <nori/timer.h>
#include <tbb/tbb.h>
#include <Eigen/Geometry>
#include <atomic>

/*
 * =======================================================================
 *   WARNING    WARNING    WARNING    WARNING    WARNING    WARNING
 * =======================================================================
 *   Remember to put on SAFETY GOGGLES before looking at this file. Youa
 *   are most certainly not expected to read or understand any of it.
 * =======================================================================
 */

NORI_NAMESPACE_BEGIN

/* Bin data structure for counting triangles and computing their bounding box */
struct Bins {
    static const int BIN_COUNT = 16;
    Bins() { memset(counts, 0, sizeof(uint32_t) * BIN_COUNT); }
    uint32_t counts[BIN_COUNT];
    BoundingBox3f bbox[BIN_COUNT];
};

/**
 * \brief Build task for parallel BVH construction
 *
 * This class uses the task scheduling system of Intel' Thread Building Blocks
 * to parallelize the divide and conquer BVH build at all levels.
 *
 * The used methodology is roughly that described in
 * "Fast and Parallel Construction of SAH-based Bounding Volume Hierarchies"
 * by Ingo Wald (Proc. IEEE/EG Symposium on Interactive Ray Tracing, 2007)
 */
class BVHBuildTask : public tbb::task {
private:
    Accel &bvh;
    uint32_t node_idx;
    uint32_t *start, *end, *temp;

public:
    /// Build-related parameters
    enum {
        /// Switch to a serial build when less than 32 triangles are left
        SERIAL_THRESHOLD = 32,

        /// Process triangles in batches of 1K for the purpose of parallelization
        GRAIN_SIZE = 1000,

        /// Heuristic cost value for traversal operations
        TRAVERSAL_COST = 1,

        /// Heuristic cost value for intersection operations
        INTERSECTION_COST = 1
    };

public:
    /**
     * Create a new build task
     *
     * \param bvh
     *    Reference to the underlying BVH
     *
     * \param node_idx
     *    Index of the BVH node that should be built
     *
     * \param start
     *    Start pointer into a list of triangle indices to be processed
     *
     * \param end
     *    End pointer into a list of triangle indices to be processed
     *
     *  \param temp
     *    Pointer into a temporary memory region that can be used for
     *    construction purposes. The usable length is <tt>end-start</tt>
     *    unsigned integers.
     */
    BVHBuildTask(Accel &bvh, uint32_t node_idx, uint32_t *start, uint32_t *end, uint32_t *temp)
        : bvh(bvh), node_idx(node_idx), start(start), end(end), temp(temp) { }

    task *execute() {
        uint32_t size = (uint32_t) (end-start);
        Accel::BVHNode &node = bvh.m_nodes[node_idx];

        /* Switch to a serial build when less than SERIAL_THRESHOLD triangles are left */
        if (size < SERIAL_THRESHOLD) {
            execute_serially(bvh, node_idx, start, end, temp);
            return nullptr;
        }

        /* Always split along the largest axis */
        int axis = node.bbox.getLargestAxis();
        float min = node.bbox.min[axis], max = node.bbox.max[axis],
              inv_bin_size = Bins::BIN_COUNT / (max-min);

        /* Accumulate all triangles into bins */
        Bins bins = tbb::parallel_reduce(
            tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE),
            Bins(),
            /* MAP: Bin a number of triangles and return the resulting 'Bins' data structure */
            [&](const tbb::blocked_range<uint32_t> &range, Bins result) {
                for (uint32_t i = range.begin(); i != range.end(); ++i) {
                    uint32_t f = start[i];
                    float centroid = bvh.getCentroid(f)[axis];

                    int index = std::min(std::max(
                        (int) ((centroid - min) * inv_bin_size), 0),
                        (Bins::BIN_COUNT - 1));

                    result.counts[index]++;
                    result.bbox[index].expandBy(bvh.getBoundingBox(f));
                }
                return result;
            },
            /* REDUCE: Combine two 'Bins' data structures */
            [](const Bins &b1, const Bins &b2) {
                Bins result;
                for (int i=0; i < Bins::BIN_COUNT; ++i) {
                    result.counts[i] = b1.counts[i] + b2.counts[i];
                    result.bbox[i] = BoundingBox3f::merge(b1.bbox[i], b2.bbox[i]);
                }
                return result;
            }
        );

        /* Choose the best split plane based on the binned data */
        BoundingBox3f bbox_left[Bins::BIN_COUNT];
        bbox_left[0] = bins.bbox[0];
        for (int i=1; i<Bins::BIN_COUNT; ++i) {
            bins.counts[i] += bins.counts[i-1];
            bbox_left[i] = BoundingBox3f::merge(bbox_left[i-1], bins.bbox[i]);
        }

        BoundingBox3f bbox_right = bins.bbox[Bins::BIN_COUNT-1], best_bbox_right;
        int64_t best_index = -1;
        float best_cost = (float) INTERSECTION_COST * size;
        float tri_factor = (float) INTERSECTION_COST / node.bbox.getSurfaceArea();

        for (int i=Bins::BIN_COUNT - 2; i >= 0; --i) {
            uint32_t prims_left = bins.counts[i], prims_right = (uint32_t) (end - start) - bins.counts[i];
            float sah_cost = 2.0f * TRAVERSAL_COST +
                tri_factor * (prims_left * bbox_left[i].getSurfaceArea() +
                              prims_right * bbox_right.getSurfaceArea());
            if (sah_cost < best_cost) {
                best_cost = sah_cost;
                best_index = i;
                best_bbox_right = bbox_right;
            }
            bbox_right = BoundingBox3f::merge(bbox_right, bins.bbox[i]);
        }

        if (best_index == -1) {
            /* Could not find a good split plane -- retry with
               more careful serial code just to be sure.. */
            execute_serially(bvh, node_idx, start, end, temp);
            return nullptr;
        }

        uint32_t left_count = bins.counts[best_index];
        int node_idx_left = node_idx+1;
        int node_idx_right = node_idx+2*left_count;

        bvh.m_nodes[node_idx_left ].bbox = bbox_left[best_index];
        bvh.m_nodes[node_idx_right].bbox = best_bbox_right;
        node.inner.rightChild = node_idx_right;
        node.inner.axis = axis;
        node.inner.flag = 0;

        std::atomic<uint32_t> offset_left(0),
                              offset_right(bins.counts[best_index]);

        tbb::parallel_for(
            tbb::blocked_range<uint32_t>(0u, size, GRAIN_SIZE),
            [&](const tbb::blocked_range<uint32_t> &range) {
                uint32_t count_left = 0, count_right = 0;
                for (uint32_t i = range.begin(); i != range.end(); ++i) {
                    uint32_t f = start[i];
                    float centroid = bvh.getCentroid(f)[axis];
                    int index = (int) ((centroid - min) * inv_bin_size);
                    (index <= best_index ? count_left : count_right)++;
                }
                uint32_t idx_l = offset_left.fetch_add(count_left);
                uint32_t idx_r = offset_right.fetch_add(count_right);
                for (uint32_t i = range.begin(); i != range.end(); ++i) {
                    uint32_t f = start[i];
                    float centroid = bvh.getCentroid(f)[axis];
                    int index = (int) ((centroid - min) * inv_bin_size);
                    if (index <= best_index)
                        temp[idx_l++] = f;
                    else
                        temp[idx_r++] = f;
                }
            }
        );
        memcpy(start, temp, size * sizeof(uint32_t));
        assert(offset_left == left_count && offset_right == size);

        /* Create an empty parent task */
        tbb::task& c = *new (allocate_continuation()) tbb::empty_task;
        c.set_ref_count(2);

        /* Post right subtree to scheduler */
        BVHBuildTask &b = *new (c.allocate_child())
            BVHBuildTask(bvh, node_idx_right, start + left_count,
                         end, temp + left_count);
        spawn(b);

        /* Directly start working on left subtree */
        recycle_as_child_of(c);
        node_idx = node_idx_left;
        end = start + left_count;

        return this;
    }

    /// Single-threaded build function
    static void execute_serially(Accel &bvh, uint32_t node_idx, uint32_t *start, uint32_t *end, uint32_t *temp) {
        Accel::BVHNode &node = bvh.m_nodes[node_idx];
        uint32_t size = (uint32_t) (end - start);
        float best_cost = (float) INTERSECTION_COST * size;
        int64_t best_index = -1, best_axis = -1;
        float *left_areas = (float *) temp;

        /* Try splitting along every axis */
        for (int axis=0; axis<3; ++axis) {
            /* Sort all triangles based on their centroid positions projected on the axis */
            std::sort(start, end, [&](uint32_t f1, uint32_t f2) {
                return bvh.getCentroid(f1)[axis] < bvh.getCentroid(f2)[axis];
            });

            BoundingBox3f bbox;
            for (uint32_t i = 0; i<size; ++i) {
                uint32_t f = *(start + i);
                bbox.expandBy(bvh.getBoundingBox(f));
                left_areas[i] = (float) bbox.getSurfaceArea();
            }
            if (axis == 0)
                node.bbox = bbox;

            bbox.reset();

            /* Choose the best split plane */
            float tri_factor = INTERSECTION_COST / node.bbox.getSurfaceArea();
            for (uint32_t i = size-1; i>=1; --i) {
                uint32_t f = *(start + i);
                bbox.expandBy(bvh.getBoundingBox(f));

                float left_area = left_areas[i-1];
                float right_area = bbox.getSurfaceArea();
                uint32_t prims_left = i;
                uint32_t prims_right = size-i;

                float sah_cost = 2.0f * TRAVERSAL_COST +
                    tri_factor * (prims_left * left_area +
                                  prims_right * right_area);

                if (sah_cost < best_cost) {
                    best_cost = sah_cost;
                    best_index = i;
                    best_axis = axis;
                }
            }
        }

        if (best_index == -1) {
            /* Splitting does not reduce the cost, make a leaf */
            node.leaf.flag = 1;
            node.leaf.start = (uint32_t) (start - bvh.m_indices.data());
            node.leaf.size  = size;
            return;
        }

        std::sort(start, end, [&](uint32_t f1, uint32_t f2) {
            return bvh.getCentroid(f1)[best_axis] < bvh.getCentroid(f2)[best_axis];
        });

        uint32_t left_count = (uint32_t) best_index;
        uint32_t node_idx_left = node_idx + 1;
        uint32_t node_idx_right = node_idx + 2 * left_count;
        node.inner.rightChild = node_idx_right;
        node.inner.axis = best_axis;
        node.inner.flag = 0;

        execute_serially(bvh, node_idx_left, start, start + left_count, temp);
        execute_serially(bvh, node_idx_right, start+left_count, end, temp + left_count);
    }
};

void Accel::addMesh(Mesh *mesh) {
    m_meshes.push_back(mesh);
    m_meshOffset.push_back(m_meshOffset.back() + mesh->getTriangleCount());
    m_bbox.expandBy(mesh->getBoundingBox());
}

void Accel::clear() {
    for (auto mesh : m_meshes)
        delete mesh;
    m_meshes.clear();
    m_meshOffset.clear();
    m_meshOffset.push_back(0u);
    m_nodes.clear();
    m_indices.clear();
    m_bbox.reset();
    m_nodes.shrink_to_fit();
    m_meshes.shrink_to_fit();
    m_meshOffset.shrink_to_fit();
    m_indices.shrink_to_fit();
}

void Accel::build() {
    uint32_t size  = getTriangleCount();
    if (size == 0)
        return;
    //cout << "Constructing a SAH BVH (" << m_meshes.size()
    //    << (m_meshes.size() == 1 ? " mesh, " : " meshes, ")
    //    << size << " triangles) .. ";
    //cout.flush();
    Timer timer;

    /* Conservative estimate for the total number of nodes */
    m_nodes.resize(2*size);
    memset(m_nodes.data(), 0, sizeof(BVHNode) * m_nodes.size());
    m_nodes[0].bbox = m_bbox;
    m_indices.resize(size);

    if (sizeof(BVHNode) != 32)
        throw NoriException("BVH Node is not packed! Investigate compiler settings.");

    for (uint32_t i = 0; i < size; ++i)
        m_indices[i] = i;

    uint32_t *indices = m_indices.data(), *temp = new uint32_t[size];
    BVHBuildTask& task = *new(tbb::task::allocate_root())
        BVHBuildTask(*this, 0u, indices, indices + size , temp);
    tbb::task::spawn_root_and_wait(task);
    delete[] temp;
    std::pair<float, uint32_t> stats = statistics();

    /* The node array was allocated conservatively and now contains
       many unused entries -- do a compactification pass. */
    std::vector<BVHNode> compactified(stats.second);
    std::vector<uint32_t> skipped_accum(m_nodes.size());

    for (int64_t i = stats.second-1, j = m_nodes.size(), skipped = 0; i >= 0; --i) {
        while (m_nodes[--j].isUnused())
            skipped++;
        BVHNode &new_node = compactified[i];
        new_node = m_nodes[j];
        skipped_accum[j] = (uint32_t) skipped;

        if (new_node.isInner()) {
            new_node.inner.rightChild = (uint32_t)
                (i + new_node.inner.rightChild - j -
                (skipped - skipped_accum[new_node.inner.rightChild]));
        }
    }
    /*cout << "done (took " << timer.elapsedString() << " and "
        << memString(sizeof(BVHNode) * m_nodes.size() + sizeof(uint32_t)*m_indices.size())
        << ", SAH cost = " << stats.first
        << ")." << endl;*/

    m_nodes = std::move(compactified);
}

std::pair<float, uint32_t> Accel::statistics(uint32_t node_idx) const {
    const BVHNode &node = m_nodes[node_idx];
    if (node.isLeaf()) {
        return std::make_pair((float) BVHBuildTask::INTERSECTION_COST * node.leaf.size, 1u);
    } else {
        std::pair<float, uint32_t> stats_left = statistics(node_idx + 1u);
        std::pair<float, uint32_t> stats_right = statistics(node.inner.rightChild);
        float saLeft = m_nodes[node_idx + 1u].bbox.getSurfaceArea();
        float saRight = m_nodes[node.inner.rightChild].bbox.getSurfaceArea();
        float saCur = node.bbox.getSurfaceArea();
        float sahCost =
            2 * BVHBuildTask::TRAVERSAL_COST +
            (saLeft * stats_left.first + saRight * stats_right.first) / saCur;
        return std::make_pair(
            sahCost,
            stats_left.second + stats_right.second + 1u
        );
    }
}

bool Accel::rayIntersect(const Ray3f &_ray, Intersection &its, bool shadowRay) const {
    uint32_t node_idx = 0, stack_idx = 0, stack[64];

    its.t = std::numeric_limits<float>::infinity();

    /* Use an adaptive ray epsilon */
    Ray3f ray(_ray);
    if (ray.mint == Epsilon)
        ray.mint = std::max(ray.mint, ray.mint * ray.o.array().abs().maxCoeff());

    if (m_nodes.empty() || ray.maxt < ray.mint)
        return false;

    bool foundIntersection = false;
    uint32_t f = 0;

    while (true) {
        const BVHNode &node = m_nodes[node_idx];

        if (!node.bbox.rayIntersect(ray)) {
            if (stack_idx == 0)
                break;
            node_idx = stack[--stack_idx];
            continue;
        }

        if (node.isInner()) {
            stack[stack_idx++] = node.inner.rightChild;
            node_idx++;
            assert(stack_idx<64);
        } else {
            for (uint32_t i = node.start(), end = node.end(); i < end; ++i) {
                uint32_t idx = m_indices[i];
                const Mesh *mesh = m_meshes[findMesh(idx)];

                float u, v, t;
                if (mesh->rayIntersect(idx, ray, u, v, t)) {
                    if (shadowRay)
                        return true;
                    foundIntersection = true;
                    ray.maxt = its.t = t;
                    its.uv = Point2f(u, v);
                    its.mesh = mesh;
                    f = idx;
                }
            }
            if (stack_idx == 0)
                break;
            node_idx = stack[--stack_idx];
            continue;
        }
    }

    if (foundIntersection) {
        /* Find the barycentric coordinates */
        Vector3f bary;
        bary << 1-its.uv.sum(), its.uv;

        /* References to all relevant mesh buffers */
        const Mesh *mesh   = its.mesh;
        const MatrixXf &V  = mesh->getVertexPositions();
        const MatrixXf &N  = mesh->getVertexNormals();
        const MatrixXf &UV = mesh->getVertexTexCoords();
        const MatrixXu &F  = mesh->getIndices();

        /* Vertex indices of the triangle */
        uint32_t idx0 = F(0, f), idx1 = F(1, f), idx2 = F(2, f);

        Point3f p0 = V.col(idx0), p1 = V.col(idx1), p2 = V.col(idx2);

        /* Compute the intersection positon accurately
           using barycentric coordinates */
        its.p = bary.x() * p0 + bary.y() * p1 + bary.z() * p2;

        /* Compute proper texture coordinates if provided by the mesh */
        if (UV.size() > 0)
            its.uv = bary.x() * UV.col(idx0),
                bary.y() * UV.col(idx1),
                bary.z() * UV.col(idx2);

        /* Compute the geometry frame */
        its.geoFrame = Frame((p1-p0).cross(p2-p0).normalized());

        if (N.size() > 0) {
            /* Compute the shading frame. Note that for simplicity,
               the current implementation doesn't attempt to provide
               tangents that are continuous across the surface. That
               means that this code will need to be modified to be able
               use anisotropic BRDFs, which need tangent continuity */

            its.shFrame = Frame(
                (bary.x() * N.col(idx0) +
                 bary.y() * N.col(idx1) +
                 bary.z() * N.col(idx2)).normalized());
        } else {
            its.shFrame = its.geoFrame;
        }
    }

    return foundIntersection;
}

NORI_NAMESPACE_END


#ifndef VEXCL_CONSTANTS_HPP
#define VEXCL_CONSTANTS_HPP

/*
The MIT License

Copyright (c) 2012-2018 Denis Demidov <dennis.demidov@gmail.com>

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
*/

/**
 * \file   vexcl/constants.hpp
 * \author Denis Demidov <dennis.demidov@gmail.com>
 * \brief  Constants for use in vector expressions.
 */

#include <sstream>
#include <iomanip>
#include <string>
#include <type_traits>

#include <vexcl/backend.hpp>

#include <vexcl/types.hpp>
#include <vexcl/operations.hpp>

#include <boost/math/constants/constants.hpp>
#include <boost/version.hpp>

namespace vex {

//---------------------------------------------------------------------------
// std::integral_constant
//---------------------------------------------------------------------------
template <class T, T v>
struct is_cl_native< std::integral_constant<T, v> > : std::true_type {};

namespace traits {

template <class T, T v>
struct kernel_param_declaration< std::integral_constant<T, v> >
{
    static void get(backend::source_generator&,
            const std::integral_constant<T, v>&,
            const backend::command_queue&, const std::string &/*prm_name*/,
            detail::kernel_generator_state_ptr)
    { }
};

template <class T, T v>
struct partial_vector_expr< std::integral_constant<T, v> >
{
    static void get(backend::source_generator &src,
            const std::integral_constant<T, v>&,
            const backend::command_queue&, const std::string &/*prm_name*/,
            detail::kernel_generator_state_ptr)
    {
        src << v;
    }
};

template <class T, T v>
struct kernel_arg_setter< std::integral_constant<T, v> >
{
    static void set(const std::integral_constant<T, v>&,
            backend::kernel&, unsigned/*part*/, size_t/*index_offset*/,
            detail::kernel_generator_state_ptr)
    {
    }
};

} // namespace traits

//---------------------------------------------------------------------------
// boost::math::constants wrappers
//---------------------------------------------------------------------------
template <class Impl>
struct user_constant {
    typedef typename Impl::value_type value_type;
};

template <class Impl>
struct is_cl_native< user_constant<Impl> > : std::true_type {};

namespace traits {

template <class Impl>
struct kernel_param_declaration< user_constant<Impl> >
{
    static void get(backend::source_generator&,
            const user_constant<Impl>&,
            const backend::command_queue&, const std::string &/*prm_name*/,
            detail::kernel_generator_state_ptr)
    { }
};

template <class Impl>
struct partial_vector_expr< user_constant<Impl> >
{
    static void get(backend::source_generator &src,
            const user_constant<Impl>&,
            const backend::command_queue&, const std::string &/*prm_name*/,
            detail::kernel_generator_state_ptr)
    {
        src << Impl::get();
    }
};

template <class Impl>
struct kernel_arg_setter< user_constant<Impl> >
{
    static void set(const user_constant<Impl>&,
            backend::kernel&, unsigned/*part*/, size_t/*index_offset*/,
            detail::kernel_generator_state_ptr)
    {
    }
};

} // namespace traits

/**
 * Creates user-defined constan functor for use in VexCL expressions.
 * ``value`` will be copied verbatim into kernel source.
 */
#define VEX_CONSTANT(name, value)                                              \
  struct constant_##name {                                                     \
    typedef decltype(value) value_type;                                        \
    static std::string get() {                                                 \
      static const value_type v = value;                                       \
      std::ostringstream s;                                                    \
      s << "( " << std::scientific << std::setprecision(16) << v << " )";      \
      return s.str();                                                          \
    }                                                                          \
    decltype(boost::proto::as_expr<vex::vector_domain>(                        \
          vex::user_constant<constant_##name>()))                              \
    operator()() const {                                                       \
      return boost::proto::as_expr<vex::vector_domain>(                        \
          vex::user_constant<constant_##name>());                              \
    }                                                                          \
    operator value_type() const {                                              \
      static const value_type v = value;                                       \
      return v;                                                                \
    }                                                                          \
  };                                                                           \
  const constant_##name name = {}

/// Mathematical constants.
namespace constants {

VEX_CONSTANT( pi, boost::math::constants::pi<double>() );
VEX_CONSTANT( root_pi, boost::math::constants::root_pi<double>() );
VEX_CONSTANT( root_half_pi, boost::math::constants::root_half_pi<double>() );
VEX_CONSTANT( root_two_pi, boost::math::constants::root_two_pi<double>() );
VEX_CONSTANT( root_ln_four, boost::math::constants::root_ln_four<double>() );
VEX_CONSTANT( e, boost::math::constants::e<double>() );
VEX_CONSTANT( half, boost::math::constants::half<double>() );
VEX_CONSTANT( euler, boost::math::constants::euler<double>() );
VEX_CONSTANT( root_two, boost::math::constants::root_two<double>() );
VEX_CONSTANT( ln_two, boost::math::constants::ln_two<double>() );
VEX_CONSTANT( ln_ln_two, boost::math::constants::ln_ln_two<double>() );
VEX_CONSTANT( third, boost::math::constants::third<double>() );
VEX_CONSTANT( twothirds, boost::math::constants::twothirds<double>() );
VEX_CONSTANT( pi_minus_three, boost::math::constants::pi_minus_three<double>() );
VEX_CONSTANT( four_minus_pi, boost::math::constants::four_minus_pi<double>() );
VEX_CONSTANT( two_pi, boost::math::constants::two_pi<double>() );
VEX_CONSTANT( half_root_two, boost::math::constants::half_root_two<double>() );
VEX_CONSTANT( exp_minus_half, boost::math::constants::exp_minus_half<double>() );
#if (BOOST_VERSION >= 105000) || defined(DOXYGEN)
VEX_CONSTANT( one_div_two_pi, boost::math::constants::one_div_two_pi<double>() );
VEX_CONSTANT( catalan, boost::math::constants::catalan<double>() );
VEX_CONSTANT( cbrt_pi, boost::math::constants::cbrt_pi<double>() );
VEX_CONSTANT( cosh_one, boost::math::constants::cosh_one<double>() );
VEX_CONSTANT( cos_one, boost::math::constants::cos_one<double>() );
VEX_CONSTANT( degree, boost::math::constants::degree<double>() );
VEX_CONSTANT( e_pow_pi, boost::math::constants::e_pow_pi<double>() );
VEX_CONSTANT( euler_sqr, boost::math::constants::euler_sqr<double>() );
VEX_CONSTANT( extreme_value_skewness, boost::math::constants::extreme_value_skewness<double>() );
VEX_CONSTANT( four_thirds_pi, boost::math::constants::four_thirds_pi<double>() );
VEX_CONSTANT( glaisher, boost::math::constants::glaisher<double>() );
VEX_CONSTANT( half_pi, boost::math::constants::half_pi<double>() );
VEX_CONSTANT( khinchin, boost::math::constants::khinchin<double>() );
VEX_CONSTANT( ln_phi, boost::math::constants::ln_phi<double>() );
VEX_CONSTANT( ln_ten, boost::math::constants::ln_ten<double>() );
VEX_CONSTANT( log10_e, boost::math::constants::log10_e<double>() );
VEX_CONSTANT( one_div_cbrt_pi, boost::math::constants::one_div_cbrt_pi<double>() );
VEX_CONSTANT( one_div_euler, boost::math::constants::one_div_euler<double>() );
VEX_CONSTANT( one_div_ln_phi, boost::math::constants::one_div_ln_phi<double>() );
VEX_CONSTANT( one_div_log10_e, boost::math::constants::one_div_log10_e<double>() );
VEX_CONSTANT( one_div_root_pi, boost::math::constants::one_div_root_pi<double>() );
VEX_CONSTANT( one_div_root_two, boost::math::constants::one_div_root_two<double>() );
VEX_CONSTANT( one_div_root_two_pi, boost::math::constants::one_div_root_two_pi<double>() );
VEX_CONSTANT( phi, boost::math::constants::phi<double>() );
VEX_CONSTANT( pi_cubed, boost::math::constants::pi_cubed<double>() );
VEX_CONSTANT( pi_pow_e, boost::math::constants::pi_pow_e<double>() );
VEX_CONSTANT( pi_sqr, boost::math::constants::pi_sqr<double>() );
VEX_CONSTANT( pi_sqr_div_six, boost::math::constants::pi_sqr_div_six<double>() );
VEX_CONSTANT( radian, boost::math::constants::radian<double>() );
VEX_CONSTANT( rayleigh_kurtosis, boost::math::constants::rayleigh_kurtosis<double>() );
VEX_CONSTANT( rayleigh_kurtosis_excess, boost::math::constants::rayleigh_kurtosis_excess<double>() );
VEX_CONSTANT( rayleigh_skewness, boost::math::constants::rayleigh_skewness<double>() );
VEX_CONSTANT( root_e, boost::math::constants::root_e<double>() );
VEX_CONSTANT( root_one_div_pi, boost::math::constants::root_one_div_pi<double>() );
VEX_CONSTANT( root_three, boost::math::constants::root_three<double>() );
VEX_CONSTANT( root_two_div_pi, boost::math::constants::root_two_div_pi<double>() );
VEX_CONSTANT( sinh_one, boost::math::constants::sinh_one<double>() );
VEX_CONSTANT( sin_one, boost::math::constants::sin_one<double>() );
VEX_CONSTANT( sixth_pi, boost::math::constants::sixth_pi<double>() );
VEX_CONSTANT( third_pi, boost::math::constants::third_pi<double>() );
VEX_CONSTANT( three_quarters, boost::math::constants::three_quarters<double>() );
VEX_CONSTANT( three_quarters_pi, boost::math::constants::three_quarters_pi<double>() );
VEX_CONSTANT( two_div_pi, boost::math::constants::two_div_pi<double>() );
VEX_CONSTANT( two_thirds, boost::math::constants::two_thirds<double>() );
VEX_CONSTANT( two_thirds_pi, boost::math::constants::two_thirds_pi<double>() );
VEX_CONSTANT( zeta_three, boost::math::constants::zeta_three<double>() );
VEX_CONSTANT( zeta_two, boost::math::constants::zeta_two<double>() );
#endif

} //namespace constants

} // namespace vex

#endif


/*
 * Copyright 2009-2018 The VOTCA Development Team (http://www.votca.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */

#include <votca/xtp/kmccalculator.h>
#include <votca/xtp/gnode.h>
#include <votca/tools/constants.h>
#include <boost/format.hpp>
#include <votca/ctp/topology.h>
#include <locale>

using namespace std;

namespace votca {
    namespace xtp {

        KMCCalculator::KMCCalculator(){};

    void KMCCalculator::LoadGraph(ctp::Topology *top) {

        std::vector< ctp::Segment* >& seg = top->Segments();

        if(seg.size()<1){
          throw std::runtime_error("Your sql file contains no segments!");
        }

        for (unsigned i = 0; i < seg.size(); i++) {
            GNode *newNode = new GNode();
            newNode->ReadfromSegment(seg[i], _carriertype);
            if (tools::wildcmp(_injection_name.c_str(), seg[i]->getName().c_str())) {
                newNode->injectable = true;
            } else {
                newNode->injectable = false;
            }
            _nodes.push_back(newNode);
        }

        ctp::QMNBList &nblist = top->NBList();
        if(nblist.size()<1){
          throw std::runtime_error("Your sql file contains no pairs!");
        }

        for (ctp::QMNBList::iterator it = nblist.begin(); it < nblist.end(); ++it) {
            _nodes[(*it)->Seg1()->getId()-1]->AddEventfromQmPair(*it, _carriertype);
            _nodes[(*it)->Seg2()->getId()-1]->AddEventfromQmPair(*it, _carriertype);
        }

        unsigned events=0;
        unsigned max=std::numeric_limits<unsigned>::min();
        unsigned min=std::numeric_limits<unsigned>::max();
        minlength=std::numeric_limits<double>::max();
        double maxlength=0;
        for(const auto& node:_nodes){

            unsigned size=node->events.size();
            for( const auto& event:node->events){
                if(event.decayevent){continue;}
                double dist=abs(event.dr);
                if(dist>maxlength){
                    maxlength=dist;
                } else if(dist<minlength){
                    minlength=dist;
                }
            }

            events+=size;
            if(size==0){
                cout<<"Node "<<node->id<<" has 0 jumps"<<endl;
            }
            else if(size<min){
                min=size;
            }
            else if(size>max){
                max=size;
            }
        }
        double avg=double(events)/double(_nodes.size());
        double deviation=0.0;
        for(const auto& node:_nodes){
            double size=node->events.size();
            deviation+=(size-avg)*(size-avg);
        }
        deviation=std::sqrt(deviation/double(_nodes.size()));

        cout<<"Nblist has "<<nblist.size()<<" pairs. Nodes contain "<<events<<" jump events"<<endl;
        cout<<"with avg="<<avg<<" std="<<deviation<<" max="<<max<<" min="<<min<<endl;
        cout<<"Minimum jumpdistance ="<<minlength<<" nm Maximum distance ="<<maxlength<<" nm"<<endl;
        cout<<"Grouping into "<<lengthdistribution<<" boxes"<<endl;
        lengthresolution=(1.00001*maxlength-minlength)/double(lengthdistribution);
        cout<<"Resolution is "<<lengthresolution<<" nm"<<endl;

        _jumplengthdistro=std::vector<long unsigned>(lengthdistribution,0);
        _jumplengthdistro_weighted=std::vector<double>(lengthdistribution,0);

        cout << "spatial density: " << _numberofcharges / top->BoxVolume() << " nm^-3" << endl;

        for (unsigned int i = 0; i < _nodes.size(); i++) {
            _nodes[i]->InitEscapeRate();
        }

        return;
    }

        void KMCCalculator::ResetForbiddenlist(std::vector<int> &forbiddenid) {
            forbiddenid.clear();
            return;
        }

        void KMCCalculator::AddtoForbiddenlist(int id, std::vector<int> &forbiddenid) {
            forbiddenid.push_back(id);
            return;
        }

        bool KMCCalculator::CheckForbidden(int id,const std::vector<int> &forbiddenlist) {
            // cout << "forbidden list has " << forbiddenlist.size() << " entries" << endl;
            bool forbidden = false;
            for (unsigned int i = 0; i < forbiddenlist.size(); i++) {
                if (id == forbiddenlist[i]) {
                    forbidden = true;
                    //cout << "ID " << id << " has been found as element " << i << " (" << forbiddenlist[i]<< ") in the forbidden list." << endl;
                    break;
                }
            }
            return forbidden;
        }

        bool KMCCalculator::CheckSurrounded(GNode* node,const std::vector<int> & forbiddendests) {
            bool surrounded = true;
            for (unsigned  i = 0; i < node->events.size(); i++) {
                bool thisevent_possible = true;
                for (unsigned int j = 0; j < forbiddendests.size(); j++) {
                    if (node->events[i].destination == forbiddendests[j]) {
                        thisevent_possible = false;
                        break;
                    }
                }
                if (thisevent_possible == true) {
                    surrounded = false;
                    break;
                }
            }
            return surrounded;
        }

        std::string KMCCalculator::CarrierInttoLongString(int carriertype){
            std::string name="";
            if (carriertype==-1){
                name="electron";
            }
            else if(carriertype==1){
                name="hole";
            }
            else if(carriertype==2){
                name="singlet";
            }
            else if(carriertype==3){
                name="triplet";
            }
            else{
                throw runtime_error((boost::format("Carriertype %i not known") % carriertype).str());
            }
            return name;
        }

        std::string KMCCalculator::CarrierInttoShortString(int carriertype){
            std::string name="";
            if (carriertype==-1){
                name="e";
            }
            else if(carriertype==1){
                name="h";
            }
            else if(carriertype==2){
                name="s";
            }
            else if(carriertype==3){
                name="t";
            }
            else{
                throw runtime_error((boost::format("Carriertype %i not known") % carriertype).str());
            }
            return name;
        }

         int KMCCalculator::StringtoCarriertype(std::string name){
             char firstcharacter=std::tolower(name.at(0), std::locale());
             int carriertype=0;
            if (firstcharacter=='e'){
                carriertype=-1;
            }
            else if(firstcharacter=='h'){
                carriertype=1;
            }
            else if(firstcharacter=='s'){
                carriertype=2;
            }
            else if(firstcharacter=='t'){
                carriertype=3;
            }
            else{
                throw runtime_error((boost::format("Carriername %s not known") % name).str());
            }
            return carriertype;
        }

         void KMCCalculator::RandomlyCreateCharges(){

        cout << "looking for injectable nodes..." << endl;
        for (unsigned int i = 0; i < _numberofcharges; i++) {
            Chargecarrier *newCharge = new Chargecarrier;
            newCharge->id = i;
            RandomlyAssignCarriertoSite(newCharge);

            cout << "starting position for charge " << i + 1 << ": segment " << newCharge->getCurrentNodeId()+1 << endl;
            _carriers.push_back(newCharge);
        }
        return;
         }

         void KMCCalculator::RandomlyAssignCarriertoSite(Chargecarrier* Charge){
            int nodeId_guess=-1;
            do{
            nodeId_guess=_RandomVariable.rand_uniform_int(_nodes.size());
            }
            while (_nodes[nodeId_guess]->occupied || _nodes[nodeId_guess]->injectable==false ); // maybe already occupied? or maybe not injectable?
            if (Charge->hasNode()){
                Charge->jumpfromCurrentNodetoNode(_nodes[nodeId_guess]);
            }
            else{
            Charge->settoNote(_nodes[nodeId_guess]);
            }
             return;
         }

        void KMCCalculator::InitialRates() {

            cout << endl << "Calculating initial Marcus rates." << endl;
            cout << "    Temperature T = " << _temperature << " K." << endl;

            cout << "    carriertype: " << CarrierInttoLongString(_carriertype) << endl;
            unsigned numberofsites = _nodes.size();
            cout << "    Rates for " << numberofsites << " sites are computed." << endl;
            double charge=0.0;
            if (_carriertype == -1)
            {
                charge = -1.0;
            }
            else if (_carriertype == 1)
            {
                charge = 1.0;
            }
            cout<<"electric field ="<<_field<<" V/nm"<<endl;

            double maxreldiff = 0;
            double maxrate=0;
            double minrate=std::numeric_limits<double>::max();
            int totalnumberofrates = 0;
            for (unsigned int i = 0; i < numberofsites; i++) {
                unsigned numberofneighbours = _nodes[i]->events.size();
                for (unsigned int j = 0; j < numberofneighbours; j++) {
                    if(_nodes[i]->events[j].decayevent){
                        //if event is a decay event there is no point in calculating its rate, because it already has that from the reading in.
                        continue;
                    }

                    double destindex = _nodes[i]->events[j].destination;
                    double reorg = _nodes[i]->reorg_intorig + _nodes[destindex]->reorg_intdest + _nodes[i]->events[j].reorg_out;
                     if(std::abs(reorg)<1e-12){
                        throw std::runtime_error("Reorganisation energy for a pair is extremly close to zero,\n"
                                " you probably forgot to import reorganisation energies into your sql file.");
                    }
                    double dG_Field =0.0;
                    if(charge!=0.0){
                        dG_Field=charge * (_nodes[i]->events[j].dr*_field);
                    }
                    double dG_Site = _nodes[destindex]->siteenergy - _nodes[i]->siteenergy;
                    double dG=dG_Site-dG_Field;
                    double J2 = _nodes[i]->events[j].Jeff2;

                    double rate = 2 * tools::conv::Pi / tools::conv::hbar * J2 / sqrt(4 * tools::conv::Pi * reorg * tools::conv::kB * _temperature)
                    * exp(-(dG + reorg)*(dG + reorg) / (4 * reorg * tools::conv::kB * _temperature));

                    // calculate relative difference compared to values in the table
                    double reldiff = (_nodes[i]->events[j].rate - rate) / _nodes[i]->events[j].rate;
                    if (reldiff > maxreldiff) {
                        maxreldiff = reldiff;
                    }
                    reldiff = (_nodes[i]->events[j].rate - rate) / rate;
                    if (reldiff > maxreldiff) {
                        maxreldiff = reldiff;
                    }

                    // set rates to calculated values
                    _nodes[i]->events[j].rate = rate;
                    _nodes[i]->events[j].initialrate = rate;

                    if(rate>maxrate){
                        maxrate=rate;
                    }
                    else if(rate<minrate){
                        minrate=rate;
                    }

                    totalnumberofrates++;
                }

                // Initialise escape rates
                for (unsigned int i = 0; i < _nodes.size(); i++) {
                    _nodes[i]->InitEscapeRate();
                }

            }

            cout << "    " << totalnumberofrates << " rates have been calculated." << endl;
            cout<< " Largest rate="<<maxrate<<" 1/s  Smallest rate="<<minrate<<" 1/s"<<endl;
            if (maxreldiff < 0.01) {
                cout << "    Good agreement with rates in the state file. Maximal relative difference: " << maxreldiff * 100 << " %" << endl;
            } else {
                cout << "    WARNING: Rates differ from those in the state file up to " << maxreldiff * 100 << " %." << " If the rates in the state file are calculated for a different temperature/field or if they are not Marcus rates, this is fine. Otherwise something might be wrong here." << endl;
            }

            return;
        }

        double KMCCalculator::Promotetime(double cumulated_rate){
            double dt = 0;
                double rand_u = 1 - _RandomVariable.rand_uniform();
                while (rand_u == 0) {
                    cout << "WARNING: encountered 0 as a random variable! New try." << endl;
                    rand_u = 1 - _RandomVariable.rand_uniform();
                }
                dt = -1 / cumulated_rate * log(rand_u);
            return dt;
        }

        GLink* KMCCalculator::ChooseHoppingDest(GNode* node){
            double u = 1 - _RandomVariable.rand_uniform();

            for (unsigned int j = 0; j < node->events.size(); j++) {
                u -= node->events[j].rate / node->getEscapeRate();
                if (u <= 0 || j==node->events.size()-1) {
                    return &(node->events[j]);
                }
            }
            throw runtime_error("Choose Hopping Destination, somehow no event was found");
            return NULL;
        }

        Chargecarrier* KMCCalculator::ChooseAffectedCarrier(double cumulated_rate){
            if(_carriers.size()==1){
                return _carriers[0];
            }
            Chargecarrier* carrier=NULL;
            double u = 1 - _RandomVariable.rand_uniform();
            for (unsigned int i = 0; i < _numberofcharges; i++) {
                u -= _carriers[i]->getCurrentEscapeRate() / cumulated_rate;

                if (u <= 0 || i==_numberofcharges-1) {

                    carrier = _carriers[i];
                    break;}

            }
            return carrier;
        }

        void KMCCalculator::AddtoJumplengthdistro(const GLink* event,double dt){
            if(dolengthdistributon){
            double dist=abs(event->dr)-minlength;
            int index=int(dist/lengthresolution);

            _jumplengthdistro[index]++;
            _jumplengthdistro_weighted[index]+=dt;

            }
            return;
        }

        void KMCCalculator::PrintJumplengthdistro(){
            if(dolengthdistributon){
            long unsigned noofjumps=0;

            double weightedintegral=0;
            for(unsigned i=0;i<_jumplengthdistro.size();++i){
                noofjumps+=_jumplengthdistro[i];
                weightedintegral+=_jumplengthdistro_weighted[i];
            }
            double noofjumps_double=double(noofjumps);
            cout<<"Total number of jumps: "<<noofjumps<<endl;
            cout<<" distance[nm] |   # of jumps   | # of jumps [%] | .w. by dist [nm] | w. by timestep [%]"<<endl;
            cout<<"------------------------------------------------------------------------------------"<<endl;
            for(unsigned i=0;i<_jumplengthdistro.size();++i){
                double dist=lengthresolution*(i+0.5)+minlength;
                double percent=_jumplengthdistro[i]/noofjumps_double;
                double rtimespercent=percent*dist;
                cout<<(boost::format("    %4.3f    | %15d |    %04.2f    |     %4.3e     |     %04.2f")
                            % (dist) % (_jumplengthdistro[i]) % (percent*100) %(rtimespercent) % (_jumplengthdistro_weighted[i]/weightedintegral*100)).str()<<endl;
            }
            cout<<"------------------------------------------------------------------------------------"<<endl;

            }
            return;
        }

    }
}


// This file is part of the dune-hdd project:
//   http://users.dune-project.org/projects/dune-hdd
// Copyright holders: Felix Schindler
// License: BSD 2-Clause License (http://opensource.org/licenses/BSD-2-Clause)

#ifndef DUNE_HDD_LINEARELLIPTIC_DISCRETEPROBLEM_HH
#define DUNE_HDD_LINEARELLIPTIC_DISCRETEPROBLEM_HH

#include <memory>
#include <limits>

#include <boost/numeric/conversion/cast.hpp>

#include <dune/stuff/common/disable_warnings.hh>
# include <dune/common/mpihelper.hh>
# include <dune/common/timer.hh>

# if HAVE_DUNE_FEM
#   include <dune/fem/misc/mpimanager.hh>
# endif
#include <dune/stuff/common/reenable_warnings.hh>

#if HAVE_DUNE_GRID_MULTISCALE
# include <dune/grid/multiscale/provider.hh>
#endif

#include <dune/stuff/common/configuration.hh>
#include <dune/stuff/common/logging.hh>
#include <dune/stuff/common/string.hh>
#include <dune/stuff/grid/provider.hh>
#include <dune/stuff/grid/boundaryinfo.hh>
#include <dune/stuff/common/configuration.hh>
#include <dune/stuff/common/exceptions.hh>
#include <dune/stuff/grid/layers.hh>
#include <dune/stuff/common/memory.hh>

#include "problems.hh"

namespace Dune {
namespace HDD {
namespace LinearElliptic {

template< class GridImp >
class DiscreteProblem
{
public:
  typedef GridImp GridType;
  typedef Stuff::Grid::ProviderInterface< GridType >  GridProviderType;
private:
  typedef Stuff::GridProviders< GridType > GridProviders;
  typedef typename GridType::LeafIntersection IntersectionType;
  typedef Stuff::Grid::BoundaryInfoProvider< IntersectionType > BoundaryInfoProvider;
  typedef typename GridType::template Codim< 0 >::Entity EntityType;
  typedef typename GridType::ctype DomainFieldType;
  static const unsigned int dimDomain = GridType::dimension;
public:
  typedef double RangeFieldType;
  static const unsigned int dimRange = 1;
  typedef LinearElliptic::ProblemInterface< EntityType, DomainFieldType, dimDomain, RangeFieldType, dimRange > ProblemType;
  typedef LinearElliptic::ProblemsProvider< EntityType, DomainFieldType, dimDomain, RangeFieldType, dimRange > ProblemsProvider;

  static void write_config(const std::string filename, const std::string id)
  {
    std::ofstream file;
    file.open(filename);
    file << "[" << id << "]" << std::endl;
    write_keys_to_file("gridprovider", GridProviders::available(), file);
    write_keys_to_file("boundaryinfo", BoundaryInfoProvider::available(), file);
    write_keys_to_file("problem", ProblemsProvider::available(), file);
    file << "[logging]" << std::endl;
    file << "info  = true" << std::endl;
    file << "debug = true" << std::endl;
    file << "file  = false" << std::endl;
    file << "visualize = true" << std::endl;
    file << "[parameter]" << std::endl;
    file << "0.diffusion_factor = [0.1 0.1 1.0 1.0]" << std::endl;
    file << "1.diffusion_factor = [1.0 1.0 0.1 0.1]" << std::endl;
    write_config_to_file< GridProviders >(file);
    write_config_to_file< BoundaryInfoProvider >(file);
    write_config_to_file< ProblemsProvider >(file);
    file.close();
  } // ... write_config(...)

  DiscreteProblem(const std::string id, const std::vector< std::string >& arguments)
  {
    // mpi
    assert(arguments.size() < std::numeric_limits< int >::max());
    int argc = boost::numeric_cast< int >(arguments.size());
    char** argv = Stuff::Common::String::vectorToMainArgs(arguments);
#if HAVE_DUNE_FEM
    Fem::MPIManager::initialize(argc, argv);
#else
    MPIHelper::instance(argc, argv);
#endif

    // configuration
    config_ = Stuff::Common::Configuration(argc, argv, id + ".cfg");
    if (!config_.has_sub(id))
      DUNE_THROW(Stuff::Exceptions::configuration_error,
                 "Missing sub '" << id << "' in the following Configuration:\n\n" << config_);
    filename_ = config_.get(id + ".filename", id);

    // logger
    const Stuff::Common::Configuration logger_config = config_.sub("logging");
    int log_flags = Stuff::Common::LOG_CONSOLE;
    debug_logging_ = logger_config.get< bool >("debug", false);
    if (logger_config.get< bool >("info"))
      log_flags = log_flags | Stuff::Common::LOG_INFO;
    if (debug_logging_)
      log_flags = log_flags | Stuff::Common::LOG_DEBUG;
    if (logger_config.get< bool >("file", false))
      log_flags = log_flags | Stuff::Common::LOG_FILE;
    Stuff::Common::Logger().create(log_flags, id, "", "");
    auto& info  = Stuff::Common::Logger().info();

    Timer timer;
    const std::string griprovider_type = config_.get< std::string >(id + ".gridprovider");
    info << "creating grid with '" << griprovider_type << "'... " << std::flush;
    grid_provider_ = GridProviders::create(griprovider_type, config_);
    const auto grid_view = grid_provider_->leaf_view();
    info << " done (took " << timer.elapsed()
         << "s, has " << grid_view.indexSet().size(0) << " element";
    if (grid_view.indexSet().size(0) > 1)
      info << "s";
    info << ")" << std::endl;

    const std::string boundary_info_type = config_.get< std::string >(id + ".boundaryinfo");
    if (config_.has_sub(boundary_info_type))
      boundary_info_ = config_.sub(boundary_info_type);
    else
      boundary_info_ = Stuff::Common::Configuration("type", boundary_info_type);

    info << "setting up ";
    timer.reset();
    const std::string problem_type = config_.get< std::string >(id + ".problem");
    if (!debug_logging_)
      info << "'" << problem_type << "'... " << std::flush;
    problem_ = ProblemsProvider::create(problem_type, config_);
    if (debug_logging_)
      info << *problem_ << std::endl;
    else
      info << "done (took " << timer.elapsed() << "s)" << std::endl;

    if (logger_config.get("visualize", true)) {
      info << "visualizing grid and problem... " << std::flush;
      timer.reset();
      grid_provider_->visualize(boundary_info_, filename_ + ".grid");
      problem_->visualize(grid_view, filename_ + ".problem");
      info << "done (took " << timer.elapsed() << "s)" << std::endl;
    } // if (visualize)
  } // DiscreteProblem

  std::string filename() const
  {
    return filename_;
  }

  const Stuff::Common::Configuration& config() const
  {
    return config_;
  }

  bool debug_logging() const
  {
    return debug_logging_;
  }

  GridProviderType& grid_provider()
  {
    return *grid_provider_;
  }

  const GridProviderType& grid_provider() const
  {
    return *grid_provider_;
  }

  const Stuff::Common::Configuration& boundary_info() const
  {
    return boundary_info_;
  }

  const ProblemType& problem() const
  {
    return *problem_;
  }

private:
  static void write_keys_to_file(const std::string name, const std::vector< std::string > keys, std::ofstream& file)
  {
    std::string whitespace = Stuff::Common::whitespaceify(name + " = ");
    file << name << " = " << keys[0] << std::endl;
    for (size_t ii = 1; ii < keys.size(); ++ii)
      file << whitespace << keys[ii] << std::endl;
  } // ... write_keys_to_file(...)

  template< class ConfigProvider >
  static void write_config_to_file(std::ofstream& file)
  {
    for (const auto& type : ConfigProvider::available()) {
      auto config = ConfigProvider::default_config(type, type);
      if (!config.empty())
      file << config;
    }
  } // ... write_config_to_file(...)

  std::string filename_;
  Stuff::Common::Configuration config_;
  bool debug_logging_;
  std::unique_ptr< GridProviderType > grid_provider_;
  Stuff::Common::Configuration boundary_info_;
  std::unique_ptr< const ProblemType > problem_;
}; // class DiscreteProblem

#if HAVE_DUNE_GRID_MULTISCALE

template< class GridImp >
class DiscreteBlockProblem
{
public:
  typedef GridImp GridType;
  typedef grid::Multiscale::ProviderInterface< GridType > GridProviderType;
  typedef grid::Multiscale::MsGridProviders< GridType > GridProviders;
  typedef typename GridType::LeafIntersection IntersectionType;
  typedef Stuff::Grid::BoundaryInfoProvider< IntersectionType > BoundaryInfoProvider;
  typedef typename GridType::template Codim< 0 >::Entity EntityType;
  typedef typename GridType::ctype DomainFieldType;
  static const unsigned int dimDomain = GridType::dimension;
  typedef double RangeFieldType;
  static const unsigned int dimRange = 1;
  typedef LinearElliptic::ProblemInterface
      < EntityType, DomainFieldType, dimDomain, RangeFieldType, dimRange > ProblemType;
  typedef LinearElliptic::ProblemsProvider
      < EntityType, DomainFieldType, dimDomain, RangeFieldType, dimRange > ProblemsProvider;

  static void write_config(const std::string filename,
                           const std::string id,
                           const Stuff::Common::Configuration& problem_cfg = Stuff::Common::Configuration(),
                           const Stuff::Common::Configuration& grid_cfg = Stuff::Common::Configuration(),
                           const Stuff::Common::Configuration& additional_cfg = Stuff::Common::Configuration())
  {
    std::ofstream file;
    file.open(filename);
    file << "[" << id << "]" << std::endl;
    if (grid_cfg.has_key("type"))
      file << "gridprovider = " << grid_cfg.get< std::string >("type") << std::endl;
    else
      write_keys_to_file("gridprovider", GridProviders::available(), file);
    if (problem_cfg.has_key("type"))
      file << "problem = " << problem_cfg.get< std::string >("type") << std::endl;
    else
      write_keys_to_file("problem", ProblemsProvider::available(), file);
    file << "[logging]" << std::endl;
    file << "info  = true" << std::endl;
    file << "debug = false" << std::endl;
    file << "file  = false" << std::endl;
    file << "visualize = true" << std::endl;
    if (grid_cfg.has_key("type"))
      file << Stuff::Common::Configuration(grid_cfg, grid_cfg.get< std::string >("type"));
    else
      write_config_to_file< GridProviders >(file);
    if (problem_cfg.has_key("type"))
      file << Stuff::Common::Configuration(problem_cfg, problem_cfg.get< std::string >("type"));
    else
      write_config_to_file< ProblemsProvider >(file);
    file << additional_cfg;
    file.close();
  } // ... write_config(...)

  DiscreteBlockProblem(const std::string id, const std::vector< std::string >& arguments)
  {
    // mpi
    int argc = boost::numeric_cast< int >(arguments.size());
    char** argv = Stuff::Common::String::vectorToMainArgs(arguments);
#if HAVE_DUNE_FEM
    Fem::MPIManager::initialize(argc, argv);
#else
    MPIHelper::instance(argc, argv);
#endif

    // configuration
    config_ = Stuff::Common::Configuration(argc, argv, id + ".cfg");
    if (!config_.has_sub(id))
      DUNE_THROW(Stuff::Exceptions::configuration_error,
                 "Missing sub '" << id << "' in the following Configuration:\n\n" << config_);
    filename_ = config_.get(id + ".filename", id);

    // logger
    const Stuff::Common::Configuration logger_config = config_.sub("logging");
    int log_flags = Stuff::Common::LOG_CONSOLE;
    debug_logging_ = logger_config.get< bool >("debug", false);
    if (logger_config.get< bool >("info"))
      log_flags = log_flags | Stuff::Common::LOG_INFO;
    if (debug_logging_)
      log_flags = log_flags | Stuff::Common::LOG_DEBUG;
    if (logger_config.get< bool >("file", false))
      log_flags = log_flags | Stuff::Common::LOG_FILE;
    Stuff::Common::Logger().create(log_flags, id, "", "");
    auto& info  = Stuff::Common::Logger().info();

    Timer timer;
    const std::string griprovider_type = config_.get< std::string >(id + ".gridprovider");
    info << "creating grid with '" << griprovider_type << "'..." << std::flush;
    grid_provider_ = GridProviders::create(griprovider_type, config_);
    const auto grid_view = grid_provider_->leaf_view();
    info << " done (took " << timer.elapsed()
         << "s, has " << grid_view.indexSet().size(0) << " element";
    if (grid_view.indexSet().size(0) > 1)
      info << "s";
    info << ")" << std::endl;

    boundary_info_ = Stuff::Grid::BoundaryInfoConfigs::AllDirichlet::default_config();

//    info << "setting up ";
//    timer.reset();
    const std::string problem_type = config_.get< std::string >(id + ".problem");
//    if (!debug_logging_)
//      info << "'" << problem_type << "'... " << std::flush;
    problem_ = ProblemsProvider::create(problem_type, config_);
//    if (debug_logging_)
//      info << *problem_ << std::endl;
//    else
//      info << "done (took " << timer.elapsed() << "s)" << std::endl;

    if (logger_config.get("visualize", true)) {
      info << "visualizing grid and problem... " << std::flush;
      timer.reset();
      grid_provider_->visualize(filename_ + ".ms_grid", false);
      grid_provider_->visualize(boundary_info_, filename_ + ".grid");
      problem_->visualize(grid_view, filename_ + ".problem");
      info << "done (took " << timer.elapsed() << "s)" << std::endl;
    } // if (visualize)
  } // DiscreteBlockProblem

  std::string filename() const
  {
    return filename_;
  }

  const Stuff::Common::Configuration& config() const
  {
    return config_;
  }

  bool debug_logging() const
  {
    return debug_logging_;
  }

  GridProviderType& grid_provider()
  {
    return *grid_provider_;
  }

  const GridProviderType& grid_provider() const
  {
    return *grid_provider_;
  }

  const Stuff::Common::Configuration& boundary_info() const
  {
    return boundary_info_;
  }

  const ProblemType& problem() const
  {
    return *problem_;
  }

private:
  static void write_keys_to_file(const std::string name, const std::vector< std::string > keys, std::ofstream& file)
  {
    std::string whitespace = Stuff::Common::whitespaceify(name + " = ");
    file << name << " = " << keys[0] << std::endl;
    for (size_t ii = 1; ii < keys.size(); ++ii)
      file << whitespace << keys[ii] << std::endl;
  } // ... write_keys_to_file(...)

  template< class ConfigProvider >
  static void write_config_to_file(std::ofstream& file)
  {
    for (const auto& type : ConfigProvider::available()) {
      auto config = ConfigProvider::default_config(type, type);
      if (!config.empty())
      file << config;
    }
  } // ... write_config_to_file(...)

  std::string filename_;
  Stuff::Common::Configuration config_;
  bool debug_logging_;
  std::unique_ptr< GridProviderType > grid_provider_;
  Stuff::Common::Configuration boundary_info_;
  std::unique_ptr< const ProblemType > problem_;
}; // class DiscreteBlockProblem

#endif // HAVE_DUNE_GRID_MULTISCALE

} // namespace LinearElliptic
} // namespace HDD
} // namespace Dune

#endif // DUNE_HDD_LINEARELLIPTIC_DISCRETEPROBLEM_HH


//
//=======================================================================
// Copyright 1997, 1998, 1999, 2000 University of Notre Dame.
// Authors: Andrew Lumsdaine, Lie-Quan Lee, Jeremy G. Siek
//
// This file is part of the Boost Graph Library
//
// You should have received a copy of the License Agreement for the
// Boost Graph Library along with the software; see the file LICENSE.
// If not, contact Office of Research, University of Notre Dame, Notre
// Dame, IN 46556.
//
// Permission to modify the code and to distribute modified code is
// granted, provided the text of this NOTICE is retained, a notice that
// the code was modified is included with the above COPYRIGHT NOTICE and
// with the COPYRIGHT NOTICE in the LICENSE file, and that the LICENSE
// file is distributed with the modified code.
//
// LICENSOR MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR IMPLIED.
// By way of example, but not limitation, Licensor MAKES NO
// REPRESENTATIONS OR WARRANTIES OF MERCHANTABILITY OR FITNESS FOR ANY
// PARTICULAR PURPOSE OR THAT THE USE OF THE LICENSED SOFTWARE COMPONENTS
// OR DOCUMENTATION WILL NOT INFRINGE ANY PATENTS, COPYRIGHTS, TRADEMARKS
// OR OTHER RIGHTS.
//=======================================================================
//
#ifndef BOOST_GRAPH_UNDIRECTED_DFS_HPP
#define BOOST_GRAPH_UNDIRECTED_DFS_HPP

#include <boost/graph/depth_first_search.hpp>

namespace boost {

  namespace detail {

    template <typename IncidenceGraph, typename DFSVisitor,
              typename VertexColorMap, typename EdgeColorMap>
    void undir_dfv_impl
      (const IncidenceGraph& g,
       typename graph_traits<IncidenceGraph>::vertex_descriptor u,
       DFSVisitor& vis,  // pass-by-reference here, important!
       VertexColorMap vertex_color,
       EdgeColorMap edge_color)
    {
      function_requires<IncidenceGraphConcept<IncidenceGraph> >();
      function_requires<DFSVisitorConcept<DFSVisitor, IncidenceGraph> >();
      typedef typename graph_traits<IncidenceGraph>::vertex_descriptor Vertex;
      typedef typename graph_traits<IncidenceGraph>::edge_descriptor Edge;
      function_requires<ReadWritePropertyMapConcept<VertexColorMap,Vertex> >();
      function_requires<ReadWritePropertyMapConcept<EdgeColorMap,Edge> >();
      typedef typename property_traits<VertexColorMap>::value_type ColorValue;
      typedef typename property_traits<EdgeColorMap>::value_type EColorValue;
      function_requires< ColorValueConcept<ColorValue> >();
      function_requires< ColorValueConcept<EColorValue> >();
      typedef color_traits<ColorValue> Color;
      typedef color_traits<EColorValue> EColor;
      typename graph_traits<IncidenceGraph>::out_edge_iterator ei, ei_end;

      put(vertex_color, u, Color::gray());   vis.discover_vertex(u, g);
      for (tie(ei, ei_end) = out_edges(u, g); ei != ei_end; ++ei) {
        Vertex v = target(*ei, g);           vis.examine_edge(*ei, g);
        ColorValue v_color = get(vertex_color, v);
        EColorValue uv_color = get(edge_color, *ei);
        put(edge_color, *ei, EColor::black());
        if (v_color == Color::white()) {     vis.tree_edge(*ei, g);
          undir_dfv_impl(g, v, vis, vertex_color, edge_color);
        } else if (v_color == Color::gray() && uv_color == EColor::white())
                                             vis.back_edge(*ei, g);
      }
      put(vertex_color, u, Color::black());  vis.finish_vertex(u, g);
    }
  } // namespace detail

  template <typename Graph, typename DFSVisitor,
            typename VertexColorMap, typename EdgeColorMap,
            typename Vertex>
  void
  undirected_dfs(const Graph& g, DFSVisitor vis,
                 VertexColorMap vertex_color, EdgeColorMap edge_color,
                 Vertex start_vertex)
  {
    function_requires<DFSVisitorConcept<DFSVisitor, Graph> >();
      function_requires<EdgeListGraphConcept<Graph> >();

    typedef typename property_traits<VertexColorMap>::value_type ColorValue;
    typedef color_traits<ColorValue> Color;

    typename graph_traits<Graph>::vertex_iterator ui, ui_end;
    for (tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) {
      put(vertex_color, *ui, Color::white());   vis.initialize_vertex(*ui, g);
    }
    typename graph_traits<Graph>::edge_iterator ei, ei_end;
    for (tie(ei, ei_end) = edges(g); ei != ei_end; ++ei)
      put(edge_color, *ei, Color::white());

    if (start_vertex != *vertices(g).first){ vis.start_vertex(start_vertex, g);
      detail::undir_dfv_impl(g, start_vertex, vis, vertex_color, edge_color);
    }

    for (tie(ui, ui_end) = vertices(g); ui != ui_end; ++ui) {
      ColorValue u_color = get(vertex_color, *ui);
      if (u_color == Color::white()) {       vis.start_vertex(*ui, g);
        detail::undir_dfv_impl(g, *ui, vis, vertex_color, edge_color);
      }
    }
  }

  template <typename Graph, typename DFSVisitor, typename VertexColorMap,
    typename EdgeColorMap>
  void
  undirected_dfs(const Graph& g, DFSVisitor vis,
                 VertexColorMap vertex_color, EdgeColorMap edge_color)
  {
    undirected_dfs(g, vis, vertex_color, edge_color, *vertices(g).first);
  }

  namespace detail {
    template <typename VertexColorMap>
    struct udfs_dispatch {

      template <typename Graph, typename Vertex,
                typename DFSVisitor, typename EdgeColorMap,
                typename P, typename T, typename R>
      static void
      apply(const Graph& g, DFSVisitor vis, Vertex start_vertex,
            const bgl_named_params<P, T, R>&,
            EdgeColorMap edge_color,
            VertexColorMap vertex_color)
      {
        undirected_dfs(g, vis, vertex_color, edge_color, start_vertex);
      }
    };

    template <>
    struct udfs_dispatch<detail::error_property_not_found> {
      template <typename Graph, typename Vertex, typename DFSVisitor,
                typename EdgeColorMap,
                typename P, typename T, typename R>
      static void
      apply(const Graph& g, DFSVisitor vis, Vertex start_vertex,
            const bgl_named_params<P, T, R>& params,
            EdgeColorMap edge_color,
            detail::error_property_not_found)
      {
        std::vector<default_color_type> color_vec(num_vertices(g));
        default_color_type c = white_color; // avoid warning about un-init
        undirected_dfs
          (g, vis, make_iterator_property_map
           (color_vec.begin(),
            choose_const_pmap(get_param(params, vertex_index),
                              g, vertex_index), c),
           edge_color,
           start_vertex);
      }
    };

  } // namespace detail

  // Named Parameter Variant
  template <typename Graph, typename P, typename T, typename R>
  void
  undirected_dfs(const Graph& g,
                 const bgl_named_params<P, T, R>& params)
  {
    typedef typename property_value< bgl_named_params<P, T, R>,
      vertex_color_t>::type C;
    detail::udfs_dispatch<C>::apply
      (g,
       choose_param(get_param(params, graph_visitor),
                    make_dfs_visitor(null_visitor())),
       choose_param(get_param(params, root_vertex_t()),
                    *vertices(g).first),
       params,
       get_param(params, edge_color),
       get_param(params, vertex_color)
       );
  }

  template <typename IncidenceGraph, typename DFSVisitor,
    typename VertexColorMap, typename EdgeColorMap>
  void undirected_depth_first_visit
    (const IncidenceGraph& g,
     typename graph_traits<IncidenceGraph>::vertex_descriptor u,
     DFSVisitor vis, VertexColorMap vertex_color, EdgeColorMap edge_color)
  {
    detail::undir_dfv_impl(g, u, vis, vertex_color, edge_color);
  }

} // namespace boost

#endif