//| Copyright Inria May 2015
//| This project has received funding from the European Research Council (ERC) under
//| the European Union's Horizon 2020 research and innovation programme (grant
//| agreement No 637972) - see http://www.resibots.eu
//|
//| Contributor(s):
//|   - Jean-Baptiste Mouret (jean-baptiste.mouret@inria.fr)
//|   - Antoine Cully (antoinecully@gmail.com)
//|   - Konstantinos Chatzilygeroudis (konstantinos.chatzilygeroudis@inria.fr)
//|   - Federico Allocati (fede.allocati@gmail.com)
//|   - Vaios Papaspyros (b.papaspyros@gmail.com)
//|   - Roberto Rama (bertoski@gmail.com)
//|
//| This software is a computer library whose purpose is to optimize continuous,
//| black-box functions. It mainly implements Gaussian processes and Bayesian
//| optimization.
//| Main repository: http://github.com/resibots/limbo
//| Documentation: http://www.resibots.eu/limbo
//|
//| This software is governed by the CeCILL-C license under French law and
//| abiding by the rules of distribution of free software.  You can  use,
//| modify and/ or redistribute the software under the terms of the CeCILL-C
//| license as circulated by CEA, CNRS and INRIA at the following URL
//| "http://www.cecill.info".
//|
//| As a counterpart to the access to the source code and  rights to copy,
//| modify and redistribute granted by the license, users are provided only
//| with a limited warranty  and the software's author,  the holder of the
//| economic rights,  and the successive licensors  have only  limited
//| liability.
//|
//| In this respect, the user's attention is drawn to the risks associated
//| with loading,  using,  modifying and/or developing or reproducing the
//| software by the user in light of its specific status of free software,
//| that may mean  that it is complicated to manipulate,  and  that  also
//| therefore means  that it is reserved for developers  and  experienced
//| professionals having in-depth computer knowledge. Users are therefore
//| encouraged to load and test the software's suitability as regards their
//| requirements in conditions enabling the security of their systems and/or
//| data to be ensured and,  more generally, to use and operate it in the
//| same conditions as regards security.
//|
//| The fact that you are presently reading this means that you have had
//| knowledge of the CeCILL-C license and that you accept its terms.
//|
#ifndef LIMBO_OPT_CMAES_HPP
#define LIMBO_OPT_CMAES_HPP

#include <Eigen/Core>
#include <iostream>
#include <vector>

#include <limbo/opt/optimizer.hpp>
#include <limbo/tools/macros.hpp>
#include <limbo/tools/parallel.hpp>

#ifndef USE_LIBCMAES
#warning NO libcmaes support
#else

#include <libcmaes/cmaes.h>

namespace limbo {
    namespace defaults {
        struct opt_cmaes {
            /// @ingroup opt_defaults
            /// number of restarts of CMA-ES
            BO_PARAM(int, restarts, 1);
            /// @ingroup opt_defaults
            /// maximum number of calls to the function to be optimized
            BO_PARAM(int, max_fun_evals, -1);
            /// @ingroup opt_defaults
            /// maximum number of iterations to be optimized
            BO_PARAM(int, max_iters, -1);
            /// @ingroup opt_defaults
            /// enable/disable automatic max iterations
            BO_PARAM(bool, auto_max_iters, true);
            /// @ingroup opt_defaults
            /// enable/disable stopping if getting the same function value repeatedly
            BO_PARAM(bool, equal_fun_evals, true);
            /// @ingroup opt_defaults
            BO_PARAM(bool, stagnation, true);
            /// @ingroup opt_defaults
            /// threshold based on the difference in value of a fixed number
            /// of trials: if bigger than 0, it enables the tolerance criteria
            /// for stopping based in the history of rewards.
            BO_PARAM(double, fun_tolerance, -1);
            /// @ingroup opt_defaults
            /// tolerance for convergence: stop when an optimization step (or an
            /// estimate of the optimum) changes all the parameter values by
            /// less than tol multiplied by the absolute value of the parameter
            /// value.
            /// IGNORED if negative
            BO_PARAM(double, xrel_tolerance, -1);
            /// @ingroup opt_defaults
            /// function value target: if bigger than 0, enables the function
            /// target criteria for stopping if the performance is greater than this value.
            BO_PARAM(double, fun_target, -1);
            /// @ingroup opt_defaults
            /// computes initial objective function value: if true, it evaluates the
            /// provided starting point (if any).
            BO_PARAM(bool, fun_compute_initial, false);
            /// @ingroup opt_defaults
            /// sets the version of cmaes to use (possible values are: CMAES_DEFAULT, IPOP_CMAES, BIPOP_CMAES, aCMAES, aIPOP_CMAES, aBIPOP_CMAES, sepCMAES, sepIPOP_CMAES, sepBIPOP_CMAES, sepaCMAES, sepaIPOP_CMAES, sepaBIPOP_CMAES, VD_CMAES, VD_IPOP_CMAES, VD_BIPOP_CMAES
            BO_PARAM(int, variant, aIPOP_CMAES);
            /// @ingroup opt_defaults
            /// defines elitism strategy:
            /// 0 -> no elitism
            /// 1 -> elitism: reinjects the best-ever seen solution
            /// 2 -> initial elitism: reinject x0 as long as it is not improved upon
            /// 3 -> initial elitism on restart: restart if best encountered solution is not the the final
            /// solution and reinjects the best solution until the population has better fitness, in its majority
            BO_PARAM(int, elitism, 0);
            /// @ingroup opt_defaults
            /// enables or disables uncertainty handling: https://hal.inria.fr/file/index/docid/276216/filename/TEC2008.pdf
            BO_PARAM(bool, handle_uncertainty, false);
            /// @ingroup opt_defaults
            /// enables or disables verbose mode for cmaes
            BO_PARAM(bool, verbose, false);
            /// @ingroup opt_defaults
            /// lower bound (in input) for cmaes
            BO_PARAM(double, lbound, 0.0);
            /// @ingroup opt_defaults
            /// upper bound (in input) for cmaes
            BO_PARAM(double, ubound, 1.0);
            /// @ingroup opt_defaults
            /// if stochastic, the mean of the
            /// last distribution is returned
            /// otherwise, the best ever candidate
            /// is returned. If handle_uncertainty is on,
            /// this is also enabled.
            BO_PARAM(bool, stochastic, false);
            /// @ingroup opt_defaults
            /// number of parent population
            /// -1 to automatically determine
            BO_PARAM(int, lambda, -1);
        };
    } // namespace defaults

    namespace opt {
        /// @ingroup opt
        /// Covariance Matrix Adaptation Evolution Strategy by Hansen et al.
        /// (See: https://www.lri.fr/~hansen/cmaesintro.html)
        /// - our implementation is based on libcmaes (https://github.com/beniz/libcmaes)
        /// - Support bounded and unbounded optimization
        /// - Only available if libcmaes is installed (see the compilation instructions)
        ///
        /// - Parameters :
        ///   - int variant
        ///   - int elitism
        ///   - int restarts
        ///   - int max_fun_evals
        ///   - int max_iters
        ///   - bool auto_max_iters
        ///   - bool equal_fun_evals
        ///   - bool stagnation
        ///   - double fun_tolerance
        ///   - double xrel_tolerance
        ///   - double fun_target
        ///   - bool fun_compute_initial
        ///   - bool handle_uncertainty
        ///   - bool verbose
        ///   - double lb (lower bounds)
        ///   - double ub (upper bounds)
        ///   - bool stochastic
        ///   - int lambda
        template <typename Params>
        struct Cmaes {
        public:
            using ProgressFunction = std::function<void(const libcmaes::CMASolutions&)>;
            using ProgressFunctionUnbounded = std::function<int(const libcmaes::CMAParameters<libcmaes::GenoPheno<libcmaes::NoBoundStrategy>>&, const libcmaes::CMASolutions&)>;
            using ProgressFunctionBounded = std::function<int(const libcmaes::CMAParameters<libcmaes::GenoPheno<libcmaes::pwqBoundStrategy>>&, const libcmaes::CMASolutions&)>;

            template <typename F>
            Eigen::VectorXd operator()(const F& f, const Eigen::VectorXd& init, double bounded) const
            {
                size_t dim = init.size();

                // wrap the function
                libcmaes::FitFunc f_cmaes = [&](const double* x, const int n) {
                    Eigen::Map<const Eigen::VectorXd> m(x, n);
                    // remember that our optimizers maximize
                    return -eval(f, m);
                };

                if (bounded)
                    return _opt_bounded(f_cmaes, dim, init);
                else
                    return _opt_unbounded(f_cmaes, dim, init);
            }

            void set_progress_function(const ProgressFunction& pfunc) { _pfunc = pfunc; }
            void set_unbounded_progress_function(const ProgressFunctionUnbounded& pfunc) { _pfunc_unbounded = pfunc; }
            void set_bounded_progress_function(const ProgressFunctionBounded& pfunc) { _pfunc_bounded = pfunc; }

        private:
            ProgressFunction _pfunc;
            ProgressFunctionUnbounded _pfunc_unbounded;
            ProgressFunctionBounded _pfunc_bounded;

