#ifndef BOXES_DOMAIN_HPP
#define BOXES_DOMAIN_HPP

/**************************************************************************
 * A wrapper for a disjunctive domain of intervals based on "Boxes: A
 * Symbolic Abstract Domain of Boxes" by A. Gurfinkel and S. Chaki
 * published in SAS'10.
 **************************************************************************/

#include <crab/config.h>
#include <crab/common/debug.hpp>
#include <crab/common/stats.hpp>
#include <crab/common/types.hpp>
#include <crab/domains/operators_api.hpp>

using namespace boost;
using namespace ikos;

#ifndef HAVE_LDD
/*
 * Dummy implementation if ldd not found
 */
#define LDD_NOT_FOUND "No LDD. Run cmake with -DUSE_LDD=ON"
namespace crab {
   namespace domains {
      template<typename Number, typename VariableName, size_t LddSize = 100>
      class boxes_domain:
         public ikos::writeable,
         public numerical_domain< Number, VariableName>,
         public bitwise_operators< Number, VariableName >,
         public division_operators< Number, VariableName >,
         public array_operators< Number, VariableName>,
         public pointer_operators< Number, VariableName> {

       public:
        using typename numerical_domain< Number, VariableName>::linear_expression_t;
        using typename numerical_domain< Number, VariableName>::linear_constraint_t;
        using typename numerical_domain< Number, VariableName>::linear_constraint_system_t;
	typedef disjunctive_linear_constraint_system<Number, VariableName>
	disjunctive_linear_constraint_system_t;
        using typename numerical_domain< Number, VariableName>::variable_t;
        using typename numerical_domain< Number, VariableName>::number_t;
        using typename numerical_domain< Number, VariableName>::varname_t;
        typedef boxes_domain <Number, VariableName, LddSize> boxes_domain_t;
        typedef interval <Number> interval_t;

        boxes_domain(): ikos::writeable() { }

        static boxes_domain_t top() { CRAB_ERROR (LDD_NOT_FOUND); }

        static boxes_domain_t bottom() { CRAB_ERROR (LDD_NOT_FOUND); }

        boxes_domain (const boxes_domain_t& other):
            ikos::writeable() { }

        bool is_bottom() { CRAB_ERROR (LDD_NOT_FOUND); }

        bool is_top() { CRAB_ERROR (LDD_NOT_FOUND); }

        bool operator<=(boxes_domain_t other) { CRAB_ERROR (LDD_NOT_FOUND); }

        void operator|=(boxes_domain_t other)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        boxes_domain_t operator|(boxes_domain_t other)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        boxes_domain_t operator&(boxes_domain_t other)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        boxes_domain_t operator||(boxes_domain_t other)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        template<typename Thresholds>
        boxes_domain_t widening_thresholds (boxes_domain_t other,
                                            const Thresholds &ts)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        boxes_domain_t operator&& (boxes_domain_t other)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void operator-=(VariableName var)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        interval_t operator[](VariableName v)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void set(VariableName v, interval_t ival)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void operator += (linear_constraint_system_t csts)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void assign (VariableName x, linear_expression_t e)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply (operation_t op, VariableName x, VariableName y, Number z)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(operation_t op, VariableName x, VariableName y, VariableName z)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(operation_t op, VariableName x, Number k)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(conv_operation_t op, VariableName x, VariableName y, unsigned width)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(conv_operation_t op, VariableName x, Number k, unsigned width)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(bitwise_operation_t op, VariableName x, VariableName y, VariableName z)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(bitwise_operation_t op, VariableName x, VariableName y, Number k)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(div_operation_t op, VariableName x, VariableName y, VariableName z)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void apply(div_operation_t op, VariableName x, VariableName y, Number k)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        linear_constraint_system_t to_linear_constraint_system ()
        { CRAB_ERROR (LDD_NOT_FOUND); }

        disjunctive_linear_constraint_system_t
	to_disjunctive_linear_constraint_system ()
        { CRAB_ERROR (LDD_NOT_FOUND); }

        void write(crab_os& o)
        { CRAB_ERROR (LDD_NOT_FOUND); }

        static std::string getDomainName () {
          return "Dummy Boxes";
        }
      };

   } // namespace domains
}// namespace crab

#else
/*
 *  Real implementation starts here
 */

#include <crab/domains/ldd/ldd.hpp>
#include <crab/domains/ldd/ldd_print.hpp>
#include <crab/domains/domain_traits.hpp>
#include <boost/bimap.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/range/algorithm/set_algorithm.hpp>

namespace crab {

   namespace domains {

      using namespace crab::domains::ldd;

