// Distributed under the MIT License.
// See LICENSE.txt for details.

/// \file
/// Defines class AdamsBashforthN

#pragma once

#include <algorithm>
#include <boost/iterator/transform_iterator.hpp>
#include <cstddef>
#include <cstdint>
#include <iterator>
#include <map>
#include <ostream>
#include <pup.h>
#include <type_traits>
#include <vector>

#include "ErrorHandling/Assert.hpp"
#include "ErrorHandling/Error.hpp"
#include "NumericalAlgorithms/Interpolation/LagrangePolynomial.hpp"
#include "Options/Options.hpp"
#include "Parallel/CharmPupable.hpp"
#include "Time/Time.hpp"
#include "Time/TimeId.hpp"
#include "Time/TimeSteppers/TimeStepper.hpp"  // IWYU pragma: keep
#include "Utilities/CachedFunction.hpp"
#include "Utilities/ConstantExpressions.hpp"
#include "Utilities/Gsl.hpp"
#include "Utilities/MakeWithValue.hpp"
#include "Utilities/TMPL.hpp"

/// \cond
namespace TimeSteppers {
template <typename LocalVars, typename RemoteVars, typename CouplingResult>
class BoundaryHistory;  // IWYU pragma: keep
template <typename Vars, typename DerivVars>
class History;
}  // namespace TimeSteppers
/// \endcond

// IWYU pragma: no_include <sys/types.h>

namespace TimeSteppers {

/// \ingroup TimeSteppersGroup
///
/// An Nth Adams-Bashforth time stepper.
class AdamsBashforthN : public TimeStepper::Inherit {
 public:
  static constexpr const size_t maximum_order = 8;

  struct TargetOrder {
    using type = size_t;
    static constexpr OptionString help = {
        "Target order of Adams-Bashforth method."};
    static type lower_bound() { return 1; }
    static type upper_bound() { return maximum_order; }
  };
  struct SelfStart {
    using type = bool;
    static constexpr OptionString help = {
        "Start at first order and increase."};
    static type default_value() { return false; }
  };
  using options = tmpl::list<TargetOrder, SelfStart>;
  static constexpr OptionString help = {
      "An Adams-Bashforth Nth order time-stepper. The target order is the\n"
      "order of the method. If a self-starting approach is chosen then the\n"
      "method starts at first order and increases the step-size until the\n"
      "desired order is reached."};

  AdamsBashforthN() = default;
  explicit AdamsBashforthN(size_t target_order, bool self_start = false,
                           const OptionContext& context = {});
  AdamsBashforthN(const AdamsBashforthN&) noexcept = default;
  AdamsBashforthN& operator=(const AdamsBashforthN&) noexcept = default;
  AdamsBashforthN(AdamsBashforthN&&) noexcept = default;
  AdamsBashforthN& operator=(AdamsBashforthN&&) noexcept = default;
  ~AdamsBashforthN() noexcept override = default;

  template <typename Vars, typename DerivVars>
  void update_u(gsl::not_null<Vars*> u,
                gsl::not_null<History<Vars, DerivVars>*> history,
                const TimeDelta& time_step) const noexcept;

  // This is defined as a separate type alias to keep the doxygen page
  // width somewhat under control.
  template <typename LocalVars, typename RemoteVars, typename Coupling>
  using BoundaryHistoryType =
      BoundaryHistory<LocalVars, RemoteVars,
                      std::result_of_t<const Coupling&(LocalVars, RemoteVars)>>;

  /*!
   * An explanation of the computation being performed by this
   * function:
   * \f$\newcommand\tL{t^L}\newcommand\tR{t^R}\newcommand\tU{\tilde{t}\!}
   * \newcommand\mat{\mathbf}\f$
   *
   * Suppose the local and remote sides of the interface are evaluated
   * at times \f$\ldots, \tL_{-1}, \tL_0, \tL_1, \ldots\f$ and
   * \f$\ldots, \tR_{-1}, \tR_0, \tR_1, \ldots\f$, respectively, with
   * the starting location of the numbering arbitrary in each case.
   * Let the step we wish to calculate the effect of be the step from
   * \f$\tL_{m_S}\f$ to \f$\tL_{m_S+1}\f$.  We call the sequence
   * produced from the union of the local and remote time sequences
   * \f$\ldots, \tU_{-1}, \tU_0, \tU_1, \ldots\f$.  For example, one
   * possible sequence of times is:
   * \f{equation}
   *   \begin{aligned}
   *     \text{Local side:} \\ \text{Union times:} \\ \text{Remote side:}
   *   \end{aligned}
   *   \cdots
   *   \begin{gathered}
   *     \, \\ \tU_1 \\ \tR_5
   *   \end{gathered}
   *   \leftarrow \Delta \tU_1 \rightarrow
   *   \begin{gathered}
   *     \tL_4 \\ \tU_2 \\ \,
   *   \end{gathered}
   *   \leftarrow \Delta \tU_2 \rightarrow
   *   \begin{gathered}
   *     \, \\ \tU_3 \\ \tR_6
   *   \end{gathered}
   *   \leftarrow \Delta \tU_3 \rightarrow
   *   \begin{gathered}
   *    \, \\ \tU_4 \\ \tR_7
   *   \end{gathered}
   *   \leftarrow \Delta \tU_4 \rightarrow
   *   \begin{gathered}
   *     \tL_5 \\ \tU_5 \\ \,
   *   \end{gathered}
   *   \cdots
   * \f}
   * We call the indices of the step's start and end times in the
   * union time sequence \f$n_S\f$ and \f$n_E\f$, respectively.  We
   * define \f$n^L_m\f$ to be the union-time index corresponding to
   * \f$\tL_m\f$ and \f$m^L_n\f$ to be the index of the last local
   * time not later than \f$\tU_n\f$ and similarly for the remote
   * side.  So for the above example, \f$n^L_4 = 2\f$ and \f$m^R_2 =
   * 5\f$, and if we wish to compute the step from \f$\tL_4\f$ to
   * \f$\tL_5\f$ we would have \f$m_S = 4\f$, \f$n_S = 2\f$, and
   * \f$n_E = 5\f$.
   *
   * If we wish to evaluate the change over this step to \f$k\f$th
   * order, we can write the change in the value as a linear
   * combination of the values of the coupling between the elements at
   * unequal times:
   * \f{equation}
   *   \mat{F}_{m_S} =
   *   \mspace{-10mu}
   *   \sum_{q^L = m_S-(k-1)}^{m_S}
   *   \,
   *   \sum_{q^R = m^R_{n_S}-(k-1)}^{m^R_{n_E-1}}
   *   \mspace{-10mu}
   *   \mat{D}_{q^Lq^R}
   *   I_{q^Lq^R},
   * \f}
   * where \f$\mat{D}_{q^Lq^R}\f$ is the coupling function evaluated
   * between data from \f$\tL_{q^L}\f$ and \f$\tR_{q^R}\f$.  The
   * coefficients can be written as the sum of three terms,
   * \f{equation}
   *   I_{q^Lq^R} = I^E_{q^Lq^R} + I^R_{q^Lq^R} + I^L_{q^Lq^R},
   * \f}
   * which can be interpreted as a contribution from equal-time
   * evaluations and contributions related to the remote and local
   * evaluation times.  These are given by
   * \f{align}
   *   I^E_{q^Lq^R} &=
   *   \mspace{-10mu}
   *   \sum_{n=n_S}^{\min\left\{n_E, n^L+k\right\}-1}
   *   \mspace{-10mu}
   *   \tilde{\alpha}_{n,n-n^L} \Delta \tU_n
   *   &&\text{if $\tL_{q^L} = \tR_{q^R}$, otherwise 0}
   *   \\
   *   I^R_{q^Lq^R} &=
   *   \ell_{q^L - m_S + k}\!\left(
   *     \tU_{n^R}; \tL_{m_S - (k-1)}, \ldots, \tL_{m_S}\right)
   *   \mspace{-10mu}
   *   \sum_{n=\max\left\{n_S, n^R\right\}}
   *       ^{\min\left\{n_E, n^R+k\right\}-1}
   *   \mspace{-10mu}
   *   \tilde{\alpha}_{n,n-n^R} \Delta \tU_n
   *   &&\text{if $\tR_{q^R}$ is not in $\{\tL_{\vphantom{|}\cdots}\}$,
   *     otherwise 0}
   *   \\
   *   I^L_{q^Lq^R} &=
   *   \mspace{-10mu}
   *   \sum_{n=\max\left\{n_S, n^R\right\}}
   *       ^{\min\left\{n_E, n^L+k, n^R_{q^R+k}\right\}-1}
   *   \mspace{-10mu}
   *   \ell_{q^R - m^R_n + k}\!\left(\tU_{n^L};
   *     \tR_{m^R_n - (k-1)}, \ldots, \tR_{m^R_n}\right)
   *   \tilde{\alpha}_{n,n-n^L} \Delta \tU_n
   *   &&\text{if $\tL_{q^L}$ is not in $\{\tR_{\vphantom{|}\cdots}\}$,
   *     otherwise 0,}
   * \f}
   * where for brevity we write \f$n^L = n^L_{q^L}\f$ and \f$n^R =
   * n^R_{q^R}\f$, and where \f$\ell_a(t; x_1, \ldots, x_k)\f$ a
   * Lagrange interpolating polynomial and \f$\tilde{\alpha}_{nj}\f$
   * is the \f$j\f$th coefficient for an Adams-Bashforth step over the
   * union times from step \f$n\f$ to step \f$n+1\f$.
   */
  template <typename LocalVars, typename RemoteVars, typename Coupling>
  std::result_of_t<const Coupling&(LocalVars, RemoteVars)>
  compute_boundary_delta(
      const Coupling& coupling,
      gsl::not_null<BoundaryHistoryType<LocalVars, RemoteVars, Coupling>*>
          history,
      const TimeDelta& time_step) const noexcept;

  uint64_t number_of_substeps() const noexcept override;

  size_t number_of_past_steps() const noexcept override;

  bool is_self_starting() const noexcept override;

  double stable_step() const noexcept override;

  TimeId next_time_id(const TimeId& current_id,
                      const TimeDelta& time_step) const noexcept override;

  template <typename Vars, typename DerivVars>
  bool can_change_step_size(
      const TimeId& time_id,
      const TimeSteppers::History<Vars, DerivVars>& history) const noexcept;

  WRAPPED_PUPable_decl_template(AdamsBashforthN);  // NOLINT

  explicit AdamsBashforthN(CkMigrateMessage* /*unused*/) noexcept {}

  // clang-tidy: do not pass by non-const reference
  void pup(PUP::er& p) noexcept override;  // NOLINT

 private:
  friend bool operator==(const AdamsBashforthN& lhs,
                         const AdamsBashforthN& rhs) noexcept;

  /// Get coefficients for a time step.  Arguments are an iterator
  /// pair to past times, oldest to newest, and the time step to take.
  template <typename Iterator>
  static std::vector<double> get_coefficients(const Iterator& times_begin,
                                              const Iterator& times_end,
                                              const TimeDelta& step) noexcept;

  static std::vector<double> get_coefficients_impl(
      const std::vector<double>& steps) noexcept;

  static std::vector<double> variable_coefficients(
      const std::vector<double>& steps) noexcept;

  static std::vector<double> constant_coefficients(size_t order) noexcept;

  /// Comparator for ordering by "simulation time"
  class SimulationLess {
   public:
    explicit SimulationLess(const bool forward_in_time) noexcept
        : forward_in_time_(forward_in_time) {}

    bool operator()(const Time& a, const Time& b) const noexcept {
      return forward_in_time_ ? a < b : b < a;
    }

   private:
    bool forward_in_time_;
  };

