#include "Day25-HaltingProblem.h"

#include "TuringMachine.h"

#include <AdventOfCodeCommon/DisableLibraryWarningsMacros.h>

__BEGIN_LIBRARIES_DISABLE_WARNINGS
#include <boost/algorithm/string.hpp>
#include <boost/lexical_cast.hpp>

#include <cassert>
__END_LIBRARIES_DISABLE_WARNINGS

namespace AdventOfCode
{
namespace Year2017
{
namespace Day25
{

StateModifier createStateModifierFromLines(const std::vector<std::string>& stateModifierLines)
{
    assert(stateModifierLines.size() == 3);

    // Parse written value
    std::vector<std::string> tokens;
    boost::split(tokens, stateModifierLines[0], boost::is_any_of(" "), boost::token_compress_on);

    if (tokens.size() != 4)
    {
        throw std::runtime_error("Invalid new written value.");
    }

    const bool writtenValue = std::stoi(tokens[3]);

    // Parse cursor offset
    boost::split(tokens, stateModifierLines[1], boost::is_any_of(" "), boost::token_compress_on);

    if (tokens.size() != 6)
    {
        throw std::runtime_error("Invalid cursor offset.");
    }

    int cursorOffset{};
    if (tokens[5] == "left")
    {
        cursorOffset = -1;
    }
    else if (tokens[5] == "right")
    {
        cursorOffset = 1;
    }
    else
    {
        throw std::runtime_error("Invalid cursor direction.");
    }

    // Parse next state
    boost::split(tokens, stateModifierLines[2], boost::is_any_of(" "), boost::token_compress_on);

    if (tokens.size() != 4 || tokens[3].size() != 1)
    {
        throw std::runtime_error("Invalid next state.");
    }

    const StateIDType nextStateID = tokens[3].front();

    return StateModifier{writtenValue, cursorOffset, nextStateID};
}

TuringMachine::TransitionMap createTransitionMapFromLines(const std::vector<std::string>& transitionMapLines)
{
    if (transitionMapLines.size() % 10 != 0)
    {
        throw std::runtime_error("Invalid number of lines.");
    }

    TuringMachine::TransitionMap transitionMap;

    for (unsigned i = 0; i < transitionMapLines.size(); i+=10)
    {
        std::vector<std::string> tokens;
        boost::split(tokens, transitionMapLines[i + 1], boost::is_any_of(" "));

        if (tokens.size() != 3 || tokens[2].size() == 0)
        {
            throw std::runtime_error("Invalid state transition headline.");
        }

        StateIDType stateID = tokens[2].front();

        StateModifier valueFalseModifier = createStateModifierFromLines({transitionMapLines.cbegin() + i + 3, transitionMapLines.cbegin() + i + 6});
        StateModifier valueTrueModifier = createStateModifierFromLines({transitionMapLines.cbegin() + i + 7, transitionMapLines.cbegin() + i + 10});

        StateTransition stateTransition{std::move(valueFalseModifier), std::move(valueTrueModifier)};

        transitionMap.emplace(stateID, std::move(stateTransition));
    }

    return transitionMap;
}

TuringMachine createTuringMachineFromLines(std::vector<std::string> turingMachineLines)
{
    if (turingMachineLines.size() < 2)
    {
        throw std::runtime_error("Not enough lines.");
    }

    std::for_each(turingMachineLines.begin(), turingMachineLines.end(), [](auto& line)
                  {
                      boost::trim_if(line, boost::is_any_of("-. "));
                  });

    // Parse initial state
    std::vector<std::string> tokens;
    boost::split(tokens, turingMachineLines[0], boost::is_any_of(" "), boost::token_compress_on);

    if (tokens.size() != 4 || tokens[3].size() != 1)
    {
        throw std::runtime_error("Invalid initial state.");
    }

    const StateIDType startingStateID = tokens[3].front();

    // Parse number of iterations
    boost::split(tokens, turingMachineLines[1], boost::is_any_of(" "), boost::token_compress_on);

    if (tokens.size() != 7)
    {
        throw std::runtime_error("Invalid number of iterations.");
    }

    const unsigned numIterations = boost::lexical_cast<unsigned>(tokens[5]);

    // Parse state transition map
    TuringMachine::TransitionMap stateIDToTransition = createTransitionMapFromLines({turingMachineLines.cbegin() + 2, turingMachineLines.cend()});

    return TuringMachine{stateIDToTransition, startingStateID, numIterations};
}

unsigned diagnosticChecksum(const std::vector<std::string>& turingMachineLines)
{
    TuringMachine turingMachine = createTuringMachineFromLines(turingMachineLines);
    turingMachine.run();
    return turingMachine.getChecksum();
}

}
}
}