      /*
       * The wrapper has two global datastructures:
       * 1) a ldd manager and
       * 2) a map from VariableName to ldd dimension.
       *
       * FIXME: since the ldd manager is shared we need to fix a
       * single size for all ldds. Since ldds are sparse we can fix a
       * size big enough for our programs.
       */
      template<typename Number, typename VariableName, size_t LddSize = 3000>
      class boxes_domain:
         public ikos::writeable,
         public numerical_domain< Number, VariableName>,
         public bitwise_operators< Number, VariableName >,
         public division_operators< Number, VariableName >,
         public array_operators< Number, VariableName>,
         public pointer_operators< Number, VariableName> {

        typedef interval_domain <Number, VariableName> interval_domain_t;
       public:
        using typename numerical_domain< Number, VariableName>::linear_expression_t;
        using typename numerical_domain< Number, VariableName>::linear_constraint_t;
        using typename numerical_domain< Number, VariableName>::linear_constraint_system_t;
	typedef disjunctive_linear_constraint_system<Number, VariableName>
	disjunctive_linear_constraint_system_t;
        using typename numerical_domain< Number, VariableName>::variable_t;
        using typename numerical_domain< Number, VariableName>::number_t;
        using typename numerical_domain< Number, VariableName>::varname_t;

        typedef boxes_domain <Number, VariableName, LddSize> boxes_domain_t;
        typedef interval <Number> interval_t;

       private:
        // --- map from crab variable index to ldd term index
        typedef boost::bimap< VariableName , int > var_bimap_t;
        typedef boost::shared_ptr<var_bimap_t> var_map_ptr;
        typedef typename var_bimap_t::value_type binding_t;

        LddNodePtr m_ldd;
        static LddManager* m_ldd_man;
        static var_map_ptr m_var_map;

        static LddManager* get_ldd_man () {
          if (!m_ldd_man) {
            DdManager* cudd = Cudd_Init (0, 0, CUDD_UNIQUE_SLOTS, 127, 0);
            theory_t* theory = tvpi_create_boxz_theory (LddSize);
            CRAB_LOG ("boxes",crab::outs() << "Created a ldd of size " << LddSize <<"\n";);
            m_ldd_man = Ldd_Init (cudd, theory);
            Cudd_AutodynEnable (cudd, CUDD_REORDER_GROUP_SIFT);
          }
          return m_ldd_man;
        }

        static int num_of_vars () {
          if (!m_var_map) return 0;

          return (*m_var_map).left.size ();
        }

        static var_map_ptr get_var_map () {
          if (!m_var_map) {
            m_var_map = var_map_ptr (new var_bimap_t ());
          }
          return m_var_map;
        }

        int get_var_dim (VariableName v) const {
          auto it = get_var_map()->left.find (v);
          if (it != get_var_map()->left.end ()) {
            return it->second;
          } else {
            int id = get_var_map ()->size ();
            if (id >= LddSize) {
              CRAB_ERROR ("The Ldd size of ", LddSize, " needs to be larger");
            }
            get_var_map ()->insert (binding_t (v, id));
            return id;
          }
        }

        inline constant_t mkCst (Number k) {
          mpq_class kk ((mpz_class) k);
          return (constant_t) tvpi_create_cst (kk.get_mpq_t ());
        }

        // convex approximation
        LddNodePtr convex_approx () const {
          return lddPtr (get_ldd_man(), Ldd_TermMinmaxApprox(get_ldd_man(), &*m_ldd));
        }

        // pre: ldd is not either true or false
        void project (LddNodePtr& ldd, VariableName v) const {
          for (auto p: (*m_var_map).left) {
            if (!(p.first == v)) {
              int id = get_var_dim (p.first);
              ldd = lddPtr (get_ldd_man(),
                            Ldd_ExistsAbstract (get_ldd_man(), &*ldd, id));

            }
          }
        }

        // non-relational approximation (but still non-convex)
        // expensive operation
        LddNodePtr non_relational_approx () const {
          if (is_top () || is_bottom ())
            return m_ldd;

          LddNodePtr res = lddPtr (get_ldd_man(), Ldd_GetTrue (get_ldd_man()));
          for (auto p: (*m_var_map).left) {
            LddNodePtr tmp (m_ldd);
            project (tmp, p.first);
            res = lddPtr (get_ldd_man(), Ldd_And (get_ldd_man(), &*res, &*tmp));
          }
          return res;
        }

        LddNodePtr join (LddNodePtr v1, LddNodePtr v2) {
          return lddPtr (get_ldd_man(), Ldd_Or (get_ldd_man(), &*v1, &*v2));
        }

        /** return term for variable v, neg for negation of variable */
        linterm_t termForVal(VariableName v, bool neg = false)
        {
          int varId = get_var_dim (v);
          int sgn = neg ? -1 : 1;
          linterm_t term =
              Ldd_GetTheory (get_ldd_man())->create_linterm_sparse_si (&varId, &sgn, 1);
          return term;
        }

        /** v := a * x + k, where a, k are constants and x variable */
        void apply (VariableName v, VariableName x, Number a, Number k) {
          if (is_top () || is_bottom ()) return ;

          linterm_t t = termForVal (v);
          linterm_t r = termForVal (x);

          constant_t c1 = mkCst (a);
          constant_t c2 = mkCst (k);
          // FIXME: Ldd_TermReplace seems to leak memory
          m_ldd = lddPtr(get_ldd_man(),
                         Ldd_TermReplace(get_ldd_man(), &(*m_ldd), t, r, c1, c2, c2));

          Ldd_GetTheory (get_ldd_man())->destroy_term(t);
          Ldd_GetTheory (get_ldd_man())->destroy_term(r);
          Ldd_GetTheory (get_ldd_man())->destroy_cst(c1);
          Ldd_GetTheory (get_ldd_man())->destroy_cst(c2);
        }

        Number numFromLddCst (constant_t cst, theory_t *theory) {
          mpq_class v;
          // XXX We know that the theory is tvpi, use its method direclty.
          tvpi_cst_set_mpq (v.get_mpq_t (), (tvpi_cst_t) cst);
          // FIXME: it's assuming that Number has a constructor that
          // takes mpz_class
          mpz_class n = static_cast<mpz_class> (v);
          return Number (n);
        }

        linear_expression_t exprFromLddTerm (linterm_t term, theory_t *theory) {
          linear_expression_t e (0);
          for(size_t i = 0;i < theory->term_size (term); i++) {
            Number k = numFromLddCst (theory->term_get_coeff (term,i), theory);
            VariableName v =  getVarName (theory->term_get_var(term,i));
            e = e + (k * linear_expression_t (v));
          }
          return e;
        }

        linear_constraint_t cstFromLddCons(lincons_t lincons, theory_t *theory) {

          linear_expression_t lhs = exprFromLddTerm(theory->get_term(lincons), theory);
          Number rhs = numFromLddCst(theory->get_constant(lincons), theory);

          if (theory->is_strict (lincons)) {
            // lhs < rhs <-> lhs+1 <= rhs but only for integers!
            linear_expression_t e = lhs +1;
            e = e - rhs;
            return linear_constraint_t(e, linear_constraint_t::INEQUALITY);
          }
          else  {
            // lhs <= rhs
            linear_expression_t e = lhs - rhs;
            return linear_constraint_t(e, linear_constraint_t::INEQUALITY);
          }
        }

        // Pre: n is convex and cannot be bottom
        void toLinCstSysRecur(LddNode* n,
                              linear_constraint_system_t& csts) {

          LddNode *N = Ldd_Regular (n);
          if (N == Ldd_GetTrue (get_ldd_man ())) return;

          auto cst = cstFromLddCons (Ldd_GetCons(get_ldd_man (), N),
                                     Ldd_GetTheory (get_ldd_man ()));

          if (Ldd_Regular (Ldd_T(N)) == Ldd_GetTrue (get_ldd_man ()) &&
              Ldd_Regular (Ldd_E(N)) == Ldd_GetTrue (get_ldd_man ()))
            csts += cst;
          else if (Ldd_Regular (Ldd_T(N)) == Ldd_GetTrue (get_ldd_man ()))
            csts += cst.negate ();
          else
            csts += cst;

          toLinCstSysRecur (Ldd_T (N), csts);
          toLinCstSysRecur (Ldd_E (N), csts);

        }

        // Restricts the ldd by the constraint e
        // where e can be one of
        //    c*x <= k | c*x == k | c*x != k
        //    and c is 1 or -1 and k is an integer constant
        typedef typename linear_constraint_t::kind_t kind_t;

        void add_unit_constraint (Number coef, variable_t x, kind_t kind, Number k) {

          assert (coef == 1 || coef == -1);

          auto theory = Ldd_GetTheory (get_ldd_man());
          if (kind == kind_t::EQUALITY) {  // x == k <-> x<=k and !(x<k)
            // x<=k
            linterm_t term1 = termForVal (x.name (), (coef == 1 ? false : true));
            constant_t c1 = mkCst (k);
            lincons_t cons1 = theory->create_cons (term1, 0 /*non-strict*/, c1);
            LddNodePtr n1 = lddPtr (get_ldd_man(), theory->to_ldd (get_ldd_man(), cons1));
            theory->destroy_lincons (cons1);
            m_ldd = lddPtr (get_ldd_man(), Ldd_And (get_ldd_man(), &*m_ldd, &*n1));
            // !(x<k)
            linterm_t term2 = termForVal (x.name (), (coef == 1 ? false : true));
            constant_t c2 = mkCst (k);
            lincons_t cons2 = theory->create_cons (term2, 1 /*strict*/, c2);
            LddNodePtr n2 = lddPtr (get_ldd_man(), Ldd_Not (theory->to_ldd (get_ldd_man(), cons2)));
            theory->destroy_lincons (cons2);
            m_ldd = lddPtr (get_ldd_man(), Ldd_And (get_ldd_man(), &*m_ldd, &*n2));
          }
          else if (kind == kind_t::INEQUALITY) {
            // case 1:  x <= k
            // case 2: -x <= k
            linterm_t term = termForVal (x.name (), (coef == 1 ? false : true));
            constant_t c = mkCst (k);
            lincons_t cons = theory->create_cons (term, 0 /*non-strict*/, c);
            LddNodePtr n = lddPtr (get_ldd_man(), theory->to_ldd (get_ldd_man(), cons));
            theory->destroy_lincons (cons);
            m_ldd = lddPtr (get_ldd_man(), Ldd_And (get_ldd_man(), &*m_ldd, &*n));
          }
          else { // assert (kind == kind_t::DISEQUALITY)
            // case 1:  x != k  <->  x < k OR  x > k <->  x < k OR -x < -k
            // case 2: -x != k  <-> -x < k OR -x > k <-> -x < k OR  x < -k
            linterm_t term1 = termForVal (x.name (), (coef == 1 ? false : true));
            constant_t c1 = mkCst (k);
            lincons_t cons1 = theory->create_cons (term1, 1 /*strict*/, c1);
            LddNodePtr n1 = lddPtr (get_ldd_man(), theory->to_ldd (get_ldd_man(), cons1));
            theory->destroy_lincons (cons1);

            linterm_t term2 = termForVal (x.name (), (coef == 1 ? true : false));
            constant_t c2 = mkCst (-k);
            lincons_t cons2 = theory->create_cons (term2, 1 /*strict*/, c2);
            LddNodePtr n2 = lddPtr (get_ldd_man(), theory->to_ldd (get_ldd_man(), cons2));
            theory->destroy_lincons (cons2);

            LddNodePtr n3 = lddPtr (get_ldd_man (), Ldd_Or (get_ldd_man(), &*n1, &*n2));
            m_ldd = lddPtr (get_ldd_man(), Ldd_And (get_ldd_man(), &*m_ldd, &*n3));
          }
        }

        boxes_domain (LddNodePtr ldd): m_ldd (ldd) {
#if 1
          const int CST_FACTOR = 10; /* JN: some magic constant factor */
          int threshold = num_of_vars () * CST_FACTOR;
          if (threshold > 0) {
            int before_num_paths = Ldd_PathSize (NULL, &*m_ldd);
            // -- we throw away relationships between variables if the
            //    number of ldd paths is not linear with some constant
            //    factor in the size of the diagram.
            if (before_num_paths >= threshold) {
              m_ldd = non_relational_approx ();
              int after_num_paths = Ldd_PathSize (NULL, &*m_ldd);
              if (after_num_paths == before_num_paths) {
                // -- if the previous approximation did not work then
                // -- we further simplify the ldd by making it convex
                m_ldd = convex_approx ();
                CRAB_WARN ("ldd is growing too fast (number of paths= " , before_num_paths, "). ",
                           "After making convex the ldd ",
                           "the number of paths is ", Ldd_PathSize (NULL, &*m_ldd));

              }
              else {
                CRAB_WARN ("ldd is growing too fast (number of paths= " , before_num_paths, "). ",
                           "After deleting relationships between variables ",
                           "the number of paths is ", after_num_paths);
              }
            }
          }
#endif
        }

	void to_disjunctive_linear_constraint_system_aux
	(LddManager *ldd, LddNode *n,
	 disjunctive_linear_constraint_system_t &e,
	 std::vector<int> &list) {
	    /**
	     * the latest negative constraint to be printed.
	     * is NULL if there isn't one
	     */
	    lincons_t negc = nullptr;

	    DdNode *N = Cudd_Regular (n);

	    if (cuddIsConstant (N)) {
	      /* n == N here implies that n is one */
	      if (n == N) {

		linear_constraint_system_t csts;

		/* for each level */
		for (int i = 0; i < ldd->cudd->size; i++) {
		  /* let p be the index of level i */
		  int p = ldd->cudd->invperm [i];
		  /* let v be the value of p */
		  int v = list [p];

		  /* skip don't care */
		  if (v == 2) continue;

		  if (v == 0 && ldd->ddVars [p] != nullptr)
		    {
		      lincons_t c;
		      c = THEORY->negate_cons (ldd->ddVars [p]);

		      if (negc != nullptr) {
			/* consider negative constraint if it is not implied
			   by c
			*/
			if (!THEORY->is_stronger_cons (c, negc)) {
			  csts += cstFromLddCons (negc, Ldd_GetTheory (ldd));
			}
			THEORY->destroy_lincons (negc);
		      }

		      /* store the current constraint to be considered later */
		      negc = c;
		      continue;
		    }

		  /* if there is a negative constraint waiting to be
		     considered, conjoin it now
		  */
		  if (negc != nullptr) {
		    csts += cstFromLddCons (negc, Ldd_GetTheory (ldd));
		    THEORY->destroy_lincons (negc);
		    negc = nullptr;
		  }

		  /* if v is not a don't care but p does not correspond to a
		   * constraint, consider it as a Boolean variable */
		  if (v != 2 && ldd->ddVars [p] == nullptr)  {
		    // TODO ????
		    //fprintf (stderr, "%sb%d", (v == 0 ? "!" : " "), p);
		  }
		  /* v is true */
		  else if (v == 1) {
		    csts += cstFromLddCons (ldd->ddVars [p],Ldd_GetTheory (ldd));
		  }
		} // end for

		/* if there is a constraint waiting to be considered, do it
		   now */
		if (negc != nullptr) {
		  csts += cstFromLddCons (negc, Ldd_GetTheory (ldd));
		  THEORY->destroy_lincons (negc);
		  negc = nullptr;
		}
		e += csts;
	      }
	    }
	    else {
	      DdNode *Nv = Cudd_NotCond (cuddT(N), N != n);
	      DdNode *Nnv = Cudd_NotCond (cuddE(N), N != n);
	      int index = N->index;
	      list[index] = 0;
	      to_disjunctive_linear_constraint_system_aux(ldd, Nnv, e, list);
	      list[index] = 1;
	      to_disjunctive_linear_constraint_system_aux(ldd, Nv, e, list);
	      list[index] = 2;
	    }
	    return;
       }

        LddNodePtr getLdd () {
          return m_ldd;
        }

        VariableName getVarName (int v) const {
          auto it = get_var_map ()->right.find (v);
          if (it != get_var_map ()->right.end ())
             return it->second;
          else {
             CRAB_ERROR ("Index ", v, " cannot be mapped back to a variable name");
          }
        }

       public:

        boxes_domain(): ikos::writeable() {
          m_ldd = lddPtr (get_ldd_man(), Ldd_GetTrue (get_ldd_man()));
        }

        ~boxes_domain () {
          // DdManager *cudd = nullptr;
          // theory_t *theory = nullptr;
          // if (m_ldd_man)  {
	//   cudd = Ldd_GetCudd (m_ldd_man);
	//   theory = Ldd_GetTheory (m_ldd_man);
	//   Ldd_Quit (m_ldd_man);
	// }
          // if (theory) tvpi_destroy_theory(theory);
          // if (cudd) Cudd_Quit(cudd);
        }

        static boxes_domain_t top() {
          return boxes_domain_t (lddPtr (get_ldd_man(), Ldd_GetTrue (get_ldd_man())));
        }

        static boxes_domain_t bottom() {
          return boxes_domain_t (lddPtr (get_ldd_man(), Ldd_GetFalse (get_ldd_man())));
        }

        boxes_domain (const boxes_domain_t& other):
            ikos::writeable(), m_ldd (other.m_ldd) {
          crab::CrabStats::count (getDomainName () + ".count.copy");
          crab::ScopedCrabStats __st__(getDomainName() + ".copy");
        }

        boxes_domain_t& operator=(const boxes_domain_t& other) {
          crab::CrabStats::count (getDomainName() + ".count.copy");
          crab::ScopedCrabStats __st__(getDomainName() + ".copy");
          if (this != &other)
            m_ldd = other.m_ldd;
          return *this;
        }

        bool is_bottom() const {
          return &*m_ldd == Ldd_GetFalse (get_ldd_man());
        }

        bool is_top() const {
          return &*m_ldd == Ldd_GetTrue (get_ldd_man());
        }

        bool operator<=(boxes_domain_t other) {
          crab::CrabStats::count (getDomainName() + ".count.leq");
          crab::ScopedCrabStats __st__(getDomainName() + ".leq");

          bool res = Ldd_TermLeq (get_ldd_man(), &(*m_ldd), &(*other.m_ldd));

          CRAB_LOG ("boxes",
                    crab::outs() << "Check if " <<  *this << " <= " <<  other
                              <<  " ---> " <<  res <<"\n";);
          return res;
        }

        void operator|=(boxes_domain_t other) {
          *this = *this | other;
        }

        boxes_domain_t operator|(boxes_domain_t other) {
          crab::CrabStats::count (getDomainName() + ".count.join");
          crab::ScopedCrabStats __st__(getDomainName() + ".join");

          return boxes_domain_t (join (m_ldd, other.m_ldd));
        }

        boxes_domain_t operator&(boxes_domain_t other) {
          crab::CrabStats::count (getDomainName() + ".count.meet");
          crab::ScopedCrabStats __st__(getDomainName() + ".meet");

          return boxes_domain_t (lddPtr (get_ldd_man(),
                                         Ldd_And (get_ldd_man(), &*m_ldd, &*other.m_ldd)));
        }

        boxes_domain_t operator||(boxes_domain_t other) {
          crab::CrabStats::count (getDomainName() + ".count.widening");
          crab::ScopedCrabStats __st__(getDomainName() + ".widening");

          // It is not necessarily true that the new value is bigger
          // than the old value so we apply
          // widen(old, new) = widen (old, (join (old,new)))
          LddNodePtr v = join (m_ldd, other.m_ldd);
          LddNodePtr w = lddPtr (get_ldd_man (),
                                 Ldd_BoxWiden2 (get_ldd_man (), &*m_ldd, &*v));

#if 1
          /** ensure that 'w' is only the fronteer of the compution.
              Not sure whether this is still a widening though
          */
          w = lddPtr (get_ldd_man (),
                      Ldd_And (get_ldd_man (), &*w, Ldd_Not (&*m_ldd)));
          /** ensure the output is at least as big as newV */
          w = lddPtr (get_ldd_man (), Ldd_Or (get_ldd_man (), &*w, &*other.m_ldd));
#endif
          boxes_domain_t res (w);

          CRAB_LOG ("boxes",
                    crab::outs() << "Widening " <<  *this << " and " <<  other
                              <<  "=" << res <<"\n";);
          return res;
        }

        template<typename Thresholds>
        boxes_domain_t widening_thresholds (boxes_domain_t other,
                                            const Thresholds & /*ts*/) {
          CRAB_WARN (" boxes widening operator with thresholds not implemented");
          return (*this || other);
        }

        boxes_domain_t operator&& (boxes_domain_t other) {
          crab::CrabStats::count (getDomainName() + ".count.narrowing");
          crab::ScopedCrabStats __st__(getDomainName() + ".narrowing");

          boxes_domain_t res (*this & other);
          CRAB_WARN (" boxes narrowing operator replaced with meet");

          CRAB_LOG ("boxes",
                    crab::outs() << "Narrowing " << *this << " and " << other << "=" << res <<"\n";);
          return res;
        }

        void operator-=(VariableName var) {
          crab::CrabStats::count (getDomainName() + ".count.forget");
          crab::ScopedCrabStats __st__(getDomainName() + ".forget");

          if (is_bottom ()) return;

          int id = get_var_dim (var);
          m_ldd =  lddPtr (get_ldd_man(),
                           Ldd_ExistsAbstract (get_ldd_man(), &*m_ldd, id));
        }

        // remove all variables [begin,...end)
        template<typename Iterator>
        void forget (Iterator begin, Iterator end) {
          if (is_bottom ()) return;

          for (auto v: boost::make_iterator_range (begin, end))
            operator-= (v);
        }

        // dual of forget: remove all variables except [begin,...end)
        template<typename Iterator>
        void project (Iterator begin, Iterator end) {
          crab::CrabStats::count (getDomainName() + ".count.project");
          crab::ScopedCrabStats __st__(getDomainName() + ".project");

          if (is_bottom ()) return;

          std::set<VariableName> s1,s2,s3;
          for (auto p: (*m_var_map).left) s1.insert (p.first);
          s2.insert (begin, end);
          boost::set_difference (s1,s2,std::inserter (s3, s3.end ()));
          forget (s3.begin (), s3.end ());
        }

        void operator+= (linear_constraint_t cst)
        {
          crab::CrabStats::count (getDomainName() + ".count.add_constraints");
          crab::ScopedCrabStats __st__(getDomainName() + ".add_constraints");

          if (is_bottom () || cst.is_tautology ())
            return;

          if (cst.is_contradiction ())  {
            m_ldd = lddPtr (get_ldd_man(), Ldd_GetFalse (get_ldd_man()));
            return;
          }

          linear_expression_t exp = cst.expression();
          unsigned int size = exp.size ();
          if (size == 0)
            return; // this should not happen
          else if (size == 1) {
            auto it = exp.begin();
            Number cx = it->first;
            if (cx == 1 || cx == -1) {
              Number k = -exp.constant();
              variable_t x = it->second;
              add_unit_constraint (cx, x, cst.kind (), k);
            }
            else
              CRAB_WARN (" boxes only supports constraints with unit coefficients");
          }
          else if (size >= 2) {
            // TODO: we can always do the operation in the interval
            //       domain and meet the result with the ldd. But it
            //       might be expensive if a basic block has two many
            //       assume's.
            CRAB_WARN (" boxes only supports constraints with at most one variable.");
          }

          CRAB_LOG("boxes", crab::outs() << "Assume(" << cst << ") --> " <<  *this <<"\n";);
        }

        void operator += (linear_constraint_system_t csts) {
          if (is_bottom ()) return;

          for (auto cst : csts) operator += (cst);
        }

        void set (VariableName v, interval_t ival) {
          crab::CrabStats::count (getDomainName() + ".count.assign");
          crab::ScopedCrabStats __st__(getDomainName() + ".assign");

          if (is_bottom ()) return ;

          constant_t kmin = NULL, kmax = NULL;
          if (boost::optional <Number> l = ival.lb ().number ())
            kmin = mkCst (*l);
          if (boost::optional <Number> u = ival.ub ().number ())
            kmax = mkCst (*u);

          linterm_t t = termForVal(v);
          // FIXME: Ldd_TermReplace seems to leak memory
          m_ldd = lddPtr(get_ldd_man(),
                         Ldd_TermReplace(get_ldd_man(), &(*m_ldd), t, NULL, NULL, kmin, kmax));

          if (kmin) Ldd_GetTheory (get_ldd_man())->destroy_cst(kmin);
          if (kmax) Ldd_GetTheory (get_ldd_man())->destroy_cst(kmax);
          Ldd_GetTheory (get_ldd_man())->destroy_term(t);
        }

        // expensive operation
        interval_t operator[](VariableName v) {

          crab::CrabStats::count (getDomainName() + ".count.to_intervals");
          crab::ScopedCrabStats __st__(getDomainName() + ".to_intervals");

          if (is_bottom ())
            return interval_t::bottom ();

          if (is_top ())
            return interval_t::top ();

          // make convex the ldd
          LddNodePtr tmp = convex_approx ();
          // forget any variable that is not v
          for (auto p: (*m_var_map).left) {
            if (! (p.first == v)) {
              int id = get_var_dim (p.first);
              tmp =  lddPtr (get_ldd_man(),
                             Ldd_ExistsAbstract (get_ldd_man(), &*tmp, id));
            }
          }
          // convert to interval domain
          linear_constraint_system_t csts;
          toLinCstSysRecur (&*tmp, csts);
          interval_domain_t intv = interval_domain_t::top ();
          for (auto cst: csts) { intv += cst; }
          // do projection with intervals
          return intv[v];
        }

        void assign (VariableName x, linear_expression_t e) {
          crab::CrabStats::count (getDomainName() + ".count.assign");
          crab::ScopedCrabStats __st__(getDomainName() + ".assign");

          if (is_bottom ())
            return;

          if (e.is_constant ()) {
            constant_t c = mkCst (e.constant ());
            linterm_t t = termForVal (x);
            m_ldd = lddPtr(get_ldd_man(),
                           Ldd_TermReplace(get_ldd_man(), &(*m_ldd), t, NULL, NULL, c, c));
            Ldd_GetTheory (get_ldd_man())->destroy_cst (c);
            Ldd_GetTheory (get_ldd_man())->destroy_term(t);
          }
          else if (optional<variable_t> v = e.get_variable()){
            VariableName y = (*v).name();
            if (!(x==y))
              apply (x, y, 1, 0);
          }
          else {
            CRAB_WARN(" boxes only supports cst or var on the rhs of assignment");
            *this -= x;
          }
          CRAB_LOG("boxes", crab::outs() << "---" << x << ":=" << e << "\n" << *this <<"\n";);
        }

        void apply (operation_t op, VariableName x, VariableName y, Number k) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          switch(op){
            case OP_ADDITION: apply (x, y, 1, k); break;
            case OP_SUBTRACTION: apply (x, y, 1, -k); break;
            case OP_MULTIPLICATION: apply (x, y, k, 0); break;
            case OP_DIVISION:
              {
                // Convert to intervals and perform the operation
                interval_t yi = operator[](y);
                interval_t zi (k);
                interval_t xi = yi / zi;
                set (x, xi);
                break;
              }
            default: CRAB_ERROR ("Boxes unreachable");
          }

          CRAB_LOG("boxes",
                   crab::outs() << "apply " << x << " := " << y << " " <<  op
                             << " " << k << " --- " <<  *this <<"\n";);
        }