  size_t target_order_ = 3;
  bool is_self_starting_ = true;
};

bool operator!=(const AdamsBashforthN& lhs,
                const AdamsBashforthN& rhs) noexcept;

template <typename Vars, typename DerivVars>
void AdamsBashforthN::update_u(
    const gsl::not_null<Vars*> u,
    const gsl::not_null<History<Vars, DerivVars>*> history,
    const TimeDelta& time_step) const noexcept {
  ASSERT(is_self_starting_ or target_order_ == history->size(),
         "Length of history should be the order, so "
         << target_order_ << ", but is: " << history->size());
  ASSERT(history->size() > 0, "No history provided");
  ASSERT(history->size() <= target_order_,
         "Length of history (" << history->size() << ") "
         << "should not exceed target order (" << target_order_ << ")");

  const auto& coefficients =
      get_coefficients(history->begin(), history->end(), time_step);

  const auto do_update =
      [u, &time_step, &coefficients, &history](auto order) noexcept {
    *u += time_step.value() * constexpr_sum<order>(
        [order, &coefficients, &history](auto i) noexcept {
          return coefficients[order - 1 - i] *
              (history->begin() + static_cast<ssize_t>(i)).derivative();
        });
  };

  switch (history->size()) {
    case 1:
      do_update(std::integral_constant<size_t, 1>{});
      break;
    case 2:
      do_update(std::integral_constant<size_t, 2>{});
      break;
    case 3:
      do_update(std::integral_constant<size_t, 3>{});
      break;
    case 4:
      do_update(std::integral_constant<size_t, 4>{});
      break;
    case 5:
      do_update(std::integral_constant<size_t, 5>{});
      break;
    case 6:
      do_update(std::integral_constant<size_t, 6>{});
      break;
    case 7:
      do_update(std::integral_constant<size_t, 7>{});
      break;
    case 8:
      do_update(std::integral_constant<size_t, 8>{});
      break;
    default:
      ERROR("Bad amount of history data: " << history->size());
  }

  history->mark_unneeded(history->begin() + 1);
}

template <typename LocalVars, typename RemoteVars, typename Coupling>
std::result_of_t<const Coupling&(LocalVars, RemoteVars)>
AdamsBashforthN::compute_boundary_delta(
    const Coupling& coupling,
    const gsl::not_null<BoundaryHistoryType<LocalVars, RemoteVars, Coupling>*>
        history,
    const TimeDelta& time_step) const noexcept {
  const auto order = history->local_size();

  // Avoid billions of casts
  const auto order_s = static_cast<ssize_t>(order);

  ASSERT(is_self_starting_ or order == target_order_,
         "Local history has wrong length (" << order
         << " should be " << target_order_ << ")");
  ASSERT(order <= target_order_,
         "Local history is too long for target order (" << order
         << " should not exceed " << target_order_ << ")");
  ASSERT(history->remote_size() >= order,
         "Remote history is too short (" << history->remote_size()
         << " should be at least " << order << ")");

  // Start and end of the step we are trying to take
  const Time start_time = *(history->local_end() - 1);
  const Time end_time = start_time + time_step;

  // If a remote evaluation is done at the start of the step then that
  // is part of the history for the first union step.  When we did
  // history cleanup at the end of the previous step we didn't know we
  // were going to get this point so we kept an extra remote history
  // value.
  if (history->remote_size() > order and
      *(history->remote_begin() + order_s) == start_time) {
    history->remote_mark_unneeded(history->remote_begin() + 1);
  }

  // Result variable.  We evaluate the coupling only for the
  // structure.  This evaluation may be expensive, but by choosing the
  // most recent times on both sides we should guarantee that it is a
  // result we need later, so this will serve to get it into the
  // coupling cache so we don't have to compute it when we actually use it.
  auto accumulated_change =
      make_with_value<std::result_of_t<const Coupling&(LocalVars, RemoteVars)>>(
          history->coupling(coupling, history->local_end() - 1,
                            history->remote_end() - 1),
          0.);

  if (history->local_size() == history->remote_size() and
      std::equal(history->local_begin(), history->local_end(),
                 history->remote_begin())) {
    // No local time-stepping going on.
    const auto coefficients = get_coefficients(history->local_begin(),
                                               history->local_end(), time_step);

    auto local_it = history->local_begin();
    auto remote_it = history->remote_begin();
    for (auto coefficients_it = coefficients.rbegin();
         coefficients_it != coefficients.rend();
         ++coefficients_it, ++local_it, ++remote_it) {
      accumulated_change +=
          *coefficients_it * history->coupling(coupling, local_it, remote_it);
    }
    accumulated_change *= time_step.value();

    // The remote-side values will all be needed if the remote step is
    // larger than our local one.  If not, they will be cleaned up
    // next time.
    history->local_mark_unneeded(history->local_begin() + 1);

    return accumulated_change;
  }

  ASSERT(order == target_order_,
         "Cannot perform local time-stepping while self-starting.");

  const SimulationLess simulation_less(time_step.is_positive());

  ASSERT(std::is_sorted(history->local_begin(), history->local_end(),
                        simulation_less),
         "Local history not in order");
  ASSERT(std::is_sorted(history->remote_begin(), history->remote_end(),
                        simulation_less),
         "Remote history not in order");
  ASSERT(not simulation_less(start_time,
                             *(history->remote_begin() + (order_s - 1))),
         "Remote history does not extend far enough back");
  ASSERT(simulation_less(*(history->remote_end() - 1), end_time),
         "Please supply only older data: " << *(history->remote_end() - 1)
         << " is not before " << end_time);

  // Union of times of all step boundaries on any side.
  const auto union_times = [&end_time, &history, &simulation_less]() noexcept {
    std::vector<Time> ret;
    ret.reserve(history->local_size() + history->remote_size() + 1);
    std::set_union(history->local_begin(), history->local_end(),
                   history->remote_begin(), history->remote_end(),
                   std::back_inserter(ret), simulation_less);
    ret.push_back(end_time);
    return ret;
  }();

  using UnionIter = typename decltype(union_times)::const_iterator;

  // Find the union times iterator for a given time.
  const auto union_step =
      [&union_times, &simulation_less](const Time& t) noexcept {
    return std::lower_bound(union_times.cbegin(), union_times.cend(), t,
                            simulation_less);
  };

  // The union time index for the step start and end.
  const auto union_step_start = union_step(start_time);
  const auto union_step_end = union_times.cend() - 1;

  // min(union_step_end, it + order_s) except being careful not
  // to create out-of-range iterators.
  const auto advance_within_step =
      [order_s, union_step_end](const UnionIter& it) noexcept {
    return union_step_end - it > order_s ? it + order_s : union_step_end;
  };

  // Calculating the Adams-Bashforth coefficients is somewhat
  // expensive, so we cache them.  ab_coefs(it) returns the
  // coefficients used to step from it to *(it + 1).
  auto ab_coefs = make_cached_function<UnionIter, std::map>([order_s](
      const UnionIter& times_end) noexcept {
    return get_coefficients(times_end - (order_s - 1), times_end + 1,
                            *(times_end + 1) - *times_end);
  });

  for (auto local_evaluation_step = history->local_begin();
       local_evaluation_step != history->local_end();
       ++local_evaluation_step) {
    const auto union_local_evaluation_step = union_step(*local_evaluation_step);
    for (auto remote_evaluation_step = history->remote_begin();
         remote_evaluation_step != history->remote_end();
         ++remote_evaluation_step) {
      double deriv_coef = 0.;

      // The value of the coefficient of `evaluation_step` when doing
      // a standard Adams-Bashforth integration over the union times
      // from `step` to `step + 1`.
      const auto base_summand = [&ab_coefs](
          const UnionIter& step, const UnionIter& evaluation_step) noexcept {
        return ((step + 1)->value() - step->value()) *
               ab_coefs(step)[static_cast<size_t>(step - evaluation_step)];
      };

      if (*local_evaluation_step == *remote_evaluation_step) {
        // The two elements stepped at the same time.  This gives a
        // standard Adams-Bashforth contribution to each segment
        // making up the current step.
        const auto union_step_upper_bound =
            advance_within_step(union_local_evaluation_step);
        for (auto step = union_step_start;
             step < union_step_upper_bound;
             ++step) {
          deriv_coef += base_summand(step, union_local_evaluation_step);
        }
      } else {
        // In this block we consider a coupling evaluation that is not
        // performed at equal times on the two sides of the mortar.

        // Makes an iterator with a map to give time as a double.
        const auto make_lagrange_iterator = [](const auto& it) noexcept {
          return boost::make_transform_iterator(
              it, [](const Time& t) noexcept { return t.value(); });
        };

        const auto union_remote_evaluation_step =
            union_step(*remote_evaluation_step);
        const auto union_step_lower_bound =
            std::max(union_step_start, union_remote_evaluation_step);

        // Compute the contribution to an interpolation over the local
        // times to `remote_evaluation_step->value()`, which we will
        // use as the coupling value for that time.  If there is an
        // actual evaluation at that time then skip this because the
        // Lagrange polynomial will be zero.
        if (not std::binary_search(history->local_begin(), history->local_end(),
                                   *remote_evaluation_step, simulation_less)) {
          const auto union_step_upper_bound =
              advance_within_step(union_remote_evaluation_step);
          for (auto step = union_step_lower_bound;
               step < union_step_upper_bound;
               ++step) {
            deriv_coef += base_summand(step, union_remote_evaluation_step);
          }
          deriv_coef *= lagrange_polynomial(
              make_lagrange_iterator(local_evaluation_step),
              remote_evaluation_step->value(),
              make_lagrange_iterator(history->local_begin()),
              make_lagrange_iterator(history->local_end()));
        }

        // Same qualitative calculation as the previous block, but
        // interpolating over the remote times.  This case is somewhat
        // more complicated because the latest remote time that can be
        // used varies for the different segments making up the step.
        if (not std::binary_search(history->remote_begin(),
                                   history->remote_end(),
                                   *local_evaluation_step, simulation_less)) {
          auto union_step_upper_bound =
              advance_within_step(union_local_evaluation_step);
          if (history->remote_end() - remote_evaluation_step > order_s) {
            union_step_upper_bound = std::min(
                union_step_upper_bound,
                union_step(*(remote_evaluation_step + order_s)));
          }

          auto control_points = make_lagrange_iterator(
              remote_evaluation_step - history->remote_begin() >= order_s
                  ? remote_evaluation_step - (order_s - 1)
                  : history->remote_begin());
          for (auto step = union_step_lower_bound;
               step < union_step_upper_bound;
               ++step, ++control_points) {
            deriv_coef += base_summand(step, union_local_evaluation_step) *
                          lagrange_polynomial(
                              make_lagrange_iterator(remote_evaluation_step),
                              local_evaluation_step->value(),
                              control_points,
                              control_points + order_s);
          }
        }
      }

      if (deriv_coef != 0.) {
        // Skip the (potentially expensive) coupling calculation if
        // the coefficient is zero.
        accumulated_change +=
            deriv_coef * history->coupling(coupling, local_evaluation_step,
                                           remote_evaluation_step);
      }
    }  // for remote_evaluation_step
  }  // for local_evaluation_step

  // Clean up old history

  // We know that the local side will step at end_time, so the step
  // containing that time will be the next step, which is not
  // currently in the history.  We therefore know we won't need the
  // oldest value for the next step.
  history->local_mark_unneeded(history->local_begin() + 1);
  // We don't know whether the remote side will step at end_time, so
  // we have to be conservative and assume they will not.  If it does
  // we will remove the first value at the start of the next call to
  // this function.
  history->remote_mark_unneeded(history->remote_end() - order_s);

  return accumulated_change;
}

template <typename Vars, typename DerivVars>
bool AdamsBashforthN::can_change_step_size(
    const TimeId& time_id,
    const TimeSteppers::History<Vars, DerivVars>& history) const noexcept {
  // We need to forbid local time-stepping before initialization is
  // complete.  The self-start procedure itself should never consider
  // changing the step size, but we need to wait during the main
  // evolution until the self-start history has been replaced with
  // "real" values.
  const SimulationLess less(time_id.time_runs_forward());
  return history.size() == 0 or
         (less(history.back(), time_id.time()) and
          std::is_sorted(history.begin(), history.end(), less));
}

template <typename Iterator>
std::vector<double> AdamsBashforthN::get_coefficients(
    const Iterator& times_begin, const Iterator& times_end,
    const TimeDelta& step) noexcept {
  std::vector<double> steps;
  // This may be slightly more space than we need, but we can't get
  // the exact amount without iterating through the iterators, which
  // is not necessarily cheap depending on the iterator type.
  steps.reserve(maximum_order);
  for (auto t = times_begin; std::next(t) != times_end; ++t) {
    steps.push_back((*std::next(t) - *t) / step);
  }
  steps.push_back(1.);
  return get_coefficients_impl(steps);
}

}  // namespace TimeSteppers