/*
 * Copyright (c) 2017, Niklas Gürtler
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification, are permitted provided that the
 * following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following
 *    disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the
 *    following disclaimer in the documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <climits>
#include <iostream>
#include <map>
#include <cstdint>
#include <algorithm>
#include <utility>
#include <map>

#include "ltl.hh"
#include <boost/functional/hash.hpp>
#include <boost/fusion/include/hash.hpp>

std::ostream& LTL::operator << (std::ostream& os, const LTL::ExpSet& c) {
	if (os.iword (Formula::Formula_Xalloc) == 2) os << "\\left\\{";
	else os << "{";
	bool first = true;
	for (auto& exp : c) {
		if (!first)
			os << ", ";
		else
			first = false;
		os << *exp;
	}
	if (os.iword (Formula::Formula_Xalloc) == 2) os << "\\right\\}";
	else os << '}';
	return os;
}

LTL::Algorithm::Algorithm (const Formula::Expression& exp, const TS::TranSys& ts) :formula (exp) {
	ClosureVisitor visitor { closure, auxExp, &exp};
	boost::apply_visitor (visitor, exp);

	using Counter = std::uint64_t;

	if (closure.size () >= sizeof (Counter) * CHAR_BIT)
		throw std::runtime_error ("Too many closure elements!");

	const Counter max = (1 << closure.size ()) - 1;

	atomExpressions.push_back ({});

	for (Counter iComb = 0; iComb < max; ++iComb) {
		ExpSet& atom = atomExpressions.back ();

		Counter mask = 1;
		for (auto& cexp : closure) {
			if (iComb & mask) {
				atom.insert (cexp);
			}

			mask <<= 1;
		}
		bool needed = false;
		for (auto& state : ts.statesSet) {
			Consistency_ClosureVisitor v (atom, ts, *state);
			bool consistent = std::all_of (closure.begin (), closure.end (), [&](const Formula::ExpRef& elem) { v.m_parentExp = elem; return ::boost::apply_visitor (v, *elem); });

			if (consistent) {
				needed = true;
				atomMap.insert (std::make_pair<const TS::State*, std::size_t> (state, atoms.size ()));
				atoms.emplace_back (state, atomExpressions.size () - 1);
			}
		}
		if (needed) {
			atomExpressions.push_back ({});
		} else {
			atom.clear ();
		}
	}
	atomExpressions.pop_back ();

	for (std::size_t iStartAtom = 0; iStartAtom < atoms.size (); ++iStartAtom) {
		Atom& startAtom = atoms [iStartAtom];
		for (const TS::State* succ : startAtom.state->successors) {
			auto endRange = atomMap.equal_range (succ);
			for (auto iter = endRange.first; iter != endRange.second; ++iter) {
				std::size_t iEndAtom = iter->second;
				const Atom& endAtom = atoms [iEndAtom];

				const ExpSet& startExp = atomExpressions [startAtom.expressions];
				const ExpSet& endExp = atomExpressions [endAtom.expressions];

				if (std::all_of (closure.begin (), closure.end (), [&] (const Formula::ExpRef& elem) {
					if (const Formula::E_Next* next = boost::get<Formula::E_Next> (&*elem)) {
						return	(startExp.find (elem) == startExp.end ())
							==	(endExp.find(&next->exp) == endExp.end ());
					} else {
						return true;
					}
				})) {
					edges.emplace_back (iStartAtom, iEndAtom);
				}
			}
		}
	}
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Label& exp) {
	auto iter = m_tranSys.labels.find (exp.name);
	if (iter == m_tranSys.labels.end ())
		throw std::runtime_error ("Unknown label: " + exp.name);

	const bool inState = m_state.atomicPropositions.find (const_cast<TS::Label*>(&iter->second)) == m_state.atomicPropositions.end ();
	const bool inAtom = m_atom.find (m_parentExp) == m_atom.end ();
	return inState == inAtom;
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Literal& exp) {
	return (m_atom.find (m_parentExp) != m_atom.end ()) == exp.value;
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Negation&) {
	throw std::runtime_error ("Negation in closure!");
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_And& exp) {
	return	(containsConsistent (exp.lhs)	&&	containsConsistent (exp.rhs))
		==	(m_atom.find (m_parentExp) != m_atom.end ());
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Or& exp) {
	return	(containsConsistent (exp.lhs)	||	containsConsistent (exp.rhs))
		==	(m_atom.find (m_parentExp) != m_atom.end ());
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Implication& exp) {
	return	(!containsConsistent (exp.lhs)	||	containsConsistent (exp.rhs))
		==	(m_atom.find (m_parentExp) != m_atom.end ());
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Until& exp) {
	Formula::Expression next { Formula::E_Next { {}, exp, {}}};
	return	(m_atom.find (m_parentExp) != m_atom.end ())
		==	(
				containsConsistent (exp.rhs)
			||	(containsConsistent (exp.lhs) && m_atom.find (&next) != m_atom.end ())
			);
}

bool LTL::Consistency_ClosureVisitor::operator () (const Formula::E_Next&) {
	return true;
}

int LTL::ClosureVisitor::operator () (const Formula::E_Negation& exp) {
	m_parentExp = &exp.exp;
	boost::apply_visitor (*this, exp.exp);
	return 0;
}

int LTL::ClosureVisitor::operator () (const Formula::E_Until& exp) {
	m_closure.insert (m_parentExp);

	m_auxExp.emplace_back (Formula::E_Next ({}, exp, {}));
	m_closure.insert (&m_auxExp.back ());

	m_parentExp = &exp.lhs;
	boost::apply_visitor (*this, exp.lhs);
	m_parentExp = &exp.rhs;
	boost::apply_visitor (*this, exp.rhs);
	return 0;
}

int LTL::ClosureVisitor::operator () (const Formula::E_Literal&) {
	m_closure.insert (m_parentExp);
	return 0;
}

int LTL::ClosureVisitor::operator () (const Formula::E_Label&) {
	m_closure.insert (m_parentExp);
	return 0;
}


/**
 * Copyright (c) 2022 <Daumantas Kavolis>
 *
 * 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.
 */

#pragma once

#include "config.hpp"

MSVC_WARNING_DISABLE(4619)
#include <boost/container/small_vector.hpp>
#include <boost/iterator/iterator_facade.hpp>
#include <boost/range/adaptor/filtered.hpp>
#include <boost/range/adaptor/indexed.hpp>
MSVC_WARNING_POP()

#include "traits.hpp"
#include "utils.hpp"

namespace poly {

template <class Real_,
          class Index = std::conditional_t<std::is_const_v<Real_>, std::size_t const, std::size_t>>
class SobolItemView : public view<Index> {
 public:
  using Base = view<Index>;
  using IndexType = Index;
  using Real = Real_;

  SobolItemView(Index* indices, std::size_t count, Real& coefficient) noexcept
      : Base(indices, count), coefficient_(coefficient) {}

  // NOLINTNEXTLINE(hicpp-explicit-conversions)
  operator SobolItemView<Real const, Index const>() const noexcept {
    return {this->data(), this->size(), coefficient_};
  }

  auto coefficient() const noexcept -> Real& { return coefficient_; }

  template <bool check = true, class Range,
            typename = std::enable_if_t<detail::is_range<Range>::value>>
  [[nodiscard]] auto matches_non_zeros(Range const& non_zeros,
                                       bounds_check<check> /* unused */ = check_bounds) const
      -> bool {
    if constexpr (check) {
      if (boost::size(non_zeros) != this->size())
        throw std::out_of_range("Dimensions do not match");
    }

    for (auto&& [index, is_non_zero] : non_zeros | boost::adaptors::indexed())
      if (((*this)[index] != 0) != static_cast<bool>(is_non_zero)) return false;

    return true;
  }

  template <bool check = true, class Range,
            typename = std::enable_if_t<detail::is_range<Range>::value>>
  [[nodiscard]] auto matches_zeros(Range const& zeros,
                                   bounds_check<check> /* unused */ = check_bounds) const -> bool {
    if constexpr (check) {
      if (boost::size(zeros) != this->size()) throw std::out_of_range("Dimensions do not match");
    }

    for (auto&& [index, is_zero] : zeros | boost::adaptors::indexed())
      if (((*this)[index] == 0) != static_cast<bool>(is_zero)) return false;

    return true;
  }

 private:
  Real& coefficient_;
};

template <class Real>
class SobolIterator : public boost::iterator_facade<
                          /* Derived = */ SobolIterator<Real>,
                          /* Value type = */ SobolItemView<Real>,
                          /* traversal tag = */ boost::random_access_traversal_tag,
                          /* reference = */ SobolItemView<Real>> {
 public:
  using Index = typename SobolItemView<Real>::IndexType;

  SobolIterator(Index* indices, Real* coefficients, std::size_t dimensions) noexcept
      : iterator_(indices), coefficients_(coefficients), dimensions_(dimensions) {}

 private:
  friend class boost::iterator_core_access;
  template <class>
  friend class SobolIterator;

  template <class OtherValue>
  auto equal(SobolIterator<OtherValue> const& other) const noexcept -> bool {
    return this->coefficients_ == other.coefficients_;
  }

  void increment() noexcept {
    iterator_ += dimensions_;
    ++coefficients_;
  }
  void decrement() noexcept {
    iterator_ -= dimensions_;
    --coefficients_;
  }

  void advance(std::ptrdiff_t n) noexcept {
    iterator_ += n * dimensions_;
    coefficients_ += n;
  }
  auto distance_to(SobolIterator const& other) const noexcept -> std::ptrdiff_t {
    return other.coefficients_ - coefficients_;
  }

  auto dereference() const noexcept -> SobolItemView<Real> {
    return {iterator_, dimensions_, *coefficients_};
  }

  Index* iterator_;
  Real* coefficients_;
  std::size_t dimensions_;
};

/// \brief Sobol calculator for orthonormal PCEs. Under such assumption, inner products between
/// different polynomials are 0, same - 1.
///
/// Sudret, B. (2008). Global sensitivity analysis using polynomial chaos expansions.
/// Reliability Engineering & System Safety, 93(7), 964–979.
/// https://doi.org/10.1016/j.ress.2007.04.002
/// \tparam Real
/// \tparam Alloc
template <class Real, template <class> class Alloc = std::allocator>
class Sobol {
 private:
  using match_vector = boost::container::small_vector<bool, 128>;