            // F is a CMA-ES style function, not our function
            template <typename F>
            Eigen::VectorXd _opt_unbounded(F& f_cmaes, int dim, const Eigen::VectorXd& init) const
            {
                using namespace libcmaes;
                // initial step-size, i.e. estimated initial parameter error.
                double sigma = 0.5;
                std::vector<double> x0(init.data(), init.data() + init.size());

                CMAParameters<> cmaparams(x0, sigma, Params::opt_cmaes::lambda());
                _set_common_params(cmaparams, dim);

                auto pfunc = CMAStrategy<CovarianceUpdate, GenoPheno<NoBoundStrategy>>::_defaultPFunc;
                if (_pfunc_unbounded) {
                    pfunc = _pfunc_unbounded;
                }
                else if (_pfunc) {
                    pfunc = [this](const CMAParameters<GenoPheno<NoBoundStrategy>>& params, const CMASolutions& sols) { _pfunc(sols); return 0; };
                }

                // the optimization itself
                CMASolutions cmasols = cmaes<>(f_cmaes, cmaparams, pfunc);
                if (Params::opt_cmaes::stochastic() || Params::opt_cmaes::handle_uncertainty())
                    return cmasols.xmean();

                return cmasols.get_best_seen_candidate().get_x_dvec();
            }

            // F is a CMA-ES style function, not our function
            template <typename F>
            Eigen::VectorXd _opt_bounded(F& f_cmaes, int dim, const Eigen::VectorXd& init) const
            {
                using namespace libcmaes;
                // create the parameter object
                // boundary_transformation
                double lbounds[dim], ubounds[dim]; // arrays for lower and upper parameter bounds, respectively
                for (int i = 0; i < dim; i++) {
                    lbounds[i] = Params::opt_cmaes::lbound();
                    ubounds[i] = Params::opt_cmaes::ubound();
                }
                GenoPheno<pwqBoundStrategy> gp(lbounds, ubounds, dim);
                // initial step-size, i.e. estimated initial parameter error.
                double sigma = 0.5 * std::abs(Params::opt_cmaes::ubound() - Params::opt_cmaes::lbound());
                Eigen::VectorXd init_geno = gp.geno(init);
                std::vector<double> x0(init_geno.data(), init_geno.data() + init_geno.size());
                // -1 for automatically decided lambda, 0 is for random seeding of the internal generator.
                CMAParameters<GenoPheno<pwqBoundStrategy>> cmaparams(dim, &x0.front(), sigma, Params::opt_cmaes::lambda(), 0, gp);
                _set_common_params(cmaparams, dim);

                auto pfunc = CMAStrategy<CovarianceUpdate, GenoPheno<pwqBoundStrategy>>::_defaultPFunc;
                if (_pfunc_bounded) {
                    pfunc = _pfunc_bounded;
                }
                else if (_pfunc) {
                    pfunc = [this](const CMAParameters<GenoPheno<pwqBoundStrategy>>& params, const CMASolutions& sols) { _pfunc(sols); return 0; };
                }

                // the optimization itself
                CMASolutions cmasols = cmaes<GenoPheno<pwqBoundStrategy>>(f_cmaes, cmaparams, pfunc);
                if (Params::opt_cmaes::stochastic() || Params::opt_cmaes::handle_uncertainty())
                    return gp.pheno(cmasols.xmean());

                return gp.pheno(cmasols.get_best_seen_candidate().get_x_dvec());
            }

            template <typename P>
            void _set_common_params(P& cmaparams, int dim) const
            {
                using namespace libcmaes;

                // set multi-threading to true
                cmaparams.set_mt_feval(true);
                // aCMAES should be the best choice
                // [see: https://github.com/beniz/libcmaes/wiki/Practical-hints ]
                // but we want the restart -> aIPOP_CMAES
                cmaparams.set_algo(Params::opt_cmaes::variant());
                cmaparams.set_restarts(Params::opt_cmaes::restarts());
                cmaparams.set_elitism(Params::opt_cmaes::elitism());

                // if no max fun evals provided, we compute a recommended value
                size_t max_evals = Params::opt_cmaes::max_fun_evals() < 0
                    ? (900.0 * (dim + 3.0) * (dim + 3.0))
                    : Params::opt_cmaes::max_fun_evals();
                cmaparams.set_max_fevals(max_evals);
                cmaparams.set_stopping_criteria(MAXFEVALS, true);

                // if no max iters provided, we put a safety limit (to not take forever)
                size_t max_iters = Params::opt_cmaes::max_iters() < 0
                    ? 1000000
                    : Params::opt_cmaes::max_iters();
                cmaparams.set_max_iter(max_iters);
                cmaparams.set_stopping_criteria(MAXITER, true);

                // enable/disable automatic max iterations
                cmaparams.set_stopping_criteria(AUTOMAXITER, Params::opt_cmaes::auto_max_iters());

                if (Params::opt_cmaes::fun_tolerance() < 0) {
                    cmaparams.set_stopping_criteria(TOLHISTFUN, false);
                }
                else {
                    // the FTARGET criteria also allows us to enable ftolerance
                    cmaparams.set_stopping_criteria(TOLHISTFUN, true);
                    cmaparams.set_ftolerance(Params::opt_cmaes::fun_tolerance());
                }

                // we allow to set the ftarget parameter
                if (Params::opt_cmaes::fun_target() > 0) {
                    cmaparams.set_stopping_criteria(FTARGET, true);
                    cmaparams.set_ftarget(-Params::opt_cmaes::fun_target());
                }
                else {
                    // we do not know what is the actual maximum / minimum of the function
                    // therefore we deactivate this stopping criterion
                    cmaparams.set_stopping_criteria(FTARGET, false);
                }

                // enable stopping criteria by several equalfunvals
                cmaparams.set_stopping_criteria(EQUALFUNVALS, Params::opt_cmaes::equal_fun_evals());

                // enable additional criteria to stop
                // set different tolerance if available
                if (Params::opt_cmaes::xrel_tolerance() > 0) {
                    cmaparams.set_stopping_criteria(TOLX, true);
                    cmaparams.set_xtolerance(Params::opt_cmaes::xrel_tolerance());
                }
                else {
                    cmaparams.set_stopping_criteria(TOLX, false);
                }

                // enable stopping criteria because of mal-conditions
                cmaparams.set_stopping_criteria(CONDITIONCOV, true);
                cmaparams.set_stopping_criteria(TOLUPSIGMA, true);

                // disable stopping criteria for partial success
                cmaparams.set_stopping_criteria(NOEFFECTAXIS, false);
                cmaparams.set_stopping_criteria(NOEFFECTCOOR, false);

                cmaparams.set_stopping_criteria(STAGNATION, Params::opt_cmaes::stagnation());

                // enable or disable different parameters
                cmaparams.set_initial_fvalue(Params::opt_cmaes::fun_compute_initial());
                cmaparams.set_uh(Params::opt_cmaes::handle_uncertainty());
                cmaparams.set_quiet(!Params::opt_cmaes::verbose());
            }
        };
    } // namespace opt
} // namespace limbo
#endif
#endif


// Copyright 2019, Intel Corporation

#include "pmlc/dialect/stripe/analysis.h"

#include <utility>

#include <boost/math/common_factor.hpp>

#include "pmlc/dialect/stripe/dialect.h"

namespace pmlc {
namespace dialect {
namespace stripe {

AffineRange::AffineRange(int64_t _min, int64_t _max, uint64_t _stride) : min(_min), max(_max), stride(_stride) {
  if (min == max) {
    stride = 0;
  }
}

AffineRange& AffineRange::operator*=(int64_t x) {
  min *= x;
  max *= x;
  if (x < 0) {
    std::swap(min, max);
  }
  stride *= x;
  return *this;
}

AffineRange& AffineRange::operator+=(const AffineRange& x) {
  min += x.min;
  max += x.max;
  stride = boost::math::gcd(stride, x.stride);
  return *this;
}

AffineRange& AffineRange::operator|=(const AffineRange& x) {
  min = std::min(min, x.min);
  max = std::max(max, x.max);
  stride = boost::math::gcd(stride, x.stride);
  return *this;
}

AffineRange::AffineRange(const AffinePolynomial& poly) : min(poly.constant), max(poly.constant), stride(0) {
  // Go over each term and adjust
  for (const auto& kvp : poly.terms) {
    // Update the stride
    stride = boost::math::gcd(stride, uint64_t(std::abs(kvp.second)));
    // Extract the parallel for this affine is an argument of
    auto pf = mlir::cast<ParallelForOp>(kvp.first.getOwner()->getParentOp());
    // Extract the appropriate attribute from ranges
    Attribute ra = pf.ranges().getValue()[kvp.first.getArgNumber()];
    // Turn the range into an integer
    int64_t range = ra.cast<IntegerAttr>().getInt();
    // Update min/max
    if (kvp.second >= 0) {
      max += (range - 1) * kvp.second;
    } else {
      min += (range - 1) * kvp.second;
    }
  }
}

bool FlatTensorAccess::operator<(const FlatTensorAccess& rhs) const {
  if (base != rhs.base) {
    return base.getAsOpaquePointer() < rhs.base.getAsOpaquePointer();
  }
  return access < rhs.access;
}

bool FlatTensorAccess::operator==(const FlatTensorAccess& rhs) const {
  return base == rhs.base && access == rhs.access;
}

FlatTensorAccess ComputeAccess(Value tensor) {
  FlatTensorAccess ret;
  if (auto bop = tensor.getDefiningOp()) {
    if (auto op = mlir::dyn_cast<AllocateOp>(bop)) {
      ret.base = op.result();
      ret.base_type = op.layout();
      ret.access.resize(ret.base_type.getRank());
    } else if (auto op = mlir::dyn_cast<RefineOp>(bop)) {
      ret = ComputeAccess(op.in());
      for (size_t i = 0; i < ret.access.size(); i++) {
        ret.access[i] += AffinePolynomial(op.getOffset(i));
      }
    } else {
      throw std::runtime_error("Invalid tensor value in ComputeAccess");
    }
  } else if (auto arg = tensor.dyn_cast<mlir::BlockArgument>()) {
    auto parentOp = arg.getOwner()->getParentOp();
    auto funcOp = mlir::dyn_cast<mlir::FuncOp>(parentOp);
    if (!funcOp) {
      throw std::runtime_error("Invalid tensor value: block argument not contained by FuncOp");
    }
    auto attrName = stripe::Dialect::getDialectAttrName("layout");
    auto attr = funcOp.getArgAttrOfType<mlir::TypeAttr>(arg.getArgNumber(), attrName);
    assert(attr && "Expected 'layout' attribute in TensorRefType function argument");
    ret.base = tensor;
    ret.base_type = attr.getValue().cast<TensorType>();
    ret.access.resize(ret.base_type.getRank());
  } else {
    throw std::runtime_error("Invalid tensor value");
  }
  return ret;
}

bool SafeConstraintInterior(ParallelForOp op) {
  // Get an iterator to the begining of the interior
  auto block = &op.inner().front();
  // Get the penultimate Op (ignoring the terminator), which should be a constraint
  auto it_con = std::prev(block->end(), 2);
  // Check that it's good
  if (it_con == block->end() || !mlir::isa<ConstraintOp>(*it_con)) {
    return false;
  }
  // Check that all prior ops are no-side-effect and fail if not
  for (auto it = block->begin(); it != it_con; ++it) {
    if (!it->hasNoSideEffect()) {
      return false;
    }
  }
  return true;
}

}  // namespace stripe
}  // namespace dialect
}  // namespace pmlc