/*
	This software is distributed under MIT License, which means:
		- Do whatever you want
		- Please keep this notice and include the license file to your project
		- I provide no warranty

	Created by Kyrylo Sovailo (github.com/Meta-chan, k.sovailo@gmail.com)
	Reinventing bicycles since 2020
*/

#include "../header/p6_construction.hpp"
#include "../header/p6_linear_material.hpp"
#include "../header/p6_nonlinear_material.hpp"
#include "../header/p6_file.hpp"
#include <cassert>
#include <Eigen>

class p6::Construction::Vector : public Eigen::Vector<p6::real, Eigen::Dynamic>
{
public:
	using Eigen::Vector<p6::real, Eigen::Dynamic>::Vector;
};

class p6::Construction::Matrix : public Eigen::Matrix<p6::real, Eigen::Dynamic, Eigen::Dynamic> {};

p6::uint p6::Construction::create_node() noexcept
{
	assert(!_simulation);
	Node node;
	node.freedom = 0;
	node.coord = Coord(0.0, 0.0);
	node.angle = 0.0;
	node.coord_simulated = Coord(0.0, 0.0);
	_node.push_back(node);
	return _node.size() - 1;
}

void p6::Construction::delete_node(uint node) noexcept
{
	assert(!_simulation);
	for (uint i = _stick.size() - 1; i != (uint)-1; i--)
	{
		if (_stick[i].node[0] == node || _stick[i].node[1] == node)
		{
			_stick.erase(_stick.begin() + i);
		}
		else
		{
			if (_stick[i].node[0] > node) _stick[i].node[0]--;
			if (_stick[i].node[1] > node) _stick[i].node[1]--;
		}
	}
	for (uint i = _force.size() - 1; i != (uint)-1; i--)
	{
		if (_force[i].node == node) _force.erase(_force.begin() + i);
		else if (_force[i].node > node) _force[i].node--;
	}
	_node.erase(_node.begin() + node);
}

void p6::Construction::set_node_coord(uint node, Coord coord) noexcept
{
	assert(!_simulation);
	assert(coord.x == coord.x);
	assert(abs(coord.x) != std::numeric_limits<real>::infinity());
	assert(coord.y == coord.y);
	assert(abs(coord.y) != std::numeric_limits<real>::infinity());
	_node[node].coord = coord;
}

void p6::Construction::set_node_freedom(uint node, unsigned char freedom) noexcept
{
	assert(!_simulation);
	assert(freedom <= 2);
	_node[node].freedom = freedom;
}

void p6::Construction::set_node_rail_angle(uint node, real angle) noexcept
{
	assert(!_simulation);
	assert(_node[node].freedom == 1);
	assert(angle == angle);
	assert(abs(angle) != std::numeric_limits<real>::infinity());
	_node[node].angle = angle;
}

p6::uint p6::Construction::get_node_count() const noexcept
{
	return _node.size();
}

p6::Coord p6::Construction::get_node_coord(uint node) const noexcept
{
	return _simulation ? _node[node].coord_simulated : _node[node].coord;
}

unsigned char p6::Construction::get_node_freedom(uint node) const noexcept
{
	return _node[node].freedom;
}

p6::real p6::Construction::get_node_rail_angle(uint node) const noexcept
{
	assert(_node[node].freedom == 1);
	return _node[node].angle;
}

p6::uint p6::Construction::create_stick(const uint node[2]) noexcept
{
	assert(!_simulation);
	assert(node[0] != node[1]);
	assert(node[0] < _node.size());
	assert(node[1] < _node.size());
	for (uint i = 0; i < _stick.size(); i++)
	{
		if((_stick[i].node[0] == node[0] && _stick[i].node[0] == node[1])
		&& (_stick[i].node[1] == node[1] && _stick[i].node[1] == node[1]))
			return i;
	}

	Stick stick;
	stick.node[0] = node[0];
	stick.node[1] = node[1];
	stick.material = (uint)-1;
	stick.area = 0.0;
	_stick.push_back(stick);
	return _stick.size() - 1;
}

void p6::Construction::delete_stick(uint stick) noexcept
{
	assert(!_simulation);
	_stick.erase(_stick.begin() + stick);
}

void p6::Construction::set_stick_material(uint stick, uint material) noexcept
{
	assert(!_simulation);
	_stick[stick].material = material;
}

void p6::Construction::set_stick_area(uint stick, real area) noexcept
{
	assert(!_simulation);
	assert(area == area);
	_stick[stick].area = area;
}

p6::uint p6::Construction::get_stick_count() const noexcept
{
	return _stick.size();
}

p6::uint p6::Construction::get_stick_material(uint stick) const noexcept
{
	return _stick[stick].material;
}

p6::real p6::Construction::get_stick_area(uint stick) const noexcept
{
	return _stick[stick].area;
}

void p6::Construction::get_stick_node(uint stick, uint node[2]) const noexcept
{
	node[0] = _stick[stick].node[0];
	node[1] = _stick[stick].node[1];
}

p6::real p6::Construction::get_stick_length(uint stick) const noexcept
{
	const Node *node[2] = { &_node[_stick[stick].node[0]], &_node[_stick[stick].node[1]] };
	return _simulation ?
		node[0]->coord_simulated.distance(node[1]->coord_simulated) :
		node[0]->coord.distance(node[1]->coord);
}

p6::real p6::Construction::get_stick_strain(uint stick) const noexcept
{
	assert(_simulation);
	const Node *node[2] = { &_node[_stick[stick].node[0]], &_node[_stick[stick].node[1]] };
	return (
		node[0]->coord_simulated.distance(node[1]->coord_simulated) /
		node[0]->coord.distance(node[1]->coord)
		) - 1.0;
}

p6::real p6::Construction::get_stick_force(uint stick) const noexcept
{
	assert(_simulation);
	return _stick[stick].area * _material[_stick[stick].material]->stress(get_stick_strain(stick));
}

p6::uint p6::Construction::create_force(uint node) noexcept
{
	assert(!_simulation);
	assert(node < _node.size());
	Force force;
	force.node = node;
	force.direction = Coord(0.0, 0.0);
	_force.push_back(force);
	return _force.size() - 1;
}

void p6::Construction::delete_force(uint force) noexcept
{
	assert(!_simulation);
	_force.erase(_force.begin() + force);
}

void p6::Construction::set_force_direction(uint force, Coord direction) noexcept
{
	assert(!_simulation);
	assert(direction.x == direction.x);
	assert(direction.y == direction.y);
	_force[force].direction = direction;
}

p6::uint p6::Construction::get_force_count() const noexcept
{
	return _force.size();
}

p6::Coord p6::Construction::get_force_direction(uint force) const noexcept
{
	return _force[force].direction;
}

p6::uint p6::Construction::get_force_node(uint force) const noexcept
{
	return _force[force].node;
}

p6::uint p6::Construction::create_linear_material(const String name, real modulus)
{
	assert(!_simulation);
	for (uint i = 0; i < _material.size(); i++)
	{
		if (name == _material[i]->name())
		{
			Material *material = new LinearMaterial(name, modulus);
			delete _material[i];
			_material[i] = material;
			return i;
		}
	}
	_material.push_back(new LinearMaterial(name, modulus));
	return _material.size() - 1;
}

p6::uint p6::Construction::create_nonlinear_material(const String name, const String formula)
{
	assert(!_simulation);
	for (uint i = 0; i < _material.size(); i++)
	{
		if (name == _material[i]->name())
		{
			Material *material = new NonlinearMaterial(name, formula);
			delete _material[i];
			_material[i] = material;
			return i;
		}
	}

	_material.push_back(new NonlinearMaterial(name, formula));
	return _material.size() - 1;
}

void p6::Construction::delete_material(uint material) noexcept
{
	assert(!_simulation);
	for (uint i = 0; i < _stick.size(); i++)
	{
		if (_stick[i].material == material) _stick[i].material = (uint)-1;
	}
	delete _material[material];
	_material.erase(_material.begin() + material);
}

p6::uint p6::Construction::get_material_count() const noexcept
{
	return _material.size();
}

p6::String p6::Construction::get_material_name(uint material) const noexcept
{
	return _material[material]->name();
}

p6::Material::Type p6::Construction::get_material_type(uint material) const noexcept
{
	return _material[material]->type();
}

p6::real p6::Construction::get_material_modulus(uint material) const noexcept
{
	assert(_material[material]->type() == Material::Type::linear);
	return ((LinearMaterial*)_material[material])->modulus();
}

p6::String p6::Construction::get_material_formula(uint material) const noexcept
{
	assert(_material[material]->type() == Material::Type::nonlinear);
	return ((NonlinearMaterial*)_material[material])->formula();
}

void p6::Construction::save(const String filepath) const
{
	//Open file
	OutputFile file(filepath);
	if (!file.ok()) throw std::runtime_error("File cannot be opened for write");
	Header header;
	header.node = _node.size();
	header.stick = _stick.size();
	header.force = _force.size();
	header.material = _material.size();
	file.write(&header, sizeof(Header));

	//Nodes
	for (uint i = 0; i < _node.size(); i++)
	{
		file.write(&_node[i], sizeof(StaticNode));
	}

	//Sticks
	for (uint i = 0; i < _stick.size(); i++)
	{
		file.write(&_stick[i], sizeof(Stick));
	}

	//Forces
	for (uint i = 0; i < _force.size(); i++)
	{
		file.write(&_force[i], sizeof(Force));
	}

	//Materials
	for (uint i = 0; i < _material.size(); i++)
	{
		//Name
		String name = _material[i]->name();
		uint len = name.size();
		file.write(&len, sizeof(uint));
		file.write(name.data(), len);

		//Type
		Material::Type type = _material[i]->type();
		file.write(&type, sizeof(Material::Type));

		if (type == Material::Type::linear)
		{
			//Modulus
			real modulus = ((LinearMaterial*)_material[i])->modulus();
			file.write(&modulus, sizeof(real));
		}
		else
		{
			//Formula
			String formula = ((NonlinearMaterial*)_material[i])->formula();
			len = formula.size();
			file.write(&len, sizeof(uint));
			file.write(formula.data(), len);
		}
	}
}

