//
// Created by przemek on 27.03.2020.
//

#include <AST/expression/base_math_expr.h>
#include <AST/expression/realtion_expr.h>
#include <boost/test/unit_test.hpp>
#include <error/exeception.h>
#include <mock_expr.h>

using namespace vecc;
using namespace vecc::error;
using namespace vecc::ast;
using namespace vecc::test;

using std::make_unique;
using std::move;
using std::unique_ptr;

BOOST_AUTO_TEST_SUITE(AST_Test_Suite)

BOOST_AUTO_TEST_SUITE(Relation_Expr_Test_Suite)

BOOST_AUTO_TEST_CASE(GivenTwoValues_EqualityReturnsCorrectValue) {
  Variable var1 = Variable({1});
  Variable var2 = Variable({2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var2));

  BOOST_CHECK_EQUAL(expr1.calculate(), true);
  BOOST_CHECK_EQUAL(expr2.calculate(), false);
  BOOST_CHECK_EQUAL(expr3.calculate(), true);
}

BOOST_AUTO_TEST_CASE(GivenTwoVecValues_EqualityReturnsCorrectValue) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::Equal,
                     make_unique<BaseMathExpr>(var2));

  BOOST_CHECK_EQUAL(expr1.calculate(), true);
  BOOST_CHECK_EQUAL(expr2.calculate(), false);
  BOOST_CHECK_EQUAL(expr3.calculate(), true);
}

BOOST_AUTO_TEST_CASE(GivenTwoDiffrentDimVecValues_EqualityThrows) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2, 3});

  RelationExpr expr(make_unique<BaseMathExpr>(var1),
                    RelationExpr::OperatorType::Equal,
                    make_unique<BaseMathExpr>(var2));

  Variable temp;
  BOOST_CHECK_THROW(temp = expr.calculate(), MathException);
}

BOOST_AUTO_TEST_CASE(GivenTwoValues_InequalityReturnsCorrectValue) {
  Variable var1 = Variable({1});
  Variable var2 = Variable({2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var2));

  BOOST_CHECK_EQUAL(expr1.calculate(), false);
  BOOST_CHECK_EQUAL(expr2.calculate(), true);
  BOOST_CHECK_EQUAL(expr3.calculate(), false);
}

BOOST_AUTO_TEST_CASE(GivenTwoVecValues_InequalityReturnsCorrectValue) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::NotEqual,
                     make_unique<BaseMathExpr>(var2));

  BOOST_CHECK_EQUAL(expr1.calculate(), false);
  BOOST_CHECK_EQUAL(expr2.calculate(), true);
  BOOST_CHECK_EQUAL(expr3.calculate(), false);
}

BOOST_AUTO_TEST_CASE(GivenTwoDiffrentDimVecValues_InequalityThrows) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2, 3});

  RelationExpr expr(make_unique<BaseMathExpr>(var1),
                    RelationExpr::OperatorType::NotEqual,
                    make_unique<BaseMathExpr>(var2));
  Variable temp;
  BOOST_CHECK_THROW(temp = expr.calculate(), MathException);
}

BOOST_AUTO_TEST_CASE(GivenTwoValues_LessReturnsCorrectValue) {
  Variable var1 = Variable({1});
  Variable var2 = Variable({2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr4(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var1));

  BOOST_CHECK_EQUAL(expr1.calculate(), false);
  BOOST_CHECK_EQUAL(expr2.calculate(), true);
  BOOST_CHECK_EQUAL(expr3.calculate(), false);
  BOOST_CHECK_EQUAL(expr4.calculate(), false);
}

BOOST_AUTO_TEST_CASE(GivenTwoVecValues_LessThrows) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::Less,
                     make_unique<BaseMathExpr>(var2));

  Variable temp;
  BOOST_CHECK_THROW(temp = expr1.calculate(), MathException);
  BOOST_CHECK_THROW(temp = expr2.calculate(), MathException);
  BOOST_CHECK_THROW(temp = expr3.calculate(), MathException);
}

BOOST_AUTO_TEST_CASE(GivenTwoValues_LessOrEqualReturnsCorrectValue) {
  Variable var1 = Variable({1});
  Variable var2 = Variable({2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr4(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var1));

  BOOST_CHECK_EQUAL(expr1.calculate(), true);
  BOOST_CHECK_EQUAL(expr2.calculate(), true);
  BOOST_CHECK_EQUAL(expr3.calculate(), true);
  BOOST_CHECK_EQUAL(expr4.calculate(), false);
}

BOOST_AUTO_TEST_CASE(GivenTwoVecValues_LessOrEqualThrows) {
  Variable var1 = Variable({1, 1});
  Variable var2 = Variable({2, 2});

  RelationExpr expr1(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var1));

  RelationExpr expr2(make_unique<BaseMathExpr>(var1),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var2));

  RelationExpr expr3(make_unique<BaseMathExpr>(var2),
                     RelationExpr::OperatorType::LessOrEqual,
                     make_unique<BaseMathExpr>(var2));

  Variable temp;
  BOOST_CHECK_THROW(temp = expr1.calculate(), MathException);
  BOOST_CHECK_THROW(temp = expr2.calculate(), MathException);
  BOOST_CHECK_THROW(temp = expr3.calculate(), MathException);
}

BOOST_AUTO_TEST_CASE(GivenTwoVal_ToStringCorrect) {
  auto expr1 = MockExpr("e1");
  auto expr2 = MockExpr("e2");

  RelationExpr ex1(make_unique<MockExpr>(expr1),
                   RelationExpr::OperatorType::LessOrEqual,
                   make_unique<MockExpr>(expr2));
  RelationExpr ex2(make_unique<MockExpr>(expr1),
                   RelationExpr::OperatorType::Greater,
                   make_unique<MockExpr>(expr2));
  RelationExpr ex3(make_unique<MockExpr>(expr1),
                   RelationExpr::OperatorType::NotEqual,
                   make_unique<MockExpr>(expr2));

  BOOST_CHECK_EQUAL(ex1.toString(), "(e1 <= e2)");
  BOOST_CHECK_EQUAL(ex2.toString(), "(e1 > e2)");
  BOOST_CHECK_EQUAL(ex3.toString(), "(e1 != e2)");
}

BOOST_AUTO_TEST_SUITE_END()

BOOST_AUTO_TEST_SUITE_END()


/*******************************************************************************
 *
 * An implementation of discrete domains based on Patricia trees.
 *
 * Author: Arnaud J. Venet (arnaud.j.venet@nasa.gov)
 *
 * Notices:
 *
 * Copyright (c) 2011 United States Government as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All Rights Reserved.
 *
 * Disclaimers:
 *
 * No Warranty: THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF
 * ANY KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT LIMITED
 * TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO SPECIFICATIONS,
 * ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE,
 * OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL BE
 * ERROR FREE, OR ANY WARRANTY THAT DOCUMENTATION, IF PROVIDED, WILL CONFORM TO
 * THE SUBJECT SOFTWARE. THIS AGREEMENT DOES NOT, IN ANY MANNER, CONSTITUTE AN
 * ENDORSEMENT BY GOVERNMENT AGENCY OR ANY PRIOR RECIPIENT OF ANY RESULTS,
 * RESULTING DESIGNS, HARDWARE, SOFTWARE PRODUCTS OR ANY OTHER APPLICATIONS
 * RESULTING FROM USE OF THE SUBJECT SOFTWARE.  FURTHER, GOVERNMENT AGENCY
 * DISCLAIMS ALL WARRANTIES AND LIABILITIES REGARDING THIRD-PARTY SOFTWARE,
 * IF PRESENT IN THE ORIGINAL SOFTWARE, AND DISTRIBUTES IT "AS IS."
 *
 * Waiver and Indemnity:  RECIPIENT AGREES TO WAIVE ANY AND ALL CLAIMS AGAINST
 * THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS, AS WELL
 * AS ANY PRIOR RECIPIENT.  IF RECIPIENT'S USE OF THE SUBJECT SOFTWARE RESULTS
 * IN ANY LIABILITIES, DEMANDS, DAMAGES, EXPENSES OR LOSSES ARISING FROM SUCH
 * USE, INCLUDING ANY DAMAGES FROM PRODUCTS BASED ON, OR RESULTING FROM,
 * RECIPIENT'S USE OF THE SUBJECT SOFTWARE, RECIPIENT SHALL INDEMNIFY AND HOLD
 * HARMLESS THE UNITED STATES GOVERNMENT, ITS CONTRACTORS AND SUBCONTRACTORS,
 * AS WELL AS ANY PRIOR RECIPIENT, TO THE EXTENT PERMITTED BY LAW.
 * RECIPIENT'S SOLE REMEDY FOR ANY SUCH MATTER SHALL BE THE IMMEDIATE,
 * UNILATERAL TERMINATION OF THIS AGREEMENT.
 *
 ******************************************************************************/

#pragma once

#include <crab/common/types.hpp>
#include <crab/domains/patricia_trees.hpp>
#include <crab/domains/separate_domains.hpp>

#include <boost/range.hpp>

namespace ikos {

  template< typename Element >
  class discrete_domain: public writeable {

  private:
    typedef patricia_tree_set< Element > ptset_t;

  public:
    typedef discrete_domain< Element > discrete_domain_t;
    typedef typename ptset_t::iterator iterator;

  private:
    bool _is_top;
    ptset_t _set;

  private:
    discrete_domain(bool is_top): _is_top(is_top) { }

    discrete_domain(ptset_t set): _is_top(false), _set(set) { }

  public:
    static discrete_domain_t bottom() {
      return discrete_domain_t(false);
    }

    static discrete_domain_t top() {
      return discrete_domain_t(true);
    }

  public:
    discrete_domain(): _is_top(true) { }

    discrete_domain(const discrete_domain_t& other):
        writeable(), _is_top(other._is_top), _set(other._set) { }

    discrete_domain(Element s): _is_top(false), _set(s) { }

    template<typename Iterator>
    discrete_domain(Iterator eIt, Iterator eEt)
        : _is_top(false) {
      for (auto e: boost::make_iterator_range (eIt, eEt)) {
        this->_set += e;
      }
    }