        void apply(operation_t op, VariableName x, Number k) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          switch(op){
            case OP_ADDITION:
              apply (x, x, 1, k);
              break;
            case OP_SUBTRACTION:
              apply (x, x, 1, -k);
              break;
            case OP_MULTIPLICATION:
              apply (x, x, k, 0);
              break;
            case OP_DIVISION:
              {
                // Convert to intervals and perform the operation
                interval_t yi = operator[](x);
                interval_t zi (k);
                interval_t xi = yi / zi;
                set(x, xi);
                break;
              }
          }
          CRAB_LOG("boxes",
                   crab::outs() << "apply " << x << " := " << x << " " << op << " "
                                << k <<  "---" <<  *this <<"\n";);
        }

        void apply(operation_t op, VariableName x, VariableName y, VariableName z) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          interval_t zi = operator[](z);
          if (auto k = zi.singleton ())
            apply (op, x, y, *k);
          else {
            // Convert to intervals and perform the operation
            interval_t yi = operator[](y);
            interval_t xi = interval_t::bottom();
            switch (op) {
              case OP_ADDITION: xi = yi + zi; break;
              case OP_SUBTRACTION: xi = yi - zi; break;
              case OP_MULTIPLICATION: xi = yi * zi; break;
              case OP_DIVISION: xi = yi / zi; break;
            }
            set(x, xi);
          }
        }

        // bitwise_operators_api
        void apply(conv_operation_t op, VariableName x, VariableName y, unsigned width) {
          // since reasoning about infinite precision we simply assign and
          // ignore the width.
          assign(x, linear_expression_t(y));
        }

        void apply(conv_operation_t op, VariableName x, Number k, unsigned width) {
          // since reasoning about infinite precision we simply assign
          // and ignore the width.
          assign(x, k);
        }

        void apply(bitwise_operation_t op, VariableName x, VariableName y, VariableName z) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          // Convert to intervals and perform the operation
          interval_t yi = operator[](y);
          interval_t zi = operator[](z);
          interval_t xi = interval_t::bottom();
          switch (op) {
            case OP_AND:  xi = yi.And(zi); break;
            case OP_OR:   xi = yi.Or(zi) ; break;
            case OP_XOR:  xi = yi.Xor(zi); break;
            case OP_SHL:  xi = yi.Shl(zi); break;
            case OP_LSHR: xi = yi.LShr(zi); break;
            case OP_ASHR: xi = yi.AShr(zi); break;
          }
          set(x, xi);
        }

        void apply(bitwise_operation_t op, VariableName x, VariableName y, Number k) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          // Convert to intervals and perform the operation
          interval_t yi = operator[](y);
          interval_t zi(k);
          interval_t xi = interval_t::bottom();
          switch (op) {
            case OP_AND:  xi = yi.And(zi); break;
            case OP_OR:   xi = yi.Or(zi) ; break;
            case OP_XOR:  xi = yi.Xor(zi); break;
            case OP_SHL:  xi = yi.Shl(zi); break;
            case OP_LSHR: xi = yi.LShr(zi); break;
            case OP_ASHR: xi = yi.AShr(zi); break;
          }
          set(x, xi);
        }

        // division_operators_api
        void apply(div_operation_t op, VariableName x, VariableName y, VariableName z) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          if (op == OP_SDIV){
            apply(OP_DIVISION, x, y, z);
          }
          else{
            // Convert to intervals and perform the operation
            interval_t yi = operator[](y);
            interval_t zi = operator[](z);
            interval_t xi = interval_t::bottom();
            switch (op) {
              case OP_UDIV: xi = yi.UDiv(zi); break;
              case OP_SREM: xi = yi.SRem(zi); break;
              case OP_UREM: xi = yi.URem(zi); break;
              default: CRAB_ERROR("Boxes: unreachable");
            }
            set(x, xi);
          }
        }

        void apply(div_operation_t op, VariableName x, VariableName y, Number k) {
          crab::CrabStats::count (getDomainName() + ".count.apply");
          crab::ScopedCrabStats __st__(getDomainName() + ".apply");

          if (is_bottom ())
            return;

          if (op == OP_SDIV){
            apply(OP_DIVISION, x, y, k);
          }
          else{
            // Convert to intervals and perform the operation
            interval_t yi = operator[](y);
            interval_t zi(k);
            interval_t xi = interval_t::bottom();
            switch (op) {
              case OP_UDIV: xi = yi.UDiv(zi); break;
              case OP_SREM: xi = yi.SRem(zi); break;
              case OP_UREM: xi = yi.URem(zi); break;
              default: CRAB_ERROR("Boxes: unreachable");
            }
            set(x, xi);
          }
        }

        linear_constraint_system_t to_linear_constraint_system () {
          linear_constraint_system_t csts;

          if(is_bottom ()) {
            csts += linear_constraint_t (linear_expression_t (Number(1)) ==
                                         linear_expression_t (Number(0)));
            return csts;
          }

          if(is_top ()) {
            csts += linear_constraint_t (linear_expression_t (Number(1)) ==
                                         linear_expression_t (Number(1)));
            return csts;
          }

          // --- produce convex approximation
          LddNodePtr v = convex_approx ();

          // --- extract linear inequalities from the convex ldd
          toLinCstSysRecur(&*v, csts);

          return csts;
        }

 	disjunctive_linear_constraint_system_t
 	to_disjunctive_linear_constraint_system () {
 	  LddManager *ldd_man = getLddManager (m_ldd);

 	  std::vector<int> list;
 	  list.reserve (ldd_man->cudd->size);
 	  for (int i=0; i < ldd_man->cudd->size; i++) list.push_back(2);

 	  disjunctive_linear_constraint_system_t r;
 	  to_disjunctive_linear_constraint_system_aux (ldd_man, m_ldd.get (),
 						       r, list);
 	  return r;
 	}

 	void write (crab_os& o) {
	  if (is_top ()) {
	    o << "{}";
 	  } else if (is_bottom ())  {
	    o << "_|_";
 	  } else  {
 	    auto r = to_disjunctive_linear_constraint_system ();
 	    o << r;
	  }
	}

        static std::string getDomainName () {
          return "Boxes";
        }

      };

     template<typename N, typename V, size_t S>
     LddManager* boxes_domain<N,V,S>::m_ldd_man = nullptr;

     template<typename N, typename V, size_t S>
     typename boxes_domain<N,V,S>::var_map_ptr boxes_domain<N,V,S>::m_var_map = nullptr;

     template<typename Number, typename VariableName, size_t LddSize>
     class domain_traits <boxes_domain<Number,VariableName, LddSize> > {
      public:
       typedef boxes_domain<Number, VariableName, LddSize> boxes_domain_t;

       static void normalize (boxes_domain_t& inv) { }

       template <typename Iter>
       static void forget (boxes_domain_t& inv, Iter it, Iter end) {
         inv.forget (it, end);
       }

       template <typename Iter>
       static void project (boxes_domain_t& inv, Iter it, Iter end) {
         inv.project (it, end);
       }

       static void expand (boxes_domain_t& inv, VariableName x, VariableName new_x) {
         CRAB_WARN ("boxes expand operation not implemented");
       }

     };
   } // namespace domains
}// namespace crab
#endif /* HAVE_LDD */
#endif