void p6::Construction::load(const String filepath)
{
	//Open file
	InputFile file(filepath);
	if (!file.ok()) throw std::runtime_error("File cannot be opened for read");
	Header header, sample;
	file.read(&header, sizeof(Header));
	if (memcmp(header.signature, sample.signature, 8) != 0) throw ("Invalid file format");

	//Nodes
	_node.resize(header.node);
	for (uint i = 0; i < _node.size(); i++)
	{
		file.read(&_node[i], sizeof(StaticNode));
	}

	//Sticks
	_stick.resize(header.stick);
	for (uint i = 0; i < _stick.size(); i++)
	{
		file.read(&_stick[i], sizeof(Stick));
	}

	//Forces
	_force.resize(header.force);
	for (uint i = 0; i < _force.size(); i++)
	{
		file.read(&_force[i], sizeof(Force));
	}

	//Materials
	for (uint i = 0; i < _material.size(); i++) delete _material[i];
	_material.resize(header.material);
	for (uint i = 0; i < _material.size(); i++)
	{
		//Name
		uint len;
		file.read(&len, sizeof(uint));
		String name(len, '\0');
		file.read(&name[0], len);

		//Type
		Material::Type type;
		file.read(&type, sizeof(Material::Type));

		if (type == Material::Type::linear)
		{
			//Modulus
			real modulus;
			file.read(&modulus, sizeof(real));
			_material[i] = new LinearMaterial(name, modulus);
		}
		else
		{
			//Formula
			file.read(&len, sizeof(uint));
			String formula(len, '\0');
			file.read(&formula[0], len);
			_material[i] = new NonlinearMaterial(name, formula);
		}
	}
}

void p6::Construction::import(const String filepath)
{
	//Open file
	InputFile file(filepath);
	if (!file.ok()) throw std::runtime_error("File cannot be opened for read");
	Header header, sample;
	file.read(&header, sizeof(Header));
	if (memcmp(header.signature, sample.signature, 8) != 0) throw ("Invalid file format");

	uint old_node_size = _node.size();
	uint old_stick_size = _stick.size();
	uint old_force_size = _force.size();
	uint old_material_size = _material.size();

	//Nodes
	_node.resize(old_node_size + header.node);
	for (uint i = old_node_size; i < _node.size(); i++)
	{
		file.read(&_node[i], sizeof(StaticNode));
	}

	//Sticks
	_stick.resize(old_stick_size + header.stick);
	for (uint i = old_stick_size; i < _stick.size(); i++)
	{
		file.read(&_stick[i], sizeof(Stick));
		_stick[i].node[0] += old_node_size;
		_stick[i].node[1] += old_node_size;
		_stick[i].material += old_material_size;
	}

	//Forces
	_force.resize(old_force_size + header.force);
	for (uint i = old_force_size; i < _force.size(); i++)
	{
		file.read(&_force[i], sizeof(Force));
		_force[i].node += old_node_size;
	}

	//Materials
	for (uint i = 0; i < header.material; i++)
	{
		//Name
		uint len;
		file.read(&len, sizeof(uint));
		String name(len, '\0');
		file.read(&name[0], len);

		//Find existing material
		uint existing_material = (uint)-1;
		for (uint j = 0; j < old_material_size; j++)
		{
			if (_material[j]->name() == name)
			{
				existing_material = j;
				break;
			}
		}

		//Type
		Material::Type type;
		file.read(&type, sizeof(Material::Type));

		//Creating material
		Material *new_material = nullptr;
		if (type == Material::Type::linear)
		{
			//Modulus
			real modulus;
			file.read(&modulus, sizeof(real));
			if (existing_material == (uint)-1) new_material = new LinearMaterial(name, modulus);
		}
		else
		{
			//Formula
			file.read(&len, sizeof(uint));
			String formula(len, '\0');
			file.read(&formula[0], len);
			if (existing_material == (uint)-1) new_material = new NonlinearMaterial(name, formula);
		}

		//Adding material to materials
		if (existing_material == (uint)-1)
		{
			_material.push_back(new_material);
			existing_material = _material.size() - 1;
		}

		//Correcting sticks
		for (uint i = old_stick_size; i < _stick.size(); i++)
		{
			if (_stick[i].material == old_material_size + i) _stick[i].material = existing_material;
		}
	}
}

p6::uint p6::Construction::_node_equation_fx(uint free2d) const noexcept
{
	assert(free2d < _nfree2d);
	return 2 * free2d;
}

p6::uint p6::Construction::_node_equation_fy(uint free2d) const noexcept
{
	assert(free2d < _nfree2d);
	return 2 * free2d + 1;
}

p6::uint p6::Construction::_node_equation_fr(uint free1d) const noexcept
{
	assert(free1d < _nfree1d);
	return 2 * _nfree2d + free1d;
}

p6::uint p6::Construction::_equation_number() const noexcept
{
	return 2 * _nfree2d + _nfree1d;
}

p6::uint p6::Construction::_node_variable_x(uint free2d) const noexcept
{
	assert(free2d < _nfree2d);
	return 2 * free2d;
}

p6::uint p6::Construction::_node_variable_y(uint free2d) const noexcept
{
	assert(free2d < _nfree2d);
	return 2 * free2d + 1;
}

p6::uint p6::Construction::_node_variable_r(uint free1d) const noexcept
{
	assert(free1d < _nfree1d);
	return 2 * _nfree2d + free1d;
}

p6::uint p6::Construction::_variable_number() const noexcept
{
	return 2 * _nfree2d + _nfree1d;
}

void p6::Construction::_check_materials_specified() const
{
	for (uint i = 0; i < _stick.size(); i++)
	{
		assert(_stick[i].material < _material.size() || _stick[i].material == (uint)-1);
		if (_stick[i].material == (uint)-1) throw std::runtime_error("Material is not specified");
	}
}

void p6::Construction::_create_map(std::vector<uint> *node_to_free) noexcept
{
	_nfree2d = 0;
	_nfree1d = 0;
	node_to_free->resize(_node.size(), (uint)-1);
	for (uint i = 0; i < _node.size(); i++)
	{
		if (_node[i].freedom == 1)
		{
			node_to_free->at(i) = _nfree1d;
			_nfree1d++;
		}
		else if (_node[i].freedom == 2)
		{
			node_to_free->at(i) = _nfree2d;
			_nfree2d++;
		}
	}
}

p6::real p6::Construction::_get_tolerance() const noexcept
{
	real minforce = std::numeric_limits<real>::infinity();
	for (uint i = 0; i < _force.size(); i++)
	{
		real newforce = _force[i].direction.norm();
		if (newforce < minforce) minforce = newforce;
	}
	return minforce / 1000.0;
}

void p6::Construction::_create_vectors(
	const std::vector <uint> *node_to_free,
	Vector *s,
	Vector *z,
	Vector *m,
	Matrix *d) const noexcept
{
	s->resize(_variable_number());
	for (uint i = 0; i < _node.size(); i++)
	{
		if (_node[i].freedom == 1)
		{
			uint free1d = node_to_free->at(i);
			(*s)(_node_variable_r(free1d)) = 0.0;
		}
		else if (_node[i].freedom == 2)
		{
			uint free2d = node_to_free->at(i);
			(*s)(_node_variable_x(free2d)) = _node[i].coord.x;
			(*s)(_node_variable_y(free2d)) = _node[i].coord.y;
		}
	}
	z->resize(_equation_number());
	z->setZero();
	m->resize(_equation_number());
	m->setZero();
	d->resize(_equation_number(), _variable_number());
	d->setZero();
}

void p6::Construction::_set_z_to_external_forces(
	const std::vector <uint> *node_to_free,
	Vector *z) const noexcept
{
	for (uint i = 0; i < _force.size(); i++)
	{
		uint node = _force[i].node;
		if (_node[node].freedom == 1)
		{
			uint free1d = node_to_free->at(node);
			real angle = _node[node].angle;
			(*z)(_node_variable_r(free1d)) = _force[i].direction.x * cos(angle) + _force[i].direction.y * sin(angle);
		}
		else if (_node[node].freedom == 2)
		{
			uint free2d = node_to_free->at(node);
			(*z)(_node_variable_x(free2d)) = _force[i].direction.x;
			(*z)(_node_variable_y(free2d)) = _force[i].direction.y;
		}
	}
}

void p6::Construction::_set_d_to_zero(
	const std::vector <uint> *node_to_free,
	Matrix *d) const noexcept
{
	for (uint i = 0; i < _stick.size(); i++)
	{
		const uint *node = _stick[i].node;
		for (uint j = 0; j < 2; j++)
		{
			//If current point is fixed on rail
			if (_node[node[j]].freedom == 1)
			{
				//It sets own derivatives on own coordinates
				uint free1d = node_to_free->at(node[j]);
				(*d)(_node_equation_fr(free1d), _node_variable_r(free1d)) = 0.0;

				//And own derivatives on coordinates of other point
				if (_node[node[j ^ 1]].freedom == 1)
				{
					uint other_free1d = node_to_free->at(node[j ^ 1]);
					(*d)(_node_equation_fr(free1d), _node_variable_r(other_free1d)) = 0.0;
				}
				else if (_node[node[j ^ 1]].freedom == 2)
				{
					uint other_free2d = node_to_free->at(node[j ^ 1]);
					(*d)(_node_equation_fr(free1d), _node_variable_x(other_free2d)) = 0.0;
					(*d)(_node_equation_fr(free1d), _node_variable_y(other_free2d)) = 0.0;
				}
			}
			//If current point is free
			else if (_node[node[j]].freedom == 2)
			{
				//It sets own derivatives on own coordinates
				uint free2d = node_to_free->at(node[j]);
				(*d)(_node_equation_fx(free2d), _node_variable_x(free2d)) = 0.0;
				(*d)(_node_equation_fx(free2d), _node_variable_y(free2d)) = 0.0;
				(*d)(_node_equation_fy(free2d), _node_variable_x(free2d)) = 0.0;
				(*d)(_node_equation_fy(free2d), _node_variable_y(free2d)) = 0.0;

				//And own derivatives on coordinates of other point
				if (_node[node[j ^ 1]].freedom == 1)
				{
					uint other_free1d = node_to_free->at(node[j ^ 1]);
					(*d)(_node_equation_fx(free2d), _node_variable_r(other_free1d)) = 0.0;
					(*d)(_node_equation_fy(free2d), _node_variable_r(other_free1d)) = 0.0;
				}
				else if (_node[node[j ^ 1]].freedom == 2)
				{
					uint other_free2d = node_to_free->at(node[j ^ 1]);
					(*d)(_node_equation_fx(free2d), _node_variable_x(other_free2d)) = 0.0;
					(*d)(_node_equation_fx(free2d), _node_variable_y(other_free2d)) = 0.0;
					(*d)(_node_equation_fy(free2d), _node_variable_x(other_free2d)) = 0.0;
					(*d)(_node_equation_fy(free2d), _node_variable_y(other_free2d)) = 0.0;
				}
			}
		}
	}
}

p6::Coord p6::Construction::_get_delta(
	uint stick,
	const std::vector <uint> *node_to_free,
	const Vector *s) const noexcept
{
	const uint *node = _stick[stick].node;
	Coord coord[2];
	for (uint i = 0; i < 2; i++)
	{
		if (_node[node[i]].freedom == 1)
		{
			uint free1d = node_to_free->at(node[i]);
			real angle = _node[node[i]].angle;
			coord[i] = _node[node[i]].coord + Coord(cos(angle), sin(angle)) * (*s)(_node_variable_r(free1d));
		}
		else if (_node[node[i]].freedom == 2)
		{
			uint free2d = node_to_free->at(node[i]);
			coord[i] = Coord((*s)(_node_variable_x(free2d)), (*s)(_node_variable_y(free2d)));
		}
		else coord[i] = _node[node[i]].coord;
	}
	return coord[1] - coord[0];
}