 public:
  using indices_vector = std::vector<std::size_t, Alloc<std::size_t>>;
  using coefficients_vector = std::vector<Real, Alloc<Real>>;
  using View = SobolItemView<Real, std::size_t>;
  using ConstView = SobolItemView<Real const, std::size_t const>;

  using iterator = SobolIterator<Real const>;
  using const_iterator = iterator;

  Sobol(indices_vector indices, coefficients_vector coefficients, std::size_t dimensions)
      : indices_(std::move(indices)),
        coefficients_(std::move(coefficients)),
        dimensions_(dimensions) {
    calculate_mean_variance();
  }

  auto operator[](std::size_t index) noexcept -> View {
    return View{indices_.data() + index * dimensions_, dimensions_, coefficients_[index]};
  }
  auto operator[](std::size_t index) const noexcept -> ConstView {
    return ConstView{indices_.data() + index * dimensions_, dimensions_, coefficients_[index]};
  }

  auto at(std::size_t index) noexcept -> View {
    return View{indices_.data() + index * dimensions_, dimensions_, coefficients_.at(index)};
  }
  auto at(std::size_t index) const -> ConstView {
    return ConstView{indices_.data() + index * dimensions_, dimensions_, coefficients_.at(index)};
  }

  [[nodiscard]] auto size() const noexcept -> std::size_t { return coefficients_.size(); }
  [[nodiscard]] auto index_count() const noexcept -> std::size_t { return indices_.size(); }
  [[nodiscard]] auto dimensions() const noexcept -> std::size_t { return dimensions_; }
  [[nodiscard]] auto variance() const noexcept -> Real { return variance_; }
  [[nodiscard]] auto mean() const noexcept -> Real { return mean_; }
  [[nodiscard]] auto indices() const noexcept -> view<std::size_t const> {
    return {indices_.data(), indices_.size()};
  }
  [[nodiscard]] auto coefficients() const noexcept -> view<Real const> {
    return {coefficients_.data(), coefficients_.size()};
  }

  auto mutable_indices() noexcept -> view<std::size_t> {
    return {indices_.data(), indices_.size()};
  }
  auto mutable_coefficients() noexcept -> view<Real> {
    return {coefficients_.data(), coefficients_.size()};
  }
  void recalculate() { calculate_mean_variance(); }

  [[nodiscard]] auto begin() const noexcept -> SobolIterator<Real const> {
    return {indices_.data(), coefficients_.data(), dimensions_};
  }
  [[nodiscard]] auto end() const noexcept -> SobolIterator<Real const> {
    return {indices_.data() + index_count(), coefficients_.data() + size(), dimensions_};
  }
  [[nodiscard]] auto mutable_range() noexcept -> boost::iterator_range<SobolIterator<Real>> {
    using It = SobolIterator<Real>;
    It begin = {indices_.data(), coefficients_.data(), dimensions_};
    return boost::make_iterator_range_n(begin, size());
  }

  template <bool check = true>
  [[nodiscard]] auto sensitivity(std::size_t index,
                                 bounds_check<check> /* unused */ = check_bounds) const -> Real {
    if constexpr (check) {
      if (index >= dimensions_) throw std::out_of_range("Sensitivity index out of range");
    }
    match_vector non_zeros(dimensions_, false);
    non_zeros[index] = true;
    return calculate_sensitivity(non_zeros);
  }

  template <bool check = true>
  [[nodiscard]] auto sensitivity(std::pair<std::size_t, std::size_t> index,
                                 bounds_check<check> /* unused */ = check_bounds) const -> Real {
    if constexpr (check) {
      if (index.first >= dimensions_ || index.second >= dimensions_)
        throw std::out_of_range("Sensitivity index out of range");
    }
    match_vector non_zeros(dimensions_, false);
    non_zeros[index.first] = true;
    non_zeros[index.second] = true;
    return calculate_sensitivity(non_zeros);
  }

  template <bool check = true, class Range,
            typename = std::enable_if_t<detail::is_range<Range>::value>>
  [[nodiscard]] auto sensitivity(Range const& indices,
                                 bounds_check<check> /* unused */ = check_bounds) const -> Real {
    match_vector non_zeros(dimensions_, false);
    for (auto&& index : indices) {
      if constexpr (check) {
        if (index >= dimensions_) throw std::out_of_range("Sensitivity index out of range");
      }
      non_zeros[index] = true;
    }
    return calculate_sensitivity(non_zeros);
  }

  template <bool check = true>
  [[nodiscard]] auto total_sensitivity(std::size_t index,
                                       bounds_check<check> /* unused */ = check_bounds) const
      -> Real {
    if constexpr (check) {
      if (index >= dimensions_) throw std::out_of_range("Sensitivity index out of range");
    }
    return calculate_total_sensitivity(index);
  }

 private:
  // copying indices and coefficients allows to have precomputed variance and mean
  indices_vector indices_;
  coefficients_vector coefficients_;
  std::size_t dimensions_;
  Real variance_;
  Real mean_;
  Real inverse_variance_;

  void calculate_mean_variance() {
    if (coefficients_.size() * dimensions_ != indices_.size())
      throw std::out_of_range("Coefficients and indices sizes do not match");

    // mean is coefficient with all indices 0
    mean_ = 0;
    // should have a fake range that always dereferences to true
    match_vector zeros(dimensions_, true);
    for (auto&& item : *this) {
      if (item.matches_zeros(zeros, no_bounds_check)) {
        mean_ = item.coefficient();
        break;
      }
    }

    // variance is sum of squares of all the other coefficients
    variance_ = -(mean_ * mean_);
    for (auto&& coefficient : coefficients_) variance_ += coefficient * coefficient;

    inverse_variance_ = 1.0L / variance_;
  }

  [[nodiscard]] auto calculate_sensitivity(match_vector const& non_zeros) const -> Real {
    Real s = 0;
    for (auto&& item : *this) {
      if (item.matches_non_zeros(non_zeros, no_bounds_check))
        s += item.coefficient() * item.coefficient();
    }

    return s * inverse_variance_;
  }

  [[nodiscard]] auto calculate_total_sensitivity(std::size_t index) const -> Real {
    Real s = 0;
    for (auto&& item : *this) {
      if (item[index] != 0) s += item.coefficient() * item.coefficient();
    }

    return s * inverse_variance_;
  }
};
}  // namespace poly

POLY_TEMPLATE_RANGE(poly::Sobol)