#define BOOST_TEST_MODULE
#include <boost/test/unit_test.hpp>
#include <util/test_macros.hpp>
#include <numerics/armadillo.hpp>

#include <unity/lib/variant.hpp>
#include <unity/lib/unity_sframe.hpp>
#include <unity/lib/gl_sframe.hpp>
#include <toolkits/supervised_learning/logistic_regression.hpp>
#include <unity/dml/dml_class_registry.hpp>
#include <sframe/testing_utils.hpp>

#include <unity/dml/dml_toolkit_runner.hpp>

typedef Eigen::Matrix<double, Eigen::Dynamic,1>  DenseVector;
using namespace turi;
using namespace turi::supervised;

/**
 * Test suite for distributed logistic regression.
*/
struct distributed_logistic_regression_test  {

 public:
  void test_logistic_regression_basic_2d() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 100},
      {"features", 1}};

    size_t n = runner.get_default_num_workers_from_env();
    test_impl(opts, n);
  }

  void test_logistic_regression_small() {
    std::map<std::string, flexible_type> opts = {
      {"examples", 1000},
      {"features", 10}};

    size_t n = runner.get_default_num_workers_from_env();
    test_impl(opts, n);
  }

  void setup() {
    runner.set_library("libdistributed_supervised_learning.so");
    dml_class_registry::get_instance().register_model<logistic_regression>();
    working_dir = turi::get_temp_name();
    fileio::create_directory(working_dir);
  }

  void teardown() {
    fileio::delete_path_recursive(working_dir);
  }

  void test_impl(std::map<std::string, flexible_type> opts, size_t num_workers) {

    setup();

    try {
      // Arrange
      // ----------------------------------------------------------------------
      size_t examples = opts.at("examples");
      size_t features = opts.at("features");

      // Coefficients
      DenseVector coefs(features+1);
      coefs.randn();

      // Feature names
      std::string feature_types;
      for(size_t i=0; i < features; i++){
        feature_types += "n";
      }

      // Generate some data.
      sframe data = make_random_sframe(examples, feature_types, true);
      // Binary target.
      sframe _y = data.select_columns({"target"});
      std::shared_ptr<unity_sframe> _uy(new unity_sframe());
      _uy->construct_from_sframe(_y);
      gl_sframe gl_y(_uy);
      gl_y["target"] = gl_y["target"] > gl_y["target"].mean();

      // Make the data into the right format.
      sframe y = (*gl_y.get_proxy()->get_underlying_sframe());
      sframe X = data;
      X = X.remove_column(X.column_index("target"));

      // Setup the arguments.
      std::map<std::string, flexible_type> options = {
        {"convergence_threshold", 1e-2},
        {"step_size", 1.0},
        {"lbfgs_memory_level", 3},
        {"max_iterations", 10},
        {"solver", "newton"},
        {"l1_penalty", 0.0},
        {"l2_penalty", 0.0}
      };
      variant_map_type params;
      std::shared_ptr<unity_sframe> uX(new unity_sframe());
      std::shared_ptr<unity_sframe> uy(new unity_sframe());
      uX->construct_from_sframe(X);
      uy->construct_from_sframe(y);
      params["model_name"] = std::string("classifier_logistic_regression");
      params["features"] = to_variant(uX);
      params["target"] = to_variant(uy);
      for (const auto& kvp: options){
        params[kvp.first] = to_variant(kvp.second);
      }

      // Act
      // ----------------------------------------------------------------------
      // Train the model.
      variant_type ret = runner.run("distributed_supervised_train", params, working_dir, num_workers);
      std::shared_ptr<logistic_regression> model =
            variant_get_value<std::shared_ptr<logistic_regression>>(ret);

      // Assert
      // ----------------------------------------------------------------------
      // Check options.
      std::map<std::string, flexible_type> _options = model->get_current_options();
      for (auto& kvp: options){
        TS_ASSERT(_options[kvp.first] == kvp.second);
      }
      TS_ASSERT(model->is_trained() == true);
    } catch (...) {
      teardown();
      throw;
    }
    teardown();
  }

  dml_toolkit_runner runner;
  std::string working_dir;
};

BOOST_FIXTURE_TEST_SUITE(_distributed_logistic_regression_test, distributed_logistic_regression_test)
BOOST_AUTO_TEST_CASE(test_logistic_regression_basic_2d) {
  distributed_logistic_regression_test::test_logistic_regression_basic_2d();
}
BOOST_AUTO_TEST_CASE(test_logistic_regression_small) {
  distributed_logistic_regression_test::test_logistic_regression_small();
}
BOOST_AUTO_TEST_SUITE_END()


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

The Medical Imaging Interaction Toolkit (MITK)

Copyright (c) German Cancer Research Center,
Division of Medical and Biological Informatics.
All rights reserved.

This software is distributed WITHOUT ANY WARRANTY; without
even the implied warranty of MERCHANTABILITY or FITNESS FOR
A PARTICULAR PURPOSE.

See LICENSE.txt or http://www.mitk.org for details.

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

//misc
#define _USE_MATH_DEFINES
#include <math.h>

// Blueberry
#include <berryISelectionService.h>
#include <berryIWorkbenchWindow.h>

// Qmitk
#include "QmitkBrainExtractionView.h"

// MITK
#include <mitkNodePredicateDataType.h>

// Qt
#include <QMessageBox>
#include <QFileDialog>
#include <QDir>
#include <QDirIterator>
#include <QTimer>

#include <mitkNodePredicateDimension.h>
#include <mitkNodePredicateAnd.h>
#include <mitkIOUtil.h>
#include <mitkIPythonService.h>
#include <itkResampleImageFilter.h>
#include <mitkImageCast.h>
#include <itkBSplineInterpolateImageFunction.h>
#include <itkLinearInterpolateImageFunction.h>
#include <itkThresholdImageFilter.h>
#include <itksys/SystemTools.hxx>
#include <itkB0ImageExtractionToSeparateImageFilter.h>

#include <usGetModuleContext.h>
#include <usModuleContext.h>
#include <usModule.h>
#include <usModuleResource.h>
#include <usModuleResourceStream.h>
#include <boost/algorithm/string.hpp>

#include <BetData.h>

#define _USE_MATH_DEFINES
#include <math.h>

typedef itksys::SystemTools ist;

const std::string QmitkBrainExtractionView::VIEW_ID = "org.mitk.views.brainextraction";

QmitkBrainExtractionView::QmitkBrainExtractionView()
  : QmitkAbstractView()
  , m_Controls( 0 )
  , m_DiffusionImage( nullptr )
{

}

// Destructor
QmitkBrainExtractionView::~QmitkBrainExtractionView()
{
}

void QmitkBrainExtractionView::CreateQtPartControl( QWidget *parent )
{
  // build up qt view, unless already done
  if ( !m_Controls )
  {
    // create GUI widgets from the Qt Designer's .ui file
    m_Controls = new Ui::QmitkBrainExtractionViewControls;
    m_Controls->setupUi( parent );
    connect( m_Controls->m_ImageBox, SIGNAL(currentIndexChanged(int)), this, SLOT(UpdateGUI()) );
    connect( m_Controls->m_StartButton, SIGNAL(clicked()), this, SLOT(StartBrainExtraction()) );
    this->m_Parent = parent;

    m_Controls->m_ImageBox->SetDataStorage(this->GetDataStorage());
    mitk::NodePredicateDimension::Pointer dimPred = mitk::NodePredicateDimension::New(3);
    mitk::TNodePredicateDataType<mitk::Image>::Pointer isImagePredicate = mitk::TNodePredicateDataType<mitk::Image>::New();
    m_Controls->m_ImageBox->SetPredicate(isImagePredicate);

    UpdateGUI();
  }
}

void QmitkBrainExtractionView::OnSelectionChanged(berry::IWorkbenchPart::Pointer, const QList<mitk::DataNode::Pointer>& )
{
}

void QmitkBrainExtractionView::UpdateGUI()
{
  if (m_Controls->m_ImageBox->GetSelectedNode().IsNotNull())
    m_Controls->m_StartButton->setEnabled(true);
  else
    m_Controls->m_StartButton->setEnabled(false);
}

void QmitkBrainExtractionView::SetFocus()
{
  UpdateGUI();
  m_Controls->m_StartButton->setFocus();
}

std::string QmitkBrainExtractionView::GetPythonFile(std::string filename)
{
  std::string out = "";
  std::string exec_dir = QCoreApplication::applicationDirPath().toStdString();
  for (auto dir : mitk::bet::relative_search_dirs)
  {
    if ( ist::FileExists( ist::GetCurrentWorkingDirectory() + dir + filename) )
    {
      out = ist::GetCurrentWorkingDirectory() + dir + filename;
      return out;
    }
    if ( ist::FileExists( exec_dir + dir + filename) )
    {
      out = exec_dir + dir + filename;
      return out;
    }
  }
  for (auto dir : mitk::bet::absolute_search_dirs)
  {
    if ( ist::FileExists( dir + filename) )
    {
      out = dir + filename;
      return out;
    }
  }

  return out;
}

void QmitkBrainExtractionView::StartBrainExtraction()
{
  mitk::DataNode::Pointer node = m_Controls->m_ImageBox->GetSelectedNode();
  mitk::Image::Pointer mitk_image = dynamic_cast<mitk::Image*>(node->GetData());

  bool missing_file = false;
  std::string missing_file_string = "";
  if ( GetPythonFile("run_mitk.py").empty() )
  {
    missing_file_string += "Brain extraction script file missing: run_mitk.py\n\n";
    missing_file = true;
  }

  if ( GetPythonFile("model_final.model").empty() )
  {
    missing_file_string += "Brain extraction model file missing: model_final.model\n\n";
    missing_file = true;
  }

  if ( GetPythonFile("basic_config_just_like_braintumor.py").empty() )
  {
    missing_file_string += "Config file missing: basic_config_just_like_braintumor.py\n\n";
    missing_file = true;
  }

  if (missing_file)
  {
    QMessageBox::warning(nullptr, "Error", (missing_file_string).c_str(), QMessageBox::Ok);
    return;
  }

  try
  {
    us::ModuleContext* context = us::GetModuleContext();
    us::ServiceReference<mitk::IPythonService> m_PythonServiceRef = context->GetServiceReference<mitk::IPythonService>();
    mitk::IPythonService* m_PythonService = dynamic_cast<mitk::IPythonService*> ( context->GetService<mitk::IPythonService>(m_PythonServiceRef) );
    mitk::IPythonService::ForceLoadModule();

    // load essential modules
    m_PythonService->Execute("import SimpleITK as sitk");
    m_PythonService->Execute("import SimpleITK._SimpleITK as _SimpleITK");
    m_PythonService->Execute("import numpy");

    // extend python search path
    std::string exec_dir = QCoreApplication::applicationDirPath().toStdString();
    std::string pythonpath = "";
    for (auto dir : mitk::bet::relative_search_dirs)
      pythonpath += "','" + ist::GetCurrentWorkingDirectory() + dir;
    for (auto dir : mitk::bet::relative_search_dirs)
      pythonpath += "','" + exec_dir + dir;
    for (auto dir : mitk::bet::absolute_search_dirs)
      pythonpath += "','" + dir;
    m_PythonService->Execute("paths=['"+pythonpath+"']");

    // set input files (model and config)
    m_PythonService->Execute("model_file=\""+GetPythonFile("model_final.model")+"\"");
    m_PythonService->Execute("config_file=\""+GetPythonFile("basic_config_just_like_braintumor.py")+"\"");

    // copy input  image to python
    m_PythonService->CopyToPythonAsSimpleItkImage( mitk_image, "in_image");

    // run segmentation script
    m_PythonService->ExecuteScript( GetPythonFile("run_mitk.py") );

    // clean up after running script (better way than deleting individual variables?)
    if(m_PythonService->DoesVariableExist("in_image"))
      m_PythonService->Execute("del in_image");

    // check for errors
    if(!m_PythonService->GetVariable("error_string").empty())
    {
      QMessageBox::warning(nullptr, "Error", QString(m_PythonService->GetVariable("error_string").c_str()), QMessageBox::Ok);
      return;
    }

    // get output images and add to datastorage
    std::string output_variables = m_PythonService->GetVariable("output_variables");
    std::vector<std::string> outputs;
    boost::split(outputs, output_variables, boost::is_any_of(","));

    std::string output_types = m_PythonService->GetVariable("output_types");
    std::vector<std::string> types;
    boost::split(types, output_types, boost::is_any_of(","));

    for (unsigned int i=0; i<outputs.size(); ++i)
    {
      if (m_PythonService->DoesVariableExist(outputs.at(i)))
      {
        mitk::Image::Pointer image = m_PythonService->CopySimpleItkImageFromPython(outputs.at(i));

        if(types.at(i)=="input" && mitk::DiffusionPropertyHelper::IsDiffusionWeightedImage(mitk_image))
        {
          mitk::DiffusionPropertyHelper::CopyProperties(mitk_image, image, true);
          mitk::DiffusionPropertyHelper::InitializeImage(image);
        }

        mitk::DataNode::Pointer corrected_node = mitk::DataNode::New();
        corrected_node->SetData( image );
        std::string name = node->GetName();
        name += "_";
        name += outputs.at(i);
        corrected_node->SetName(name);
        GetDataStorage()->Add(corrected_node, node);
        m_PythonService->Execute("del " + outputs.at(i));

        mitk::RenderingManager::GetInstance()->InitializeViews( corrected_node->GetData()->GetTimeGeometry(),
                                                                mitk::RenderingManager::REQUEST_UPDATE_ALL,
                                                                true);
      }
    }
  }
  catch(...)
  {
    QMessageBox::warning(nullptr, "Error", "File could not be processed.\nIs pytorch installed on your system?\nDoes your script use the correct input and output variable names (in: in_image & model, out: brain_mask & brain_extracted)?", QMessageBox::Ok);
  }
}


/*!
@file
Defines `boost::hana::Foldable::mcd`.

@copyright Louis Dionne 2014
Distributed under the Boost Software License, Version 1.0.
(See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
 */

#ifndef BOOST_HANA_FOLDABLE_MCD_HPP
#define BOOST_HANA_FOLDABLE_MCD_HPP

#include <boost/hana/foldable/foldable.hpp>

#include <boost/hana/integral.hpp>
#include <boost/hana/logical/logical.hpp>
#include <boost/hana/maybe.hpp>
#include <boost/hana/monoid/monoid.hpp>
#include <boost/hana/orderable/orderable.hpp>

namespace boost { namespace hana {
    //! @details
    //! Minimal complete definition: `foldl` and `foldr`
    struct Foldable::mcd {
        template <typename Foldable_, typename F>
        static constexpr auto foldr1_impl(Foldable_ foldable, F f) {
            auto g = [=](auto x, auto mstate) {
                return just(maybe(
                    x,
                    [=](auto state) { return f(x, state); },
                    mstate
                ));
            };
            return from_just(foldr(foldable, nothing, g));
        }

        template <typename Foldable_, typename F>
        static constexpr auto foldl1_impl(Foldable_ foldable, F f) {
            auto g = [=](auto mstate, auto x) {
                return maybe(
                    just(x),
                    [=](auto state) { return just(f(state, x)); },
                    mstate
                );
            };
            return from_just(foldl(foldable, nothing, g));
        }

        template <typename Foldable_>
        static constexpr auto length_impl(Foldable_ foldable) {
            auto plus1 = [](auto n, auto _) { return n + size_t<1>; };
            return foldl(foldable, size_t<0>, plus1);
        }

        template <typename Foldable_>
        static constexpr auto minimum_impl(Foldable_ foldable)
        { return minimum_by(less, foldable); }

        template <typename Foldable_>
        static constexpr auto maximum_impl(Foldable_ foldable)
        { return maximum_by(less, foldable); }

        template <typename Pred, typename Foldable_>
        static constexpr auto minimum_by_impl(Pred pred, Foldable_ foldable) {
            return foldl1(foldable, [=](auto x, auto y) {
                return if_(pred(x, y), x, y);
            });
        }

        template <typename Pred, typename Foldable_>
        static constexpr auto maximum_by_impl(Pred pred, Foldable_ foldable) {
            return foldl1(foldable, [=](auto x, auto y) {
                return if_(pred(x, y), y, x);
            });
        }