    bool is_top() {
      return this->_is_top;
    }

    bool is_bottom() {
      return (!this->_is_top && this->_set.empty());
    }

    bool operator<=(discrete_domain_t other) {
      return other._is_top || (!this->_is_top && this->_set <= other._set);
    }

    bool operator==(discrete_domain_t other) {
      return (this->_is_top && other._is_top) || (this->_set == other._set);
    }

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

    discrete_domain_t operator|(discrete_domain_t other) {
      if (this->_is_top || other._is_top) {
        return discrete_domain_t(true);
      } else {
        return discrete_domain_t(this->_set | other._set);
      }
    }

    discrete_domain_t operator&(discrete_domain_t other) {
      if (this->is_bottom() || other.is_bottom()) {
        return discrete_domain_t(false);
      } else if (this->_is_top) {
        return other;
      } else if (other._is_top) {
        return *this;
      } else {
        return discrete_domain_t(this->_set & other._set);
      }
    }

    discrete_domain_t operator||(discrete_domain_t other) {
      return this->operator|(other);
    }

    discrete_domain_t operator&&(discrete_domain_t other) {
      return this->operator&(other);
    }

    discrete_domain_t& operator+=(Element s) {
      if (!this->_is_top) {
        this->_set += s;
      }
      return *this;
    }

    template<typename Range>
    discrete_domain_t& operator+=(Range es) {
      if (!this->_is_top)
        for (auto e: es)
          this->_set += e;
      return *this;
    }

    discrete_domain_t operator+(Element s) {
      discrete_domain_t r(*this);
      r.operator+=(s);
      return r;
    }

    template<typename Range>
    discrete_domain_t operator+(Range es) {
      discrete_domain_t r(*this);
      r.operator+=(es);
      return r;
    }

    discrete_domain_t& operator-=(Element s) {
      if (!this->_is_top) {
        this->_set -= s;
      }
      return *this;
    }

    template<typename Range>
    discrete_domain_t& operator-=(Range es) {
      if (!this->_is_top)
        for (auto e: es)
          this->_set -= e;
      return *this;
    }

    discrete_domain_t operator-(Element s) {
      discrete_domain_t r(*this);
      r.operator-=(s);
      return r;
    }

    template<typename Range>
    discrete_domain_t operator-(Range es) {
      discrete_domain_t r(*this);
      r.operator-=(es);
      return r;
    }

    std::size_t size() {
      if (this->_is_top) {
        CRAB_ERROR("Size for discrete domain TOP is undefined");
      } else {
        return this->_set.size();
      }
    }

    iterator begin() {
      if (this->_is_top) {
        CRAB_ERROR("Iterator for discrete domain TOP is undefined");
      } else {
        return this->_set.begin();
      }
    }

    iterator end() {
      if (this->_is_top) {
        CRAB_ERROR("Iterator for discrete domain TOP is undefined");
      } else {
        return this->_set.end();
      }
    }

    void write(crab::crab_os& o) {
      if (this->_is_top) {
	o << "{...}";
      } else if (this->_set.empty()) {
	o << "_|_";
      } else {
	o << this->_set;
      }
    }

  }; // class discrete_domain

} // namespace ikos


//==============================================================================
//         Copyright 2003 - 2012 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2012 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//         Copyright 2012        MetaScale SAS
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#include <nt2/sdk/unit/details/main.hpp>
#include <nt2/sdk/unit/details/ulp.hpp>
#include <nt2/sdk/unit/tests/ulp.hpp>
#include <nt2/sdk/unit/io.hpp>
#include <boost/type_traits/is_integral.hpp>
#include <boost/fusion/include/adapt_struct.hpp>
#include <boost/fusion/include/io.hpp>

#include <cmath>
#include <vector>
#include <limits>
#include <typeinfo>
#include <iostream>

struct foo { double d; float f; char c; };
BOOST_FUSION_ADAPT_STRUCT(foo,(double,d)(float,f)(char,c));

using boost::fusion::operator<<;

template<class T> inline bool check_ulp_fundamental()
{
  T value(1), different_value;
  std::vector< nt2::details::failed_value<T,T> >  fails;

  different_value = value + 16*std::numeric_limits<T>::epsilon();

  std::cout << "Check for: " << typeid(T).name() << "\t";
  double u0 = 0;
  bool ok1 = nt2::unit::max_ulp(value,value,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u1 = 0;
  bool ok2 = nt2::unit::max_ulp(value,different_value,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8) ? "OK" : "NOT OK") << "\n";

  return ok1 && ok2 && !u0 && u1==8;
}

inline bool check_ulp_fusion_sequence()
{
  foo value = { 1.,1.f,'Z' };
  foo different_value = {1.,1.f,'Z'};
  std::vector< nt2::details::failed_value<foo,foo> >  fails;

  different_value.f = value.f + 16*std::numeric_limits<float>::epsilon();

  std::cout << "Check for: foo\t";
  double u0 = 0;
  bool ok1 = nt2::unit::max_ulp(value,value,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u1 = 0;
  bool ok2 = nt2::unit::max_ulp(value,different_value,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8) ? "OK" : "NOT OK") << "\n";

  return ok1 && ok2 && !u0 && u1==8;
}

template<class T> inline bool check_ulp_sequence()
{
  std::vector<T> values(17), different_values(17);
  std::vector< nt2::details::failed_value<T,T> >  fails;

  for(std::size_t i=0;i<values.size();++i)
    values[i] = different_values[i] = T(1);

  different_values[9] += 16*std::numeric_limits<T>::epsilon();
  different_values[7] += 5*std::numeric_limits<T>::epsilon();

  std::cout << "Check for: " << typeid(T).name() << "\t";
  double u0 = 0;
  bool ok1 = nt2::unit::max_ulp(values,values,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u1 = 0;
  bool ok2 = nt2::unit::max_ulp(values,different_values,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8) ? "OK" : "NOT OK")  << "\n";

  return ok1 && ok2 && (!u0 && u1==8);
}