/* ----------------------------------------------------------------------- *//**
 * @file matrix_ops.cpp
 *
 * @date May 8, 2013
 *//* ----------------------------------------------------------------------- */

#include <dbconnector/dbconnector.hpp>
#include <modules/shared/HandleTraits.hpp>

#include <math.h>
#include <iostream>
#include <algorithm>
#include <functional>
#include <numeric>
#include <boost/random/uniform_real.hpp>
#include <boost/random/normal_distribution.hpp>
#include <boost/random/bernoulli_distribution.hpp>
#include <boost/random/variate_generator.hpp>
#include <boost/generator_iterator.hpp>
#include <boost/random/linear_congruential.hpp>
#include "matrix_ops.hpp"

namespace madlib {
namespace modules {
namespace linalg {

using madlib::dbconnector::postgres::madlib_construct_array;
using madlib::dbconnector::postgres::madlib_construct_md_array;

// Use Eigen
using namespace dbal::eigen_integration;

AnyType matrix_densify_sfunc::run(AnyType & args)
{
    int32_t col_dim = args[1].getAs<int32_t>();
    int32_t col = args[2].getAs<int32_t>();
    double val = args[3].getAs<double>();

    if(col_dim < 1){
        std::stringstream errorMsg;
        errorMsg << "invalid argument - col (" << col <<
                    ") should be positive";
        throw std::invalid_argument(errorMsg.str());
    }

    if(col < 1 || col > col_dim){
        std::stringstream errorMsg;
        errorMsg << "invalid argument - col (" << col <<
                    ") should be in the range of [1, " << col_dim << "]";
        throw std::invalid_argument(errorMsg.str());
    }

    MutableArrayHandle<double> state(NULL);
    if (args[0].isNull()){
        state =  madlib_construct_array(
            NULL, col_dim, FLOAT8OID, sizeof(double), true, 'd');
    }else{
        state = args[0].getAs<MutableArrayHandle<double> >();
    }

    // we use col - 1 since
    // database expects col in [1, col_dim]; c++ expects col in [0, col_dim - 1]
    *(state.ptr() + col - 1) = val;

    return state;
}

AnyType matrix_mem_sum_sfunc::run(AnyType & args)
{
    ArrayHandle<double> m = args[1].getAs<ArrayHandle<double> >();

    if (m.dims() !=2){
        throw std::invalid_argument(
            "invalid argument - 2-d array expected");
    }

    int row_m = static_cast<int>(m.sizeOfDim(0));
    int col_m = static_cast<int>(m.sizeOfDim(1));

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> state(NULL);
    if (args[0].isNull()){
        int dims[2] = {row_m, col_m};
        int lbs[2] = {1, 1};
        state =  madlib_construct_md_array(
            NULL, NULL, 2, dims, lbs, FLOAT8OID, sizeof(double), true, 'd');
    }else{
        state = args[0].getAs<MutableArrayHandle<double> >();
    }

    for(int i = 0; i < row_m; i++){
        for(int j = 0; j < col_m; j++){
            *(state.ptr() + i * col_m + j) += *(m.ptr() + i * col_m + j);
        }
    }

    return state;
}

AnyType matrix_blockize_sfunc::run(AnyType & args)
{
    if(args[1].isNull())
        return args[0];

    int32_t row_id = args[1].getAs<int32_t>();
    ArrayHandle<double> row_vec  = args[2].getAs<ArrayHandle<double> >();
    int32_t rsize = args[3].getAs<int32_t>();
    int32_t csize = static_cast<int32_t>(row_vec.sizeOfDim(0));

    if(rsize < 1){
        throw std::invalid_argument(
            "invalid argument - block size should be positive");
    }

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> state(NULL);
    if (args[0].isNull()){
        int32_t dims[2] = {rsize, csize};
        int lbs[2] = {1, 1};
        state = madlib_construct_md_array(
                NULL, NULL, 2, dims, lbs, FLOAT8OID, sizeof(double), true, 'd');
    }else{
        state = args[0].getAs<MutableArrayHandle<double> >();
    }

    // database represents row_id in [1, row_dim]
    memcpy(state.ptr() + ((row_id - 1) % rsize) * csize,
           row_vec.ptr(),
           csize * sizeof(double));

    return state;
}

AnyType matrix_mem_mult::run(AnyType & args)
{
    ArrayHandle<double> a = args[0].getAs<ArrayHandle<double> >();
    ArrayHandle<double> b = args[1].getAs<ArrayHandle<double> >();
    bool trans_b = args[2].getAs<bool>();

    if (a.dims() != 2 || b.dims() !=2){
        throw std::invalid_argument(
            "invalid argument - 2-d array expected");
    }

    int row_a = static_cast<int>(a.sizeOfDim(0));
    int col_a = static_cast<int>(a.sizeOfDim(1));
    int row_b = static_cast<int>(b.sizeOfDim(0));
    int col_b = static_cast<int>(b.sizeOfDim(1));

    if ((!trans_b && col_a != row_b) || (trans_b && col_a != col_b)){
        throw std::invalid_argument(
            "invalid argument - dimension mismatch");
    }

    int dims[2] = {row_a, col_b};
    if (trans_b)
        dims[1] = row_b;
    int lbs[2] = {1, 1};
    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> r = madlib_construct_md_array(
            NULL, NULL, 2, dims, lbs, FLOAT8OID, sizeof(double), true, 'd');

    for (int i = 0; i < row_a; i++){
        for(int j = 0; j < col_a; j++){
            for(int k = 0; k < dims[1]; k++){
                *(r.ptr() + i * dims[1] + k) += trans_b ?
                    *(a.ptr() + i * col_a + j) * *(b.ptr() + k * col_b + j) :
                        *(a.ptr() + i * col_a + j) * *(b.ptr() + j * col_b + k);
            }
        }
    }
    return r;
}

AnyType matrix_mem_trans::run(AnyType & args)
{
    ArrayHandle<double> m = args[0].getAs<ArrayHandle<double> >();

    if (m.dims() != 2){
        throw std::invalid_argument(
            "invalid argument - 2-d array expected");
    }

    int row_m = static_cast<int>(m.sizeOfDim(0));
    int col_m = static_cast<int>(m.sizeOfDim(1));

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    int dims[2] = {col_m, row_m};
    int lbs[2] = {1, 1};
    MutableArrayHandle<double> r = madlib_construct_md_array(
            NULL, NULL, 2, dims, lbs, FLOAT8OID, sizeof(double), true, 'd');

    for (int i = 0; i < row_m; i++){
        for(int j = 0; j < col_m; j++){
                *(r.ptr() + j * row_m + i) = *(m.ptr() + i * col_m + j);
        }
    }
    return r;
}

AnyType rand_vector::run(AnyType & args)
{
    int dim = args[0].getAs<int>();
    if (dim < 1) {
        throw std::invalid_argument("invalid argument - dim should be positive");
    }
    MutableArrayHandle<int> r =  madlib_construct_array(
            NULL, dim, INT4OID, sizeof(int32_t), true, 'i');

    for (int i = 0; i < dim; i++){
        *(r.ptr() + i) = (int)(drand48() * 1000);
    }
    return r;
}

AnyType normal_vector::run(AnyType & args)
{
    int dim = args[0].getAs<int>();
    double mu = args[1].getAs<double>();
    double sigma = args[2].getAs<double>();
    int seed = args[3].getAs<int>();

    if (dim < 1) {
        throw std::invalid_argument("invalid argument - dim should be positive");
    }
    ColumnVector res(dim);
    boost::minstd_rand generator(seed);
    boost::normal_distribution<> nd_dist(mu, sigma);
    boost::variate_generator<boost::minstd_rand&, boost::normal_distribution<> > nd(generator, nd_dist);

    for (int i = 0; i < dim; i++){
        res(i) = (double)nd();
    }
    return res;
}

AnyType bernoulli_vector::run(AnyType & args)
{
    int dim = args[0].getAs<int>();
    double upper_val = args[1].getAs<double>();
    double lower_val = args[2].getAs<double>();
    double prob = args[3].getAs<double>();
    int seed = args[4].getAs<int>();

    if (dim < 1) {
        throw std::invalid_argument("invalid argument - dim should be positive");
    }
    if (prob > 1 || prob < 0) {
        throw std::invalid_argument("invalid argument - probability should be in [0,1]");
    }

    ColumnVector res(dim);
    boost::minstd_rand generator(seed);
    boost::bernoulli_distribution<> bn_dist(prob);
    boost::variate_generator<boost::minstd_rand&, boost::bernoulli_distribution<> > bn(generator, bn_dist);

    for (int i = 0; i < dim; i++) {
        res(i) = bn() ? upper_val : lower_val;
    }
    return res;
}

AnyType uniform_vector::run(AnyType & args)
{
    int dim = args[0].getAs<int>();
    double min_ = args[1].getAs<double>();
    double max_ = args[2].getAs<double>();
    int seed = args[3].getAs<int>();

    if (dim < 1) {
        throw std::invalid_argument("invalid argument - dim should be positive");
    }
    ColumnVector res(dim);
    boost::minstd_rand generator(seed);
    boost::uniform_real<> uni_dist(min_, max_);
    boost::variate_generator<boost::minstd_rand&, boost::uniform_real<> > uni(generator, uni_dist);
    for (int i = 0; i < dim; i++){
        res(i) = (double)uni();
    }
    return res;
}

AnyType matrix_vec_mult_in_mem_2d::run(AnyType & args){
    MappedColumnVector vec = args[0].getAs<MappedColumnVector>();
    MappedMatrix mat = args[1].getAs<MappedMatrix>();

    // Note mat is constructed in the column-first order
    // which means that mat is actually transposed
    if(vec.size() != mat.cols()){
        throw std::invalid_argument(
            "dimensions mismatch: vec.size() != matrix.rows()");
    };

    // trans(vec) * trans(mat) = mat * vec
    Matrix r = mat * vec;
    ColumnVector v = r.col(0);
    return v;
}

AnyType matrix_vec_mult_in_mem_1d::run(AnyType & args){
    MappedColumnVector vec1 = args[0].getAs<MappedColumnVector>();
    // matrix stored as a 1-d array
    MappedColumnVector vec2 = args[1].getAs<MappedColumnVector>();

    if(vec2.size() % vec1.size() != 0){
        throw std::invalid_argument(
            "dimensions mismatch: matrix.size() is not multiples of vec.size()");
    };

    MappedMatrix mat;
    // the rebinding happens in column-major
    mat.rebind(vec2.memoryHandle(), vec1.size(), vec2.size()/vec1.size());
    Matrix r = trans(mat) * vec1;
    ColumnVector v = r.col(0);
    return v;
}

AnyType row_fold::run(AnyType & args){
    MappedColumnVector vec = args[0].getAs<MappedColumnVector>();
    MappedIntegerVector pat = args[1].getAs<MappedIntegerVector>();

    if (vec.size() != pat.sum()) {
        throw std::invalid_argument(
            "dimensions mismatch: row_in.size() != pattern.sum()");
    }

    ColumnVector r(pat.size());
    for (int i = 0, j = 0; i < pat.size(); j += pat[i++])
        r[i] = vec.segment(j, pat[i]).prod();

    return r;
}

AnyType rand_block::run(AnyType & args)
{
    int row_dim = args[0].getAs<int>();
    int col_dim = args[1].getAs<int>();
    if (row_dim < 1 || col_dim < 1) {
        throw std::invalid_argument("invalid argument - row_dim and col_dim \
        should be positive");
    }

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    int dims[2] = {row_dim, col_dim};
    int lbs[2] = {1, 1};
    MutableArrayHandle<int32_t> r = madlib_construct_md_array(
            NULL, NULL, 2, dims, lbs, INT4OID, sizeof(int32_t), true, 'i');

    for (int i = 0; i < row_dim; i++){
        for(int j = 0; j < col_dim; j++){
                *(r.ptr() + i * col_dim + j) = (int32_t)(drand48() * 1000);
        }
    }
    return r;
}

typedef struct __sr_ctx1{
    const double * inarray;
    int32_t dim;
    int32_t maxcall;
    int32_t size;
    int32_t curcall;
} sr_ctx1;

// @brief: split a vector into multiple vectors
void * row_split::SRF_init(AnyType &args)
{
    // vector to be split
    ArrayHandle<double> inarray = args[0].getAs<ArrayHandle<double> >();
    // size of each split
    int32_t size = args[1].getAs<int32_t>();

    if (size < 1) {
        // throw std::invalid_argument will cause crash
        elog(ERROR, "invalid argument - the spliting size should be positive");
    }

    sr_ctx1 * ctx = new sr_ctx1;
    ctx->inarray = inarray.ptr();
    // length of inarray
    ctx->dim = static_cast<int32_t>(inarray.sizeOfDim(0));
    ctx->size = size;
    // max number of splits to be formed
    ctx->maxcall = static_cast<int32_t>(
        ceil(static_cast<double>(ctx->dim) / size));
    // current split index
    ctx->curcall = 0;

    return ctx;
}

/**
 * @brief The function is used to return the next row by the SRF.
 **/
AnyType row_split::SRF_next(void *user_fctx, bool *is_last_call)
{
    sr_ctx1 * ctx = (sr_ctx1 *) user_fctx;
    if (ctx->maxcall == 0) {
        *is_last_call = true;
        return Null();
    }

    int32_t size = ctx->size;
    if (ctx->maxcall == 1 && ctx->dim % ctx->size != 0) {
        // for last split we might not have enough elements to fill the whole split
        // in this case we update size to the number of residual elements in last split
        size = ctx->dim % ctx->size;
    }

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> outarray(
        madlib_construct_array(
            NULL, size, FLOAT8OID, sizeof(double), true, 'd'));
    memcpy(outarray.ptr(), ctx->inarray + ctx->curcall * ctx->size,
           size * sizeof(double));

    ctx->curcall++;
    ctx->maxcall--;
    *is_last_call = false;

    return outarray;
}

AnyType matrix_unblockize_sfunc::run(AnyType & args)
{
    if (args[1].isNull() || args[2].isNull() || args[3].isNull())
        return args[0];

    int32_t total_col_dim = args[1].getAs<int32_t>();
    int32_t col_id = args[2].getAs<int32_t>();
    ArrayHandle<double> row_vec = args[3].getAs<ArrayHandle<double> >();
    int32_t col_dim = static_cast<int32_t>(row_vec.sizeOfDim(0));

    if(total_col_dim < 1){
        throw std::invalid_argument(
            "invalid argument - total_col_dim should be positive");
    }

    if(col_id <= 0){
        throw std::invalid_argument(
            "invalid argument - col_id should be positive");
    }

    if(col_id > total_col_dim){
        throw std::invalid_argument(
            "invalid argument - col_id should be in the range of [1, total_col_dim]");
    }

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> state(NULL);
    if (args[0].isNull()){
        state =  madlib_construct_array(
            NULL, total_col_dim, FLOAT8OID, sizeof(double), true, 'd');
    }else{
        state = args[0].getAs<MutableArrayHandle<double> >();
    }

    memcpy(state.ptr() + col_id - 1, row_vec.ptr(), col_dim * sizeof(double));

    return state;
}

typedef struct __sr_ctx2{
    const double * inarray;
    int32_t maxcall;
    int32_t dim;
    int32_t curcall;
} sr_ctx2;

void * unnest_block::SRF_init(AnyType &args)
{
    ArrayHandle<double> inarray = args[0].getAs<ArrayHandle<double> >();
    if(inarray.dims() != 2)
        throw std::invalid_argument("invalid dimension");

    sr_ctx2 * ctx = new sr_ctx2;
    ctx->inarray = inarray.ptr();
    ctx->maxcall = static_cast<int32_t>(inarray.sizeOfDim(0));
    ctx->dim = static_cast<int32_t>(inarray.sizeOfDim(1));
    ctx->curcall = 0;

    return ctx;
}

AnyType unnest_block::SRF_next(void *user_fctx, bool *is_last_call)
{
    sr_ctx2 * ctx = (sr_ctx2 *) user_fctx;
    if (ctx->maxcall == 0) {
        *is_last_call = true;
        return Null();
    }

    // FIXME: construct_array functions circumvent the abstraction layer. These
    // should be replaced with appropriate Allocator:: calls.
    MutableArrayHandle<double> outarray(
        madlib_construct_array(
            NULL, ctx->dim, FLOAT8OID, sizeof(double), true, 'd'));
    memcpy(
        outarray.ptr(), ctx->inarray + ctx->curcall * ctx->dim, ctx->dim *
        sizeof(double));

    ctx->curcall++;
    ctx->maxcall--;
    *is_last_call = false;

    return outarray;
}

}
}
}