#include "SummaryElementScalarCollector.hpp"
#include <assert.h>
#include <boost/detail/limits.hpp>
#include "application/output/recordsets/ResultRecordset.hpp"
#include "domain/algorithms/messages/ModelStateUpdateMessage.hpp"
#include "domain/analyses/NumericalModel.hpp"
#include "domain/collections/ElementSet.hpp"

namespace aaoc = axis::application::output::collectors;
namespace aaocs = axis::application::output::collectors::summarizers;
namespace aaor = axis::application::output::recordsets;
namespace ada = axis::domain::analyses;
namespace adam = axis::domain::algorithms::messages;
namespace adc = axis::domain::collections;
namespace ade = axis::domain::elements;
namespace asmm = axis::services::messaging;
namespace afb = axis::foundation::blas;

aaocs::SummaryElementScalarCollector::SummaryElementScalarCollector( const axis::String& targetSetName,
                                                            SummaryType summaryType ) :
targetSetName_(targetSetName), summaryType_(summaryType)
{
  // nothing to do here
}

aaocs::SummaryElementScalarCollector::~SummaryElementScalarCollector( void )
{
  // nothing to do here
}

void aaocs::SummaryElementScalarCollector::Collect(
  const asmm::ResultMessage& message, aaor::ResultRecordset& recordset,
  const ada::NumericalModel& numericalModel )
{
  adc::ElementSet& set = targetSetName_.empty()? numericalModel.Elements() :
    numericalModel.GetElementSet(targetSetName_);
  size_type count = set.Count();

  StartCollect();
  for (size_type idx = 0; idx < count; ++idx)
  {
    const ade::FiniteElement& element = set.GetByPosition(idx);
    real v = CollectValue(message, element, numericalModel);
    switch (summaryType_)
    {
    case kSum:
    case kAverage:
      value_ += v;
      break;
    case kMaximum:
      if (v > value_) value_ = v;
      break;
    case kMinimum:
      if (v < value_) value_ = v;
      break;
    default:
      assert(!_T("Unexpected summary type!"));
      break;
    }
  }
  if (summaryType_ == kAverage)
  {
    value_ /= (real)count;
  }
  recordset.WriteData(value_);
}

bool aaocs::SummaryElementScalarCollector::IsOfInterest( const asmm::ResultMessage& message ) const
{
  return adam::ModelStateUpdateMessage::IsOfKind(message);
}

void aaocs::SummaryElementScalarCollector::StartCollect( void )
{
  for (int i = 0; i < 6; i++)
  {
    switch (summaryType_)
    {
    case kSum:
    case kAverage:
      value_ = 0;
      break;
    case kMaximum:
      value_ = std::numeric_limits<real>::min();
      break;
    case kMinimum:
      value_ = std::numeric_limits<real>::max();
      break;
    default:
      assert(!_T("Unexpected summary type!"));
      break;
    }
  }
}

axis::String aaocs::SummaryElementScalarCollector::GetFriendlyDescription( void ) const
{
  axis::String desc;
  switch (summaryType_)
  {
  case kSum:
    desc = _T("Total ");
    break;
  case kAverage:
    desc = _T("Average ");
    break;
  case kMaximum:
    desc = _T("Maximum ");
    break;
  case kMinimum:
    desc = _T("Minimum ");
    break;
  default:
    assert(!_T("Unexpected summary type!"));
    break;
  }
  desc += GetVariableName();
  if (targetSetName_.empty())
  {
    desc += _T(" on all elements");
  }
  else
  {
    desc += _T(" on element set '") + targetSetName_ + _T("'");
  }
  return desc;
}


//=============================================================================================================
/**
* @file     mne_project_to_surface.cpp
* @author   Jana Kiesel <jana.kiesel@tu-ilmenau.de>;
*           Matti Hamalainen <msh@nmr.mgh.harvard.edu>
* @version  1.0
* @date     August, 2016
*
* @section  LICENSE
*
* Copyright (C) 2016, Jana Kiesel and Matti Hamalainen. All rights reserved.
*
* 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 MNE-CPP authors 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 OWNER 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.
*
*
* @brief    MNEProjectToSurface class definition.
*
*/

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include "mne_project_to_surface.h"

//*************************************************************************************************************
//=============================================================================================================
// INCLUDES
//=============================================================================================================

#include <mne/mne_bem_surface.h>
#include <mne/mne_surface.h>

//*************************************************************************************************************
//=============================================================================================================
// QT INCLUDES
//=============================================================================================================

//*************************************************************************************************************
//=============================================================================================================
// Eigen INCLUDES
//=============================================================================================================

#include <Eigen/Geometry>

//*************************************************************************************************************
//=============================================================================================================
// USED NAMESPACES
//=============================================================================================================

using namespace MNELIB;
using namespace Eigen;

//*************************************************************************************************************
//=============================================================================================================
// DEFINE GLOBAL METHODS
//=============================================================================================================

//*************************************************************************************************************
//=============================================================================================================
// DEFINE MEMBER METHODS
//=============================================================================================================

MNEProjectToSurface::MNEProjectToSurface()
: r1(MatrixX3f::Zero(1,3))
, r12(MatrixX3f::Zero(1,3))
, r13(MatrixX3f::Zero(1,3))
, nn(MatrixX3f::Zero(1,3))
, a(VectorXf::Zero(1))
, b(VectorXf::Zero(1))
, c(VectorXf::Zero(1))
, det(VectorXf::Zero(1))
{

}

//*************************************************************************************************************

MNEProjectToSurface::MNEProjectToSurface(const MNEBemSurface &p_MNEBemSurf)
: r1(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, r12(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, r13(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, nn(MatrixX3f::Zero(p_MNEBemSurf.ntri,3))
, a(VectorXf::Zero(p_MNEBemSurf.ntri))
, b(VectorXf::Zero(p_MNEBemSurf.ntri))
, c(VectorXf::Zero(p_MNEBemSurf.ntri))
, det(VectorXf::Zero(p_MNEBemSurf.ntri))
{
    for (int i = 0; i < p_MNEBemSurf.ntri; ++i)
    {
        r1.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,0));
        r12.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,1)) - r1.row(i);
        r13.row(i) = p_MNEBemSurf.rr.row(p_MNEBemSurf.tris(i,2)) - r1.row(i);
        a(i) = r12.row(i) * r12.row(i).transpose();
        b(i) = r13.row(i) * r13.row(i).transpose();
        c(i) = r12.row(i) * r13.row(i).transpose();
    }

    if (!(p_MNEBemSurf.tri_nn.isZero(0)))
    {
        nn = p_MNEBemSurf.tri_nn.cast<float>();
    }
    else
    {
        for (int i = 0; i < p_MNEBemSurf.ntri; ++i)
        {
            nn.row(i) = r12.row(i).transpose().cross(r13.row(i).transpose()).transpose();
        }
    }
    det = (a.array()*b.array() - c.array()*c.array()).matrix();
}

//*************************************************************************************************************

MNEProjectToSurface::MNEProjectToSurface(const MNESurface &p_MNESurf)
: r1(MatrixX3f::Zero(p_MNESurf.ntri,3))
, r12(MatrixX3f::Zero(p_MNESurf.ntri,3))
, r13(MatrixX3f::Zero(p_MNESurf.ntri,3))
, nn(MatrixX3f::Zero(p_MNESurf.ntri,3))
, a(VectorXf::Zero(p_MNESurf.ntri))
, b(VectorXf::Zero(p_MNESurf.ntri))
, c(VectorXf::Zero(p_MNESurf.ntri))
, det(VectorXf::Zero(p_MNESurf.ntri))
{
    for (int i = 0; i < p_MNESurf.ntri; ++i)
    {
        r1.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,0));
        r12.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,1)) - r1.row(i);
        r13.row(i) = p_MNESurf.rr.row(p_MNESurf.tris(i,2)) - r1.row(i);
        nn.row(i) = r12.row(i).transpose().cross(r13.row(i).transpose()).transpose();
        a(i) = r12.row(i) * r12.row(i).transpose();
        b(i) = r13.row(i) * r13.row(i).transpose();
        c(i) = r12.row(i) * r13.row(i).transpose();
    }

    det = (a.array()*b.array() - c.array()*c.array()).matrix();
}

//*************************************************************************************************************

bool MNEProjectToSurface::mne_find_closest_on_surface(const MatrixXf &r, const int np, MatrixXf &rTri,
                                                      VectorXi &nearest, VectorXf &dist)
{
    nearest.resize(np);
    dist.resize(np);
    if (this->r1.isZero(0))
    {
        qDebug() << "No surface loaded to make the projection./n";
        return false;
    }
    int bestTri = -1;
    float bestDist = -1;
    Vector3f rTriK;
    for (int k = 0; k < np; ++k)
    {
        /*
         * To do: decide_search_restriction for the use in an iterative closest point to plane algorithm
         * For now it's OK to go through all triangles.
         */
        if (!this->mne_project_to_surface(r.row(k).transpose(), rTriK, bestTri, bestDist))
        {
            qDebug() << "The projection of point number " << k << " didn't work./n";
            return false;
        }
        rTri.row(k) = rTriK.transpose();
        nearest[k] = bestTri;
        dist[k] = bestDist;
    }
    return true;
}

//*************************************************************************************************************

bool MNEProjectToSurface::mne_project_to_surface(const Vector3f &r, Vector3f &rTri, int &bestTri, float &bestDist)
{
    float p = 0, q = 0, p0 = 0, q0 = 0, dist0 = 0;
    bestDist = 0.0f;
    bestTri = -1;
    for (int tri = 0; tri < a .size(); ++tri)
    {
        if (!this->nearest_triangle_point(r, tri, p0, q0, dist0))
        {
            qDebug() << "The projection on triangle " << tri << " didn't work./n";
            return false;
        }

        if ((bestTri < 0) || (std::fabs(dist0) < std::fabs(bestDist)))
        {
            bestDist = dist0;
            p = p0;
            q = q0;
            bestTri = tri;
        }
    }

    if (bestTri >= 0)
    {
        if (!this->project_to_triangle(rTri, p, q, bestTri))
        {
            qDebug() << "The coordinate transform to cartesian system didn't work./n";
            return false;
        }
        return true;
    }

    qDebug() << "No best Triangle found./n";
    return false;
}

//*************************************************************************************************************

bool MNEProjectToSurface::nearest_triangle_point(const Vector3f &r, const int tri, float &p, float &q, float &dist)
{
    //Calculate some helpers
    Vector3f rr = r - this->r1.row(tri).transpose(); //Vector from triangle corner #1 to r
    float v1 = this->r12.row(tri)*rr;
    float v2 = this->r13.row(tri)*rr;

    //Calculate the orthogonal projection of the point r on the plane
    dist = this->nn.row(tri)*rr;
    p = (this->b(tri)*v1 - this->c(tri)*v2)/det(tri);
    q = (this->a(tri)*v2 - this->c(tri)*v1)/det(tri);

    //If the point projects into the triangle we are done
    if (p >= 0.0 && p <= 1.0 && q >= 0.0 && q <= 1.0 && (p+q) <= 1.0)
         {
        return true;
    }

    /*
     * Tough: must investigate the sides
     * We might do something intelligent here. However, for now it is ok
     * to do it in the hard way
     */
    float p0, q0, t0, dist0, best, bestp, bestq;

    /*
     * Side 1 -> 2
     */
    p0 = p + (q * this->c(tri)) / this->a(tri);
    // Place the point in the corner if it is not on the side
    if (p0 < 0.0)
    {
        p0 = 0.0;
    }
    else if (p0 > 1.0)
    {
        p0 = 1.0;
    }
    q0 = 0;
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);

    best = dist0;
    bestp = p0;
    bestq = q0;
    /*
    * Side 2 -> 3
    */
    t0 = ((a(tri)-c(tri))*(-p) + (b(tri)-c(tri))*q)/(a(tri)+b(tri)-2*c(tri));
    // Place the point in the corner if it is not on the side
    if (t0 < 0.0)
    {
        t0 = 0.0;
    }
    else if (t0 > 1.0)
    {
        t0 = 1.0;
    }
    p0 = 1.0 - t0;
    q0 = t0;
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);
    if (dist0 < best)
    {
        best = dist0;
        bestp = p0;
        bestq = q0;
    }
    /*
    * Side 1 -> 3
    */
    p0 = 0.0;
    q0 = q + (p * c(tri))/b(tri);
    // Place the point in the corner if it is not on the side
    if (q0 < 0.0)
    {
        q0 = 0.0;

    }
    else if (q0 > 1.0)
    {
        q0 = 1.0;
    }
    // Distance
    dist0 = sqrt((p-p0)*(p-p0)*this->a(tri) +
                 (q-q0)*(q-q0)*this->b(tri) +
                 2*(p-p0)*(q-q0)*this->c(tri) +
                 dist*dist);
    if (dist0 < best)
    {
        best = dist0;
        bestp = p0;
        bestq = q0;
    }
    dist = best;
    p = bestp;
    q = bestq;
    return true;
}

//*************************************************************************************************************

bool MNEProjectToSurface::project_to_triangle(Vector3f &rTri, const float p, const float q, const int tri)
{
    rTri = this->r1.row(tri) + p*this->r12.row(tri) + q*this->r13.row(tri);
    return true;
}


#include <cradle/geometry/line_strip.hpp>
#include <boost/assign/std/vector.hpp>

#define BOOST_TEST_MODULE line_strip
#include <cradle/test.hpp>

double const tolerance = 0.00001;

using namespace boost::assign;
using namespace cradle;

BOOST_AUTO_TEST_CASE(actual_poly_test)
{
    std::vector<vertex2> vertices;
    vertices +=
        make_vector<double>(-1, 0),
        make_vector<double>( 1, 0),
        make_vector<double>( 0, 1);
    polygon2 original;
    initialize(&original.vertices, vertices);

    std::vector<line_strip> strips;
    std::vector<line_segment<2,double> > segments;

    for (polygon2_edge_view ev(original); !ev.done(); ev.advance())
        segments.push_back(line_segment<2,double>(ev.p0(), ev.p1()));

    strips = connect_line_segments(segments, tolerance);

    BOOST_CHECK(is_polygon(strips, tolerance));
    BOOST_CHECK(as_polygon(strips, tolerance));

    optional<polygon2> poly;
    as_polygon(&poly, strips, tolerance);
    BOOST_CHECK(poly);
    BOOST_CHECK(almost_equal(original, poly.get()));
}