template<class T> inline bool check_ulp_singular_sequence()
{
  std::vector<T> values(1);
  T different_values(1);

  std::vector< nt2::details::failed_value<T,T> >  fails;

  values[0] = T(1);

  different_values += 16*std::numeric_limits<T>::epsilon();

  std::cout << "Check for: " << typeid(T).name() << "\t";
  double u0 = 0;
  bool ok1 = nt2::unit::max_ulp(values,values,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u1 = 0;
  bool ok2 = nt2::unit::max_ulp(values,different_values,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u2 = 0;
  bool ok3 = nt2::unit::max_ulp(different_values,values,0.5,fails, u2);
  std::cout << "max_ulp(b,a) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8 && u2==8) ? "OK" : "NOT OK")  << "\n";
  return ok1 && ok2 && ok3 && (!u0 && u1==8 && u2==8);
}

inline bool check_ulp_sequence_fusionsequence()
{
  foo values            = {1.,1.f,'A'};
  foo different_values  = {1.,1.f,'I'};
  std::vector<foo> seq(2), diff_seq(2);

  typedef nt2::details::max_ulp_ < std::vector<foo>, std::vector<foo> >::failure_type  f_t;
  std::vector< f_t >  fails;

  seq[0] = seq[1] = values;
  diff_seq[0] = values;
  diff_seq[1] = different_values;

  double u0 = 0;
  double u1 = 0;

  std::cout << "Check for foo :\t";
  bool ok1 = nt2::unit::max_ulp(seq,seq,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  bool ok2 = nt2::unit::max_ulp(seq,diff_seq,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8) ? "OK" : "NOT OK")  << "\n";
  return ok1 && ok2 && (!u0 && u1==8);
}

template<class T> inline bool check_ulp_sequence_sequence()
{
  std::vector<T> values(3), different_values(3);
  std::vector< std::vector<T> > seq(2), diff_seq(2);

  typedef typename nt2::details::max_ulp_ < std::vector< std::vector<T> >
                                          , std::vector< std::vector<T> >
                                          >::failure_type  f_t;
  std::vector< f_t >  fails;

  for(std::size_t i=0;i<values.size();++i)
    values[i] = different_values[i] = T(1);

  different_values[0] += 16*std::numeric_limits<T>::epsilon();
  different_values[2] += 5*std::numeric_limits<T>::epsilon();

  seq[0] = seq[1] = values;
  diff_seq[0] = values;
  diff_seq[1] = different_values;

  double u0 = 0;
  double u1 = 0;

  std::cout << "Check for: " << typeid(T).name() << "\t";
  bool ok1 = nt2::unit::max_ulp(seq,seq,0.5,fails,u0);
  std::cout << "max_ulp(a,a) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  bool ok2 = nt2::unit::max_ulp(seq,diff_seq,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";
  std::cout << ((!u0 && u1==8) ? "OK" : "NOT OK")  << "\n";
  return ok1 && ok2 && (!u0 && u1==8);
}

template<class T> inline bool check_ulp_invalid()
{
  T value(0), different_value;
  std::vector< nt2::details::failed_value<T,T> >  fails;

  different_value = std::numeric_limits<T>::quiet_NaN();

  std::cout << "Check for: " << typeid(T).name() << "\t";
  double u0 = 0;
  bool ok1 = nt2::unit::max_ulp(value,different_value,0.5,fails, u0);
  std::cout << "max_ulp(a,b) = " << u0 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u1 = 0;
  bool ok2 = nt2::unit::max_ulp(different_value,value,0.5,fails, u1);
  std::cout << "max_ulp(a,b) = " << u1 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";

  fails.clear();
  different_value = std::numeric_limits<T>::infinity();

  std::cout << "Check for: " << typeid(T).name() << "\t";
  double u2 = 0;
  bool ok3 = nt2::unit::max_ulp(value,different_value,0.5,fails, u2);
  std::cout << "max_ulp(a,b) = " << u2 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\t";

  fails.clear();
  double u3 = 0;
  bool ok4 = nt2::unit::max_ulp(different_value,value,0.5,fails, u3);
  std::cout << "max_ulp(a,b) = " << u3 << "\t";
  std::cout << "nb of fails: " << fails.size() << "\n";

  bool stat =   (u0 != u0) && (u1 != u1)
            &&  (u2 == different_value) && (u3 == different_value)
            &&  ok1 && ok2 && ok3 && ok4;

  std::cout << ( stat ? "OK" : "NOT OK") << "\n";

  return stat;
}

NT2_UNIT_MAIN_SPEC int NT2_UNIT_MAIN(int, char**)
{
  std::cout << "Check for basic types\n";
  bool  check;

  check =   check_ulp_fundamental<double>()
        &&  check_ulp_fundamental<float>();

  std::cout << "\nCheck for Fusion Sequence\n";
  check = check_ulp_fusion_sequence() && check;

  std::cout << "\nCheck for Sequence\n";
  check =   check
        &&  check_ulp_sequence<double>()
        &&  check_ulp_sequence<float>();

  std::cout << "\nCheck for Singular Sequence\n";
  check =   check_ulp_singular_sequence<double>()
        &&  check_ulp_singular_sequence<float>()
        &&  check;

  std::cout << "\nCheck for Sequence of Sequence\n";
  check =   check_ulp_sequence_sequence<double>()
        &&  check_ulp_sequence_sequence<float>()
        &&  check;

  std::cout << "\nCheck for Sequence of Fusion Sequence\n";
  check =   check_ulp_sequence_fusionsequence()
        &&  check;

  std::cout << "\nCheck for Nan\n";
  check =   check_ulp_invalid<double>()
        &&  check_ulp_invalid<float>()
        &&  check;

  return check ? 0 : 1;
}


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

// Copyright (c) 2013 Barend Gehrels, Amsterdam, the Netherlands.
// Copyright (c) 2013 Bruno Lalande, Paris, France.
// Copyright (c) 2013 Mateusz Loskot, London, UK.
// Copyright (c) 2013 Adam Wulkiewicz, Lodz, Poland.

// 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_ZOOM_TO_ROBUST_HPP
#define BOOST_GEOMETRY_ALGORITHMS_DETAIL_ZOOM_TO_ROBUST_HPP

#include <cstddef>

#include <boost/type_traits.hpp>

#include <boost/geometry/algorithms/envelope.hpp>
#include <boost/geometry/algorithms/expand.hpp>
#include <boost/geometry/algorithms/detail/recalculate.hpp>

namespace boost { namespace geometry
{

#ifndef DOXYGEN_NO_DETAIL
namespace detail { namespace zoom_to_robust
{

template <typename Box, std::size_t Dimension>
struct get_max_size
{
    static inline typename coordinate_type<Box>::type apply(Box const& box)
    {
        typename coordinate_type<Box>::type s
            = geometry::math::abs(geometry::get<1, Dimension>(box) - geometry::get<0, Dimension>(box));

        return (std::max)(s, get_max_size<Box, Dimension - 1>::apply(box));
    }
};

template <typename Box>
struct get_max_size<Box, 0>
{
    static inline typename coordinate_type<Box>::type apply(Box const& box)
    {
        return geometry::math::abs(geometry::get<1, 0>(box) - geometry::get<0, 0>(box));
    }
};

template <typename FpPoint, typename IntPoint, typename CalculationType>
struct rescale_strategy
{
    typedef typename geometry::coordinate_type<IntPoint>::type output_ct;

    rescale_strategy(FpPoint const& fp_min, IntPoint const& int_min, CalculationType const& the_factor)
        : m_fp_min(fp_min)
        , m_int_min(int_min)
        , m_multiplier(the_factor)
    {
    }

    template <std::size_t Dimension, typename Value>
    inline output_ct apply(Value const& value) const
    {
        // a + (v-b)*f
        CalculationType const a = static_cast<CalculationType>(get<Dimension>(m_int_min));
        CalculationType const b = static_cast<CalculationType>(get<Dimension>(m_fp_min));
        CalculationType const result = a + (value - b) * m_multiplier;
        return static_cast<output_ct>(result);
    }

    FpPoint const& m_fp_min;
    IntPoint const& m_int_min;
    CalculationType m_multiplier;
};

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

template <typename Box>
inline typename coordinate_type<Box>::type get_max_size(Box const& box)
{
    return detail::zoom_to_robust::get_max_size<Box, dimension<Box>::value - 1>::apply(box);
}

#ifndef DOXYGEN_NO_DISPATCH
namespace dispatch
{

template <typename CoordinateType, typename IsFloatingPoint>
struct robust_type
{
};

template <typename CoordinateType>
struct robust_type<CoordinateType, boost::false_type>
{
    typedef CoordinateType type;
};

template <typename CoordinateType>
struct robust_type<CoordinateType, boost::true_type>
{
    typedef int type; // long long?
};

template <typename IsFloatingPoint>
struct zoom_to_robust
{
    template
    <
        typename Geometry1, typename Geometry2, typename Geometry3,
        typename Geometry4, typename Geometry5, typename Geometry6,
        typename GeometryOut
    >
    static inline void apply(Geometry1 const& g1, Geometry2 const& g2, Geometry3 const& g3,
          Geometry4 const& g4, Geometry5 const& g5, Geometry6 const& g6,
          GeometryOut& og1, GeometryOut& og2, GeometryOut& og3,
          GeometryOut& og4, GeometryOut& og5, GeometryOut& og6)
    {
        // By default, just convert these geometries (until now: points or maybe segments)
        geometry::convert(g1, og1);
        geometry::convert(g2, og2);
        geometry::convert(g3, og3);
        geometry::convert(g4, og4);
        geometry::convert(g5, og5);
        geometry::convert(g6, og6);
    }
};

template <>
struct zoom_to_robust<boost::true_type>
{
    template
    <
        typename Geometry1, typename Geometry2, typename Geometry3,
        typename Geometry4, typename Geometry5, typename Geometry6,
        typename GeometryOut
    >
    static inline void apply(Geometry1 const& g1, Geometry2 const& g2, Geometry3 const& g3,
          Geometry4 const& g4, Geometry5 const& g5, Geometry6 const& g6,
          GeometryOut& og1, GeometryOut& og2, GeometryOut& og3,
          GeometryOut& og4, GeometryOut& og5, GeometryOut& og6)
    {
        typedef typename point_type<Geometry1>::type point1_type;

        // Get the envelop of inputs
        model::box<point1_type> env;
        geometry::assign_inverse(env);
        geometry::expand(env, g1);
        geometry::expand(env, g2);
        geometry::expand(env, g3);
        geometry::expand(env, g4);
        geometry::expand(env, g5);
        geometry::expand(env, g6);

        // Scale this to integer-range
        typename geometry::coordinate_type<point1_type>::type diff = get_max_size(env);
        double range = 1000000000.0; // Define a large range to get precise integer coordinates
        double factor = double(int(range / double(diff)));

        // Assign input/output minimal points
        point1_type min_point1;
        detail::assign_point_from_index<0>(env, min_point1);

        typedef typename point_type<GeometryOut>::type point2_type;
        point2_type min_point2;
        assign_values(min_point2, int(-range/2.0), int(-range/2.0));

        detail::zoom_to_robust::rescale_strategy<point1_type, point2_type, double> strategy(min_point1, min_point2, factor);

        geometry::recalculate(og1, g1, strategy);
        geometry::recalculate(og2, g2, strategy);
        geometry::recalculate(og3, g3, strategy);
        geometry::recalculate(og4, g4, strategy);
        geometry::recalculate(og5, g5, strategy);
        geometry::recalculate(og6, g6, strategy);
    }
};

} // namespace dispatch
#endif // DOXYGEN_NO_DISPATCH

template <typename CoordinateType>
struct robust_type
{
    typedef typename dispatch::robust_type
        <
            CoordinateType,
            typename boost::is_floating_point<CoordinateType>::type
        >::type type;
};

template <typename Geometry1, typename Geometry2>
inline void zoom_to_robust(Geometry1 const& g1a, Geometry1 const& g1b, Geometry2& g2a, Geometry2& g2b)
{
    typedef typename point_type<Geometry1>::type point1_type;
    typedef typename point_type<Geometry2>::type point2_type;

    point1_type min_point1;
    point2_type min_point2;

    // Get the envelop of inputs
    model::box<point1_type> env;
    envelope(g1a, env);
    expand(env, g1b);

    // Scale this to integer-range
    typename coordinate_type<point1_type>::type diff = get_max_size(env);
    double range = 1000000000.0; // Define a large range to get precise integer coordinates
    double factor = range / diff;

    // Assign input/output minimal points
    detail::assign_point_from_index<0>(env, min_point1);
    assign_values(min_point2, int(-range/2.0), int(-range/2.0));

    detail::zoom_to_robust::rescale_strategy<point1_type, point2_type, double> strategy(min_point1, min_point2, factor);
    recalculate(g2a, g1a, strategy);
    recalculate(g2b, g1b, strategy);
}

template
<
    typename Geometry1, typename Geometry2, typename Geometry3,
    typename Geometry4, typename Geometry5, typename Geometry6,
    typename GeometryOut
>
void zoom_to_robust(Geometry1 const& g1, Geometry2 const& g2, Geometry3 const& g3,
          Geometry4 const& g4, Geometry5 const& g5, Geometry6 const& g6,
          GeometryOut& og1, GeometryOut& og2, GeometryOut& og3,
          GeometryOut& og4, GeometryOut& og5, GeometryOut& og6)
{
    // Make FP robust (so dispatch to true), so float and double
    // Other types as int, boost::rational, or ttmath are considered to be already robust
    dispatch::zoom_to_robust
        <
            typename boost::is_floating_point
                <
                    typename geometry::coordinate_type<Geometry1>::type
                >::type
        >::apply(g1, g2, g3, g4, g5, g6, og1, og2, og3, og4, og5, og6);
}

}} // namespace boost::geometry

#endif // BOOST_GEOMETRY_ALGORITHMS_DETAIL_ZOOM_TO_ROBUST_HPP


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

#include <streamer/config.hpp>
#include "../natives.h"

#include "../core.h"
#include "../utility.h"

#include <streamer/miscellaneous.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/geometries.hpp>
#include <boost/intrusive_ptr.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/tuple/tuple.hpp>
#include <boost/unordered_map.hpp>
#include <boost/unordered_set.hpp>
#include <boost/variant.hpp>

#include <Eigen/Core>

#include <a_actor.h>
#include <a_objects.h>
#include <a_players.h>

STREAMER_BEGIN_NS

int Streamer_GetDistanceToItem(float x, float y, float z, int type, int id, float &distance, int dimensions) {
	Eigen::Vector3f position = Eigen::Vector3f::Zero();
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(id);
		if (o != core->getData()->objects.end()) {
			if (o->second->attach) {
				position = o->second->attach->position;
			} else {
				position = o->second->position;
			}
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(id);
		if (p != core->getData()->pickups.end()) {
			position = p->second->position;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(id);
		if (c != core->getData()->checkpoints.end()) {
			position = c->second->position;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(id);
		if (r != core->getData()->raceCheckpoints.end()) {
			position = r->second->position;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(id);
		if (m != core->getData()->mapIcons.end()) {
			position = m->second->position;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
		if (t != core->getData()->textLabels.end()) {
			if (t->second->attach) {
				position = t->second->attach->position;
			} else {
				position = t->second->position;
			}
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_AREA:
	{
		boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.find(id);
		if (a != core->getData()->areas.end())
		{
			boost::variant<Polygon2d, Box2d, Box3d, Eigen::Vector2f, Eigen::Vector3f> areaPosition;
			if (a->second->attach) {
				areaPosition = a->second->position;
			} else {
				areaPosition = a->second->position;
			}

			switch (a->second->type)
			{
			case STREAMER_AREA_TYPE_CIRCLE:
			case STREAMER_AREA_TYPE_CYLINDER:
			{
				distance = static_cast<float>(boost::geometry::distance(Eigen::Vector2f(x, y), boost::get<Eigen::Vector2f>(areaPosition)));
				return 1;
			}
			case STREAMER_AREA_TYPE_SPHERE:
			{
				position = boost::get<Eigen::Vector3f>(areaPosition);
				break;
			}
			case STREAMER_AREA_TYPE_RECTANGLE:
			{
				Eigen::Vector2f centroid = boost::geometry::return_centroid<Eigen::Vector2f>(boost::get<Box2d>(areaPosition));
				distance = static_cast<float>(boost::geometry::distance(Eigen::Vector2f(x, y), centroid));
				return 1;
			}
			case STREAMER_AREA_TYPE_CUBOID:
			{
				Eigen::Vector3f centroid = boost::geometry::return_centroid<Eigen::Vector3f>(boost::get<Box3d>(areaPosition));
				distance = static_cast<float>(boost::geometry::distance(Eigen::Vector3f(x, y, z), centroid));
				return 1;
			}
			case STREAMER_AREA_TYPE_POLYGON:
			{
				Eigen::Vector2f centroid = boost::geometry::return_centroid<Eigen::Vector2f>(boost::get<Polygon2d>(areaPosition));
				distance = static_cast<float>(boost::geometry::distance(Eigen::Vector2f(x, y), centroid));
				return 1;
			}
			}
		}
		return 0;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(id);
		if (a != core->getData()->actors.end()) {
			position = a->second->position;
			break;
		}
		return 0;
	}
	default:
	{
		Utility::logError("Streamer_GetDistanceToItem: Invalid type specified");
		return 0;
	}
	}
	switch (dimensions)
	{
		case 2:
		{
			distance = static_cast<float>(boost::geometry::distance(Eigen::Vector2f(x, y), Eigen::Vector2f(position[0], position[1])));
			return 1;
		}
		case 3:
		{
			distance = static_cast<float>(boost::geometry::distance(Eigen::Vector3f(x, y, z), position));
			return 1;
		}
		default:
		{
			Utility::logError("Streamer_GetDistanceToItem: Invalid number of dimensions specified (outside range of 2-3).");
			return 0;
		}
	}
	return 0;
}

int Streamer_ToggleItem(int playerid, int type, int id, bool toggle) {
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(id);
		if (o != core->getData()->objects.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(o->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(o->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(id);
		if (p != core->getData()->pickups.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(p->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(p->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(id);
		if (c != core->getData()->checkpoints.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(c->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(c->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(id);
		if (r != core->getData()->raceCheckpoints.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(r->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(r->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(id);
		if (m != core->getData()->mapIcons.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(m->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(m->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
		if (t != core->getData()->textLabels.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(t->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(t->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_AREA:
	{
		boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.find(id);
		if (a != core->getData()->areas.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(a->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(a->second->players, playerid));
			}
		}
		break;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(id);
		if (a != core->getData()->actors.end()) {
			if (!toggle) {
				return static_cast<cell>(Utility::removeFromContainer(a->second->players, playerid));
			} else {
				return static_cast<cell>(Utility::addToContainer(a->second->players, playerid));
			}
		}
		break;
	}
	default:
	{
		Utility::logError("Streamer_ToggleItem: Invalid type specified.");
		return 0;
	}
	}
	return 0;
}

int Streamer_IsToggleItem(int playerid, int type, int id) {
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(id);
		if (o != core->getData()->objects.end()) {
			return static_cast<cell>(Utility::isInContainer(o->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(id);
		if (p != core->getData()->pickups.end()) {
			return static_cast<cell>(Utility::isInContainer(p->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(id);
		if (c != core->getData()->checkpoints.end()) {
			return static_cast<cell>(Utility::isInContainer(c->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(id);
		if (r != core->getData()->raceCheckpoints.end()) {
			return static_cast<cell>(Utility::isInContainer(r->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(id);
		if (m != core->getData()->mapIcons.end()) {
			return static_cast<cell>(Utility::isInContainer(m->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
		if (t != core->getData()->textLabels.end()) {
			return static_cast<cell>(Utility::isInContainer(t->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_AREA:
	{
		boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.find(id);
		if (a != core->getData()->areas.end()) {
			return static_cast<cell>(Utility::isInContainer(a->second->players, playerid));
		}
		break;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(id);
		if (a != core->getData()->actors.end()) {
			return static_cast<cell>(Utility::isInContainer(a->second->players, playerid));
		}
		break;
	}
	default:
	{
		Utility::logError("Streamer_IsToggleItem: Invalid type specified.");
		return 0;
	}
	}
	return 0;
}

int Streamer_ToggleAllItems(int playerid, int type, bool toggle, boost::unordered_set<int> exceptions) {
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		for (boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.begin(); o != core->getData()->objects.end(); ++o) {
			boost::unordered_set<int>::iterator e = exceptions.find(o->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(o->second->players, playerid);
				} else {
					Utility::addToContainer(o->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_PICKUP:
	{
		for (boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.begin(); p != core->getData()->pickups.end(); ++p) {
			boost::unordered_set<int>::iterator e = exceptions.find(p->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(p->second->players, playerid);
				} else {
					Utility::addToContainer(p->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_CP:
	{
		for (boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.begin(); c != core->getData()->checkpoints.end(); ++c) {
			boost::unordered_set<int>::iterator e = exceptions.find(c->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(c->second->players, playerid);
				} else {
					Utility::addToContainer(c->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		for (boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.begin(); r != core->getData()->raceCheckpoints.end(); ++r)
		{
			boost::unordered_set<int>::iterator e = exceptions.find(r->first);
			if (e == exceptions.end())
			{
				if (!toggle) {
					Utility::removeFromContainer(r->second->players, playerid);
				} else {
					Utility::addToContainer(r->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		for (boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.begin(); m != core->getData()->mapIcons.end(); ++m) {
			boost::unordered_set<int>::iterator e = exceptions.find(m->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(m->second->players, playerid);
				} else {
					Utility::addToContainer(m->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		for (boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.begin(); t != core->getData()->textLabels.end(); ++t) {
			boost::unordered_set<int>::iterator e = exceptions.find(t->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(t->second->players, playerid);
				} else {
					Utility::addToContainer(t->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_AREA:
	{
		for (boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.begin(); a != core->getData()->areas.end(); ++a) {
			boost::unordered_set<int>::iterator e = exceptions.find(a->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(a->second->players, playerid);
				} else {
					Utility::addToContainer(a->second->players, playerid);
				}
			}
		}
		return 1;
	}
	case STREAMER_TYPE_ACTOR:
	{
		for (boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.begin(); a != core->getData()->actors.end(); ++a) {
			boost::unordered_set<int>::iterator e = exceptions.find(a->first);
			if (e == exceptions.end()) {
				if (!toggle) {
					Utility::removeFromContainer(a->second->players, playerid);
				} else {
					Utility::addToContainer(a->second->players, playerid);
				}
			}
		}
		return 1;
	}
	default:
	{
		Utility::logError("Streamer_ToggleAllItems: Invalid type specified.");
		return 0;
	}
	}
	return 0;
}

int Streamer_GetItemInternalID(int playerid, int type, int streamerid) {
	switch (type)
	{
	case STREAMER_TYPE_PICKUP:
	{
		int pickupId = static_cast<int>(streamerid);
		Item::SharedPickup p = core->getData()->pickups[pickupId];
		for (boost::unordered_set<int>::const_iterator w = p->worlds.begin(); w != p->worlds.end(); ++w)
		{
			boost::unordered_map<std::pair<int, int>, int>::iterator i = core->getData()->internalPickups.find(std::make_pair(pickupId, *w));
			if (i != core->getData()->internalPickups.end())
			{
				return static_cast<cell>(i->second);
			}
		}
		return INVALID_PICKUP_ID;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, int>::iterator i = core->getData()->internalActors.find(static_cast<int>(streamerid));
		if (i != core->getData()->internalActors.end())
		{
			return static_cast<cell>(i->second);
		}
		return INVALID_ACTOR_ID;
	}
	}
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(static_cast<int>(playerid));
	if (p != core->getData()->players.end())
	{
		switch (static_cast<int>(type))
		{
		case STREAMER_TYPE_OBJECT:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalObjects.find(static_cast<int>(streamerid));
			if (i != p->second.internalObjects.end())
			{
				return static_cast<cell>(i->second);
			}
			return INVALID_OBJECT_ID;
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint == static_cast<int>(streamerid))
			{
				return 1;
			}
			return -1;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint == static_cast<int>(streamerid))
			{
				return 1;
			}
			return -1;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalMapIcons.find(static_cast<int>(streamerid));
			if (i != p->second.internalMapIcons.end())
			{
				return static_cast<cell>(i->second);
			}
			return -1;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.find(static_cast<int>(streamerid));
			if (i != p->second.internalTextLabels.end())
			{
				return static_cast<cell>(i->second);
			}
			return INVALID_3DTEXT_ID;
		}
		case STREAMER_TYPE_AREA:
		{
			boost::unordered_set<int>::iterator i = p->second.internalAreas.find(static_cast<int>(streamerid));
			if (i != p->second.internalAreas.end())
			{
				return *i;
			}
			return INVALID_STREAMER_ID;
		}
		default:
		{
			Utility::logError("Streamer_GetItemInternalID: Invalid type specified.");
			return -1;
		}
		}
	}
	return -1;
}

int Streamer_GetItemStreamerID(int playerid, int type, int internalid) {
	switch (type)
	{
	case STREAMER_TYPE_PICKUP:
	{
		for (boost::unordered_map<std::pair<int, int>, int>::iterator i = core->getData()->internalPickups.begin(); i != core->getData()->internalPickups.end(); ++i)
		{
			if (i->second == static_cast<int>(internalid))
			{
				return i->first.first;
			}
		}
		return INVALID_STREAMER_ID;
	}
	case STREAMER_TYPE_ACTOR:
	{
		for (boost::unordered_map<int, int>::iterator i = core->getData()->internalActors.begin(); i != core->getData()->internalActors.end(); ++i)
		{
			if (i->second == static_cast<int>(internalid))
			{
				return i->first;
			}
		}
		return INVALID_STREAMER_ID;
	}
	}
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(static_cast<int>(playerid));
	if (p != core->getData()->players.end())
	{
		switch (static_cast<int>(type))
		{
		case STREAMER_TYPE_OBJECT:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalObjects.begin(); i != p->second.internalObjects.end(); ++i)
			{
				if (i->second == static_cast<int>(internalid))
				{
					return i->first;
				}
			}
			return INVALID_STREAMER_ID;
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint == static_cast<int>(internalid))
			{
				return 1;
			}
			return INVALID_STREAMER_ID;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint == static_cast<int>(internalid))
			{
				return 1;
			}
			return INVALID_STREAMER_ID;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalMapIcons.begin(); i != p->second.internalMapIcons.end(); ++i)
			{
				if (i->second == static_cast<int>(internalid))
				{
					return i->first;
				}
			}
			return INVALID_STREAMER_ID;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.begin(); i != p->second.internalTextLabels.end(); ++i)
			{
				if (i->second == static_cast<int>(internalid))
				{
					return i->first;
				}
			}
			return INVALID_STREAMER_ID;
		}
		case STREAMER_TYPE_AREA:
		{
			boost::unordered_set<int>::iterator i = p->second.internalAreas.find(static_cast<int>(internalid));
			if (i != p->second.internalAreas.end())
			{
				return *i;
			}
			return INVALID_STREAMER_ID;
		}
		default:
		{
			Utility::logError("Streamer_GetItemStreamerID: Invalid type specified.");
			return INVALID_STREAMER_ID;
		}
		}
	}
	return INVALID_STREAMER_ID;
}

int Streamer_IsItemVisible(int playerid, int type, int id) {
	switch (type)
	{
	case STREAMER_TYPE_PICKUP:
	{
		int pickupId = static_cast<int>(id);
		Item::SharedPickup p = core->getData()->pickups[pickupId];
		for (boost::unordered_set<int>::const_iterator w = p->worlds.begin(); w != p->worlds.end(); ++w)
		{
			boost::unordered_map<std::pair<int, int>, int>::iterator i = core->getData()->internalPickups.find(std::make_pair(pickupId, *w));
			if (i != core->getData()->internalPickups.end())
			{
				return 1;
			}
		}
		return 0;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, int>::iterator i = core->getData()->internalActors.find(id);
		if (i != core->getData()->internalActors.end())
		{
			return 1;
		}
		return 0;
	}
	}
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(playerid);
	if (p != core->getData()->players.end())
	{
		switch (type)
		{
		case STREAMER_TYPE_OBJECT:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalObjects.find(id);
			if (i != p->second.internalObjects.end())
			{
				return 1;
			}
			return 0;
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint == id)
			{
				return 1;
			}
			return 0;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint == id)
			{
				return 1;
			}
			return 0;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalMapIcons.find(id);
			if (i != p->second.internalMapIcons.end())
			{
				return 1;
			}
			return 0;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.find(id);
			if (i != p->second.internalTextLabels.end())
			{
				return 1;
			}
		}
		case STREAMER_TYPE_AREA:
		{
			boost::unordered_set<int>::iterator i = p->second.internalAreas.find(id);
			if (i != p->second.internalAreas.end())
			{
				return 1;
			}
			return 0;
		}
		default:
		{
			Utility::logError("Streamer_IsItemVisible: Invalid type specified.");
			return 0;
		}
		}
	}
	return 0;
}

int Streamer_DestroyAllVisibleItems(int playerid, int type, bool serverWide) {
	// TODO: <sphinx> amx
	AMX* amx = nullptr;

	switch (type)
	{
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<std::pair<int, int>, int>::iterator i = core->getData()->internalPickups.begin();
		while (i != core->getData()->internalPickups.end())
		{
			boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(i->first.first);
			if (serverWide || (p != core->getData()->pickups.end() && p->second->amx == amx))
			{
				sampgdk::DestroyPickup(i->second);
				i = core->getData()->internalPickups.erase(i);
			}
			else
			{
				++i;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, int>::iterator i = core->getData()->internalActors.begin();
		while (i != core->getData()->internalActors.end())
		{
			boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(i->first);
			if (serverWide || (a != core->getData()->actors.end() && a->second->amx == amx))
			{
				sampgdk::DestroyActor(i->second);
				i = core->getData()->internalActors.erase(i);
			}
			else
			{
				++i;
			}
		}
		return 1;
	}
	}
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(playerid);
	if (p != core->getData()->players.end())
	{
		switch (type)
		{
		case STREAMER_TYPE_OBJECT:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalObjects.begin();
			while (i != p->second.internalObjects.end())
			{
				boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(i->first);
				if (serverWide || (o != core->getData()->objects.end() && o->second->amx == amx))
				{
					sampgdk::DestroyPlayerObject(p->first, i->second);
					i = p->second.internalObjects.erase(i);
				}
				else
				{
					++i;
				}
			}
			return 1;
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint)
			{
				boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(p->second.visibleCheckpoint);
				if (serverWide || (c != core->getData()->checkpoints.end() && c->second->amx == amx))
				{
					sampgdk::DisablePlayerCheckpoint(p->first);
					p->second.activeCheckpoint = 0;
					p->second.visibleCheckpoint = 0;
					return 1;
				}
			}
			return 0;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint)
			{
				boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(p->second.visibleRaceCheckpoint);
				if (serverWide || (r != core->getData()->raceCheckpoints.end() && r->second->amx == amx))
				{
					sampgdk::DisablePlayerRaceCheckpoint(p->first);
					p->second.activeRaceCheckpoint = 0;
					p->second.visibleRaceCheckpoint = 0;
					return 1;
				}
			}
			return 0;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalMapIcons.begin();
			while (i != p->second.internalMapIcons.end())
			{
				boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(i->first);
				if (serverWide || (m != core->getData()->mapIcons.end() && m->second->amx == amx))
				{
					sampgdk::RemovePlayerMapIcon(p->first, i->second);
					i = p->second.internalMapIcons.erase(i);
				}
				else
				{
					++i;
				}
			}
			return 1;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.begin();
			while (i != p->second.internalTextLabels.end())
			{
				boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(i->first);
				if (serverWide || (t != core->getData()->textLabels.end() && t->second->amx == amx))
				{
					sampgdk::DeletePlayer3DTextLabel(p->first, i->second);
					i = p->second.internalTextLabels.erase(i);
				}
				else
				{
					++i;
				}
			}
			return 1;
		}
		case STREAMER_TYPE_AREA:
		{
			boost::unordered_set<int>::iterator i = p->second.internalAreas.begin();
			while (i != p->second.internalAreas.end())
			{
				boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.find(*i);
				if (serverWide || (a != core->getData()->areas.end() && a->second->amx == amx))
				{
					i = p->second.internalAreas.erase(i);
				}
				else
				{
					++i;
				}
			}
			return 1;
		}
		default:
		{
			Utility::logError("Streamer_DestroyAllVisibleItems: Invalid type specified.");
			return 0;
		}
		}
	}
	return 0;
}

int Streamer_CountVisibleItems(int playerid, int type, bool serverWide) {
	// TODO: <sphinx> amx
	AMX* amx = nullptr;

	switch (type)
	{
	case STREAMER_TYPE_PICKUP:
	{
		return static_cast<cell>(core->getData()->internalPickups.size());
	}
	case STREAMER_TYPE_ACTOR:
	{
		return static_cast<cell>(core->getData()->internalActors.size());
	}
	}
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(playerid);
	if (p != core->getData()->players.end())
	{
		switch (type)
		{
		case STREAMER_TYPE_OBJECT:
		{
			if (serverWide) {
				return static_cast<cell>(p->second.internalObjects.size());
			} else {
				int count = 0;
				for (boost::unordered_map<int, int>::iterator o = p->second.internalObjects.begin(); o != p->second.internalObjects.end(); ++o) {
					boost::unordered_map<int, Item::SharedObject>::iterator q = core->getData()->objects.find(o->first);
					if (q != core->getData()->objects.end() && q->second->amx == amx) {
						++count;
					}
				}
				return count;
			}
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint)
			{
				boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(p->second.visibleCheckpoint);
				if (serverWide || (c != core->getData()->checkpoints.end() && c->second->amx == amx))
				{
					return 1;
				}
				return 0;
			}
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint)
			{
				boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(p->second.visibleRaceCheckpoint);
				if (serverWide || (r != core->getData()->raceCheckpoints.end() && r->second->amx == amx))
				{
					return 1;
				}
			}
			return 0;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			if (serverWide) {
				return p->second.internalMapIcons.size();
			} else {
				int count = 0;
				for (boost::unordered_map<int, int>::iterator m = p->second.internalMapIcons.begin(); m != p->second.internalObjects.end(); ++m) {
					boost::unordered_map<int, Item::SharedMapIcon>::iterator n = core->getData()->mapIcons.find(m->first);
					if (n != core->getData()->mapIcons.end() && n->second->amx == amx) {
						++count;
					}
				}
				return count;
			}
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			if (serverWide)
			{
				return static_cast<cell>(p->second.internalTextLabels.size());
			}
			else
			{
				int count = 0;
				for (boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.begin(); i != p->second.internalTextLabels.end(); ++i) {
					boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(i->first);
					if (t != core->getData()->textLabels.end() && t->second->amx == amx) {
						++count;
					}
				}
				return count;
			}
		}
		case STREAMER_TYPE_AREA:
		{
			if (serverWide) {
				return p->second.internalAreas.size();
			} else {
				int count = 0;
				for (boost::unordered_set<int>::iterator i = p->second.internalAreas.begin(); i != p->second.internalAreas.end(); ++i) {
					boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.find(*i);
					if (a != core->getData()->areas.end() && a->second->amx == amx) {
						++count;
					}
				}
				return count;
			}
		}
		default:
		{
			Utility::logError("Streamer_CountVisibleItems: Invalid type specified.");
			return 0;
		}
		}
	}
	return 0;
}

int Streamer_DestroyAllItems(int type, bool serverWide) {
	// TODO: <sphinx> amx
	AMX* amx = nullptr;

	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.begin();
		while (o != core->getData()->objects.end())
		{
			if (serverWide || o->second->amx == amx) {
				o = Utility::destroyObject(o);
			} else {
				++o;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.begin();
		while (p != core->getData()->pickups.end())
		{
			if (serverWide || p->second->amx == amx) {
				p = Utility::destroyPickup(p);
			} else {
				++p;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.begin();
		while (c != core->getData()->checkpoints.end())
		{
			if (serverWide || c->second->amx == amx) {
				c = Utility::destroyCheckpoint(c);
			} else {
				++c;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.begin();
		while (r != core->getData()->raceCheckpoints.end())
		{
			if (serverWide || r->second->amx == amx) {
				r = Utility::destroyRaceCheckpoint(r);
			} else {
				++r;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.begin();
		while (m != core->getData()->mapIcons.end())
		{
			if (serverWide || m->second->amx == amx) {
				m = Utility::destroyMapIcon(m);
			} else {
				++m;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.begin();
		while (t != core->getData()->textLabels.end())
		{
			if (serverWide || t->second->amx == amx) {
				t = Utility::destroyTextLabel(t);
			} else {
				++t;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_AREA:
	{
		Utility::executeFinalAreaCallbacksForAllAreas(amx, serverWide);
		boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.begin();
		while (a != core->getData()->areas.end())
		{
			if (serverWide || a->second->amx == amx) {
				a = Utility::destroyArea(a);
			} else {
				++a;
			}
		}
		return 1;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.begin();
		while (a != core->getData()->actors.end())
		{
			if (serverWide || a->second->amx == amx) {
				a = Utility::destroyActor(a);
			} else {
				++a;
			}
		}
		return 1;
	}
	default:
	{
		Utility::logError("Streamer_DestroyAllItems: Invalid type specified");
		return 0;
	}
	}
	return 0;
}

int Streamer_CountItems(int type, bool serverWide) {
	// TODO: <sphinx> amx
	AMX* amx = nullptr;

	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->objects.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.begin(); o != core->getData()->objects.end(); ++o)
			{
				if (o->second->amx == amx) {
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_PICKUP:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->pickups.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.begin(); p != core->getData()->pickups.end(); ++p)
			{
				if (p->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_CP:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->checkpoints.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.begin(); c != core->getData()->checkpoints.end(); ++c)
			{
				if (c->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_RACE_CP:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->raceCheckpoints.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.begin(); r != core->getData()->raceCheckpoints.end(); ++r)
			{
				if (r->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->mapIcons.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.begin(); m != core->getData()->mapIcons.end(); ++m)
			{
				if (m->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->textLabels.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.begin(); t != core->getData()->textLabels.end(); ++t)
			{
				if (t->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_AREA:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->areas.size());
		} else {
			int count = 0;
			for (boost::unordered_map<int, Item::SharedArea>::iterator a = core->getData()->areas.begin(); a != core->getData()->areas.end(); ++a)
			{
				if (a->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	case STREAMER_TYPE_ACTOR:
	{
		if (serverWide) {
			return static_cast<cell>(core->getData()->actors.size());
		} else{
			int count = 0;
			for (boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.begin(); a != core->getData()->actors.end(); ++a)
			{
				if (a->second->amx == amx)
				{
					++count;
				}
			}
			return count;
		}
	}
	default:
	{
		Utility::logError("Streamer_CountItems: Invalid type specified.");
		return 0;
	}
	}
	return 0;
}

std::vector<int> Streamer_GetNearbyItems(float x, float y, float z, int type, float prange) {
	Eigen::Vector2f position2D = Eigen::Vector2f(x, y);
	Eigen::Vector3f position3D = Eigen::Vector3f(x, y, z);

	float range = prange*prange;

	std::multimap<float, int> orderedItems;
	std::vector<SharedCell> pointCells;
	core->getGrid()->findMinimalCellsForPoint(position2D, pointCells);

	std::vector<int> finalItems;

	switch (type)
	{
		case STREAMER_TYPE_OBJECT:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedObject>::const_iterator o = (*p)->objects.begin(); o != (*p)->objects.end(); ++o) {
					float distance = 0.0f;
					if (o->second->attach) {
						distance = static_cast<float>(boost::geometry::comparable_distance(position3D, o->second->attach->position));
					} else {
						distance = static_cast<float>(boost::geometry::comparable_distance(position3D, o->second->position));
					}

					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, o->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_PICKUP:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedPickup>::const_iterator q = (*p)->pickups.begin(); q != (*p)->pickups.end(); ++q) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, q->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, q->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_CP:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedCheckpoint>::const_iterator c = (*p)->checkpoints.begin(); c != (*p)->checkpoints.end(); ++c) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, c->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, c->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedRaceCheckpoint>::const_iterator r = (*p)->raceCheckpoints.begin(); r != (*p)->raceCheckpoints.end(); ++r) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, r->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, r->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedMapIcon>::const_iterator m = (*p)->mapIcons.begin(); m != (*p)->mapIcons.end(); ++m) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, m->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, m->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedTextLabel>::const_iterator t = (*p)->textLabels.begin(); t != (*p)->textLabels.end(); ++t) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, t->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, t->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_AREA:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedArea>::const_iterator a = (*p)->areas.begin(); a != (*p)->areas.end(); ++a) {
					boost::variant<Polygon2d, Box2d, Box3d, Eigen::Vector2f, Eigen::Vector3f> position;
					if (a->second->attach) {
						position = a->second->position;
					} else {
						position = a->second->position;
					}

					float distance = 0.0f;
					switch (a->second->type)
					{
						case STREAMER_AREA_TYPE_CIRCLE:
						case STREAMER_AREA_TYPE_CYLINDER:
						{
							distance = static_cast<float>(boost::geometry::distance(position2D, boost::get<Eigen::Vector2f>(position)));
							break;
						}
						case STREAMER_AREA_TYPE_SPHERE:
						{
							distance = static_cast<float>(boost::geometry::comparable_distance(position3D, boost::get<Eigen::Vector3f>(position)));
							break;
						}
						case STREAMER_AREA_TYPE_RECTANGLE:
						{
							Eigen::Vector2f centroid = boost::geometry::return_centroid<Eigen::Vector2f>(boost::get<Box2d>(position));
							distance = static_cast<float>(boost::geometry::comparable_distance(position2D, centroid));
							break;
						}
						case STREAMER_AREA_TYPE_CUBOID:
						{
							Eigen::Vector3f centroid = boost::geometry::return_centroid<Eigen::Vector3f>(boost::get<Box3d>(position));
							distance = static_cast<float>(boost::geometry::comparable_distance(position3D, centroid));
							break;
						}
						case STREAMER_AREA_TYPE_POLYGON:
						{
							Eigen::Vector2f centroid = boost::geometry::return_centroid<Eigen::Vector2f>(boost::get<Polygon2d>(position));
							distance = static_cast<float>(boost::geometry::comparable_distance(position2D, centroid));
							break;
						}
					}
					if (distance < range)
					{
						orderedItems.insert(std::pair<float, int>(distance, a->first));
					}
				}
			}
			break;
		}
		case STREAMER_TYPE_ACTOR:
		{
			for (std::vector<SharedCell>::const_iterator p = pointCells.begin(); p != pointCells.end(); ++p) {
				for (boost::unordered_map<int, Item::SharedActor>::const_iterator a = (*p)->actors.begin(); a != (*p)->actors.end(); ++a) {
					float distance = static_cast<float>(boost::geometry::comparable_distance(position3D, a->second->position));
					if (distance < range) {
						orderedItems.insert(std::pair<float, int>(distance, a->first));
					}
				}
			}
			break;
		}
		default:
		{
			Utility::logError("Streamer_GetNearbyItems: Invalid type specified.");
			return finalItems; // Empty
		}
	}

	for (std::multimap<float, int>::iterator i = orderedItems.begin(); i != orderedItems.end(); ++i) {
		finalItems.push_back(i->second);
	}

	return finalItems;
}

int Streamer_GetItemOffset(int type, int id, float &x, float &y, float &z) {
	Eigen::Vector3f positionOffset = Eigen::Vector3f::Zero();
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(id);
		if (o != core->getData()->objects.end()) {
			positionOffset = o->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(id);
		if (p != core->getData()->pickups.end()) {
			positionOffset = p->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(id);
		if (c != core->getData()->checkpoints.end()) {
			positionOffset = c->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(id);
		if (r != core->getData()->raceCheckpoints.end()) {
			positionOffset = r->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(id);
		if (m != core->getData()->mapIcons.end()) {
			positionOffset = m->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
		if (t != core->getData()->textLabels.end()) {
			positionOffset = t->second->positionOffset;
			break;
		}
		return 0;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(id);
		if (a != core->getData()->actors.end()) {
			positionOffset = a->second->positionOffset;
			break;
		}
		return 0;
	}
	default:
	{
		Utility::logError("Streamer_GetItemPosOffset: Invalid type specified.");
		return 0;
	}
	}
	x = positionOffset[0];
	y = positionOffset[1];
	z = positionOffset[2];
	return 1;
}

int Streamer_SetItemOffset(int type, int id, float x, float y, float z) {
	Eigen::Vector3f positionOffset = Eigen::Vector3f::Zero();
	switch (type)
	{
	case STREAMER_TYPE_OBJECT:
	{
		boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(id);
		if (o != core->getData()->objects.end())
		{
			o->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_PICKUP:
	{
		boost::unordered_map<int, Item::SharedPickup>::iterator p = core->getData()->pickups.find(id);
		if (p != core->getData()->pickups.end())
		{
			p->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_CP:
	{
		boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(id);
		if (c != core->getData()->checkpoints.end())
		{
			c->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_RACE_CP:
	{
		boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator r = core->getData()->raceCheckpoints.find(id);
		if (r != core->getData()->raceCheckpoints.end())
		{
			r->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_MAP_ICON:
	{
		boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(id);
		if (m != core->getData()->mapIcons.end())
		{
			m->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_3D_TEXT_LABEL:
	{
		boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(id);
		if (t != core->getData()->textLabels.end())
		{
			t->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	case STREAMER_TYPE_ACTOR:
	{
		boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(id);
		if (a != core->getData()->actors.end())
		{
			a->second->positionOffset = Eigen::Vector3f(x, y, z);
			return 1;
		}
		break;
	}
	default:
	{
		Utility::logError("Streamer_SetItemPosOffset: Invalid type specified.");
		return 0;
	}
	}
	return 0;
}

std::vector<int> Streamer_GetAllVisibleItems(int playerid, int type) {
	std::multimap<float, int> orderedItems;
	boost::unordered_map<int, Player>::iterator p = core->getData()->players.find(playerid);
	if (p != core->getData()->players.end())
	{
		switch (type)
		{
		case STREAMER_TYPE_OBJECT:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalObjects.begin(); i != p->second.internalObjects.end(); ++i)
			{
				boost::unordered_map<int, Item::SharedObject>::iterator o = core->getData()->objects.find(i->first);
				if (o != core->getData()->objects.end())
				{
					float distance = 0.0f;
					if (o->second->attach)
					{
						distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, o->second->attach->position));
					}
					else
					{
						distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, o->second->position));
					}
					orderedItems.insert(std::pair<float, int>(distance, o->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_PICKUP:
		{
			for (boost::unordered_map<std::pair<int, int>, int>::iterator i = core->getData()->internalPickups.begin(); i != core->getData()->internalPickups.end(); ++i)
			{
				boost::unordered_map<int, Item::SharedPickup>::iterator q = core->getData()->pickups.find(i->first.first);
				if (q != core->getData()->pickups.end())
				{
					float distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, q->second->position));
					orderedItems.insert(std::pair<float, int>(distance, q->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_CP:
		{
			if (p->second.visibleCheckpoint != INVALID_STREAMER_ID)
			{
				boost::unordered_map<int, Item::SharedCheckpoint>::iterator c = core->getData()->checkpoints.find(p->second.visibleCheckpoint);
				if (c != core->getData()->checkpoints.end())
				{
					float distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, c->second->position));
					orderedItems.insert(std::pair<float, int>(distance, c->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_RACE_CP:
		{
			if (p->second.visibleRaceCheckpoint != INVALID_STREAMER_ID)
			{
				boost::unordered_map<int, Item::SharedRaceCheckpoint>::iterator c = core->getData()->raceCheckpoints.find(p->second.visibleRaceCheckpoint);
				if (c != core->getData()->raceCheckpoints.end())
				{
					float distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, c->second->position));
					orderedItems.insert(std::pair<float, int>(distance, c->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_MAP_ICON:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalMapIcons.begin(); i != p->second.internalMapIcons.end(); ++i)
			{
				boost::unordered_map<int, Item::SharedMapIcon>::iterator m = core->getData()->mapIcons.find(i->first);
				if (m != core->getData()->mapIcons.end())
				{
					float distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, m->second->position));
					orderedItems.insert(std::pair<float, int>(distance, m->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_3D_TEXT_LABEL:
		{
			for (boost::unordered_map<int, int>::iterator i = p->second.internalTextLabels.begin(); i != p->second.internalTextLabels.end(); ++i)
			{
				boost::unordered_map<int, Item::SharedTextLabel>::iterator t = core->getData()->textLabels.find(i->first);
				if (t != core->getData()->textLabels.end())
				{
					float distance = 0.0f;
					if (t->second->attach)
					{
						distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, t->second->attach->position));
					}
					else
					{
						distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, t->second->position));
					}
					orderedItems.insert(std::pair<float, int>(distance, t->first));
				}
			}
			break;
		}
		case STREAMER_TYPE_ACTOR:
		{
			for (boost::unordered_map<int, int>::iterator i = core->getData()->internalActors.begin(); i != core->getData()->internalActors.end(); ++i)
			{
				boost::unordered_map<int, Item::SharedActor>::iterator a = core->getData()->actors.find(i->first);
				if (a != core->getData()->actors.end())
				{
					float distance = static_cast<float>(boost::geometry::comparable_distance(p->second.position, a->second->position));
					orderedItems.insert(std::pair<float, int>(distance, a->first));
				}
			}
			break;
		}
		}
	}

	std::vector<int> finalItems;
	for (std::multimap<float, int>::iterator i = orderedItems.begin(); i != orderedItems.end(); ++i) {
		finalItems.push_back(i->second);
	}
	return finalItems;
}

STREAMER_END_NS


#include "ssph.hpp"
#include "kernel_debrun_spiky.hpp"
#include "kernel_poly6.hpp"
#include "kernel_viscosity.hpp"
#include "generic/eigen.hpp"
#include "generic/is_finite.hpp"
#include <iostream>
#include <boost/log/trivial.hpp>

namespace GooBalls {

namespace d2 {

namespace Physics {

SSPH::SSPH(){
    // Decent defaults
    m_kernelDensity = std::make_unique<Poly6>();
    m_kernelPressure = std::make_unique<DebrunSpiky>();
    m_kernelViscosity = std::make_unique<Viscosity>();
}

void SSPH::densityKernel(std::unique_ptr<Kernel>&& k) {
    m_kernelDensity = std::move(k);
}
void SSPH::pressureKernel(std::unique_ptr<Kernel>&& k) {
    m_kernelPressure = std::move(k);
}
void SSPH::viscosityKernel(std::unique_ptr<Kernel>&& k) {
    m_kernelViscosity = std::move(k);
}

bool SSPH::considerBoundary(bool consider) {
    m_consider_boundary = consider;
    return true;
}

bool SSPH::considerBoundary() const {
    return m_consider_boundary;
}

void SSPH::computeTotalForce(Scene& scene, TimeStep){
    if(scene.fluid.get() == nullptr || scene.fluid->particles_position().rows() == 0){
        return;
    }
    prepareFluid(scene);
    assert(m_kernelDensity.get() != nullptr);
    assert(m_kernelPressure.get() != nullptr);
    assert(m_kernelViscosity.get() != nullptr);
    const Float h = scene.fluid->h();
    m_kernelDensity->setH(h);
    m_kernelPressure->setH(h);
    m_kernelViscosity->setH(h);
    const auto& pos = scene.fluid->particles_position();
    const auto& vs = scene.fluid->particles_velocity();
    const auto& ms = scene.fluid->particles_mass();
    const auto& ps = scene.fluid->particles_pressure();
    // rho, density: a value measured in kg/m^3, water: 1000, air: 1.3
    // p, pressure: force per unit area
    // nu, kinematic viscosity: high values: fluid doesn't like to deform, low values: fluid likes deformation
    // often: nu = mu / rho
    // mu, dynamic viscosity coefficient:
    Float mu_boundary = scene.fluid->boundary_viscosity(); // viscosity towards wall

    Float visc_epsilon = 0.01;
    const int PN = pos.rows();
    // Müller et al., all equations we need:
    // density: rho(r_i) = sum_j m_j W(r_i - r_j, h)
    // a_i = f_i / rho_i
    // pressure: p = k (rho - rho_0)
    // f_i^pressure = - sum_j m_j (p_i + p_j)/(2 rho_j) \nabla W(r_i - r_j, h)
    // f_i^viscosity = mu * sum_j m_j (v_j - v_i) / rho_j \nabla^2 W(r_i - r_j, h)
    // color field: c_s(r_i) = sum_j m_j/rho_j W(r_i - r_j, h)
    // n = \nabla c_s
    // f^surface = - sigma \nabla^2 c_s n/|n|
    // f_external: given
    //
    // boundary particles:
    // psi(rho_0) = rho_0 * V_bi
    // corrected density of fluid particle:
    // rho_i = m_i * sum_j W_ij + sum_k psi_bk(rho_0) W_ik
    // pressure force from boundary to fluid particle:
    // F^pressure = - m_i sum_k psi_k(rho_0) p_i / (rho_i * rho_k) nabla W_ik
    // F^viscosity = mu * sum_k psi_k(rho_0) (v_k - v_i) / rho_k \nabla^2 W(r_i - r_k, h)
    // surface force should be fine I guess ..
    // implementation:
    // let's get the neighborhood information
    bool consider_boundary = considerBoundary() && scene.fluid->boundary_volume().rows() > 0;

    if(consider_boundary){
        prepareBoundary(scene);
    }
    auto& rho = scene.fluid->particles_density();
    const auto& boundary_index = scene.fluid->boundary_neighborhood->indexes();

    const Coordinates1d& psi = scene.fluid->boundary_psi();

    computeFluidDensity(scene, *m_kernelDensity);
    // density from fluid<->boundary
    if(consider_boundary){
        const auto& bPos = scene.fluid->boundary_position();
        for(int i = 0; i < PN; ++i){
            auto& index = boundary_index[i];
            if(index.empty()){
                continue;
            }
            int N = index.size();
            // adjusted density: m_i * sum_j W_ij + m_i * sum_k W_ik
            // = m_i * sum_j W_ij + sum_k psi_k * W_ik
            // sum_k psi_bk(rho_0) W_ik
            TranslationVector vi = pos.row(i);
            Float jW = 0.0;
            for(int j = 0; j < N; ++j){
                int jj = index[j];
                TranslationVector xik = vi - bPos.row(jj);
                Float w = m_kernelDensity->computeValue(xik);
                jW += psi[jj] * w;
            }
            rho[i] += jW;
        }
    }

    computeFluidPressure(scene);
    computeStandardViscosityForce(scene, *m_kernelViscosity);
    //computeStandardPressureForce(scene, *m_kernelPressure);
    computeMomentumPreservingPressureForce(scene, *m_kernelPressure);
    //computeStandardSurfaceTensionForce(scene, *m_kernelPressure, color_relevant_normal_size);
    if(consider_boundary){
        /*
        if(scene.fluid->boundary_velocity().maxCoeff() > 50){
            BOOST_LOG_TRIVIAL(info) << "largest boundary speed: " << scene.fluid->boundary_velocity().maxCoeff();
        }
        */
        scene.fluid->boundary_force().setZero();
        for(int i = 0; i < PN; ++i){
            // pressure: makes sure stuff doesn't enter the rigid body
            // F^pressure = - m_i sum_k psi_k(rho_0) p_i / (rho_i * rho_k) nabla W_ik
            // F^viscosity = mu * sum_k psi_k(rho_0) (v_k - v_i) / rho_k \nabla^2 W(r_i - r_k, h)
            // paper assumption: rho_k = rho_i
            auto& index = boundary_index[i];
            if(index.empty()){
                continue;
            }
            Coordinates2d jPress(index.size(), 2);
            Coordinates2d jVisc(index.size(), 2);
            Coordinates2d jpos, jGrad;
            Coordinates1d jLap;
            pickRows(scene.fluid->boundary_position(), index, jpos);
            Coordinates2d xik = -(jpos.rowwise() - pos.row(i));
            m_kernelPressure->compute(xik, nullptr, &jGrad, nullptr);
            m_kernelViscosity->compute(xik, nullptr, nullptr, &jLap);
            Float rhoInv = 1.0/rho[i];
            Float mA = -ms[i]*rhoInv*rhoInv*ps[i];
            Float mB = mu_boundary * ms[i]*rhoInv;
            for(size_t j = 0; j < index.size(); ++j){
                int jj = index[j];
                TranslationVector vij = -(scene.fluid->boundary_velocity().row(jj) - vs.row(i));
                jPress.row(j) = mA*psi[jj] * jGrad.row(j);
                Float PI = h * std::max(vij.dot(xik.row(j)), Float(0.0))/(xik.row(j).squaredNorm() + visc_epsilon*h*h);
                jVisc.row(j) = mB * psi[jj] * PI * jGrad.row(j);
                TranslationVector F = scene.fluid->boundary_force().row(jj) - (jPress.row(j) + jVisc.row(j));
                scene.fluid->boundary_force().row(jj) = F;
            }
            FPressure.row(i) += jPress.colwise().sum();
            FViscosity.row(i) += jVisc.colwise().sum();
        }
    }
    /*
    if(scene.fluid->boundary_force().maxCoeff() > 600000){
        BOOST_LOG_TRIVIAL(info) << "maxF: " << scene.fluid->boundary_force().maxCoeff();
    }
    */
    assert(is_finite(FPressure));
    assert(is_finite(FViscosity));
    assert(is_finite(FSurface));
    computeGravityForce(scene);
    scene.fluid->particles_total_force() = FPressure + FViscosity + FSurface + FGravity + scene.fluid->particles_external_force();
}

void SSPH::advance(Scene& scene, TimeStep dt){
    if(scene.fluid.get() == nullptr){
        return;
    }
    computeTotalForce(scene, dt);
    Coordinates2d a;
    // a_i = f_i / rho_i
    const auto& rho = scene.fluid->particles_density();
    const auto& Ftotal = scene.fluid->particles_total_force();
    assert(is_finite(Ftotal));
    assert(is_finite(rho));
    a.resize(rho.rows(), 2);
    a.col(0) = Ftotal.col(0).array() / rho.array();
    a.col(1) = Ftotal.col(1).array() / rho.array();
    assert(is_finite(a));
    auto& pos = scene.fluid->particles_position();
    auto& vs = scene.fluid->particles_velocity();
    vs = vs + dt * a;
    assert(is_finite(vs));
    scene.room.restrictFluid(* scene.fluid);
    pos = pos + dt * vs;
    assert(is_finite(pos));
    limitVelocity(scene);
}

} // Physics

} // d2

} // GooBalls


#define BOOST_TEST_MODULE LinearProgramFactoryTestSuite
#define BOOST_TEST_DYN_LINK

#include <boost/test/included/unit_test.hpp>
#include <hops/hops.hpp>

BOOST_AUTO_TEST_SUITE(LinearProgramFactory)

    BOOST_AUTO_TEST_CASE(CreateClpLinearProgram) {
        Eigen::MatrixXd A(3, 2);
        A << 1, 2, 4, 2, -1, 1;
        Eigen::VectorXd b(3);
        b << 4, 12, 1;
#if HOPS_CLP_FOUND
        Eigen::VectorXd expectedSolution(2);
        expectedSolution << 8.0 / 3, 2.0 / 3;

        Eigen::VectorXd obj(2);
        obj << 1, 1;

        auto linearProgram = hops::LinearProgramFactory::createLinearProgram(A, b, hops::LinearProgramSolver::CLP);
        auto solution = linearProgram->solve(obj);

        BOOST_CHECK(solution.status == hops::LinearProgramStatus::OPTIMAL);
        BOOST_CHECK_CLOSE(solution.objectiveValue, 10.0 / 3, 0.0001);
        BOOST_CHECK(solution.optimalParameters.isApprox(expectedSolution));
#else //HOPS_CLP_FOUND
        BOOST_CHECK_THROW( {
            hops::LinearProgramFactory::createLinearProgram(A, b, hops::LinearProgramSolver::CLP); },
                           std::runtime_error );
#endif //HOPS_CLP_FOUND
    }

    BOOST_AUTO_TEST_CASE(CreateGurobiLinearProgram) {
        Eigen::MatrixXd A(3, 2);
        A << 1, 2, 4, 2, -1, 1;
        Eigen::VectorXd b(3);
        b << 4, 12, 1;
#if HOPS_GUROBI_FOUND
        Eigen::VectorXd expectedSolution(2);
        expectedSolution << 8.0 / 3, 2.0 / 3;

        Eigen::VectorXd obj(2);
        obj << 1, 1;

        auto linearProgram = hops::LinearProgramFactory::createLinearProgram(A, b, hops::LinearProgramSolver::GUROBI);
        auto solution = linearProgram->solve(obj);

        BOOST_CHECK(solution.status == hops::LinearProgramStatus::OPTIMAL);
        BOOST_CHECK_CLOSE(solution.objectiveValue, 10.0 / 3, 0.0001);
        BOOST_CHECK(solution.optimalParameters.isApprox(expectedSolution));
#else //HOPS_GUROBI_FOUND
        BOOST_CHECK_THROW( { hops::LinearProgramFactory::createLinearProgram(A, b, hops::LinearProgramSolver::GUROBI); },
                           std::runtime_error);
#endif //HOPS_GUROBI_FOUND
    }

BOOST_AUTO_TEST_SUITE_END()


#include "intcode.hpp"

#include <boost/multiprecision/cpp_int.hpp>
#include <iostream>

using boost::multiprecision::cpp_int;

int main()
{
    for (int part = 1; part <= 2; ++part)
    {
        auto inputs = std::make_shared<IOQueue<cpp_int>>();
        auto outputs = std::make_shared<IOQueue<cpp_int>>();
        inputs->push(part);
        Process<cpp_int> p {read_program<cpp_int>("input.txt"), inputs, outputs};
        p.join();
        std::cout << "Part " << part << ": " << outputs->get_data().back() << std::endl;
    }
    return 0;
}


// Copyright 2015-2018 Hans Dembinski
//
// Distributed under 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)

//[ guide_custom_modified_axis

#include <boost/histogram.hpp>
#include <cassert>
#include <iostream>
#include <sstream>

int main() {
  using namespace boost::histogram;

  // custom axis, which adapts builtin integer axis
  struct custom_axis : public axis::integer<> {
    using value_type = const char*; // type that is fed to the axis

    using integer::integer; // inherit ctors of base

    // the customization point
    // - accept const char* and convert to int
    // - then call index method of base class
    axis::index_type index(value_type s) const { return integer::index(std::atoi(s)); }
  };

  auto h = make_histogram(custom_axis(3, 6));
  h("-10");
  h("3");
  h("4");
  h("9");

  std::ostringstream os;
  for (auto&& b : indexed(h)) {
    os << "bin " << b.index() << " [" << b.bin() << "] " << *b << "\n";
  }

  std::cout << os.str() << std::endl;

  assert(os.str() == "bin 0 [3] 1\n"
                     "bin 1 [4] 1\n"
                     "bin 2 [5] 0\n");
}

//]