// Copyright (C) 2018 Thejaka Amila Kanewala, Marcin Zalewski, Andrew Lumsdaine.

// Boost Software License - Version 1.0 - August 17th, 2003

// Permission is hereby granted, free of charge, to any person or organization
// obtaining a copy of the software and accompanying documentation covered by
// this license (the "Software") to use, reproduce, display, distribute,
// execute, and transmit the Software, and to prepare derivative works of the
// Software, and to permit third-parties to whom the Software is furnished to
// do so, all subject to the following:

// The copyright notices in the Software and this entire statement, including
// the above license grant, this restriction and the following disclaimer,
// must be included in all copies of the Software, in whole or in part, and
// all derivative works of the Software, unless such copies or derivative
// works are solely in the form of machine-executable object code generated by
// a source language processor.

// 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, TITLE AND NON-INFRINGEMENT. IN NO EVENT
// SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
// FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
// DEALINGS IN THE SOFTWARE.

//  Authors: Thejaka Kanewala
//           Marcin Zalewski
//           Andrew Lumsdaine

//======== SSSP (AGM/EAGM) Hand-coded Algortihms================//
// Driver for SSSP family of algorithms.
//===========================================================//

#include "common/sssp_params.hpp"

#define LIB_CDS 1

#include <boost/graph/distributed/thread_pq_def.hpp>
//delta eagms
#include <boost/graph/distributed/delta_stepping_shortest_paths.hpp>
#include <boost/graph/distributed/delta_stepping_shortest_paths_node.hpp>
#include <boost/graph/distributed/delta_stepping_shortest_paths_numa.hpp>
#include <boost/graph/distributed/delta_stepping_shortest_paths_thread.hpp>
//kla eagms
#include <boost/graph/distributed/kla_sssp_buffer.hpp>
#include <boost/graph/distributed/kla_sssp_numa.hpp>
#include <boost/graph/distributed/kla_sssp_node.hpp>
#include <boost/graph/distributed/kla_sssp_thread.hpp>
//chaotic agms
#include <boost/graph/distributed/distributed_control.hpp>
#include <boost/graph/distributed/distributed_control_node.hpp>
#include <boost/graph/distributed/distributed_control_chaotic.hpp>