BOOST_AUTO_TEST_CASE(not_connected_test)
{
    std::vector<vertex2> vertices;
    vertices +=
        make_vector<double>(-1, 0),
        make_vector<double>( 1, 0),
        make_vector<double>( 0, 1);
    polygon2 original;
    initialize(&original.vertices, vertices);

    std::vector<line_strip> strips;
    std::vector<line_segment<2,double> > segments;

    polygon2_edge_view ev(original);
    ev.advance();
    for (; !ev.done(); ev.advance())
        segments.push_back(line_segment<2,double>(ev.p0(), ev.p1()));

    strips = connect_line_segments(segments, tolerance);

    BOOST_CHECK(!is_polygon(strips, tolerance));
    BOOST_CHECK(!as_polygon(strips, tolerance));

    optional<polygon2> poly;
    as_polygon(&poly, strips, tolerance);
    BOOST_CHECK(!poly);
}

BOOST_AUTO_TEST_CASE(extraneous_segments_test)
{
    std::vector<vertex2> vertices;
    vertices +=
        make_vector<double>(-1, 0),
        make_vector<double>( 1, 0),
        make_vector<double>( 0, 1);
    polygon2 original;
    initialize(&original.vertices, vertices);

    std::vector<line_strip> strips;
    std::vector<line_segment<2,double> > segments;

    for (polygon2_edge_view ev(original); !ev.done(); ev.advance())
        segments.push_back(line_segment<2,double>(ev.p0(), ev.p1()));

    segments.push_back(line_segment<2,double>(
        make_vector<double>(2, 0), make_vector<double>(2, 1)));

    strips = connect_line_segments(segments, tolerance);

    BOOST_CHECK(!is_polygon(strips, tolerance));
    BOOST_CHECK(!as_polygon(strips, tolerance));

    optional<polygon2> poly;
    as_polygon(&poly, strips, tolerance);
    BOOST_CHECK(!poly);
}

BOOST_AUTO_TEST_CASE(multiple_polys_test)
{
    polygon2 original;
    {
        std::vector<vertex2> vertices;
        vertices +=
            make_vector<double>(-1, 0),
            make_vector<double>( 1, 0),
            make_vector<double>( 0, 1);
        initialize(&original.vertices, vertices);
    }

    std::vector<line_strip> strips;
    std::vector<line_segment<2,double> > segments;

    for (polygon2_edge_view ev(original); !ev.done(); ev.advance())
        segments.push_back(line_segment<2,double>(ev.p0(), ev.p1()));

    strips = connect_line_segments(segments, tolerance);

    polygon2 other;
    {
        std::vector<vertex2> vertices;
        vertices +=
            make_vector<double>( 2, 0),
            make_vector<double>( 3, 0),
            make_vector<double>( 0, 1);
        initialize(&other.vertices, vertices);
    }

    for (polygon2_edge_view ev(other); !ev.done(); ev.advance())
        segments.push_back(line_segment<2,double>(ev.p0(), ev.p1()));

    strips = connect_line_segments(segments, tolerance);

    BOOST_CHECK(!is_polygon(strips, tolerance));
    BOOST_CHECK(!as_polygon(strips, tolerance));

    optional<polygon2> poly;
    as_polygon(&poly, strips, tolerance);
    BOOST_CHECK(!poly);
}


// ======================================================================
/*!
 * \brief Format boost time objects
 */
// ======================================================================

#include "TimeFormatter.h"
#include "Exception.h"
#include "StringConversion.h"
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/date_time/gregorian/gregorian.hpp>
#include <fmt/format.h>
#include <fmt/printf.h>
#include <array>
#include <stdexcept>

// ----------------------------------------------------------------------
/*!
 * A local help subroutine to convert a UTC tm to UTC time_t
 * This code is copied from newbase NFmiStaticTime::my_timegm -method.
 *
 * The original C code is by C.A. Lademann and Richard Kettlewell.
 *
 * \param t The UTC time as a tm struct
 * \return The UTC time as a time_t
 * \bug This has not been verified to work in SGI/Windows
 */
// ----------------------------------------------------------------------