void p6::Construction::_modify_z_with_stick_force(
	uint stick,
	const std::vector <uint> *node_to_free,
	const Vector *s,
	Vector *z) const noexcept
{
	const Material *material = _material[_stick[stick].material];
	const uint *node = _stick[stick].node;
	Coord delta = _get_delta(stick, node_to_free, s);
	real length = delta.norm();
	real initial_length = _node[node[0]].coord.distance(_node[node[1]].coord);
	real force = _stick[stick].area * material->stress(length / initial_length - 1.0);

	for (uint i = 0; i < 2; i++)
	{
		if (_node[node[i]].freedom == 1)
		{
			uint free1d = node_to_free->at(node[i]);
			real sign = i == 0 ? 1.0 : -1.0;
			(*z)(_node_variable_r(free1d)) +=
				cos(_node[node[i]].angle) * sign * force * delta.x / length +
				sin(_node[node[i]].angle) * sign * force * delta.y / length;
		}
		else if (_node[node[i]].freedom == 2)
		{
			uint free2d = node_to_free->at(node[i]);
			real sign = i == 0 ? 1.0 : -1.0;
			(*z)(_node_variable_x(free2d)) += sign * force * delta.x / length;
			(*z)(_node_variable_y(free2d)) += sign * force * delta.y / length;
		}
	}
}

void p6::Construction::_modify_d_with_stick_force(
	uint stick,
	const std::vector <uint> *node_to_free,
	const Vector *s,
	Matrix *d) const noexcept
{
	const uint *node = _stick[stick].node;
	const Material *material = _material[_stick[stick].material];
	Coord delta = _get_delta(stick, node_to_free, s);
	real length = delta.norm();
	real initial_length = _node[node[0]].coord.distance(_node[node[1]].coord);
	real strain = length / initial_length - 1.0;
	real force = _stick[stick].area * material->stress(strain);

	for (uint i = 0; i < 2; i++)
	{
		Coord deltaoi = (i == 0) ? delta : delta * (-1.0);

		//If current point is fixed on rail
		if (_node[node[i]].freedom == 1)
		{
			//It sets own derivatives on own coordinates
			uint free1d = node_to_free->at(node[i]);
			real anglei = _node[node[i]].angle;
			real dl_dri = (-deltaoi.x * cos(anglei) - deltaoi.y * sin(anglei)) / length;
			real df_dri = _stick[stick].area * material->derivative(strain) * dl_dri / initial_length;
			(*d)(_node_equation_fr(free1d), _node_variable_r(free1d)) += (
				cos(anglei) * ((df_dri * deltaoi.x + force * (-cos(anglei))) * length - dl_dri * force * deltaoi.x) +
				sin(anglei) * ((df_dri * deltaoi.y + force * (-sin(anglei))) * length - dl_dri * force * deltaoi.y)
				) / sqr(length);

			//And own derivatives on coordinates of other point
			if (_node[node[i ^ 1]].freedom == 1)
			{
				uint other_free1d = node_to_free->at(node[i ^ 1]);
				real angleo = _node[node[i ^ 1]].angle;
				real dl_dro = (deltaoi.x * cos(angleo) + deltaoi.y * sin(angleo)) / length;
				real df_dro = _stick[stick].area * material->derivative(strain) * dl_dro / initial_length;
				(*d)(_node_equation_fr(free1d), _node_variable_r(other_free1d)) += (
					cos(anglei) * ((df_dro * deltaoi.x + force * cos(angleo)) * length - dl_dro * force * deltaoi.x) +
					sin(anglei) * ((df_dro * deltaoi.y + force * sin(angleo)) * length - dl_dro * force * deltaoi.y)
					) / sqr(length);
			}
			else if (_node[node[i ^ 1]].freedom == 2)
			{
				uint other_free2d = node_to_free->at(node[i ^ 1]);
				real dl_dxo = deltaoi.x / length;
				real df_dxo = _stick[stick].area * material->derivative(strain) * dl_dxo / initial_length;
				(*d)(_node_equation_fr(free1d), _node_variable_x(other_free2d)) += (
					cos(anglei) * ((df_dxo * deltaoi.x + force * 1.0) * length - dl_dxo * force * deltaoi.x) +
					sin(anglei) * deltaoi.y * (df_dxo * length - dl_dxo * force)
					) / sqr(length);
				real dl_dyo = deltaoi.y / length;
				real df_dyo = _stick[stick].area * material->derivative(strain) * dl_dyo / initial_length;
				(*d)(_node_equation_fr(free1d), _node_variable_y(other_free2d)) += (
					cos(anglei) * deltaoi.x * (df_dyo * length - dl_dyo * force) +
					sin(anglei) * ((df_dyo * deltaoi.y + force * 1.0) * length - dl_dyo * force * deltaoi.y)
					) / sqr(length);
			}
		}
		//If current point is free
		else if (_node[node[i]].freedom == 2)
		{
			//It sets own derivatives on own coordinates
			uint free2d = node_to_free->at(node[i]);

			real dl_dxi = -deltaoi.x / length;
			real df_dxi = _stick[stick].area * material->derivative(strain) * dl_dxi / initial_length;
			real dfxi_dxi = ((df_dxi * deltaoi.x + force * (-1.0)) * length - dl_dxi * force * deltaoi.x) / sqr(length);
			(*d)(_node_equation_fx(free2d), _node_variable_x(free2d)) += dfxi_dxi;
			real dl_dyi = -deltaoi.y / length;
			real df_dyi = _stick[stick].area * material->derivative(strain) * dl_dyi / initial_length;
			real dfxi_dyi = deltaoi.x * (df_dyi * length - dl_dyi * force) / sqr(length);
			(*d)(_node_equation_fx(free2d), _node_variable_y(free2d)) += dfxi_dyi;
			real dfyi_dxi = deltaoi.y * (df_dxi * length - dl_dxi * force) / sqr(length);
			(*d)(_node_equation_fy(free2d), _node_variable_x(free2d)) += dfyi_dxi;
			real dfyi_dyi = ((df_dyi * deltaoi.y + force * (-1.0)) * length - dl_dyi * force * deltaoi.y) / sqr(length);
			(*d)(_node_equation_fy(free2d), _node_variable_y(free2d)) += dfyi_dyi;

			//And own derivatives on coordinates of other point
			if (_node[node[i ^ 1]].freedom == 1)
			{
				uint other_free1d = node_to_free->at(node[i ^ 1]);
				real angleo = _node[node[i ^ 1]].angle;
				real dl_dro = (deltaoi.x * cos(angleo) + deltaoi.y * sin(angleo)) / length;
				real df_dro = _stick[stick].area * material->derivative(strain) * dl_dro / initial_length;
				(*d)(_node_equation_fx(free2d), _node_variable_r(other_free1d)) +=
					((df_dro * deltaoi.x + force * cos(angleo)) * length - dl_dro * force * deltaoi.x) / sqr(length);
				(*d)(_node_equation_fy(free2d), _node_variable_r(other_free1d)) +=
					((df_dro * deltaoi.y + force * sin(angleo)) * length - dl_dro * force * deltaoi.y) / sqr(length);
			}
			else if (_node[node[i ^ 1]].freedom == 2)
			{
				uint other_free2d = node_to_free->at(node[i ^ 1]);
				(*d)(_node_equation_fx(free2d), _node_variable_x(other_free2d)) -= dfxi_dxi;
				(*d)(_node_equation_fx(free2d), _node_variable_y(other_free2d)) -= dfxi_dyi;
				(*d)(_node_equation_fy(free2d), _node_variable_x(other_free2d)) -= dfyi_dxi;
				(*d)(_node_equation_fy(free2d), _node_variable_y(other_free2d)) -= dfyi_dyi;
			}
		}
	}
}

p6::real p6::Construction::_get_residuum(const Vector *z) const noexcept
{
	real error = 0.0;
	for (int i = 0; i < z->rows(); i++)
	{
		real newabs = abs((*z)(i));
		if (newabs > error) error = newabs;
	}
	return error;
}

void p6::Construction::_apply_state_vector(
	const std::vector<uint> *node_to_free,
	const Vector *s) noexcept
{
	for (uint i = 0; i < _node.size(); i++)
	{
		if (_node[i].freedom == 1)
		{
			uint free1d = node_to_free->at(i);
			real angle = _node[i].angle;
			_node[i].coord_simulated = _node[i].coord + Coord(cos(angle), sin(angle)) * (*s)(_node_variable_r(free1d));
		}
		else if (_node[i].freedom == 2)
		{
			uint free2d = node_to_free->at(i);
			_node[i].coord_simulated = Coord((*s)(_node_variable_x(free2d)), (*s)(_node_variable_y(free2d)));
		}
		else _node[i].coord_simulated = _node[i].coord;
	}
}

bool p6::Construction::_is_adequate(
	const Vector *m,
	const std::vector <uint> *node_to_free,
	const Vector *s) const noexcept
{
	for (int i = 0; i < m->rows(); i++)
	{
		if ((*m)(i) != (*m)(i)) return false;
		for (uint i = 0; i < _stick.size(); i++)
		{
			const uint *node = _stick[i].node;
			Coord delta = _get_delta(i, node_to_free, s);
			real length = delta.norm();
			for (uint j = 0; j < 2; j++)
			{
				if (_node[node[j]].freedom == 1)
				{
					uint free1d = node_to_free->at(node[j]);
					real modification = abs((*m)(_node_equation_fr(free1d)));
					if (modification > length * 0.01) return false;
				}
				else if (_node[node[j]].freedom == 2)
				{
					uint free2d = node_to_free->at(node[j]);
					real modification = Coord((*m)(_node_equation_fx(free2d)), (*m)(_node_equation_fy(free2d))).norm();
					if (modification > length * 0.01) return false;
				}
			}
		}
	}
	return true;
}

p6::real  p6::Construction::_get_flow_coefficient(
	const std::vector <uint> *node_to_free,
	const Vector *s,
	const Vector *z) const noexcept
{
	real coef = std::numeric_limits<real>::infinity();
	for (uint i = 0; i < _stick.size(); i++)
	{
		const uint *node = _stick[i].node;
		Coord delta = _get_delta(i, node_to_free, s);
		real length = delta.norm();
		real initial_length = _node[node[0]].coord.distance(_node[node[1]].coord);
		real strain = length / initial_length - 1.0;
		real df_dl = _stick[i].area * _material[_stick[i].material]->derivative(strain) / initial_length;
		if (1.0 / df_dl < coef) coef = 1.0 / df_dl;

		for (uint j = 0; j < 2; j++)
		{
			if (_node[node[j]].freedom == 1)
			{
				uint free1d = node_to_free->at(node[j]);
				real unbalanced_force = abs((*z)(_node_equation_fr(free1d)));
				if (length / unbalanced_force < coef) coef = length / unbalanced_force;
			}
			else if (_node[node[j]].freedom == 2)
			{
				uint free2d = node_to_free->at(node[j]);
				real unbalanced_force = Coord((*z)(_node_equation_fx(free2d)), (*z)(_node_equation_fy(free2d))).norm();
				if (length / unbalanced_force < coef) coef = length / unbalanced_force;
			}
		}
	}
	return coef;
}