#define NODE_PRIORITY_Q_GEN boost::graph::distributed::node_priority_queue_gen
#define NUMA_PRIORITY_Q_GEN boost::graph::distributed::numa_priority_queue_gen
#define THREAD_PRIORITY_Q_GEN boost::graph::distributed::thread_priority_queue_gen

class SSSPExecutor {

private:
  template <typename Graph, typename DistMap, typename WeightMap>
  bool verify_sssp(amplusplus::transport& trans,  Graph& g, DistMap& distance, WeightMap& weight) {
    distance.set_consistency_model(boost::parallel::cm_forward);
    distance.set_max_ghost_cells(0);

    if (trans.rank() == 0) std::cout<<"[INFO] Verifying results......";

    {
      amplusplus::scoped_epoch epoch(g.transport());

      BGL_FORALL_VERTICES_T(v, g, Graph) {
	BGL_FORALL_OUTEDGES_T(v, e, g, Graph) {
	  get(distance, target(e, g));
	}
      }
    }

    BGL_FORALL_VERTICES_T(v, g, Graph) {
      BGL_FORALL_OUTEDGES_T(v, e, g, Graph) {
#ifdef PRINT_DEBUG
	if (get(distance, target(e, g)) >
	    boost::closed_plus<weight_type>()(get(distance, source(e, g)), get(weight, e)))
	  std::cout << get(get(vertex_local, g), source(e, g)) << "@"
		    << get(get(vertex_owner, g), source(e, g)) << "->"
		    << get(get(vertex_local, g), target(e, g)) << "@"
		    << get(get(vertex_owner, g), target(e, g)) << "  weight = "
		    << get(weight, e)
		    << "  distance(" << get(get(vertex_local, g), source(e, g)) << "@"
		    << get(get(vertex_owner, g), source(e, g))
		    << ") = " << get(distance, source(e, g)) << "  distance("
		    << get(get(vertex_local, g), target(e, g)) << "@"
		    << get(get(vertex_owner, g), target(e, g)) << ") = "
		    << get(distance, target(e, g)) << std::endl;
#else
	if(get(distance, target(e, g)) > boost::closed_plus<weight_type>()(get(distance, v), get(weight, e))) std::abort();
#endif
      }
    }

    if (trans.rank() == 0) std::cout << "Verified." << std::endl;
    distance.clear(); // Clear memory used by ghost cells

    return true;
  }