static time_t my_timegm(struct tm* t)
{
  try
  {
#if 0
    // THIS IS NOT THREAD SAFE IF LOCALTIME_R IS CALLED SIMULTANEOUSLY!!!
    return ::timegm(t);  // timegm is a GNU extension

#else  // Windows
    const int MINUTE = 60;
    const int HOUR = 60 * MINUTE;
    const int DAY = 24 * HOUR;
    const int YEAR = 365 * DAY;

    const std::array<int, 12> mon{31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

    if (t->tm_year < 70)
      return (static_cast<time_t>(-1));

    int n = t->tm_year + 1900 - 1;
    time_t epoch = (t->tm_year - 70) * YEAR +
                   ((n / 4 - n / 100 + n / 400) - (1969 / 4 - 1969 / 100 + 1969 / 400)) * DAY;

    int y = t->tm_year + 1900;
    int m = 0;
    for (int i = 0; i < t->tm_mon; i++)
    {
      epoch += mon[m] * DAY;
      if (m == 1 && y % 4 == 0 && (y % 100 != 0 || y % 400 == 0))
        epoch += DAY;
      if (++m > 11)
      {
        m = 0;
        y++;
      }
    }

    epoch += (t->tm_mday - 1) * DAY;
    epoch += t->tm_hour * HOUR;
    epoch += t->tm_min * MINUTE;
    epoch += t->tm_sec;

    return epoch;
#endif
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

namespace Fmi
{
// ----------------------------------------------------------------------
/*!
 * \brief Destructor defined here to prevent existence of a weak vtable
 */
// ----------------------------------------------------------------------

TimeFormatter::~TimeFormatter() = default;
// ----------------------------------------------------------------------
/*!
 * \brief ISO-formatter (see boost manuals)
 *
 * Convert to form YYYYMMDDTHHMMSS,fffffffff where T is the date-time separator
 */
// ----------------------------------------------------------------------

struct IsoFormatter : public TimeFormatter
{
  IsoFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief SQL-formatter
 *
 * Convert to form YYYY-MM-DD HH:MM:SS
 */
// ----------------------------------------------------------------------

struct SqlFormatter : public TimeFormatter
{
  SqlFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief XML-formatter
 *
 * Convert to form YYYY-MM-DDTHH:MM:SS
 */
// ----------------------------------------------------------------------

struct XmlFormatter : public TimeFormatter
{
  XmlFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief Epoch-formatter
 *
 * Convert to form ssssss, time in seconds since epoch 1970-01-01 00:00:00
 */
// ----------------------------------------------------------------------

struct EpochFormatter : public TimeFormatter
{
  EpochFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief Timestamp-formatter
 *
 * Convert to form YYYYMMDDHHMM
 */
// ----------------------------------------------------------------------

struct TimeStampFormatter : public TimeFormatter
{
  TimeStampFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief HTTP-date formatter
 *
 * Convert to form Sun, 06 Nov 1994 08:49:37 GMT
 */
// ----------------------------------------------------------------------

struct HttpFormatter : public TimeFormatter
{
  HttpFormatter() = default;
  std::string format(const boost::posix_time::ptime& t) const override;
  std::string format(const boost::local_time::local_date_time& t) const override;
};

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime
 */
// ----------------------------------------------------------------------

std::string IsoFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    return Fmi::to_iso_string(t);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string IsoFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    return Fmi::to_iso_string(t.local_time());
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime
 */
// ----------------------------------------------------------------------

std::string SqlFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    std::string tmp = Fmi::to_iso_extended_string(t);
    tmp[10] = ' ';
    return tmp;
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string SqlFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    std::string tmp = Fmi::to_iso_extended_string(t.local_time());
    tmp[10] = ' ';
    return tmp;
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime
 */
// ----------------------------------------------------------------------

std::string XmlFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    return Fmi::to_iso_extended_string(t);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string XmlFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    return Fmi::to_iso_extended_string(t.local_time());
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime
 */
// ----------------------------------------------------------------------

std::string EpochFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    tm tmp = boost::posix_time::to_tm(t);
    time_t epo = ::my_timegm(&tmp);
    return Fmi::to_string(epo);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string EpochFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    tm tmp = boost::posix_time::to_tm(t.utc_time());
    time_t epo = ::my_timegm(&tmp);
    return Fmi::to_string(epo);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime: YYYYMMDDHHMI
 */
// ----------------------------------------------------------------------

std::string TimeStampFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    return Fmi::to_timestamp_string(t);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string TimeStampFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    return Fmi::to_timestamp_string(t.local_time());
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a ptime into style Sun, 06 Nov 1994 08:49:37 GMT
 */
// ----------------------------------------------------------------------

std::string HttpFormatter::format(const boost::posix_time::ptime& t) const
{
  try
  {
    return Fmi::to_http_string(t);
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Format a local date time
 */
// ----------------------------------------------------------------------

std::string HttpFormatter::format(const boost::local_time::local_date_time& t) const
{
  try
  {
    return format(t.utc_time());
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

// ----------------------------------------------------------------------
/*!
 * \brief Create a time formatter
 */
// ----------------------------------------------------------------------

TimeFormatter* TimeFormatter::create(const std::string& name)
{
  try
  {
    if (name == "iso")
      return new IsoFormatter();
    if (name == "sql")
      return new SqlFormatter();
    if (name == "xml")
      return new XmlFormatter();
    if (name == "epoch")
      return new EpochFormatter();
    if (name == "timestamp")
      return new TimeStampFormatter();
    if (name == "http")
      return new HttpFormatter();

    throw Fmi::Exception(BCP, "Unknown time format '" + name + "'");
  }
  catch (...)
  {
    throw Fmi::Exception::Trace(BCP, "Operation failed!");
  }
}

}  // namespace Fmi

// ======================================================================


// Copyright (C) 2011-2012 by the BEM++ Authors
//
// 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 fiber_numerical_quadrature_hpp
#define fiber_numerical_quadrature_hpp

#include "../common/common.hpp"

/** \file
 *
 *  Low-level functions filling arrays of quadrature points and weights. */

#include "element_pair_topology.hpp"

#include "../common/armadillo_fwd.hpp"
#include <boost/tuple/tuple_comparison.hpp>
#include <ostream>

namespace Fiber
{

struct SingleQuadratureDescriptor
{
    int vertexCount;
    int order;

    bool operator<(const SingleQuadratureDescriptor& other) const {
        return std::make_pair(vertexCount, order) <
                std::make_pair(other.vertexCount, other.order);
    }

    bool operator==(const SingleQuadratureDescriptor& other) const {
        return vertexCount == other.vertexCount &&
                order == other.order;
    }

    bool operator!=(const SingleQuadratureDescriptor& other) const {
        return !operator==(other);
    }

    friend std::ostream&
    operator<< (std::ostream& dest, const SingleQuadratureDescriptor& obj)
    {
        dest << obj.vertexCount << " " << obj.order;
        return dest;
    }
};

inline size_t tbb_hasher(const SingleQuadratureDescriptor& d)
{
    return (d.vertexCount - 3) + 2 * d.order;
}

struct DoubleQuadratureDescriptor
{
    ElementPairTopology topology;
    int testOrder;
    int trialOrder;

    bool operator<(const DoubleQuadratureDescriptor& other) const {
        using boost::tuples::make_tuple;
        return make_tuple(topology, testOrder, trialOrder) <
                make_tuple(other.topology, other.testOrder, other.trialOrder);
    }

    bool operator==(const DoubleQuadratureDescriptor& other) const {
        return topology == other.topology &&
                testOrder == other.testOrder &&
                trialOrder == other.trialOrder;
    }

    bool operator!=(const DoubleQuadratureDescriptor& other) const {
        return !operator==(other);
    }

    friend std::ostream&
    operator<< (std::ostream& dest, const DoubleQuadratureDescriptor& obj)
    {
        dest << obj.topology << " " << obj.testOrder << " " << obj.trialOrder;
        return dest;
    }
};

inline size_t tbb_hasher(const DoubleQuadratureDescriptor& d)
{
    const ElementPairTopology& t = d.topology;
    return (t.testVertexCount - 3) + 2 *
            ((t.trialVertexCount - 3) + 2 *
             (t.testSharedVertex0 + 4 *
              (t.trialSharedVertex0 + 4 *
               (t.testSharedVertex1 + 4 *
                (t.trialSharedVertex1 + 4 *
                 (d.testOrder + 256 *
                  d.trialOrder))))));
}

/** \brief Retrieve points and weights for a quadrature over a single element.
 *
 *  \param[in] elementCornerCount
 *    Number of corners of the element to be integrated on.
 *  \param[in] accuracyOrder
 *    Accuracy order of the quadrature, i.e. its degree of exactness.
 *  \param[out] points
 *    Quadrature points.
 *  \param[out] weights
 *    Quadrature weights. */
template <typename ValueType>
void fillSingleQuadraturePointsAndWeights(int elementCornerCount,
                                          int accuracyOrder,
                                          arma::Mat<ValueType>& points,
                                          std::vector<ValueType>& weights);

template <typename ValueType>
void fillDoubleSingularQuadraturePointsAndWeights(
        const DoubleQuadratureDescriptor& desc,
        arma::Mat<ValueType>& testPoints,
        arma::Mat<ValueType>& trialPoints,
        std::vector<ValueType>& weights);

} // namespace Fiber

#endif


#pragma once

#include <data_model.hpp>

#include <iostream>
#include <string>
#include <vector>
#include <stdexcept>
#include <map>
#include <sstream>
#include <boost/tuple/tuple.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/copy.hpp>
#include <boost/property_map/property_map.hpp>
#include <boost/graph/graphviz.hpp>

namespace verilog
{
  namespace bdd {

    enum class NegP {
      Positive, Negative
    };

    enum class Type {
      Zero, One, Input
    };

    struct Node {
      std::string input_name;
      Type t;
    };

    typedef boost::adjacency_list<
      boost::setS,
      boost::vecS,
      boost::bidirectionalS,
      Node, NegP> GD;

    typedef unsigned int hashcode;

    struct BDD {
      GD graph;

      GD::vertex_descriptor
        /**
         * The nodes representing the sinks of the BDD
         */
        zero,
        one,
        /**
         * The node representing the source of the bdd
         */
        source;

      std::map<hashcode, std::vector<int> > bdd_heap;

      BDD() {
        zero = boost::add_vertex(graph);
        one  = boost::add_vertex(graph);
        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      BDD(const std::string name) {
        zero = boost::add_vertex(graph);
        one  = boost::add_vertex(graph);
        source = add_vertex(name);

        add_edge(source, one, NegP::Positive);
        add_edge(source, zero, NegP::Negative);

        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      /**
       * Creates a copy of a,
       * merges all nodes of b into the copy.
       * calls the conjunction_internal function for the copy.
       */

      void copy_graph(const BDD & o,
          std::map<GD::vertex_descriptor,
                   GD::vertex_descriptor> & node_map
          ) {

        GD::vertex_iterator v, vend;
        for(boost::tie(v, vend) = boost::vertices(o.graph);
            v != vend; ++v) {
          if (o.graph[*v].t == Type::Input) {
            GD::vertex_descriptor n = add_vertex(o.graph[*v].input_name);
            node_map[*v] = n;
          }
        }

        GD::edge_iterator e, eend;
        for(boost::tie(e, eend) = boost::edges(o.graph);
            e != eend; ++e) {
          GD::vertex_descriptor s = boost::source(*e, o.graph);

          GD::vertex_descriptor x;
          GD::vertex_descriptor d = boost::target(*e, o.graph);
          switch(o.graph[d].t) {
            case(Type::Zero):
              x = zero;
              break;
            case(Type::One):
              x = one;
              break;
            default:
              x = node_map[d];
          }
          boost::add_edge(node_map[s], x, o.graph[*e], graph);
        }
      }

      BDD operator&(const BDD & b) {

        std::map<GD::vertex_descriptor,
          GD::vertex_descriptor> node_map_a;

        std::map<GD::vertex_descriptor,
          GD::vertex_descriptor> node_map_b;

        BDD c;
        c.copy_graph(*this, node_map_a);
        c.copy_graph(b, node_map_b);

        c.source = c.conjunction_internal(
            node_map_a[this->source],
            node_map_b[b.source]
            );
        return c;
      }

      private:
      /**
       * Internal function that checks for all the cases
       * Accepts the nodes that must be conjoined
       * and returns the new source.
       */
      GD::vertex_descriptor
        conjunction_internal(
          GD::vertex_descriptor & a,
          GD::vertex_descriptor & b) {
        std::cout << "conjunction_internal " << a << " " << b << "\n";
        add_edge(a, b, NegP::Positive);
        return a;
      }

      GD::vertex_descriptor add_vertex(const std::string name) {
        GD::vertex_descriptor v = boost::add_vertex(graph);
        graph[v].input_name = name;
        graph[v].t          = Type::Input;
        return v;
      }

      void add_edge(
          GD::vertex_descriptor & a,
          GD::vertex_descriptor & b,
          NegP p) {
        boost::add_edge(a, b, p, graph);
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * - changes zero and one
       */
      public:
      void reverse() {
        GD::vertex_descriptor aux = one;
        one = zero;
        zero = aux;

        graph[zero].t = Type::Zero;
        graph[one].t  = Type::One;
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * merges the input edges of the node b into node a.
       */
      void merge_input_edges(
          const GD::vertex_descriptor a,
          const GD::vertex_descriptor b) {
        GD::in_edge_iterator e, end;
        for (boost::tie(e, end) = in_edges(b, graph); e != end; ++e) {
            GD::vertex_descriptor s = boost::source(*e, graph);
            boost::add_edge(s, a, graph[*e], graph);
        }
      }

      std::map<int, std::vector<int> > find_layers() {
        // TODO
      }

      // Criar um hash o mais unico possivel para um bdd
      hashcode hash(int v) {
        // TODO
      }

      // Compares BDDs
      friend bool operator<(const BDD & bdd1, const BDD & bdd2) {
        // TODO
      }

      /**
       * USE WITH CAUTION, as it changes the current BDD
       * - minimizes the BDD
       */
      void minimize() {
        // TODO
      }

      friend bool operator==(const BDD & bdd1, const BDD & bdd2) {
        return
          (bdd1.source == bdd2.source) &&
          (bdd1.one == bdd2.one) &&
          (bdd1.zero == bdd2.zero);
        // TODO &&
        //  (bdd1.graph == bdd2.graph);
      }
      friend bool operator!=(const BDD & bdd1, const BDD & bdd2) {
        return
          (bdd1.source != bdd2.source) &&
          (bdd1.one != bdd2.one) &&
          (bdd1.zero != bdd2.zero);
        // TODO&&
        //  (bdd1.graph != bdd2.graph);
      }

      public:
      int size() {
        int i = 0;
        GD::vertex_iterator n, e;
        for(boost::tie(n, e) = boost::vertices(graph);
            n != e; ++n) ++i;
        return i;
      }

      friend std::ostream& operator<<(std::ostream &o, const BDD & b) {

        int i = 0;
        std::map<GD::vertex_descriptor, int> node_map;
        //std::map<GD::vertex_descriptor, GD::vertex_descriptor> node_map;
        GD::vertex_iterator v, vend;

        for(boost::tie(v, vend) = boost::vertices(b.graph);
            v != vend; ++v) {
          node_map[*v] = ++i;
          //node_map[*v] = *v;
        }

        o << "digraph G {\n" ;
        for(boost::tie(v, vend) = boost::vertices(b.graph);
            v != vend; ++v) {
          switch(b.graph[*v].t) {
            case(Type::Input) : {
                o << node_map[*v] << " [label=\""
                  << b.graph[*v].input_name
                  << "\", ";
                if (*v == b.source)
                  o << "shape=doublecircle];\n";
                else
                  o << "shape=circle];\n";
              break;
            }
            case(Type::One) : {
                o << node_map[*v] << " [label=\"1\", shape=box]; \n";
              break;
            }
            case(Type::Zero) : {
                o << node_map[*v] << " [label=\"0\", shape=box]; \n";
              break;
            }
          }
        }

        GD::edge_iterator e, eend;
        for(boost::tie(e, eend) = boost::edges(b.graph);
            e != eend; ++e) {
          GD::vertex_descriptor s = boost::source(*e, b.graph);
          GD::vertex_descriptor d = boost::target(*e, b.graph);

          o << node_map[s] << "->" << node_map[d];
          switch(b.graph[*e]) {
            case(NegP::Positive): {
              o << " [style=solid];\n";
              break;
            }
            case(NegP::Negative): {
              o << " [style=dashed];\n";
              break;
            }
          }
        }

        o << "}\n" ;
        return o;
      }
    };
  }
}


// Boost.Geometry (aka GGL, Generic Geometry Library)

// Copyright (c) 2007-2014 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2008-2014 Bruno Lalande, Paris, France.
// Copyright (c) 2009-2014 Mateusz Loskot, London, UK.

// This file was modified by Oracle on 2014.
// Modifications copyright (c) 2014, Oracle and/or its affiliates.

// Contributed and/or modified by Menelaos Karavelas, on behalf of Oracle

// Parts of Boost.Geometry are redesigned from Geodan's Geographic Library
// (geolib/GGL), copyright (c) 1995-2010 Geodan, Amsterdam, the Netherlands.

// 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)

#ifndef BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_SINGLE_TO_MULTI_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_SINGLE_TO_MULTI_HPP

#include <boost/numeric/conversion/bounds.hpp>
#include <boost/range.hpp>

#include <boost/geometry/core/point_type.hpp>
#include <boost/geometry/core/geometry_id.hpp>
#include <boost/geometry/core/tag.hpp>
#include <boost/geometry/core/tags.hpp>

#include <boost/geometry/geometries/concepts/check.hpp>

#include <boost/geometry/strategies/distance.hpp>
#include <boost/geometry/strategies/tags.hpp>

#include <boost/geometry/util/select_coordinate_type.hpp>
#include <boost/geometry/util/math.hpp>

#include <boost/geometry/algorithms/not_implemented.hpp>
#include <boost/geometry/algorithms/dispatch/distance.hpp>

#include <boost/geometry/algorithms/detail/for_each_range.hpp>
#include <boost/geometry/algorithms/detail/sections/range_by_section.hpp>
#include <boost/geometry/algorithms/detail/sections/sectionalize.hpp>

#include <boost/geometry/views/detail/range_type.hpp>

#include <boost/geometry/algorithms/covered_by.hpp>
#include <boost/geometry/algorithms/disjoint.hpp>
#include <boost/geometry/algorithms/for_each.hpp>
#include <boost/geometry/algorithms/within.hpp>

#include <boost/geometry/algorithms/detail/distance/geometry_to_geometry_rtree.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace distance
{

template<typename Geometry, typename MultiGeometry, typename Strategy>
class distance_single_to_multi_generic
{
private:
    typedef typename strategy::distance::services::comparable_type
        <
            Strategy
        >::type comparable_strategy;

    typedef typename strategy::distance::services::return_type
        <
            comparable_strategy,
            typename point_type<Geometry>::type,
            typename point_type<MultiGeometry>::type
        >::type comparable_return_type;

public:
    typedef typename strategy::distance::services::return_type
        <
            Strategy,
            typename point_type<Geometry>::type,
            typename point_type<MultiGeometry>::type
        >::type return_type;

    static inline return_type apply(Geometry const& geometry,
                                    MultiGeometry const& multi,
                                    Strategy const& strategy)
    {
        comparable_return_type min_cdist = comparable_return_type();
        bool first = true;

        comparable_strategy cstrategy =
            strategy::distance::services::get_comparable
                <
                    Strategy
                >::apply(strategy);

        for (typename range_iterator<MultiGeometry const>::type it = boost::begin(multi);
                it != boost::end(multi);
                ++it, first = false)
        {
            comparable_return_type cdist = dispatch::distance
                <
                    Geometry,
                    typename range_value<MultiGeometry>::type,
                    comparable_strategy
                >::apply(geometry, *it, cstrategy);

            if (first || cdist < min_cdist)
            {
                min_cdist = cdist;
                if ( geometry::math::equals(min_cdist, 0) )
                {
                    break;
                }
            }
        }

        return strategy::distance::services::comparable_to_regular
            <
                comparable_strategy, Strategy, Geometry, MultiGeometry
            >::apply(min_cdist);
    }
};

template <typename MultiGeometry, typename Geometry, typename Strategy>
struct distance_multi_to_single_generic
{
    typedef typename strategy::distance::services::return_type
                     <
                         Strategy,
                         typename point_type<MultiGeometry>::type,
                         typename point_type<Geometry>::type
                     >::type return_type;

    static inline return_type apply(MultiGeometry const& multi,
                                    Geometry const& geometry,
                                    Strategy const& strategy)
    {
        return distance_single_to_multi_generic
            <
                Geometry, MultiGeometry, Strategy
            >::apply(geometry, multi, strategy);
    }
};

}} // namespace detail::distance
#endif // DOXYGEN_NO_DETAIL

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

namespace splitted_dispatch
{

template
<
    typename Geometry,
    typename MultiGeometry,
    typename Strategy,
    typename GeometryTag,
    typename MultiGeometryTag,
    typename StrategyTag
>
struct distance_single_to_multi
    : not_implemented<Geometry, MultiGeometry>
{};

template<typename Point, typename MultiPoint, typename Strategy>
struct distance_single_to_multi
    <
        Point, MultiPoint, Strategy,
        point_tag, multi_point_tag,
        strategy_tag_distance_point_point
    > : detail::distance::distance_single_to_multi_generic
        <
            Point, MultiPoint, Strategy
        >
{};

template<typename Point, typename MultiLinestring, typename Strategy>
struct distance_single_to_multi
    <
        Point, MultiLinestring, Strategy,
        point_tag, multi_linestring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_single_to_multi_generic
        <
            Point, MultiLinestring, Strategy
        >
{};

template<typename Point, typename MultiPolygon, typename Strategy>
struct distance_single_to_multi
    <
        Point, MultiPolygon, Strategy,
        point_tag, multi_polygon_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_single_to_multi_generic
        <
            Point, MultiPolygon, Strategy
        >
{};

template<typename Linestring, typename MultiLinestring, typename Strategy>
struct distance_single_to_multi
    <
        Linestring, MultiLinestring, Strategy,
        linestring_tag, multi_linestring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            Linestring, MultiLinestring, Strategy
        >
{};

template <typename Linestring, typename MultiPolygon, typename Strategy>
struct distance_single_to_multi
    <
        Linestring, MultiPolygon, Strategy,
        linestring_tag, multi_polygon_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            Linestring, MultiPolygon, Strategy
        >
{};

template <typename Polygon, typename MultiPoint, typename Strategy>
struct distance_single_to_multi
    <
        Polygon, MultiPoint, Strategy,
        polygon_tag, multi_point_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_single_to_multi_generic
        <
            Polygon, MultiPoint, Strategy
        >
{};

template <typename Polygon, typename MultiLinestring, typename Strategy>
struct distance_single_to_multi
    <
        Polygon, MultiLinestring, Strategy,
        polygon_tag, multi_linestring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            Polygon, MultiLinestring, Strategy
        >
{};

template <typename Polygon, typename MultiPolygon, typename Strategy>
struct distance_single_to_multi
    <
        Polygon, MultiPolygon, Strategy,
        polygon_tag, multi_polygon_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            Polygon, MultiPolygon, Strategy
        >
{};

template <typename MultiLinestring, typename Ring, typename Strategy>
struct distance_single_to_multi
    <
        MultiLinestring, Ring, Strategy,
        multi_linestring_tag, ring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            MultiLinestring, Ring, Strategy
        >
{};

template <typename MultiPolygon, typename Ring, typename Strategy>
struct distance_single_to_multi
    <
        MultiPolygon, Ring, Strategy,
        multi_polygon_tag, ring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            MultiPolygon, Ring, Strategy
        >
{};

template
<
    typename MultiGeometry,
    typename Geometry,
    typename Strategy,
    typename MultiGeometryTag,
    typename GeometryTag,
    typename StrategyTag
>
struct distance_multi_to_single
    : not_implemented<MultiGeometry, Geometry>
{};

template
<
    typename MultiPoint,
    typename Segment,
    typename Strategy
>
struct distance_multi_to_single
    <
        MultiPoint, Segment, Strategy,
        multi_point_tag, segment_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiPoint, Segment, Strategy
        >
{};

template
<
    typename MultiPoint,
    typename Ring,
    typename Strategy
>
struct distance_multi_to_single
    <
        MultiPoint, Ring, Strategy,
        multi_point_tag, ring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiPoint, Ring, Strategy
        >
{};

template
<
    typename MultiPoint,
    typename Box,
    typename Strategy
>
struct distance_multi_to_single
    <
        MultiPoint, Box, Strategy,
        multi_point_tag, box_tag,
        strategy_tag_distance_point_box
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiPoint, Box, Strategy
        >
{};

template <typename MultiLinestring, typename Segment, typename Strategy>
struct distance_multi_to_single
    <
        MultiLinestring, Segment, Strategy,
        multi_linestring_tag, segment_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiLinestring, Segment, Strategy
        >
{};

template <typename MultiLinestring, typename Ring, typename Strategy>
struct distance_multi_to_single
    <
        MultiLinestring, Ring, Strategy,
        multi_linestring_tag, ring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            MultiLinestring, Ring, Strategy
        >
{};

template <typename MultiLinestring, typename Box, typename Strategy>
struct distance_multi_to_single
    <
        MultiLinestring, Box, Strategy,
        multi_linestring_tag, box_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiLinestring, Box, Strategy
        >
{};

template <typename MultiPolygon, typename Segment, typename Strategy>
struct distance_multi_to_single
    <
        MultiPolygon, Segment, Strategy,
        multi_polygon_tag, segment_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiPolygon, Segment, Strategy
        >
{};

template <typename MultiPolygon, typename Ring, typename Strategy>
struct distance_multi_to_single
    <
        MultiPolygon, Ring, Strategy,
        multi_polygon_tag, ring_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::geometry_to_geometry_rtree
        <
            MultiPolygon, Ring, Strategy
        >
{};

template <typename MultiPolygon, typename Box, typename Strategy>
struct distance_multi_to_single
    <
        MultiPolygon, Box, Strategy,
        multi_polygon_tag, box_tag,
        strategy_tag_distance_point_segment
    > : detail::distance::distance_multi_to_single_generic
        <
            MultiPolygon, Box, Strategy
        >
{};

} // namespace splitted_dispatch

template
<
    typename Geometry,
    typename MultiGeometry,
    typename Strategy,
    typename GeometryTag,
    typename StrategyTag
>
struct distance
    <
        Geometry, MultiGeometry, Strategy, GeometryTag, multi_tag,
        StrategyTag, false
    > : splitted_dispatch::distance_single_to_multi
        <
            Geometry, MultiGeometry, Strategy,
            GeometryTag, typename tag<MultiGeometry>::type,
            StrategyTag
        >
{};

template
<
    typename MultiGeometry,
    typename Geometry,
    typename Strategy,
    typename GeometryTag,
    typename StrategyTag
>
struct distance
    <
        MultiGeometry, Geometry, Strategy, multi_tag, GeometryTag,
        StrategyTag, false
    > : splitted_dispatch::distance_multi_to_single
        <
            MultiGeometry, Geometry, Strategy,
            typename tag<MultiGeometry>::type, GeometryTag,
            StrategyTag
        >
{};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_DISTANCE_SINGLE_TO_MULTI_HPP