        //! @todo
        //! The base case can't be `int_<0>`, it should be the identity of
        //! a given `Monoid`?
        template <typename Xs>
        static constexpr auto sum_impl(Xs xs) {
            return foldl(xs, int_<0>, plus);
        }

        template <typename Foldable_>
        static constexpr auto product_impl(Foldable_ foldable) {
            return foldl(foldable, int_<1>, [](auto x, auto y) {
                return x * y;
            });
        }

        template <typename Foldable_, typename Pred>
        static constexpr auto count_impl(Foldable_ foldable, Pred pred) {
            return foldl(foldable, size_t<0>, [=](auto counter, auto x) {
                return if_(pred(x), plus(counter, size_t<1>), counter);
            });
        }

        template <typename Foldable_, typename F>
        static constexpr auto unpack_impl(Foldable_ foldable, F f) {
            return foldl(foldable, f, [](auto g, auto x) {
                return [=](auto ...y) { return g(x, y...); };
            })();
        }
    };
}} // end namespace boost::hana

#endif // !BOOST_HANA_FOLDABLE_MCD_HPP


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012 - 2014 MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef BOOST_SIMD_IEEE_FUNCTIONS_SIMD_COMMON_SIGN_HPP_INCLUDED
#define BOOST_SIMD_IEEE_FUNCTIONS_SIMD_COMMON_SIGN_HPP_INCLUDED

#include <boost/simd/ieee/functions/sign.hpp>
#include <boost/simd/include/functions/simd/is_nan.hpp>
#include <boost/simd/include/functions/simd/is_gtz.hpp>
#include <boost/simd/include/functions/simd/is_ltz.hpp>
#include <boost/simd/include/functions/simd/is_equal.hpp>
#include <boost/simd/include/constants/valmin.hpp>
#include <boost/simd/include/functions/simd/shift_right.hpp>
#include <boost/simd/include/functions/simd/if_one_else_zero.hpp>
#include <boost/simd/include/functions/simd/if_allbits_else.hpp>
#include <boost/simd/include/functions/simd/minus.hpp>
#include <boost/simd/include/functions/simd/unary_minus.hpp>
#include <boost/simd/sdk/config.hpp>
#include <boost/dispatch/attributes.hpp>

namespace boost { namespace simd { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT          ( sign_, tag::cpu_
                                    , (A0)(X)
                                    , ((simd_<arithmetic_<A0>,X>))
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(1)
    {
      return if_one_else_zero(is_gtz(a0))-if_one_else_zero(is_ltz(a0));
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( sign_, tag::cpu_
                                    , (A0)(X)
                                    , ((simd_<unsigned_<A0>,X>))
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(1)
    {
      return if_one_else_zero(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT          ( sign_, tag::cpu_
                                    , (A0)(X)
                                    , ((simd_<floating_<A0>,X>))
                                    )
  {
    typedef A0 result_type;
    BOOST_FORCEINLINE BOOST_SIMD_FUNCTOR_CALL_REPEAT(1)
    {
      A0 r = if_one_else_zero(is_gtz(a0))-if_one_else_zero(is_ltz(a0));
      #ifdef BOOST_SIMD_NO_NANS
      return r;
      #else
      return if_allbits_else(is_nan(a0), r);
      #endif
    }
  };
} } }
#endif


//---------------------------------------------------------------------------//
// Copyright (c) 2019 Mikhail Komarov <nemo@nil.foundation>
//
// MIT License
//
// 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.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_MAC_CBC_MAC_HPP
#define CRYPTO3_MAC_CBC_MAC_HPP

#include <boost/range/begin.hpp>
#include <boost/range/end.hpp>

#include <nil/crypto3/mac/detail/cbc_mac/cbc_mac_policy.hpp>
#include <nil/crypto3/mac/detail/cbc_mac/accumulator.hpp>

namespace nil {
    namespace crypto3 {
        namespace mac {
            /*!
             * @brief CBC-MAC
             * @tparam BlockCipher
             * @ingroup mac
             */
            template<typename BlockCipher>
            class cbc_mac {
                typedef detail::cbc_mac_policy<BlockCipher> policy_type;

            public:
                typedef BlockCipher block_cipher_type;

                constexpr static const std::size_t block_bits = policy_type::block_bits;
                constexpr static const std::size_t block_words = policy_type::block_words;
                typedef typename policy_type::block_type block_type;

                constexpr static const std::size_t key_bits = policy_type::key_bits;
                constexpr static const std::size_t key_words = policy_type::key_words;
                typedef typename policy_type::key_type key_type;

                constexpr static const std::size_t state_bits = policy_type::state_bits;
                constexpr static const std::size_t state_words = policy_type::state_words;
                typedef typename policy_type::state_type state_type;

                constexpr static const std::size_t digest_bits = policy_type::digest_bits;
                typedef typename policy_type::digest_type digest_type;

                cbc_mac(const block_cipher_type &cipher) : cipher(cipher) {
                }

                cbc_mac(const key_type &key) : cipher(key) {
                }

                inline void begin_message(state_type &state, const block_type &block) {
                    size_t xored = std::min(output_length() - m_position, length);
                    xor_buf(&m_state[m_position], input, xored);
                    m_position += xored;

                    if (m_position < output_length()) {
                        return;
                    }

                    m_cipher->encrypt(m_state);
                    input += xored;
                    length -= xored;
                    while (length >= output_length()) {
                        xor_buf(m_state, input, output_length());
                        m_cipher->encrypt(m_state);
                        input += output_length();
                        length -= output_length();
                    }

                    xor_buf(m_state, input, length);
                    m_position = length;
                }

                void process_block(state_type &state, const block_type &block) {
                    size_t xored = std::min(output_length() - m_position, length);
                    xor_buf(&m_state[m_position], input, xored);
                    m_position += xored;

                    if (m_position < output_length()) {
                        return;
                    }

                    m_cipher->encrypt(m_state);
                    input += xored;
                    length -= xored;
                    while (length >= output_length()) {
                        xor_buf(m_state, input, output_length());
                        m_cipher->encrypt(m_state);
                        input += output_length();
                        length -= output_length();
                    }

                    xor_buf(m_state, input, length);
                    m_position = length;
                }

                void end_message(digest_type &digest, const state_type &state, const block_type &block) {
                    if (m_position) {
                        m_cipher->encrypt(m_state);
                    }

                    copy_mem(mac, m_state.data(), m_state.size());
                    zeroise(m_state);
                    m_position = 0;
                }

            protected:
                void schedule_key(const key_type &key) {
                }

                block_cipher_type cipher;
            };
        }    // namespace mac
    }        // namespace crypto3
}    // namespace nil

#endif


#ifndef PERIOR_TREE_RTREE_HPP
#define PERIOR_TREE_RTREE_HPP
#include "rtree_node.hpp"
#include "expand.hpp"
#include "within.hpp"
#include "area.hpp"
#include "dump.hpp"
#include <boost/container/vector.hpp>
#include <boost/container/small_vector.hpp>
#include <boost/optional.hpp>
#include <utility>
#include <memory>
#include <functional>
#include <limits>
#include <tuple>

namespace perior
{

namespace detail
{
template<typename T>
struct indexable_type_of
{
    typedef typename std::tuple_element<0, T>::type type;

    static inline type const&
    invoke(const T& t) noexcept {return std::get<0>(t);}

    static inline type&
    invoke(T& t)       noexcept {return std::get<0>(t);}

    static inline type&&
    invoke(T&& t)      noexcept {return std::get<0>(t);}
};
} // detail

template<std::size_t Min, std::size_t Max>
struct quadratic
{
    static constexpr std::size_t min_elem = Min;
    static constexpr std::size_t max_elem = Max;
};

template<typename T, std::size_t N>
aabb<T, N> make_aabb(const aabb<T, N>& b) noexcept
{
    return b;
}

template<typename T, std::size_t N>
aabb<T, N> make_aabb(const point<T, N>& p) noexcept
{
    return aabb<T, N>(p, p);
}

template<typename T,
         typename Params,
         typename Boundary,
         typename EqualTo   = std::equal_to<T>,
         typename Allocator = boost::container::new_allocator<T> >
class rtree
{
  public:
    typedef T         value_type;
    typedef Params    parameter_type;
    typedef Boundary  boundary_type;
    typedef EqualTo   equal_to_type;
    typedef Allocator allocator_type;

    typedef detail::indexable_type_of<value_type> indexable;
    typedef typename indexable::type              indexable_type;
    typedef typename indexable_type::scalar_type  scalar_type;
    static constexpr std::size_t dim = indexable_type::dim;
    typedef point<scalar_type, dim> point_type;
    typedef aabb<scalar_type, dim>  aabb_type;

    static constexpr std::size_t min_entry = parameter_type::min_elem;
    static constexpr std::size_t max_entry = parameter_type::max_elem;

    typedef boost::container::vector<value_type, allocator_type> container_type;
    typedef typename container_type::iterator       iterator;
    typedef typename container_type::const_iterator const_iterator;

    typedef detail::rtree_node<scalar_type, dim, min_entry, max_entry> node_type;
    typedef typename allocator_type::template rebind<node_type>::other
            node_allocator_type;
    typedef boost::container::vector<node_type, node_allocator_type> tree_type;
    typedef boost::container::small_vector<std::size_t, 8> index_buffer_type;

  private:

    static constexpr std::size_t nil = std::numeric_limits<std::size_t>::max();

  public:

    rtree(): root_(nil){}
    ~rtree() = default;
    rtree(const rtree&) = default;
    rtree(rtree&&)      = default;
    rtree& operator=(const rtree&) = default;
    rtree& operator=(rtree&&)      = default;

    explicit rtree(const boundary_type& b): root_(nil), boundary_(b){}
    explicit rtree(const equal_to_type& e): root_(nil),  equal_to_(e){}
    rtree(const boundary_type& b, const equal_to_type& e)
        : root_(nil), equal_to_(e), boundary_(b)
    {}

    std::size_t size() const noexcept {return container_.size();}
    bool empty()       const noexcept {return container_.empty();}
    void clear()
    {
        this->root_ = nil;
        this->tree_.clear();
        this->container_.clear();
        this->overwritable_nodes_.clear();
        this->overwritable_values_.clear();
        return;
    }

    void insert(const value_type& v)
    {
        const std::size_t     idx   = this->add_value(v);
        std::cerr << "new value index = " << idx << std::endl;

        const indexable_type& entry = indexable::invoke(v);

        std::cerr << "seaching leaf to insert..." << std::endl;
        const std::size_t L = this->choose_leaf(entry);
        std::cerr << "leaf to insert = " << L << std::endl;

        if(tree_.at(L).has_enough_storage())
        {
            std::cerr << "node " << L << " has enough storage. insert and expand." << std::endl;
            tree_.at(L).entry.push_back(idx);
            expand(tree_.at(L).box, entry, this->boundary_);
            this->adjust_tree(L);
        }
        else
        {
            std::cerr << "node " << L << " has no storage." << std::endl;
            const std::size_t LL = this->add_node(this->split_leaf(L, idx, entry));
            std::cerr << "create new node " << LL << "." << std::endl;
            this->adjust_tree(L, LL);
        }
        return;
    }
    bool remove(const value_type& v)
    {
        if(this->root_ == nil)
        {
            return false;
        }
        if(boost::optional<std::pair<std::size_t, typename node_type::const_iterator>>
                found = this->find_leaf(this->root_, v))
        {
            const std::size_t node_idx  = found->first;
            const std::size_t value_idx = *(found->second);
            this->tree_.at(node_idx).entry.erase(found->second);
            this->erase_value(value_idx);
            this->condense_box(this->tree_.at(node_idx));
            this->condense_leaf(node_idx);
            return true;
        }
        return false;
    }

    template<typename Query, typename OutputIterator>
    void query(Query q, OutputIterator out) const
    {
        if(this->root_ == nil){return;}
        return query_impl(this->root_, q, out);
    }

    template<typename charT, typename traits>
    std::basic_ostream<charT, traits>&
    dump(std::basic_ostream<charT, traits>& os) const
    {
        if(this->root_ == nil){return os;}
        std::vector<std::string> colors;
        colors.push_back("red");
        colors.push_back("green");
        colors.push_back("blue");
        return dump_node(os, this->root_, 0, colors);
    }

  private:

    template<typename charT, typename traits>
    std::basic_ostream<charT, traits>&
    dump_node(std::basic_ostream<charT, traits>& os,
              const std::size_t N, const std::size_t depth,
              const std::vector<std::string>& clrs) const
    {
        const std::string& clr = clrs.at(depth%3);
        to_svg(os, tree_.at(N).box, this->boundary_, clr, 5, "none");
        os << '\n';
        if(tree_.at(N).is_leaf)
        {
            for(auto i=tree_.at(N).entry.cbegin(), e=tree_.at(N).entry.cend();
                    i!=e; ++i)
            {
                to_svg(os, indexable::invoke(container_.at(*i)), this->boundary_, "black", 1, "black");
                os << '\n';
            }
            return os;
        }
        else
        {
            for(auto i=tree_.at(N).entry.cbegin(), e=tree_.at(N).entry.cend();
                    i!=e; ++i)
            {
                dump_node(os, *i, depth+1, clrs);
            }
            return os;
        }
    }

    std::size_t choose_leaf(const indexable_type& entry)
    {
        std::cerr << "root = " << this->root_ << std::endl;
        if(this->root_ == nil)
        {
            std::cerr << "this tree has no node. add new root" << std::endl;

            node_type n(true, nil);
            n.box = make_aabb(entry);
            this->root_ = this->add_node(n);

            std::cerr << "new root = " << this->root_ << std::endl;
            return this->root_;
        }
        std::cerr << "now tree has root " << this->root_ << std::endl;

        std::size_t node_idx = this->root_;
        while(!(this->tree_.at(node_idx).is_leaf))
        {
            scalar_type diff_area_min = std::numeric_limits<scalar_type>::max();
            scalar_type area_min      = std::numeric_limits<scalar_type>::max();

            const node_type& node = this->tree_.at(node_idx);
            for(typename node_type::const_iterator
                    i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                aabb_type box = tree_.at(*i).box;
                const scalar_type area_initial = area(box, this->boundary_);

                expand(box, entry, this->boundary_);

                const scalar_type area_expanded = area(box, this->boundary_);
                const scalar_type diff_area     = area_expanded - area_initial;
                if((diff_area <  diff_area_min) ||
                   (diff_area == diff_area_min  && area_expanded < area_min))
                {
                    node_idx = *i;
                    diff_area_min = diff_area;
                    area_min = area_expanded;
                }
            }
        }
        return node_idx;
    }

    void adjust_tree(std::size_t node_idx)
    {
        while(tree_.at(node_idx).parent != nil)
        {
            const node_type& node = tree_.at(node_idx);
            node_type& parent_ = tree_.at(node.parent);
            expand(parent_.box, node.box, this->boundary_);

            node_idx = node.parent;
        }
        return;
    }
    void adjust_tree(const std::size_t N, const std::size_t NN)
    {
        if(tree_.at(N).parent == nil) // grow tree taller
        {
            std::cerr << "node " << N << " has no parent." << std::endl;
            node_type new_root(false, nil);
            new_root.entry.push_back(N);
            new_root.entry.push_back(NN);
            new_root.box = tree_.at(N).box;
            expand(new_root.box, tree_.at(NN).box, this->boundary_);
            this->root_ = this->add_node(new_root);
            std::cerr << "NR(" << this->root_ << ") = ";
            to_svg(std::cerr, tree_.at(this->root_).box, this->boundary_);

            this->tree_.at(N).parent = this->root_;
            this->tree_.at(NN).parent = this->root_;
            std::cerr << "\nN (" << N << ") = ";
            to_svg(std::cerr, tree_.at(N).box, this->boundary_);
            std::cerr << "\nNN(" << NN << ") = ";
            to_svg(std::cerr, tree_.at(NN).box, this->boundary_);
            std::cerr << "\nadjust_tree: grow tree taller. new root is " << this->root_ << std::endl;
            return;
        }
        else
        {
            const node_type& node    = tree_.at(N);
            const node_type& partner = tree_.at(NN);

            assert(node.parent == partner.parent);
            node_type& parent_ = tree_.at(node.parent);
            expand(parent_.box, node.box, this->boundary_);
            std::cerr << "adjust_tree(N, NN): parent has " << parent_.entry.size() << "entry." << std::endl;
            if(parent_.has_enough_storage())
            {
                std::cerr << "storage is enough." << std::endl;
                expand(parent_.box, partner.box, this->boundary_);
                parent_.entry.push_back(NN);
                return this->adjust_tree(node.parent);
            }
            else
            {
                std::cerr << "storage is not enough." << std::endl;

                const std::size_t PP = this->split_node(node.parent, NN);

                std::cerr << "adjust_tree: split node " << node.parent
                          << " and " << PP << std::endl;
                std::cerr << "node (" << node.parent << ") = ";
                to_svg(std::cerr, tree_.at(node.parent).box, this->boundary_);
                std::cerr << "\npartner(" << PP << ") = ";
                to_svg(std::cerr, tree_.at(PP).box, this->boundary_);
                std::cerr << std::endl;

                return this->adjust_tree(node.parent, PP);
            }
        }
    }

    boost::optional<std::pair<std::size_t, typename node_type::const_iterator>>
    find_leaf(std::size_t node_idx, const value_type& entry) const
    {
        const node_type& node = tree_.at(node_idx);
        if(within(indexable::invoke(entry), node.box, this->boundary_) == false)
        {
            return boost::none;
        }

        if(node.is_leaf)
        {
            for(typename node_type::const_iterator
                    i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                if(equal_to_(container_.at(*i), entry))
                {
                    return std::make_pair(node_idx, i);
                }
            }
            return boost::none;
        }
        else
        {
            for(typename node_type::const_iterator
                    i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                if(!within(indexable::invoke(entry), tree_.at(*i).box, this->boundary_))
                {
                    continue;
                }

                if(boost::optional<std::pair<std::size_t, typename node_type::const_iterator>
                    > found = this->find_leaf(*i, entry))
                {
                    return found;
                }
            }
            return boost::none;
        }
    }

    void condense_leaf(const std::size_t N)
    {
        const node_type& node = this->tree_.at(N);
        assert(node.is_leaf);

        std::cerr << "condense_leaf for " << N << std::endl;
        std::cerr << "leaf-node " << N << " has " << node.entry.size()
                  << " entries." << std::endl;

        if(node.has_enough_entry() || node.parent == nil)
        {
            std::cerr << "leaf-node " << N << " is root or has enough entry"
                      << std::endl;
            return;
        }

        std::cerr << "node " << N << " should be eliminated." << std::endl;
        std::cerr << "parent of node " << N << " is " << node.parent << std::endl;

        // copy index of objects
        boost::container::small_vector<std::size_t, min_entry> eliminated_objs;
        std::copy(node.entry.begin(), node.entry.end(),
                  std::back_inserter(eliminated_objs));

        // erase the node N from its parent and condense aabb
        typename node_type::iterator found = std::find(
                this->tree_.at(node.parent).entry.begin(),
                this->tree_.at(node.parent).entry.end(), N);
        assert(found != this->tree_.at(node.parent).entry.end());
        this->tree_.at(node.parent).entry.erase(found);
        this->condense_box(this->tree_.at(node.parent));

        // re-insert entries eliminated from node N
        for(auto i(eliminated_objs.begin()), e(eliminated_objs.end()); i!=e; ++i)
        {
            this->insert(this->container_.at(*i));
        }

        // condense ancester nodes...
        condense_node(node.parent);
        return;
    }

    void condense_node(const std::size_t N)
    {
        const node_type& node = this->tree_.at(N);
        assert(node.is_leaf == false);

        std::cerr << "condense_node for " << N << std::endl;
        std::cerr << "internal-node " << N << " has " << node.entry.size()
                  << " entries." << std::endl;

        if(node.has_enough_entry())
        {
            std::cerr << "internal-node " << N << " has enough entry"
                      << std::endl;
            return;
        }
        if(node.parent == nil && node.entry.size() == 1)
        {
            std::cerr << "root-node has only 1 entry. remove." << std::endl;
            this->root_ = node.entry.front();
            this->erase_node(N);
            std::cerr << "new root is " << this->root_ << std::endl;
            return;
        }
        std::cerr << "internal node has parent " << node.parent << std::endl;

        // collect index of nodes that are children of the node to be removed
        boost::container::small_vector<std::size_t, min_entry> eliminated_nodes;
        std::copy(node.entry.begin(), node.entry.end(),
                  std::back_inserter(eliminated_nodes));

        // erase the node N from its parent and condense its aabb
        typename node_type::iterator found = std::find(
                this->tree_.at(node.parent).entry.begin(),
                this->tree_.at(node.parent).entry.end(), N);
        assert(found != this->tree_.at(node.parent).entry.end());
        this->tree_.at(node.parent).entry.erase(found);
        this->condense_box(this->tree_.at(node.parent));

        // re-insert nodes eliminated from node N
        for(auto i(eliminated_nodes.begin()), e(eliminated_nodes.end()); i!=e; ++i)
        {
            this->re_insert(*i);
        }
        this->condense_node(node.parent);
        return;
    }

    // split nodes because of one entry
    node_type split_leaf(const std::size_t N,
                         const std::size_t vidx, const indexable_type& entry)
    {
        std::cerr << "split_leaf" << std::endl;

        node_type& node = tree_.at(N);
        node_type  partner(true, node.parent);

        boost::container::static_vector<std::pair<std::size_t, indexable_type>,
            max_entry+1> entries;
        entries.push_back(std::make_pair(vidx, entry));

        for(typename node_type::const_iterator
                i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
        {
            entries.push_back(std::make_pair(
                        *i, indexable::invoke(container_.at(*i))));
        }
        node.entry.clear();
        partner.entry.clear(); // for make it sure

        /* assign first 2 entries to node and partner */
        {
            const std::array<std::size_t, 2> seeds = this->pick_seeds(entries);
            std::cerr << "seeds = {" << entries.at(seeds[0]).first
                      << ", " << entries.at(seeds[1]).first << "}" << std::endl;
            std::cerr << "entries.size() = " << entries.size() << std::endl;
               node.entry.push_back(entries.at(seeds[0]).first);
            partner.entry.push_back(entries.at(seeds[1]).first);

            std::cerr << "clear box" << std::endl;
               node.box = make_aabb(entries.at(seeds[0]).second);
            partner.box = make_aabb(entries.at(seeds[1]).second);

            // remove them from entries pool
            entries.erase(entries.begin() + std::min(seeds[0], seeds[1]));
            entries.erase(entries.begin() + std::max(seeds[0], seeds[1]) - 1);
        }

        std::cerr << "start splitting" << std::endl;
        while(!entries.empty())
        {
            if(min_entry > node.entry.size() &&
               min_entry - node.entry.size() >= entries.size())
            {
                for(auto i(entries.begin()), e(entries.end()); i != e; ++i)
                {
                    node.entry.push_back(i->first);
                    expand(node.box, i->second, this->boundary_);
                }
                return partner;
            }
            if(min_entry > partner.entry.size() &&
               min_entry - partner.entry.size() >= entries.size())
            {
                for(auto i(entries.begin()), e(entries.end()); i != e; ++i)
                {
                    partner.entry.push_back(i->first);
                    expand(partner.box, i->second, this->boundary_);
                }
                return partner;
            }

            const std::pair<std::size_t, bool> next =
                this->pick_next(entries, node.box, partner.box);
            if(next.second) // next is for node
            {
                std::cerr << "next entry " << entries.at(next.first).first
                          << " is for node." << std::endl;
                node.entry.push_back(entries.at(next.first).first);
                expand(node.box, entries.at(next.first).second, this->boundary_);
            }
            else // next is for partner
            {
                std::cerr << "next entry " << entries.at(next.first).first
                          << " is for partner." << std::endl;
                partner.entry.push_back(entries.at(next.first).first);
                expand(partner.box, entries.at(next.first).second, this->boundary_);
            }
            entries.erase(entries.begin() + next.first);
        }
        return partner;
    }

    // objT should be indexable_type or aabb_type
    template<typename objT>
    std::array<std::size_t, 2> pick_seeds(const boost::container::static_vector<
            std::pair<std::size_t, objT>, max_entry+1>& entries)
    {
        assert(entries.size() >= 2);

        std::array<std::size_t, 2> retval;

        scalar_type max_d = 0;
        for(std::size_t i=0; i<entries.size()-1; ++i)
        {
            for(std::size_t j=i+1; j<entries.size(); ++j)
            {
                const aabb_type E1I = make_aabb(entries.at(i).second);
                const aabb_type E2I = make_aabb(entries.at(j).second);
                aabb_type J = E1I;
                expand(J, E2I, this->boundary_);
                const scalar_type d =
                    area(J, boundary_) - area(E1I, boundary_) - area(E2I, boundary_);
                if(max_d < std::abs(d))
                {
                    max_d = std::abs(d);
                    retval[0] = i;
                    retval[1] = j;
                    std::cerr << "found seeds {" << i << ", " << j << "}" << std::endl;
                    std::cerr << "max_d = " << max_d << std::endl;
                }
            }
        }
        return retval;
    }

    // objT should be indexable_type or aabb_type
    template<typename objT>
    std::pair<std::size_t, bool> pick_next(const boost::container::static_vector<
            std::pair<std::size_t, objT>, max_entry+1>& entries,
            const aabb_type& node, const aabb_type& ptnr)
    {
        std::cerr << "pick_next" << std::endl;
        assert(!entries.empty());
        bool is_node;
        std::size_t idx;
        scalar_type max_dd = -1;
        for(std::size_t i=0; i<entries.size(); ++i)
        {
            aabb_type box1 = node; expand(box1, make_aabb(entries.at(i).second), this->boundary_);
            aabb_type box2 = ptnr; expand(box2, make_aabb(entries.at(i).second), this->boundary_);
            const scalar_type d1 = area(box1, this->boundary_) - area(node, this->boundary_);
            const scalar_type d2 = area(box2, this->boundary_) - area(ptnr, this->boundary_);
            const scalar_type dd = d1 - d2;
            if(max_dd < std::abs(dd))
            {
                max_dd = std::abs(dd);
                idx = i;
                is_node = (dd < 0);
            }
        }
        return std::make_pair(idx, is_node);
    }

    // split nodes because of new node NN by quadratic algorithm
    std::size_t split_node(const std::size_t P, const std::size_t NN)
    {
        node_type& node = tree_.at(P);
        const std::size_t PP = this->add_node(node_type(false, node.parent));
        node_type& partner = tree_.at(PP);

        std::cerr << "split_node: node = " << P << ", partner = " << PP << std::endl;

        boost::container::static_vector<std::pair<std::size_t, aabb_type>,
            max_entry+1> entries;
        entries.push_back(std::make_pair(NN, tree_.at(NN).box));
        std::cerr << "entry node " << NN << " = ";
        to_svg(std::cerr, tree_.at(NN).box, this->boundary_);
        std::cerr << std::endl;

        for(typename node_type::const_iterator
                i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
        {
            entries.push_back(std::make_pair(*i, tree_.at(*i).box));

            std::cerr << "entry node " << *i << " = ";
            to_svg(std::cerr, tree_.at(*i).box, this->boundary_);
            std::cerr << std::endl;
        }
        node.entry.clear();
        partner.entry.clear(); // for make it sure

        /* assign first 2 entries to node and partner */{
            const std::array<std::size_t, 2> seeds = this->pick_seeds(entries);
            std::cerr << "seeds = " << entries.at(seeds[0]).first << " for node, "
                      << entries.at(seeds[1]).first << " for partner." << std::endl;

               node.entry.push_back(entries.at(seeds[0]).first);
            partner.entry.push_back(entries.at(seeds[1]).first);

            tree_.at(entries.at(seeds[0]).first).parent = P;
            tree_.at(entries.at(seeds[1]).first).parent = PP;

            std::cerr << "set box of node and partner" << std::endl;
               node.box = entries.at(seeds[0]).second;// these are AABB.
            partner.box = entries.at(seeds[1]).second;

            std::cerr << "node(" << P << ")    = ";
            to_svg(std::cerr, node.box, this->boundary_);
            std::cerr << std::endl;
            std::cerr << "partner(" << PP << ") = ";
            to_svg(std::cerr, partner.box, this->boundary_);
            std::cerr << std::endl;

            // remove them from entries pool
            entries.erase(entries.begin() + std::min(seeds[0], seeds[1]));
            entries.erase(entries.begin() + std::max(seeds[0], seeds[1]) - 1);
        }

        while(!entries.empty())
        {
            if(min_entry > node.entry.size() &&
               min_entry - node.entry.size() >= entries.size())
            {
                std::cerr << "min_entry = " << min_entry
                          << ", node.entry.size() = " << node.entry.size()
                          << ", entries.size() = " << entries.size() << std::endl;
                for(auto i(entries.begin()), e(entries.end()); i != e; ++i)
                {
                    node.entry.push_back(i->first);
                    expand(node.box, i->second, this->boundary_);
                }
                return PP;
            }
            if(min_entry > partner.entry.size() &&
               min_entry - partner.entry.size() >= entries.size())
            {
                std::cerr << "min_entry = " << min_entry
                          << ", partner.entry.size() = " << partner.entry.size()
                          << ", entries.size() = " << entries.size() << std::endl;
                for(auto i(entries.begin()), e(entries.end()); i != e; ++i)
                {
                    partner.entry.push_back(i->first);
                    expand(partner.box, i->second, this->boundary_);
                }
                return PP;
            }

            const std::pair<std::size_t, bool> next =
                this->pick_next(entries, node.box, partner.box);
            if(next.second) // next is partner
            {
                std::cerr << "next entry " << entries.at(next.first).first
                          << " is for node." << std::endl;
                node.entry.push_back(entries.at(next.first).first);
                tree_.at(entries.at(next.first).first).parent = P;
                expand(node.box, entries.at(next.first).second, this->boundary_);

                std::cerr << "node expanded;" << std::endl;
                std::cerr << "node(" << P << ")    = ";
                to_svg(std::cerr, node.box, this->boundary_);
                std::cerr << std::endl;
                std::cerr << "partner(" << PP << ") = ";
                to_svg(std::cerr, partner.box, this->boundary_);
                std::cerr << std::endl;
            }
            else // next is for partner
            {
                std::cerr << "next entry " << entries.at(next.first).first
                          << " is for partner." << std::endl;
                partner.entry.push_back(entries.at(next.first).first);
                tree_.at(entries.at(next.first).first).parent = PP;
                expand(partner.box, entries.at(next.first).second, this->boundary_);

                std::cerr << "partner expanded;" << std::endl;
                std::cerr << "node(" << P << ")    = ";
                to_svg(std::cerr, node.box, this->boundary_);
                std::cerr << std::endl;
                std::cerr << "partner(" << PP << ") = ";
                to_svg(std::cerr, partner.box, this->boundary_);
                std::cerr << std::endl;
            }
            entries.erase(entries.begin() + next.first);
        }
        tree_.at(P) = node;
        tree_.at(PP) = partner;
        std::cerr << "node(" << P << ")    = ";
        to_svg(std::cerr, tree_.at(P).box, this->boundary_);
        std::cerr << std::endl;
        std::cerr << "partner(" << PP << ") = ";
        to_svg(std::cerr, tree_.at(PP).box, this->boundary_);
        std::cerr << std::endl;
        std::cerr << "all entries are inserted." << std::endl;
        return PP;
    }

    template<typename Query, typename OutputIterator>
    void query_impl(std::size_t node_idx, Query q, OutputIterator out) const
    {
        const node_type& node = tree_.at(node_idx);
        if(node.is_leaf)
        {
            for(typename node_type::const_iterator
                i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                value_type const& val = container_.at(*i);
                if(q.match(indexable::invoke(val)) && q.match(val))
                {
                    *out = val;
                    ++out;
                }
            }
        }
        else
        {
            for(typename node_type::const_iterator
                i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                const std::size_t next = *i;
                if(q.match(tree_.at(next).box))
                {
                    this->query_impl(next, q, out);
                }
            }
        }
        return;
    }

  private:

    std::size_t level_of(std::size_t node_idx) const
    {
        std::size_t level = 0;
        while(!(tree_.at(node_idx).is_leaf))
        {
            ++level;
            node_idx = tree_.at(node_idx).entry.front();
        }
        return level;
    }

    void re_insert(const std::size_t N)
    {
        // reset connection to the parent!
        std::cerr << "re-insert node " << N << std::endl;

        // insert node to its proper parent. to find the parent of this node N,
        // add 1 to level. root node should NOT come here.
        const std::size_t lvl = level_of(N) + 1;
        const aabb_type&  entry = tree_.at(N).box;
        const std::size_t L = choose_node_with_level(entry, lvl);

        std::cerr << "its level is " << lvl << std::endl;
        std::cerr << "its parents node is " << lvl << std::endl;

        if(tree_.at(L).has_enough_storage())
        {
            tree_.at(L).entry.push_back(N);
            tree_.at(N).parent = L;
            expand(tree_.at(L).box, entry, this->boundary_);
            this->adjust_tree(L);
        }
        else
        {
            const std::size_t LL = this->split_node(L, N);
            this->adjust_tree(L, LL);
        }
        return;
    }

    std::size_t choose_node_with_level(const aabb_type& entry, const std::size_t lvl)
    {
        std::size_t node_idx = this->root_;
        if(level_of(this->root_) < lvl)
        {
            throw std::logic_error("root is under the node");
        }

        while(level_of(node_idx) != lvl)
        {
            scalar_type diff_area_min = std::numeric_limits<scalar_type>::max();
            scalar_type area_min      = std::numeric_limits<scalar_type>::max();

            const node_type& node = this->tree_.at(node_idx);
            for(typename node_type::const_iterator
                    i(node.entry.begin()), e(node.entry.end()); i != e; ++i)
            {
                aabb_type box = tree_.at(*i).box;
                const scalar_type area_initial = area(box, this->boundary_);

                expand(box, entry, this->boundary_);

                const scalar_type area_expanded = area(box, this->boundary_);
                const scalar_type diff_area     = area_expanded - area_initial;
                if(diff_area < diff_area_min ||
                   (diff_area == diff_area_min && area_expanded < area_min))
                {
                    node_idx = *i;
                    diff_area_min = diff_area;
                    area_min = area_expanded;
                }
            }
        }
        return node_idx;
    }

    void condense_box(node_type& node)
    {
        assert(!node.entry.empty());
        if(node.is_leaf)
        {
            std::cerr << "leaf node has values {";
            for(auto i: node.entry)
            {
                std::cerr << i << ", ";
            }
            std::cerr << '}' << std::endl;

            auto i = node.entry.cbegin();
            node.box = indexable::invoke(this->container_.at(*i));
            ++i;
            for(auto e(node.entry.cend()); i != e; ++i)
            {
                expand(node.box, indexable::invoke(this->container_.at(*i)),
                       this->boundary_);
            }
        }
        else
        {
            std::cerr << "internal node has values {";
            for(auto i: node.entry)
            {
                std::cerr << i << ", ";
            }
            std::cerr << '}' << std::endl;

            auto i = node.entry.cbegin();
            node.box = this->tree_.at(*i).box;
            ++i;
            for(auto e(node.entry.cend()); i != e; ++i)
            {
                expand(node.box, this->tree_.at(*i).box, this->boundary_);
            }
        }
        return;
    }

  private:

    std::size_t add_value(const value_type& v)
    {
        if(overwritable_values_.empty())
        {
            const std::size_t idx = container_.size();
            container_.push_back(v);
            return idx;
        }
        else
        {
            const std::size_t idx = overwritable_values_.back();
            overwritable_values_.pop_back();
            container_.at(idx) = v;
            return idx;
        }
    }
    void erase_value(const std::size_t i)
    {
        overwritable_values_.push_back(i);
        return;
    }

    std::size_t add_node(const node_type& n)
    {
        if(overwritable_nodes_.empty())
        {
            const std::size_t idx = tree_.size();
            tree_.push_back(n);
            return idx;
        }
        else
        {
            const std::size_t idx = overwritable_nodes_.back();
            overwritable_nodes_.pop_back();
            tree_.at(idx) = n;
            return idx;
        }
    }
    void erase_node(const std::size_t i)
    {
        overwritable_nodes_.push_back(i);
        return;
    }

  private:

    std::size_t       root_;
    equal_to_type     equal_to_;
    boundary_type     boundary_;
    tree_type         tree_;
    container_type    container_;
    index_buffer_type overwritable_values_;
    index_buffer_type overwritable_nodes_;
};

} // perior
#endif//PERIOR_TREE_RTREE_HPP