void p6::Construction::simulate(bool sim)
{
	if (sim == _simulation) return;
	else if (!sim) { _simulation = false; return; }

	//Checking if materials are specified
	_check_materials_specified();

	//Creating node-to-free map
	std::vector<uint> node_to_free;
	_create_map(&node_to_free);

	//Calculating tolerance
	real tolerance = _get_tolerance();

	//Creating vectors and matrixes
	Vector s;	//State vector
	Vector z;	//Should-be-zero value
	Vector m;	//Modification of state vector
	Matrix d;	//Derivative of should-be-zero value
	_create_vectors(&node_to_free, &s, &z, &m, &d);

	//Iterating
	real last_error = 0.0;
	uint not_converge_count = 0;
	while (true)
	{
		_set_z_to_external_forces(&node_to_free, &z);
		_set_d_to_zero(&node_to_free, &d);
		for (uint i = 0; i < _stick.size(); i++)
		{
			_modify_z_with_stick_force(i, &node_to_free, &s, &z);
			_modify_d_with_stick_force(i, &node_to_free, &s, &d);
		}
		real error = _get_residuum(&z);
		if (error < tolerance) break;
		else if (error < last_error) not_converge_count = 0;
		else if (++not_converge_count == 10000) throw std::runtime_error("Simulation does not converge");
		Eigen::HouseholderQR<Eigen::Matrix<real, Eigen::Dynamic, Eigen::Dynamic>> qr(d);
		m = qr.solve(z);
		if (_is_adequate(&m, &node_to_free, &s)) s -= m;
		else s += 0.01 * _get_flow_coefficient(&node_to_free, &s, &z) * z;
	}
	_apply_state_vector(&node_to_free, &s);

	_simulation = true;
}

p6::Construction::~Construction()
{
	for (uint i = 0; i < _material.size(); i++) delete _material[i];
}


// Boost.Geometry - gis-projections (based on PROJ4)

// Copyright (c) 2008-2015 Barend Gehrels, Amsterdam, the Netherlands.

// This file was modified by Oracle on 2017, 2018, 2019.
// Modifications copyright (c) 2017-2019, Oracle and/or its affiliates.
// Contributed and/or modified by Adam Wulkiewicz, on behalf of Oracle.

// Use, modification and distribution is subject to the Boost Software License,
// Version 1.0. (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

// This file is converted from PROJ4, http://trac.osgeo.org/proj
// PROJ4 is originally written by Gerald Evenden (then of the USGS)
// PROJ4 is maintained by Frank Warmerdam
// PROJ4 is converted to Boost.Geometry by Barend Gehrels

// Last updated version of proj: 5.0.0

// Original copyright notice:

// Purpose:  Stub projection implementation for lat/long coordinates. We
//           don't actually change the coordinates, but we want proj=latlong
//           to act sort of like a projection.
// Author:   Frank Warmerdam, warmerdam@pobox.com
// Copyright (c) 2000, Frank Warmerdam

// 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 BOOST_GEOMETRY_PROJECTIONS_LATLONG_HPP
#define BOOST_GEOMETRY_PROJECTIONS_LATLONG_HPP

#include <boost/geometry/srs/projections/impl/base_static.hpp>
#include <boost/geometry/srs/projections/impl/base_dynamic.hpp>
#include <boost/geometry/srs/projections/impl/projects.hpp>
#include <boost/geometry/srs/projections/impl/factory_entry.hpp>

namespace boost { namespace geometry
{

namespace projections
{
    #ifndef DOXYGEN_NO_DETAIL
    namespace detail { namespace latlong
    {

            /* very loosely based upon DMA code by Bradford W. Drew */

            template <typename T, typename Parameters>
            struct base_latlong_other
            {
                // FORWARD(forward)
                // Project coordinates from geographic (lon, lat) to cartesian (x, y)
                inline void fwd(Parameters const& par, T const& lp_lon, T const& lp_lat, T& xy_x, T& xy_y) const
                {
                    // TODO: in the original code a is not used
                    // different mechanism is probably used instead
                    xy_x = lp_lon / par.a;
                    xy_y = lp_lat / par.a;
                }

                // INVERSE(inverse)
                // Project coordinates from cartesian (x, y) to geographic (lon, lat)
                inline void inv(Parameters const& par, T const& xy_x, T const& xy_y, T& lp_lon, T& lp_lat) const
                {
                    // TODO: in the original code a is not used
                    // different mechanism is probably used instead
                    lp_lat = xy_y * par.a;
                    lp_lon = xy_x * par.a;
                }

                static inline std::string get_name()
                {
                    return "latlong_other";
                }

            };

            // Lat/long (Geodetic)
            template <typename Parameters>
            inline void setup_latlong(Parameters& par)
            {
                    par.is_latlong = 1;
                    par.x0 = 0.0;
                    par.y0 = 0.0;
            }

    }} // namespace detail::latlong
    #endif // doxygen

    /*!
        \brief Lat/long (Geodetic) projection
        \ingroup projections
        \tparam Geographic latlong point type
        \tparam Cartesian xy point type
        \tparam Parameters parameter type
        \par Example
        \image html ex_latlong.gif
    */
    template <typename T, typename Parameters>
    struct latlong_other : public detail::latlong::base_latlong_other<T, Parameters>
    {
        template <typename Params>
        inline latlong_other(Params const& , Parameters & par)
        {
            detail::latlong::setup_latlong(par);
        }
    };

    #ifndef DOXYGEN_NO_DETAIL
    namespace detail
    {

        // Static projection
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_lonlat, latlong_other)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_latlon, latlong_other)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_latlong, latlong_other)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_STATIC_PROJECTION_FI(srs::spar::proj_longlat, latlong_other)

        // Factory entry(s)
        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_ENTRY_FI(latlong_entry, latlong_other)

        BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_BEGIN(latlong_init)
        {
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(lonlat, latlong_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(latlon, latlong_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(latlong, latlong_entry)
            BOOST_GEOMETRY_PROJECTIONS_DETAIL_FACTORY_INIT_ENTRY(longlat, latlong_entry)
        }

    } // namespace detail
    #endif // doxygen

} // namespace projections

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_PROJECTIONS_LATLONG_HPP


/*  gameplayutility %{Cpp:License:ClassName} - Yann BOUCHER (yann) 04/06/2016
**
**
**            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
**                    Version 2, December 2004
**
** Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
**
** Everyone is permitted to copy and distribute verbatim or modified
** copies of this license document, and changing it is allowed as long
** as the name is changed.
**
**            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
**   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
**
**  0. You just DO WHAT THE FUCK YOU WANT TO.
*/

#include "utils/gameplayglobals.hpp"

#include <atomic>
#include <thread>

#include <boost/optional.hpp>
#include <boost/geometry/geometry.hpp>
#include <boost/geometry/geometries/box.hpp>
#include <boost/geometry/geometries/point_xy.hpp>

#include <SFML/Audio/Sound.hpp>
#include <SFML/System/Sleep.hpp>

#include <Thor/Math/Trigonometry.hpp>
#include <Thor/Math/Random.hpp>

#include "actor.hpp"

#include "raycasting.hpp"
#include "utils/mathutility.hpp"
#include "utils/graphicsutility.hpp"
#include "map.hpp"

#include "constants.hpp"

#include <iostream>

namespace Rayfun
{

static std::atomic_size_t g_soundNumber { 0 };

boost::optional<Actor&> fireBullets(Actor &t_actor, const sf::Vector2d &t_src, const sf::Vector2d &t_dir,
                                    const sf::Vector2d &t_spread, Map& t_map, const State::Context &t_context,
                                    size_t t_damage, size_t t_num, const std::string &t_dmgType,
                                    size_t t_maxDistance)
{
    std::sort(t_map.sprites.begin(), t_map.sprites.end(), [&t_actor](std::unique_ptr<DrawableActor>& rhs,
              std::unique_ptr<DrawableActor>& lhs){
        return Utility::distance(rhs->pos, t_actor.pos) < Utility::distance(lhs->pos, t_actor.pos);
    });

    for (size_t i { 0 }; i < t_num; ++i)
    {
        sf::Vector2d unitVector = Utility::angleToVector<double>(thor::random(0, 360));
        double spreadIntensity = thor::random(0.f, 1.f);
        sf::Vector2d dev { t_spread.x * spreadIntensity, t_spread.y * spreadIntensity };
        dev = thor::cwiseProduct(unitVector, dev);

        DrawableActor* actor = nullptr;
        if (Raycasting::rayIntersectsSprite(t_src, Utility::angleToVector(dev.x + thor::polarAngle(t_dir)),
                                            t_map, actor))
        {
            if (actor && actor->ondamage)
            {
                actor->ondamage(t_actor, *actor, t_damage, t_dmgType);
            }
            //TODO: implement puffs ?
        }
        else
        {
            auto result = Raycasting::castRay(t_src, Utility::angleToVector(dev.x + thor::polarAngle(t_dir)),
                                              t_map, Raycasting::HitMode::Visibility).back();

            if (result.tileHit && result.tileHit->tex[result.side] != 0)
            {
                if (result.distance > t_maxDistance)
                {
                    return boost::none;
                }

                if (!result.tileHit->decals[result.side])
                {
                    result.tileHit->decals[result.side] = sf::Image();
                }
                auto& decal = *(result.tileHit->decals[result.side]);

                // FIXME : again find a way to get texture size by ID !

                if (true /*decal.getSize() != {64, 64 }*/)
                {
                    decal.create(64,
                            64, sf::Color::Transparent);
                }

                double wallX { 0 };
                if (result.side == Side::North || result.side == Side::South)
                {
                    wallX = result.hitPos.y - std::floor(result.hitPos.y);
                }
                else
                {
                    wallX = result.hitPos.x - std::floor(result.hitPos.x);
                }
                if (result.side == Side::South || result.side == Side::West)
                {
                    wallX = 1 - wallX;
                }
                wallX *= decal.getSize().x;
                double wallY = decal.getSize().y / 2.f + (result.distance / 2.f) * std::tan(thor::toRadian(dev.y))
                               * decal.getSize().y;
                if (result.tileHit && wallY >= 0 && wallY <= decal.getSize().y)
                {
                    Utility::addDecal(decal, t_context.resources.imageHolder["decals/bullet"],
                            sf::Vector2s(wallX, wallY), true);
                    if (result.tileHit->ondamage[result.side])
                    {
                        result.tileHit->ondamage[result.side](t_actor, *result.tileHit, t_damage, t_dmgType);
                    }
                }
            }
        }
    }

    return boost::none;
}

void playSoundAt(const Resources &t_res, const std::string &t_sound,
                 const sf::Vector2f& t_pos, float t_volume)
{
    if (g_soundNumber < c_maxSounds)
    {
        std::thread([&t_res, t_volume, t_sound, t_pos]{
            ++g_soundNumber;
            sf::Sound *sound = new sf::Sound;
            sound->setBuffer(t_res.soundHolder[t_sound]);
            sound->setVolume(t_volume);
            sound->setPosition(t_pos.x, t_pos.y, 0);
            sound->play();
            while (sound->getStatus() == sf::Sound::Playing)
            {sf::sleep(sf::seconds(0.1f));};
            delete sound;
            --g_soundNumber;
        }).detach();
    }
}

void playSoundUI(const Resources &t_res, const std::string &t_sound, float t_volume)
{
    if (g_soundNumber < c_maxSounds)
    {
        std::thread([&t_res, t_volume, t_sound]{
            ++g_soundNumber;
            sf::Sound *sound = new sf::Sound;
            sound->setBuffer(t_res.soundHolder[t_sound]);
            sound->setVolume(t_volume);
            sound->setRelativeToListener(true);
            sound->setPosition(0, 0, 0);
            sound->play();
            while (sound->getStatus() == sf::Sound::Playing)
            {sf::sleep(sf::seconds(0.1f));};
            delete sound;
            --g_soundNumber;
        }).detach();
    }
}

bool spriteClipAt(const sf::Vector2d &t_pos, Map &t_map, DrawableActor *&t_hitSprite)
{
    typedef boost::geometry::model::d2::point_xy<double> Point;
    Point origin = {t_pos.x, t_pos.y};

    for (auto& sprite : t_map.sprites)
    {
        sf::Image img = sprite->renderImage();
        boost::geometry::model::polygon<Point> spriteRect;
        double largestSide = std::max<double>(img.getSize().x, img.getSize().y);
        double side = img.getSize().x / largestSide;
        std::vector<Point> points =
        {
            {sprite->pos.x - side / 2, sprite->pos.x - side / 2},
            {sprite->pos.x - side / 2, sprite->pos.x + side / 2},
            {sprite->pos.x + side / 2, sprite->pos.x + side / 2},
            {sprite->pos.x + side / 2, sprite->pos.x - side / 2}
        };
        boost::geometry::assign_points(spriteRect, points);
        if (boost::geometry::intersects(origin, spriteRect))
        {
            if (sprite->clip)
            {
                t_hitSprite = sprite.get();
                return true;
            }
        }
    }

    return false;
}

}


/*

Copyright (c) 2005-2017, University of Oxford.
All rights reserved.

University of Oxford means the Chancellor, Masters and Scholars of the
University of Oxford, having an administrative office at Wellington
Square, Oxford OX1 2JD, UK.

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * 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.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

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.

*/

#ifndef ABSTRACTODEBASEDCELLCYCLEMODEL_HPP_
#define ABSTRACTODEBASEDCELLCYCLEMODEL_HPP_

#include <vector>

#include "ChasteSerialization.hpp"
#include "ClassIsAbstract.hpp"
#include <boost/serialization/base_object.hpp>

#include "AbstractCellCycleModel.hpp"
#include "CellCycleModelOdeHandler.hpp"
#include "SimulationTime.hpp"

/**
 * This class contains all the functionality shared by 'ODE-based' cell-cycle models,
 * where the duration of the cell cycle is evaluated 'on the fly'
 * as the cell ages, according to a system of ordinary differential equations (ODEs)
 * governing (for example) the concentrations of key intracellular proteins. To
 * determine when cell division should occur, one or more stopping conditions for
 * this ODE system may be specified.
 *
 * This class of cell-cycle models is distinct from 'simple' cell-cycle models, where
 * the duration of the cell cycle is determined when the cell-cycle model is
 * created.
 */
class AbstractOdeBasedCellCycleModel : public AbstractCellCycleModel, public CellCycleModelOdeHandler
{
private:

    /** Needed for serialization. */
    friend class boost::serialization::access;
    /**
     * Archive the cell-cycle model and member variables.
     *
     * @param archive the archive
     * @param version the current version of this class
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & boost::serialization::base_object<AbstractCellCycleModel>(*this);
        archive & boost::serialization::base_object<CellCycleModelOdeHandler>(*this);
        archive & mDivideTime;
    }

protected:

    /** The time at which the cell should divide - Set this to DBL_MAX in constructor. */
    double mDivideTime;

    /**
     * Protected copy-constructor for use by CreateCellCycleModel.
     * The only way for external code to create a copy of a cell cycle model
     * is by calling that method, to ensure that a model of the correct subclass is created.
     * This copy-constructor helps subclasses to ensure that all member variables are correctly copied when this happens.
     *
     * This method is called by child classes to set member variables for a daughter cell upon cell division.
     * Note that the parent cell cycle model will have had ResetForDivision() called just before CreateCellCycleModel() is called,
     * so performing an exact copy of the parent is suitable behaviour. Any daughter-cell-specific initialisation
     * can be done in InitialiseDaughterCell().
     *
     * @param rModel the cell cycle model to copy.
     */
    AbstractOdeBasedCellCycleModel(const AbstractOdeBasedCellCycleModel& rModel);

public:

    /**
     * Creates an AbstractOdeBasedCellCycleModel, calls SetBirthTime on the
     * AbstractPhaseBasedCellCycleModel to make sure that can be set 'back in time' for
     * cells which did not divide at the current time.
     *
     * @param lastTime  The birth time of the cell / last time model was evaluated (defaults to the current SimulationTime)
     * @param pOdeSolver An optional pointer to a cell-cycle model ODE solver object (allows the use of different ODE solvers)
     */
    AbstractOdeBasedCellCycleModel(double lastTime = SimulationTime::Instance()->GetTime(),
                                   boost::shared_ptr<AbstractCellCycleModelOdeSolver> pOdeSolver = boost::shared_ptr<AbstractCellCycleModelOdeSolver>());

    /**
     * Destructor.
     */
    virtual ~AbstractOdeBasedCellCycleModel();

    /**
     * Get the time at which the ODE stopping event occurred.
     * Only called in those subclasses for which stopping events
     * are defined.
     *
     * @return the time at which the ODE system reached its stopping event
     */
    double GetOdeStopTime();

    /**
     * This overrides the AbstractCellCycleModel::SetBirthTime(double birthTime)
     * because an ODE based cell-cycle model has more to reset...
     *
     * @param birthTime the simulation time when the cell was born
     */
    void SetBirthTime(double birthTime);

    /**
     * See AbstractCellCycleModel::ResetForDivision()
     *
     * @return whether the cell is ready to divide (enter M phase).
     */
    virtual bool ReadyToDivide();

    /**
     * For a naturally cycling model this does not need to be overridden in the
     * subclasses. But most models should override this function and then
     * call AbstractOdeBasedCellCycleModel::ResetForDivision() from inside their version.
     */
    virtual void ResetForDivision();

    /**
     * Outputs cell cycle model parameters to file.
     *
     * @param rParamsFile the file stream to which the parameters are output
     */
    virtual void OutputCellCycleModelParameters(out_stream& rParamsFile);
};

CLASS_IS_ABSTRACT(AbstractOdeBasedCellCycleModel)

#endif /*ABSTRACTODEBASDCELLCYCLEMODEL_HPP_*/


/* CirKit: A circuit toolkit
 * Copyright (C) 2009-2015  University of Bremen
 * Copyright (C) 2015-2017  EPFL
 *
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use,
 * copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following
 * conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 * OTHER DEALINGS IN THE SOFTWARE.
 */

#include "exact_synthesis.hpp"

#include <cmath>
#include <fstream>
#include <iostream>
#include <tuple>

#include <boost/assign/std/vector.hpp>
#include <boost/assign/std/set.hpp>
#include <boost/dynamic_bitset.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/format.hpp>
#include <boost/range/algorithm.hpp>
#include <boost/range/irange.hpp>
#include <boost/range/iterator_range.hpp>

#include <core/utils/timer.hpp>
#include <formal/utils/z3_utils.hpp>

#include <reversible/functions/fully_specified.hpp>
#include <reversible/functions/add_gates.hpp>
#include <reversible/functions/copy_metadata.hpp>

#include <c++/z3++.h>

namespace cirkit
{
using namespace boost::assign;

using gate_constraint_fun = std::function<void( z3::context& ctx, z3::solver& solver,
                                                circuit&, std::vector<std::vector<z3::expr>>& network,
                                                std::vector<std::vector<z3::expr> >& gate_values, const binary_truth_table&,
                                                unsigned k, unsigned n )>;

using evaluation_fun = std::function<bool( z3::solver& solver, circuit& circ,
                                           std::vector<std::vector<z3::expr>>& network, unsigned k, unsigned n )>;

using symmetry_fun = std::function<void( z3::context&, z3::solver&, std::vector<std::vector<z3::expr>>&, unsigned)>;

void gate_constraints_original( z3::context& ctx, z3::solver& solver, circuit&,
                                std::vector<std::vector<z3::expr> >& network,
                                std::vector<std::vector<z3::expr> >& gate_values,
                                const binary_truth_table& spec, unsigned k, unsigned n )
{
  using boost::str;
  using boost::format;

  auto rows = boost::distance( spec );

  auto zero = ctx.bv_val(0, n);
  auto one = ctx.bv_val(1, n);

  for ( auto i = 0u; i < k; ++i )
  {
    auto control = ctx.bv_const(str(format("control-%d") % i).c_str(), n);
    auto target = ctx.bv_const(str(format("target-%d") % i).c_str(), n);

    network += std::vector<z3::expr>{ control, target };

    solver.add( (control | (one << target)) != control );
    solver.add( z3::ule(target, ctx.bv_val(n, n)) );

    for ( auto j = 0u; j < rows; ++j )
    {
      auto hit = ctx.bool_const(str(format("hit-%d-%d") % i % j).c_str());
      solver.add( hit == ((gate_values[i][j] & control) == control) );
      solver.add(
          gate_values[i + 1][j]
          == (gate_values[i][j] ^ (ite(hit, one, zero) << target)));
    }
  }
}

void gate_constraints_negative_control( z3::context& ctx, z3::solver& solver,
                                        circuit&, std::vector<std::vector<z3::expr> >& network,
                                        std::vector<std::vector<z3::expr> >& gate_values,
                                        const binary_truth_table& spec, unsigned k, unsigned n )
{
  using boost::str;
  using boost::format;

  auto rows = boost::distance( spec );

  auto zero = ctx.bv_val(0, n);
  auto one = ctx.bv_val(1, n);
  for ( auto i = 0u; i < k; ++i )
  {
    auto control = ctx.bv_const(str(format("control-%d") % i).c_str(), n);
    auto target = ctx.bv_const(str(format("target-%d") % i).c_str(), n);
    auto polarity = ctx.bv_const(str(format("polarity-%d") % i).c_str(), n);

    network += std::vector<z3::expr>{ control, target, polarity };

    solver.add( (control | (one << target)) != control );
    solver.add( z3::ule(target, ctx.bv_val(n, n)) );

    for ( auto j = 0u; j < rows; ++j )
    {
      auto hit = ctx.bool_const(str(format("hit-%d-%d") % i % j).c_str());
      solver.add(
          hit == (((gate_values[i][j] ^ polarity) & control) == control));
      solver.add(
          gate_values[i + 1][j]
          == (gate_values[i][j] ^ (ite(hit, one, zero) << target)));
    }
  }
}

void gate_constraints_negative_control_multiple_target( z3::context& ctx,
                                                        z3::solver& solver, circuit&, std::vector<std::vector<z3::expr> >& gates,
                                                        std::vector<std::vector<z3::expr> >& line_values,
                                                        const binary_truth_table& spec, unsigned k, unsigned n )
{

  using boost::str;
  using boost::format;

  auto rows = boost::distance( spec );

  auto zero = ctx.bv_val(0, n);
  auto one = ctx.bv_val(1, n);
  for ( auto i = 0u; i < k; ++i )
  {
    auto control = ctx.bv_const(str(format("control-%d") % i).c_str(), n);
    auto target = ctx.bv_const(str(format("target-%d") % i).c_str(), n);
    auto polarity = ctx.bv_const(str(format("polarity-%d") % i).c_str(), n);

    gates += std::vector<z3::expr>{ control, target, polarity };

    solver.add((control & target) == zero);

    for ( auto j = 0u; j < rows; ++j )
    {
      auto hit = ctx.bool_const(str(format("hit-%d-%d") % i % j).c_str());
      solver.add(
          hit == (((line_values[i][j] ^ polarity) & control) == control));
      solver.add(
          line_values[i + 1][j]
          == ite(hit, line_values[i][j] ^ target, line_values[i][j]));
    }
  }
}

void gate_constraints_multiple_target( z3::context& ctx, z3::solver& solver,
                                       circuit&, std::vector<std::vector<z3::expr> >& network,
                                       std::vector<std::vector<z3::expr> >& gate_values,
                                       const binary_truth_table& spec, unsigned k, unsigned n )
{
  using boost::str;
  using boost::format;

  auto rows = boost::distance( spec );

  auto zero = ctx.bv_val(0, n);
  auto one = ctx.bv_val(1, n);

  for ( auto i = 0u; i < k; ++i )
  {
    auto control = ctx.bv_const(str(format("control-%d") % i).c_str(), n);
    auto target = ctx.bv_const(str(format("target-%d") % i).c_str(), n);

    network += std::vector<z3::expr>{ control, target };

    solver.add((control & target) == zero);

    for ( auto j = 0u; j < rows; ++j )
    {
      auto hit = ctx.bool_const(str(format("hit-%d-%d") % i % j).c_str());
      solver.add(hit == ((gate_values[i][j] & control) == control));
      solver.add(
          gate_values[i + 1][j]
          == ite(hit, gate_values[i][j] ^ target, gate_values[i][j]));
    }
  }
}

bool eval_original(z3::solver& solver, circuit& circ,
    std::vector<std::vector<z3::expr>>& network, unsigned k, unsigned n)
{
  if (solver.check() == z3::sat)
  {
    z3::model m = solver.get_model();
    for (unsigned i : boost::irange(0u, k))
    {
      auto eval_control = to_bitset(m.eval(network[i][0]));
      auto eval_target = to_bitset(m.eval(network[i][1])).to_ulong();

      gate::control_container controls;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_control.test(j))
        {
          controls += make_var(j);
        }
      }

      append_toffoli(circ, controls, eval_target);
    }
    return true;
  } else
  {
    return false;
  }
}