  template <typename Graph,
	    typename DistanceMap>
  uint64_t count_visited_edges(Graph& g, DistanceMap& distance) {

    uint64_t vedges = 0;
    weight_type dist;
    BGL_FORALL_VERTICES_T(v, g, Graph) {
      dist = boost::get(distance, v);
      if(dist < std::numeric_limits<weight_type>::max()) {
	BGL_FORALL_OUTEDGES_T(v, e, g, Graph) {
	  // check whether this is a self loop; if self loop dont count it
	  if (target(e,g) == v)
	    continue;

	  vedges+=1;
	}
      }
    }

    uint64_t totaledges = 0;
    MPI_Allreduce(&vedges, &totaledges, 1, MPI_UINT64_T, MPI_SUM, MPI_COMM_WORLD);
    // Graph is undirected, Therefore, divide by two
    return (totaledges/2);
  }

  template <typename Graph,
	    typename DistanceMap>
  uint64_t count_visited_vertices(Graph& g, DistanceMap& distance) {
    uint64_t vvertices = 0;
    weight_type dist;
    BGL_FORALL_VERTICES_T(v, g, Graph) {
      dist = boost::get(distance, v);
      if(dist < std::numeric_limits<weight_type>::max()) {
	vvertices+=1;
      }
    }

    uint64_t totalverts = 0;
    MPI_Allreduce(&vvertices, &totalverts, 1, MPI_UINT64_T, MPI_SUM, MPI_COMM_WORLD);
    return totalverts;
  }

  void init_cds() {
#ifdef LIB_CDS
    // LIBCDS Stuff goes here
    if (_RANK == 0)
      std::cout << "[INFO] Initializing LIBCDS .... " << std::endl;

    cds::Initialize();
    // Initialize Hazard Pointer singleton
    cds::gc::HP hpGC;
    // Attach for the main thread
    cds::threading::Manager::attachThread();
#endif
  }

  void terminate_cds() {
    if (_RANK == 0)
      std::cout << "[INFO] Termminating LIBCDS .... " << std::endl;

    cds::Terminate();
  }

public:

  SSSPExecutor() {
  }

  template <typename Graph,
	    typename Algorithm,
	    typename WeightMap,
	    typename DistanceMap,
	    typename graph_create_params>
  time_type
  run_algorithm(amplusplus::transport& trans,
		amplusplus::transport& barrier_trans,
		Graph& g,
		Algorithm& D,
		WeightMap& weight,
		DistanceMap& distance,
		graph_create_params& gparam,
		instance_params& runtime_param,
		sssp_instance_params& ssspparam) {
#ifdef CLONE
    amplusplus::transport trans = trans.clone(); // Clone transport for this run
#endif

    trans.set_nthreads(1);

    { amplusplus::scoped_epoch epoch(barrier_trans); }

#ifdef AMPLUSPLUS_ENABLE_PERFORMANCE_COUNTERS
    epoch_times.clear();
    clear_buffer_stats();
#endif

    // Many threads now
    trans.set_nthreads(runtime_param.threads);

    boost::scoped_array<boost::thread> threads(new boost::thread[runtime_param.threads - 1]);
    for (int i = 0; i < runtime_param.threads - 1; ++i) {
      boost::thread thr(boost::ref(D), i + 1);
      threads[i].swap(thr);
    }

    D(0);

    for (int i = 0; i < runtime_param.threads - 1; ++i)
      threads[i].join();

    time_type end = get_time();
    time_type start = D.get_start_time();

    // Back to one thread
    trans.set_nthreads(1);
    clear_thread_core_data();

    // Verification
    if (ssspparam.verify) {
      verify_sssp(trans, g, distance, weight);
    }

    uint64_t total_vertices = count_visited_vertices(g, distance);
    if (total_vertices < ssspparam.visited_threshold) {
      ssspparam.work_stats.reset();
      return -1.;
    }

    time_type exec_time = end-start;
    ssspparam.work_stats.reduce_stats(exec_time);

    // Stats
#ifdef AMPLUSPLUS_ENABLE_PERFORMANCE_COUNTERS
    print_and_clear_epoch_times();
    print_buffer_stats();
#endif

    uint64_t edges_in_comp =  count_visited_edges(g, distance);

    if (trans.rank() == 0) {
      std::cout << "[INFO] Edge traversals should be : " << edges_in_comp
		<< ", vertices visited : " << total_vertices
		<< ", Time : " << exec_time
		<< " sec." << std::endl;
    }

    return exec_time;
  }

public:
  template <typename Graph, typename MessageGenerator,
	    typename graph_create_params>
  time_type operator()(Graph& g,
		       amplusplus::transport& trans,
		       MessageGenerator& msg_gen,
		       graph_create_params& gparams,
		       instance_params& runtime_params,
		       sssp_instance_params& sssp_params) {

    amplusplus::transport barrier_trans = trans.clone();

    // g must be weighted graph
    typedef boost::compressed_sparse_row_graph<boost::directedS,
					       boost::no_property,
					       WeightedEdge,
					       boost::no_property,
					       boost::distributedS<unsigned long long> > WGraph;
    //    static_assert(std::is_same<Graph,WGraph>::value, "Graph must be a weighted graph");
    // bad way of casting!
    WGraph& weightedgraph = (WGraph&)(g);

    typedef typename boost::graph_traits<WGraph>::vertex_descriptor Vertex;
    typedef typename boost::graph_traits<WGraph>::vertices_size_type vertices_size_type;
    typedef typename boost::property_map<WGraph, weight_type WeightedEdge::*>::type WeightMap;
    typedef typename boost::property_map<Graph, boost::vertex_index_t>::type VertexIndexMap;

    WeightMap weight = boost::get(&WeightedEdge::weight, weightedgraph);

    // Distance map
    std::vector<weight_type> distanceS(boost::num_vertices(weightedgraph), std::numeric_limits<weight_type>::max());
    typedef boost::iterator_property_map<std::vector<weight_type>::iterator, VertexIndexMap>  DistanceMap;
    DistanceMap distance(distanceS.begin(), get(boost::vertex_index, g));

    typedef typename boost::property_traits<WeightMap>::value_type Dist;

    time_type t = -1;
    while(t == -1) {

      Vertex current_source = boost::vertex(sssp_params.get_source(gparams.n), weightedgraph);
      // we need this as some algorithms retrive threads from the transport
      // we need this here incase if we hit a t=-1
      trans.set_nthreads(runtime_params.threads);

      if (_RANK == 0)
	std::cout << "[INFO] Executing SSSP for source : " << current_source << std::endl;

      if (sssp_params.agm == delta) {
	if (sssp_params.eagm == global) {

	  boost::graph::distributed::delta_stepping_shortest_paths<WGraph,
								   DistanceMap,
								   WeightMap,
								   agm_work_stats,
								   append_buffer<Vertex,
										 10u>,
								   MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (Dist)sssp_params.delta_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      msg_gen);

	  D.set_source(current_source);

	  if (sssp_params.level_sync)
	    D.set_level_sync();

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
        }

	if (sssp_params.eagm == node) {

	  init_cds();
	  boost::graph::distributed::delta_stepping_shortest_paths_node<WGraph,
									DistanceMap,
									WeightMap,
									agm_work_stats,
									NODE_PRIORITY_Q_GEN,
									MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (Dist)sssp_params.delta_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      runtime_params.flush,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
	  terminate_cds();
	}

	if (sssp_params.eagm == numa) {
	  init_cds();
	  boost::graph::distributed::delta_stepping_shortest_paths_numa<WGraph,
									DistanceMap,
									WeightMap,
									agm_work_stats,
									NUMA_PRIORITY_Q_GEN,
									MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (Dist)sssp_params.delta_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      runtime_params.flush,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);

	  terminate_cds();
	}

	if (sssp_params.eagm == thread) {

	  boost::graph::distributed::delta_stepping_shortest_paths_thread<WGraph,
									  DistanceMap,
									  WeightMap,
									  agm_work_stats,
									  THREAD_PRIORITY_Q_GEN,
									  MessageGenerator>
	    D(weightedgraph, distance, weight, trans, (Dist)sssp_params.delta_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      runtime_params.flush,  msg_gen);
	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
	}
      } // end of delta EAGMs

      if (sssp_params.agm == kla) {
	if (sssp_params.eagm == global) {

	  boost::graph::distributed::kla_shortest_paths_buffer<WGraph,
							       DistanceMap,
							       WeightMap,
							       agm_work_stats,
							       append_buffer<std::pair<Vertex, std::pair<Dist, size_t> >, 10u>,
							       MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (size_t)sssp_params.k_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
	}

	if (sssp_params.eagm == node) {
	  init_cds();
	  boost::graph::distributed::kla_shortest_paths_node<WGraph,
							     DistanceMap,
							     WeightMap,
							     agm_work_stats,
							     NODE_PRIORITY_Q_GEN,
							     MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (size_t)sssp_params.k_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
              runtime_params.flush,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);

          terminate_cds();
        }

	if (sssp_params.eagm == numa) {
	  init_cds();
	  boost::graph::distributed::kla_shortest_paths_numa<WGraph,
							     DistanceMap,
							     WeightMap,
							     agm_work_stats,
							     NUMA_PRIORITY_Q_GEN,
							     MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (size_t)sssp_params.k_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
              runtime_params.flush,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
          terminate_cds();
        }

	if (sssp_params.eagm == thread) {

	  boost::graph::distributed::kla_shortest_paths_thread<WGraph,
							       DistanceMap,
							       WeightMap,
							       agm_work_stats,
							       THREAD_PRIORITY_Q_GEN,
							       MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      (size_t)sssp_params.k_val,
	      sched_getcpu(),
	      sssp_params.work_stats,
              runtime_params.flush,
	      msg_gen);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
        }
      } // end of kla EAGMs

      if (sssp_params.agm == chaotic) {
	if (sssp_params.eagm == global) {

	  boost::graph::distributed::distributed_control_chaotic<WGraph,
								 DistanceMap,
								 WeightMap,
								 agm_work_stats,
								 MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      msg_gen,
              runtime_params.flush);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
	}

	if (sssp_params.eagm == node) {
	  init_cds();
	  boost::graph::distributed::distributed_control_node<WGraph,
							      DistanceMap,
							      WeightMap,
							      agm_work_stats,
							      NODE_PRIORITY_Q_GEN,
							      MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      msg_gen,
              runtime_params.flush,
              false);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
          terminate_cds();
        }

	if (sssp_params.eagm == numa) {
	  init_cds();
	  boost::graph::distributed::distributed_control_node<WGraph,
							      DistanceMap,
							      WeightMap,
							      agm_work_stats,
							      NUMA_PRIORITY_Q_GEN,
							      MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      sched_getcpu(),
	      sssp_params.work_stats,
	      msg_gen,
              runtime_params.flush,
              true); //NUMA

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
          terminate_cds();

        }

	if (sssp_params.eagm == thread) {

	  boost::graph::distributed::distributed_control<WGraph,
							 DistanceMap,
							 WeightMap,
							 agm_work_stats,
							 boost::graph::distributed::default_priority_queue_gen,
							 MessageGenerator>
	    D(weightedgraph,
	      distance,
	      weight,
	      trans,
	      sched_getcpu(),
	      sssp_params.work_stats,
              msg_gen,
              runtime_params.flush);

	  D.set_source(current_source);

	  t = run_algorithm(trans,
			    barrier_trans,
			    weightedgraph,
			    D,
			    weight,
			    distance,
			    gparams,
			    runtime_params,
			    sssp_params);
	}
      } // end of Chaotic EAGMs

      if (t == -1) {
	if (_RANK == 0)
	  std::cout << "[WARNING] Execution did not traversed threshold amount of vertices."
		    << " Threshold : " << sssp_params.visited_threshold
		    << ". Continuing..."
		    << std::endl;
      } else
	return t;
    } // end of while(t == -1)

  }

};

int main(int argc, char* argv[]) {
  std::cout << "printing core id for process ..." << std::endl;
  print_core_id();

  executor<SSSPExecutor, sssp_params, sssp_instance_params> sssp_executor;
  sssp_executor.execute(argc, argv);
  amplusplus::clear_mpi_datatype_map();
}


/*
 Copyright (c) 2015 Siyu Lei, Silviu Maniu, Luyi Mo

 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 __oim__CELFEvaluator__
#define __oim__CELFEvaluator__

#include <boost/heap/fibonacci_heap.hpp>

#include "common.hpp"
#include "Evaluator.hpp"

class CELFEvaluator : public Evaluator {
 private:
  struct celf_node_type {
    unode_int id;
    double spr;
    bool operator<(const celf_node_type &a) const {
      return (spr < a.spr) ? true : ((spr > a.spr) ? false : id > a.id);
    }
  };
  unsigned int samples_;

 public:
  CELFEvaluator(unsigned int samples) : samples_(samples) {}

  std::unordered_set<unode_int> select(
      const Graph& graph, Sampler& sampler,
      const std::unordered_set<unode_int>& activated, unsigned int k) {
    boost::heap::fibonacci_heap<celf_node_type> queue;
    std::unordered_map<unode_int,
      boost::heap::fibonacci_heap<celf_node_type>::handle_type> queue_nodes;
    std::unordered_set<unode_int> set;

    // Initial loop
    for (unode_int node : graph.get_nodes()) {
      celf_node_type u;
      u.id = node;
      std::unordered_set<unode_int> seeds;
      seeds.insert(node);
      u.spr = sampler.sample(graph, activated, seeds, samples_);
      queue_nodes[node] = queue.push(u);
    }

    // Main loop
    set.insert(queue.top().id);
    queue.pop();
    while ((set.size() < k) && (queue.size() > 0)) {
      bool found = false;
      while (!found) {
        celf_node_type u = queue.top();
        queue.pop();
        std::unordered_set<unode_int> seeds;
        for (unode_int node : set) seeds.insert(node);
        seeds.insert(u.id);
        double prev_val = u.spr;
        u.spr = sampler.sample(graph, activated, seeds, samples_) - prev_val;
        if (u.spr >= queue.top().spr) {
          set.insert(u.id);
          found = true;
        } else {
          queue_nodes[u.id] = queue.push(u);
        }
      }
    }
    return set;
  }
};

#endif /* defined(__oim__CELFEvaluator__) */