bool eval_multiple_target(z3::solver& solver, circuit& circ,
    std::vector<std::vector<z3::expr>>& gates, unsigned k, unsigned n)
{
  if (solver.check() == z3::sat)
  {
    z3::model m = solver.get_model();

    for (unsigned i : boost::irange(0u, k))
    {
      auto eval_control = to_bitset(m.eval(gates[i][0]));
      auto eval_target = to_bitset(m.eval(gates[i][1]));

      gate::control_container controls;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_control.test(j))
        {
          controls += make_var(j);
        }
      }

      gate::target_container targets;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_target.test(j))
        {
          targets += j;
        }
      }

      for ( const auto& target : targets )
      {
        append_toffoli(circ, controls, target);
      }
    }

    return true;
  } else
  {
    return false;
  }
}

bool eval_negative_control(z3::solver& solver, circuit& circ,
    std::vector<std::vector<z3::expr>>& gates, unsigned k, unsigned n)
{
  if (solver.check() == z3::sat)
  {
    z3::model m = solver.get_model();

    for (unsigned i : boost::irange(0u, k))
    {
      auto eval_control = to_bitset(m.eval(gates[i][0]));
      auto eval_target = to_bitset(m.eval(gates[i][1])).to_ulong();
      auto eval_polarity = to_bitset(m.eval(gates[i][2]));

      gate::control_container controls;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_control.test(j))
        {
          controls += make_var(j, !eval_polarity.test(j));
        }
      }

      append_toffoli(circ, controls, eval_target);
    }

    return true;
  } else
  {
    return false;
  }

}

bool eval_negative_control_multiple_target(z3::solver& solver, circuit& circ,
    std::vector<std::vector<z3::expr>>& gates, unsigned k, unsigned n)
{
  if (solver.check() == z3::sat)
  {
    z3::model m = solver.get_model();

    for (unsigned i : boost::irange(0u, k))
    {
      auto eval_control = to_bitset(m.eval(gates[i][0]));
      auto eval_polarity = to_bitset(m.eval(gates[i][2]));
      auto eval_target = to_bitset(m.eval(gates[i][1]));

//      std::cout << "control: " << eval_control << "\n";
//      std::cout << "polarity: " << eval_polarity << "\n";
//      std::cout << "target: " << eval_target << "\n";

      gate::control_container controls;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_control.test(j))
        {
          controls += make_var(j, !eval_polarity.test(j));
        }
      }

      gate::target_container targets;
      for (unsigned j : boost::irange(0u, n))
      {
        if (eval_target.test(j))
        {
//          std::cout <<"j: " <<  j << "\n";
          targets += j;
        }
      }

      for ( const auto& target : targets )
      {
        append_toffoli(circ, controls, target);
      }
    }

    return true;
  } else
  {
    return false;
  }
}

void input_output_constraints(z3::context& ctx, z3::solver solver,
    const binary_truth_table& spec,
    std::vector<std::vector<z3::expr> >& gate_values, unsigned k, unsigned n)
{
// Constraints for inputs and outputs. This is
  for (binary_truth_table::const_iterator iter = spec.begin();
      iter != spec.end(); ++iter)
  {
    boost::dynamic_bitset<> in(n);
    boost::dynamic_bitset<> mask(n);
    boost::dynamic_bitset<> out(n);

    unsigned bitpos = 0;
    for (const auto& bit : boost::make_iterator_range(iter->first))
    {
      in.set(bitpos++, *bit);
    }

    bitpos = 0;
    for (const auto& bit : boost::make_iterator_range(iter->second))
    {
      mask.set(bitpos, (bool)bit);
      out.set(bitpos++, (bool)bit && *bit);
    }
    unsigned pos = std::distance<binary_truth_table::const_iterator>(
        spec.begin(), iter);
    solver.add(gate_values[0][pos] == ctx.bv_val((__uint64) in.to_ulong(), n));
    solver.add(
        (gate_values[k][pos] & ctx.bv_val((__uint64) mask.to_ulong(), n))
            == ctx.bv_val((__uint64) out.to_ulong(), n));
  }
}

/******************************************************************************
 * symmetry breaking                                                          *
 ******************************************************************************/

void symmetry_breaking_no_double_gate( z3::context& ctx, z3::solver& solver, const std::vector<std::vector<z3::expr>>& network, unsigned n )
{
  if ( network.size() >= 2 )
  {
    for ( auto i = 0u; i < network.size() - 1; ++i )
    {
      solver.add( !( ( network[i][0] == network[i + 1][0] ) && ( network[i][1] == network[i + 1][1] ) ) );
    }
  }
}

void symmetry_breaking_colexicographic( z3::context& ctx, z3::solver& solver, const std::vector<std::vector<z3::expr>>& network, unsigned n )
{
  const auto one = ctx.bv_val( 1u, n );
  const auto zero = ctx.bv_val( 0u, n );

  if ( network.size() >= 2 )
  {
    for ( auto i = 0u; i < network.size() - 1; ++i )
    {
      const z3::expr move1 = ( ( one << network[i][0] ) & network[i + 1][1] ) == zero;
      const z3::expr move2 = ( network[i][1] & ( one << network[i + 1][0] ) ) == zero;

      const z3::expr cless = z3::ule( network[i][0], network[i + 1][0] );
      const z3::expr ceq   = ( network[i][0] == network[i + 1][0] ) && ( z3::ule( network[i][1], network[i + 1][1] ) );

      solver.add( implies( move1 && move2, cless || ceq ) );
    }
  }
}

void symmetry_breaking_one_not_per_line( z3::context& ctx, z3::solver& solver, const std::vector<std::vector<z3::expr>>& network, unsigned n )
{
  const auto zero = ctx.bv_val( 0u, n );

  for ( auto i = 0u; i < network.size(); ++i )
  {
    for ( auto j = i + 1; j < network.size(); ++j )
    {
      /* if some gate i has no controls (i.e., NOT), then there can't be another on that line */
      solver.add( implies( network[i][0] == zero && network[i][1] == network[j][1], network[j][0] != zero ) );
    }
  }
}

bool synth_len(circuit& circ, const binary_truth_table& spec,
               gate_constraint_fun gate_constraints,
               evaluation_fun eval, const std::vector<symmetry_fun>& symmetry_breaking, bool only_toffoli, unsigned gate_count)
{
  using boost::str;
  using boost::format;

  unsigned n = spec.num_inputs();
  unsigned rows = std::distance(spec.begin(), spec.end());

  z3::context ctx;
  z3::solver solver(ctx);

  // Instead of having vector<pair<exp,exp>> we use vector<vector<exp>>. This
  // allows for more flexibility. The inner vector should have a length of
  // 2 or 3.
  // In case of len 2, the first entry contains the controls, the second the targets
  // In case of len 3, the last entry contains the polarity of the controls
  std::vector<std::vector<z3::expr> > network;

  // this vector of vectores stores, for every input pattern (outer vector index), the
  // line values after the k'th gate (inner vector index)
  std::vector<std::vector<z3::expr> > gate_values;

  // initialize the gate values
  for (int i : boost::irange(0u, gate_count + 1u))
  {
    gate_values += std::vector<z3::expr>();
    for (int j : boost::irange(0u, rows))
    {
      gate_values[i] += ctx.bv_const(
          str(format("gate-value-%d-%d") % i % j).c_str(), n);
    }
  }

  input_output_constraints(ctx, solver, spec, gate_values, gate_count, n);

  gate_constraints(ctx, solver, circ, network, gate_values, spec, gate_count,
      n);

  for ( const auto& f : symmetry_breaking )
  {
    f( ctx, solver, network, n );
  }

  if ( only_toffoli )
  {
    for ( auto i = 0u; i < gate_count; ++i )
    {
      auto constr = ( network[i][0] == ctx.bv_val( 0u, n ) );
      for ( auto j = 0u; j < n; ++j )
      {
        auto mask = 1 << j;
        constr = constr || ( network[i][0] == ctx.bv_val( mask, n ) );

        for ( auto k = j + 1; k < n; ++k )
        {
          constr = constr || ( network[i][0] == ctx.bv_val( mask | ( 1 << k ), n ) );
        }
      }
      solver.add( constr );
    }
  }

  return eval(solver, circ, network, gate_count, n);
}

/******************************************************************************
 * public functions                                                           *
 ******************************************************************************/

bool exact_synthesis(circuit& circ, const binary_truth_table& spec,
    properties::ptr settings, properties::ptr statistics)
{
  const auto start_depth  = get( settings, "start_depth",  0u );
  const auto max_depth    = get( settings, "max_depth",    20u );
  const auto negative     = get( settings, "negative",     false );
  const auto multiple     = get( settings, "multiple",     false );
  const auto only_toffoli = get( settings, "only_toffoli", false );
  const auto verbose      = get( settings, "verbose",      false );

  properties_timer t( statistics );

  circ.set_lines(spec.num_inputs());

  unsigned gate_count = start_depth;
  bool result = false;

  gate_constraint_fun gate_constraints = &gate_constraints_original;
  evaluation_fun eval = &eval_original;
  std::vector<symmetry_fun> symmetry_breaking;

  symmetry_breaking.push_back( symmetry_breaking_no_double_gate );
  if (negative && multiple)
  {
    gate_constraints = &gate_constraints_negative_control_multiple_target;
    eval = &eval_negative_control_multiple_target;
    symmetry_breaking.push_back( symmetry_breaking_one_not_per_line );
  }
  else
  {
    //symmetry_breaking.push_back( symmetry_breaking_colexicographic );
    if (negative)
    {
      gate_constraints = &gate_constraints_negative_control;
      eval = &eval_negative_control;
      symmetry_breaking.push_back( symmetry_breaking_one_not_per_line );
    }
    if (multiple)
    {
      gate_constraints = &gate_constraints_multiple_target;
      eval = &eval_multiple_target;
    }
  }

  do
  {
    if ( verbose )
    {
      std::cout << "[i] check for depth " << gate_count << std::endl;
    }
    result = synth_len( circ, spec, gate_constraints, eval, symmetry_breaking, only_toffoli, gate_count );
  } while (!result && gate_count++ < max_depth);

  if (result)
  {
    copy_metadata(spec, circ);
  } else
  {
    set_error_message(statistics,
        "Could not find a circuit within the predefined depth.");
  }

  return result;
}

truth_table_synthesis_func exact_synthesis_func(properties::ptr settings,
    properties::ptr statistics)
{
  truth_table_synthesis_func f =
      [&settings, &statistics]( circuit& circ, const binary_truth_table& spec )
      {
        return exact_synthesis( circ, spec, settings, statistics );
      };
  f.init(settings, statistics);
  return f;
}

}

// Local Variables:
// c-basic-offset: 2
// eval: (c-set-offset 'substatement-open 0)
// eval: (c-set-offset 'innamespace 0)
// End: