#include "Delaunay3D.hpp"
#include "Predicates3D.hpp"
#include <limits>
#include <algorithm>
#include <fstream>
#include "HilbertOrder3D.hpp"
#include <boost/foreach.hpp>
#include <iostream>
//#define runcheks 1

namespace
{
	bool InsideBigTetra(Vector3D const& tocheck, vector<Vector3D> const& cor, size_t Norg)
	{
		int sum = 0;
#ifdef __INTEL_COMPILER
#pragma omp simd reduction(+:sum)
#endif
		for (size_t i = 0; i < 4; ++i)
		{
			Vector3D normal = CrossProduct(cor[Norg + (1 + i) % 4] - cor[Norg + i], cor[Norg + (2 + i) % 4] - cor[Norg + i]);
			double s1 = ScalarProd(normal, cor[Norg + (3 + i) % 4] - cor[Norg + i]);
			double s2 = ScalarProd(normal, tocheck - cor[Norg + i]);
			sum += (s1*s2 < 0) ? 1 : 0;
		}
		return (sum==0);
	}

	bool PlaneLineIntersection(std::array<Vector3D, 3> &plane, Vector3D const& A, Vector3D const& B, Vector3D &res)
	{
		plane[1] -= plane[0];
		plane[2] -= plane[0];
		Vector3D N = CrossProduct(plane[1], plane[2]);
		double Nsize = ScalarProd(N, N);
		double Rmin = std::min(ScalarProd(plane[1], plane[1]),ScalarProd(plane[2], plane[2]));
		plane[1] -= plane[2];
		Rmin = std::min(Rmin,ScalarProd(plane[1], plane[1]));
		if (Nsize > (Rmin*Rmin * 1e4))
			return false;
		plane[1] += plane[2];
		plane[1] += plane[0];
		plane[2] += plane[0];
		Vector3D mu = B;
		mu -= A;
		double sp = ScalarProd(mu, N);
		if (sp*sp < ScalarProd(mu,mu)*Nsize*1e-8)
			return false;
		double m = ScalarProd(N, plane[0] - A) / sp;
		res = A + m*mu;
		return true;
	}

	void GetOppositePoint(Tetrahedron const& tetra, std::size_t neighbor,size_t &res)
	{
#ifdef __INTEL_COMPILER
#pragma ivdep
#endif
		for (size_t i=0; i < 4; i++)
			if (tetra.neighbors[i] == neighbor)
			{
				res = i;
				return;
			}
	}

	void GetPointLocationInTetra(Tetrahedron const& tetra, std::size_t point,size_t &res)
	{
#ifdef __INTEL_COMPILER
#pragma ivdep
#endif
		for (size_t i = 0; i < 4; i++)
			if (tetra.points[i] == point)
			{
				res = i;
				return;
			}
	}

	std::pair<std::size_t,double> InTriangle(std::array<Vector3D, 3> &triangle, Vector3D &p)
	{
		// returns the smallest area of the cross product in units of the triangle area, negative if outside
		Vector3D temp,d;
		triangle[1] -= triangle[0];
		triangle[2] -= triangle[0];
		CrossProduct(triangle[1], triangle[2], d);
		p -= triangle[0];
		CrossProduct(triangle[1], p, temp);
		double ad = ScalarProd(d, temp);
		p -= triangle[1];
		triangle[2] -= triangle[1];
		CrossProduct(triangle[2], p, temp);
		double bd = ScalarProd(d, temp);
		p -= triangle[2];
		triangle[2] += triangle[1];
		CrossProduct(p, triangle[2], temp);
		double cd = ScalarProd(d, temp);

		std::pair<std::size_t, double> res(0, 0);
		if (ad < 0)
			++res.first;
		if (bd < 0)
			++res.first;
		if (cd < 0)
			++res.first;
		res.second = std::min(std::min(std::abs(ad), std::abs(bd)), std::abs(cd)) / (ScalarProd(d, d));
		return res;
	}

	bool Are44(Tetrahedron const& T0, Tetrahedron const& T1, std::size_t loc_in_0, vector<Tetrahedron> const& tetras,
		std::size_t &N3,std::size_t &N4)
	{
		N3 = T0.neighbors[loc_in_0];
		size_t loc1=0;
		GetPointLocationInTetra(T1, T0.points[loc_in_0], loc1);
		N4 = T1.neighbors[loc1];
		for (std::size_t i = 0; i < 4; ++i)
			if (tetras[N3].neighbors[i] == N4)
				return true;
		return false;
	}

}

Delaunay3D& Delaunay3D::operator=(Delaunay3D const& other)
{
	if (this == &other)
		return *this;
	tetras_ = other.tetras_;
	points_ = other.points_;
	empty_tetras_ = other.empty_tetras_;
	Norg_ = other.Norg_;
	outside_neighbor_ = other.outside_neighbor_;
	return *this;
}

Delaunay3D::Delaunay3D(Delaunay3D const& other) :  tetras_(other.tetras_),points_(other.points_),empty_tetras_(other.empty_tetras_),Norg_(other.Norg_),
	outside_neighbor_(other.outside_neighbor_),b3_temp_(std::array<Vector3D, 3> ()),b3_temp2_(std::array<Vector3D, 3> ()),
	b4_temp_(std::array<Vector3D, 4>()), b5_temp_(std::array<Vector3D, 5>()),b4s_temp_(std::array<std::size_t, 4> ()),
	b4s_temp2_(std::array<std::size_t, 4> ()),b8s_temp_(std::array<std::size_t, 8> ()),to_check_(vector<std::size_t>()),
	last_checked_(0), tet_temp0_(Tetrahedron()), tet_temp1_(Tetrahedron()), newtet_(Tetrahedron()) {}

void Delaunay3D::flip23(std::size_t tetra0, std::size_t tetra1, std::size_t location0,bool flat_check)
{
	bool used_empty = false;
	std::size_t Nloc = tetras_.size();
	if (!empty_tetras_.empty())
	{
		used_empty = true;
		Nloc = *empty_tetras_.begin();
		empty_tetras_.erase(empty_tetras_.begin());
	}

	// location is the location of the point in tetra that is not in the joint triangle
	tet_temp0_ = tetras_[tetra0];
	tet_temp1_ = tetras_[tetra1];
	size_t location1=0;
	GetOppositePoint(tet_temp1_, tetra0, location1);

	newtet_.points[1] = tet_temp0_.points[(location0 + 1) % 4];
	newtet_.points[2] = tet_temp0_.points[(location0 + 2) % 4];

	if (location0 % 2 == 1)
	{
		newtet_.points[0] = tet_temp1_.points[location1];
		newtet_.points[3] = tet_temp0_.points[location0];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 3) % 4], loctemp);
		newtet_.neighbors[3] = tet_temp1_.neighbors[loctemp];
		newtet_.neighbors[0] = tet_temp0_.neighbors[(location0 + 3) % 4];
		if (newtet_.neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[0]], tetra0, loctemp);
			tetras_[newtet_.neighbors[0]].neighbors[loctemp] = Nloc;
		}
		if (newtet_.neighbors[3] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[3]], tetra1, loctemp);
			tetras_[newtet_.neighbors[3]].neighbors[loctemp] = Nloc;
		}
	}
	else
	{
		newtet_.points[3] = tet_temp1_.points[location1];
		newtet_.points[0] = tet_temp0_.points[location0];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 3) % 4], loctemp);
		newtet_.neighbors[0] = tet_temp1_.neighbors[loctemp];
		newtet_.neighbors[3] = tet_temp0_.neighbors[(location0 + 3) % 4];
		if (newtet_.neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[0]], tetra1, loctemp);
			tetras_[newtet_.neighbors[0]].neighbors[loctemp] = Nloc;
		}
		if (newtet_.neighbors[3] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[3]], tetra0, loctemp);
			tetras_[newtet_.neighbors[3]].neighbors[loctemp] = Nloc;
		}
	}
	newtet_.neighbors[1] = tetra0;
	newtet_.neighbors[2] = tetra1;

	if (used_empty)
		tetras_[Nloc] = newtet_;
	else
		tetras_.push_back(newtet_);

	if (location0 % 2 == 0)
	{
		tetras_[tetra0].points[0] = tet_temp0_.points[location0];
		tetras_[tetra0].points[3] = tet_temp1_.points[location1];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 1) % 4], loctemp);
		tetras_[tetra0].neighbors[0] = tet_temp1_.neighbors[loctemp];
		tetras_[tetra0].neighbors[3] = tet_temp0_.neighbors[(location0 + 1) % 4];
		if (tetras_[tetra0].neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[tetras_[tetra0].neighbors[0]], tetra1, loctemp);
			tetras_[tetras_[tetra0].neighbors[0]].neighbors[loctemp] = tetra0;
		}
	}
	else
	{
		tetras_[tetra0].points[3] = tet_temp0_.points[location0];
		tetras_[tetra0].points[0] = tet_temp1_.points[location1];
		tetras_[tetra0].neighbors[0] = tet_temp0_.neighbors[(location0 + 1) % 4];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 1) % 4], loctemp);
		tetras_[tetra0].neighbors[3] = tet_temp1_.neighbors[loctemp];
		if (tetras_[tetra0].neighbors[3] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[tetras_[tetra0].neighbors[3]], tetra1, loctemp);
			tetras_[tetras_[tetra0].neighbors[3]].neighbors[loctemp] = tetra0;
		}
	}
	tetras_[tetra0].points[1] = tet_temp0_.points[(location0 + 2) % 4];
	tetras_[tetra0].points[2] = tet_temp0_.points[(location0 + 3) % 4];
	tetras_[tetra0].neighbors[1] = tetra1;
	tetras_[tetra0].neighbors[2] = Nloc;

	if (location0 % 2 == 0)
	{
		tetras_[tetra1].points[0] = tet_temp0_.points[location0];
		tetras_[tetra1].points[3] = tet_temp1_.points[location1];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 2) % 4], loctemp);
		tetras_[tetra1].neighbors[0] = tet_temp1_.neighbors[loctemp];
		tetras_[tetra1].neighbors[3] = tet_temp0_.neighbors[(location0 + 2) % 4];
		if (tetras_[tetra1].neighbors[3] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[tetras_[tetra1].neighbors[3]], tetra0, loctemp);
			tetras_[tetras_[tetra1].neighbors[3]].neighbors[loctemp] = tetra1;
		}
	}
	else
	{
		tetras_[tetra1].points[0] = tet_temp1_.points[location1];
		tetras_[tetra1].points[3] = tet_temp0_.points[location0];
		size_t loctemp=0;
		GetPointLocationInTetra(tet_temp1_, tet_temp0_.points[(location0 + 2) % 4], loctemp);
		tetras_[tetra1].neighbors[0] = tet_temp0_.neighbors[(location0 + 2) % 4];
		tetras_[tetra1].neighbors[3] = tet_temp1_.neighbors[loctemp];
		if (tetras_[tetra1].neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[tetras_[tetra1].neighbors[0]], tetra0, loctemp);
			tetras_[tetras_[tetra1].neighbors[0]].neighbors[loctemp] = tetra1;
		}
	}
	tetras_[tetra1].points[1] = tet_temp0_.points[(location0 + 3) % 4];
	tetras_[tetra1].points[2] = tet_temp0_.points[(location0 + 1) % 4];
	tetras_[tetra1].neighbors[1] = Nloc;
	tetras_[tetra1].neighbors[2] = tetra0;

	if(flat_check)
	{
		for (size_t i = 0; i < 4; ++i)
			b4_temp_[i] = points_[tetras_[Nloc].points[i]];
		double orient = orient3d(b4_temp_);
		if (std::abs(orient) == 0)
		{
			std::size_t my_loc=0;
			GetPointLocationInTetra(tetras_[Nloc], tet_temp0_.points[location0],my_loc);
			std::size_t neigh = tetras_[Nloc].neighbors[my_loc];
			std::size_t opp=0;
			GetOppositePoint(tetras_[neigh], Nloc,opp);
			for (size_t i = 0; i < 3; ++i)
				b4_temp_[i] = points_[tetras_[Nloc].points[(my_loc + (i) + 1) % 4]];
			b4_temp_[3] = points_[tetras_[neigh].points[opp]];
			double o = orient3d(b4_temp_);
			o *= 1.0 - static_cast<double>(2 * (location0 % 2));
			if (o > 0)
			{
				std::size_t temp = tetras_[Nloc].points[0];
				tetras_[Nloc].points[0] = tetras_[Nloc].points[1];
				tetras_[Nloc].points[1] = temp;
				temp = tetras_[Nloc].neighbors[0];
				tetras_[Nloc].neighbors[0] = tetras_[Nloc].neighbors[1];
				tetras_[Nloc].neighbors[1] = temp;
			}
		}

		for (size_t i = 0; i < 4; ++i)
			b4_temp_[i] = points_[tetras_[tetra0].points[i]];
		orient = orient3d(b4_temp_);
		if (std::abs(orient) == 0)
		{
			std::size_t my_loc=0;
			GetPointLocationInTetra(tetras_[tetra0], tet_temp0_.points[location0],my_loc);
			std::size_t neigh = tetras_[tetra0].neighbors[my_loc];
			std::size_t opp=0;
			GetOppositePoint(tetras_[neigh], tetra0,opp);
			for (size_t i = 0; i < 3; ++i)
				b4_temp_[i] = points_[tetras_[tetra0].points[(my_loc + i + 1) % 4]];
			b4_temp_[3] = points_[tetras_[neigh].points[opp]];
			double o = orient3d(b4_temp_);
			o *= 1.0 - static_cast<double>(2 * (location0 % 2));
			if (o > 0)
			{
				std::size_t temp = tetras_[tetra0].points[0];
				tetras_[tetra0].points[0] = tetras_[tetra0].points[1];
				tetras_[tetra0].points[1] = temp;
				temp = tetras_[tetra0].neighbors[0];
				tetras_[tetra0].neighbors[0] = tetras_[tetra0].neighbors[1];
				tetras_[tetra0].neighbors[1] = temp;
			}
		}
		for (size_t i = 0; i < 4; ++i)
			b4_temp_[i] = points_[tetras_[tetra1].points[i]];
		orient = orient3d(b4_temp_);
		if (std::abs(orient) == 0)
		{
			std::size_t my_loc=0;
			GetPointLocationInTetra(tetras_[tetra1], tet_temp0_.points[location0],my_loc);
			std::size_t neigh = tetras_[tetra1].neighbors[my_loc];
			std::size_t opp=0;
			GetOppositePoint(tetras_[neigh], tetra1,opp);
			for (size_t i = 0; i < 3; ++i)
				b4_temp_[i] = points_[tetras_[tetra1].points[(my_loc + i + 1) % 4]];
			b4_temp_[3] = points_[tetras_[neigh].points[opp]];
			double o = orient3d(b4_temp_);
			o *= 1.0 - static_cast<double>(2 * (location0 % 2));
			if (o > 0)
			{
				std::size_t temp = tetras_[tetra1].points[0];
				tetras_[tetra1].points[0] = tetras_[tetra1].points[1];
				tetras_[tetra1].points[1] = temp;
				temp = tetras_[tetra1].neighbors[0];
				tetras_[tetra1].neighbors[0] = tetras_[tetra1].neighbors[1];
				tetras_[tetra1].neighbors[1] = temp;
			}
		}
	}

	to_check_.push_back(tetra0);
	to_check_.push_back(tetra1);
	to_check_.push_back(Nloc);
}

void Delaunay3D::flip32(std::size_t tetra0, std::size_t tetra1, std::size_t location0,std::size_t shared_loction,
	bool flat_check)
{
	// shared_loction is the point that is to be shared in the 2 new tetras. It is the point opposite to the shared edge by the three tetras in the triangle joint with the two tetras to check
	std::size_t other_point = 20, other_point2 = 20;
	for (std::size_t i = 0; i < 4; ++i)
	{
		if (i == location0 || i == shared_loction)
			continue;
		if (other_point == 20)
			other_point = i;
		else
			other_point2 = i;
	}

	std::size_t location1=0;
	GetOppositePoint(tetras_[tetra1], tetra0,location1);
	std::size_t third_tetra = tetras_[tetra0].neighbors[shared_loction];
	tet_temp0_ = tetras_[tetra0];

	if (location0 % 2 == 0)
	{
		newtet_.points[0] = tetras_[tetra0].points[location0];
		newtet_.points[1] = tetras_[tetra0].points[shared_loction];
		size_t loctemp=0;
		GetPointLocationInTetra(tetras_[tetra1], tetras_[tetra0].points[other_point2], loctemp);
		newtet_.neighbors[0] = tetras_[tetra1].neighbors[loctemp];
		GetPointLocationInTetra(tetras_[third_tetra], tetras_[tetra0].points[other_point2], loctemp);
		newtet_.neighbors[1] = tetras_[third_tetra].neighbors[loctemp];
		if (newtet_.neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[0]], tetra1, loctemp);
			tetras_[newtet_.neighbors[0]].neighbors[loctemp] = tetra0;
		}
		if (newtet_.neighbors[1] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[1]], third_tetra, loctemp);
			tetras_[newtet_.neighbors[1]].neighbors[loctemp] = tetra0;
		}
	}
	else
	{
		newtet_.points[0] = tetras_[tetra0].points[shared_loction];
		newtet_.points[1] = tetras_[tetra0].points[location0];
		size_t loctemp=0;
		GetPointLocationInTetra(tetras_[tetra1], tetras_[tetra0].points[other_point2], loctemp);
		newtet_.neighbors[1] = tetras_[tetra1].neighbors[loctemp];
		GetPointLocationInTetra(tetras_[third_tetra], tetras_[tetra0].points[other_point2], loctemp);
		newtet_.neighbors[0] = tetras_[third_tetra].neighbors[loctemp];
		if (newtet_.neighbors[1] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[1]], tetra1, loctemp);
			tetras_[newtet_.neighbors[1]].neighbors[loctemp] = tetra0;
		}
		if (newtet_.neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[0]], third_tetra, loctemp);
			tetras_[newtet_.neighbors[0]].neighbors[loctemp] = tetra0;
		}
	}
	newtet_.points[2] = tetras_[tetra0].points[other_point];
	newtet_.points[3] = tetras_[tetra1].points[location1];

	newtet_.neighbors[2] = tetra1;
	newtet_.neighbors[3] = tetras_[tetra0].neighbors[other_point2];
	if (flat_check)
	{
		for (size_t i = 0; i < 4; ++i)
			b4_temp_[i] = points_[newtet_.points[i]];
		if (orient3d(b4_temp_) > 0)
		{
			std::size_t temp = newtet_.points[0];
			newtet_.points[0] = newtet_.points[1];
			newtet_.points[1] = temp;
			temp = newtet_.neighbors[0];
			newtet_.neighbors[0] = newtet_.neighbors[1];
			newtet_.neighbors[1] = temp;
		}
	}
	tetras_[tetra0] = newtet_;

	if (location0 % 2 == 1)
	{
		newtet_.points[0] = tet_temp0_.points[location0];
		newtet_.points[1] = tet_temp0_.points[shared_loction];
		size_t loctemp=0;
		GetPointLocationInTetra(tetras_[tetra1], tet_temp0_.points[other_point], loctemp);
		newtet_.neighbors[0] = tetras_[tetra1].neighbors[loctemp];
		GetPointLocationInTetra(tetras_[third_tetra], tet_temp0_.points[other_point], loctemp);
		newtet_.neighbors[1] = tetras_[third_tetra].neighbors[loctemp];
		if (newtet_.neighbors[1] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[1]], third_tetra, loctemp);
			tetras_[newtet_.neighbors[1]].neighbors[loctemp] = tetra1;
		}
	}
	else
	{
		newtet_.points[0] = tet_temp0_.points[shared_loction];
		newtet_.points[1] = tet_temp0_.points[location0];
		size_t loctemp=0;
		GetPointLocationInTetra(tetras_[tetra1], tet_temp0_.points[other_point], loctemp);
		newtet_.neighbors[1] = tetras_[tetra1].neighbors[loctemp];
		GetPointLocationInTetra(tetras_[third_tetra], tet_temp0_.points[other_point], loctemp);
		newtet_.neighbors[0] = tetras_[third_tetra].neighbors[loctemp];
		if (newtet_.neighbors[0] != outside_neighbor_)
		{
			GetOppositePoint(tetras_[newtet_.neighbors[0]], third_tetra, loctemp);
			tetras_[newtet_.neighbors[0]].neighbors[loctemp] = tetra1;
		}
	}
	newtet_.points[2] = tet_temp0_.points[other_point2];
	newtet_.points[3] = tetras_[tetra1].points[location1];

	newtet_.neighbors[2] = tetra0;
	newtet_.neighbors[3] = tet_temp0_.neighbors[other_point];
	if (newtet_.neighbors[3] != outside_neighbor_)
	{
		size_t loctemp=0;
		GetOppositePoint(tetras_[newtet_.neighbors[3]], tetra0, loctemp);
		tetras_[newtet_.neighbors[3]].neighbors[loctemp] = tetra1;
	}
	if (flat_check)
	{
		for (int i = 0; i < 4; ++i)
			b4_temp_[static_cast<size_t>(i)] = points_[newtet_.points[static_cast<size_t>(i)]];
		if (orient3d(b4_temp_) > 0)
		{
			std::size_t temp = newtet_.points[0];
			newtet_.points[0] = newtet_.points[1];
			newtet_.points[1] = temp;
			temp = newtet_.neighbors[0];
			newtet_.neighbors[0] = newtet_.neighbors[1];
			newtet_.neighbors[1] = temp;
		}
	}
	tetras_[tetra1] = newtet_;

	to_check_.push_back(tetra0);
	to_check_.push_back(tetra1);

	empty_tetras_.insert(third_tetra);
	if (third_tetra == last_checked_)
		last_checked_ = tetra0;
}

void Delaunay3D::flip44(std::size_t tetra0, std::size_t tetra1, std::size_t location0, std::size_t neigh0,std::size_t neigh1)
{
	std::size_t shared_location = 20;
	for (std::size_t i = 0; i < 4; ++i)
	{
		if (tetras_[neigh0].neighbors[i] == tetra0)
		{
			shared_location = i;
			break;
		}
	}
	assert(shared_location != 20);

	flip23(tetra0, tetra1, location0,true);
	location0 = 20;
	for (std::size_t i = 0; i < 4; ++i)
	{
		if (tetras_[neigh0].neighbors[i] == neigh1)
		{
			location0 = i;
			break;
		}
	}
	assert(location0 != 20);

	flip32(neigh0, neigh1, location0, shared_location,true);
}

Delaunay3D::Delaunay3D() :tetras_(vector<Tetrahedron> ()),points_(vector<Vector3D> ()),empty_tetras_(boost::container::flat_set<size_t> ()),Norg_(0),outside_neighbor_(0),
	b3_temp_(std::array<Vector3D, 3> ()),b3_temp2_(std::array<Vector3D, 3> ()),b4_temp_(std::array<Vector3D, 4> ()),b5_temp_(std::array<Vector3D, 5> ()),
	b4s_temp_(std::array<std::size_t, 4>()),b4s_temp2_(std::array<std::size_t, 4>()),b8s_temp_(std::array<std::size_t, 8>()),
	to_check_(vector<std::size_t>()),last_checked_(0), tet_temp0_(Tetrahedron()),tet_temp1_(Tetrahedron()), newtet_(Tetrahedron())
{
	empty_tetras_.reserve(15);
	to_check_.reserve(100);
}

Delaunay3D::~Delaunay3D()
{}

void Delaunay3D::BuildExtra(vector<Vector3D> const& points)
{
	size_t Nstart = points_.size();
	points_.insert(points_.end(), points.begin(), points.end());
	std::vector<size_t> order = HilbertOrder3D(points);
	assert(to_check_.empty());
	for (std::size_t i = 0; i < points.size(); ++i)
	{
		if(InsideBigTetra(points_[order[i] + Nstart],points_,Norg_))
			InsertPoint(order[i] + Nstart);
	}
}

void Delaunay3D::Build(vector<Vector3D> const & points, Vector3D const& maxv, Vector3D const& minv,
	std::vector<size_t> &order)
{
	empty_tetras_.clear();
	std::size_t Norg = points.size();
	Norg_ = Norg;
	points_.reserve(Norg+ static_cast<std::size_t>(std::pow(Norg,0.6666)*14));
	points_.assign(points.begin(), points.end());
	// Create large tetra points
	double factor = 500;
	double dx = std::max(std::max(maxv.x - minv.x,maxv.y-minv.y),maxv.z-minv.z);
	points_.push_back(Vector3D(minv.x - 1.01*factor * dx, minv.y - factor * dx, minv.z - factor * dx));
	points_.push_back(Vector3D(0.5*(minv.x + maxv.x), maxv.y + 1.02*factor *dx, minv.z - factor * dx));
	points_.push_back(Vector3D(maxv.x + 0.99*factor * dx, minv.y - factor * dx, minv.z - factor * dx));
	points_.push_back(Vector3D(0.5*(minv.x + maxv.x), 0.5*(minv.y + maxv.y), maxv.z + factor * dx));
	// Create large tetra
	outside_neighbor_=std::numeric_limits<std::size_t>::max();
	Tetrahedron tetra;
	tetra.points[0] = Norg;
	tetra.points[1] = Norg+2;
	tetra.points[2] = Norg+1;
	tetra.points[3] = Norg+3;
	tetra.neighbors[0] = outside_neighbor_;
	tetra.neighbors[1] = outside_neighbor_;
	tetra.neighbors[2] = outside_neighbor_;
	tetra.neighbors[3] = outside_neighbor_;
	tetras_.reserve(points_.capacity() * 7);
	tetras_.push_back(tetra);
	last_checked_ = 0;

	assert(to_check_.empty());
	if (order.empty())
		order = HilbertOrder3D(points);
	for (std::size_t i = 0; i < Norg; ++i)
		InsertPoint(order[i]);
}

void Delaunay3D::output(string const & filename) const
{
	std::ofstream fh(filename.c_str(), std::ios::out | std::ostream::binary);

	std::size_t temp = tetras_.size() - empty_tetras_.size();
	fh.write(reinterpret_cast<const char*>(&temp), sizeof(std::size_t));
	temp = points_.size();
	fh.write(reinterpret_cast<const char*>(&Norg_), sizeof(std::size_t));
	fh.write(reinterpret_cast<const char*>(&temp), sizeof(std::size_t));

	for (std::size_t i = 0; i<points_.size(); ++i)
	{
		fh.write(reinterpret_cast<const char*>(&points_[i].x), sizeof(double));
		fh.write(reinterpret_cast<const char*>(&points_[i].y), sizeof(double));
		fh.write(reinterpret_cast<const char*>(&points_[i].z), sizeof(double));
	}

	for (std::size_t i = 0; i<tetras_.size(); ++i)
	{
		if (empty_tetras_.find(i) == empty_tetras_.end())
		{
			for (std::size_t j = 0; j < 4; ++j)
			{
				fh.write(reinterpret_cast<const char*>(&tetras_[i].points[j]), sizeof(std::size_t));
			}
		}
	}

	fh.close();
}

std::size_t Delaunay3D::FindThirdNeighbor(std::size_t tetra0,std::size_t tetra1)
{
	for (size_t i = 0; i < 4; ++i)
	{
		for (size_t j = 0; j < 4; ++j)
		{
			if (tetras_[tetra0].neighbors[i] == tetras_[tetra1].neighbors[j])
			{
				b8s_temp_[0] = tetras_[tetra1].neighbors[j];
				return 1;
			}
		}
	}
	return 0;
}

void Delaunay3D::ExactFlip(std::size_t tetra0, std::size_t tetra1, std::size_t p)
{
	std::size_t out_counter = 0;
	std::size_t other_point = 0;
	std::size_t in_counter = 0;
	std::size_t flat_counter = 0;
	std::array<std::size_t, 3> out_check;
	Tetrahedron const& T0 = tetras_[tetra0];
	Tetrahedron const& T1 = tetras_[tetra1];
	std::size_t p_loc=0;
	GetPointLocationInTetra(T0, p, p_loc);
	for (int i = 0; i < 3; ++i)
		b3_temp_[i] = points_[T0.points[(p_loc + 1 + static_cast<size_t>(i)) % 4]];

	for (size_t i = 0; i < 4; i++)
	{
		if (T1.neighbors[i] == tetra0)
		{
			other_point = T1.points[i];
			break;
		}
	}

	b4_temp_[0] = b3_temp_[1];
	b4_temp_[1] = b3_temp_[2];
	b4_temp_[2] = points_[p];
	b4_temp_[3] = b3_temp_[0];
	double test0 = orient3d(b4_temp_);
	if (std::abs(test0) == 0)
		in_counter++;
	b4_temp_[3] = points_[other_point];
	double test1 = orient3d(b4_temp_);
	if (std::abs(test1) == 0)
		flat_counter++;
	if (test0*test1 > 0)
		out_check[0] = 0;
	else
	{
		out_check[0] = 1;
		++out_counter;
	}

	b4_temp_[0] = b3_temp_[2];
	b4_temp_[1] = b3_temp_[0];
	b4_temp_[2] = points_[p];
	b4_temp_[3] = b3_temp_[1];
	test0 = orient3d(b4_temp_);
	if (std::abs(test0) == 0)
		in_counter++;
	b4_temp_[3] = points_[other_point];
	test1 = orient3d(b4_temp_);
	if (std::abs(test1) == 0)
		flat_counter++;
	if (test0*test1 > 0)
		out_check[1] = 0;
	else
	{
		out_check[1] = 1;
		++out_counter;
	}

	b4_temp_[0] = b3_temp_[0];
	b4_temp_[1] = b3_temp_[1];
	b4_temp_[2] = points_[p];
	b4_temp_[3] = b3_temp_[2];
	test0 = orient3d(b4_temp_);
	if (std::abs(test0) == 0)
		in_counter++;
	b4_temp_[3] = points_[other_point];
	test1 = orient3d(b4_temp_);
	if (std::abs(test1) == 0)
		flat_counter++;
	if (test0*test1 > 0)
		out_check[2] = 0;
	else
	{
		out_check[2] = 1;
		++out_counter;
	}

	if (out_counter == 0)
		flip23(tetra0, tetra1, p_loc,true);
	else
	{
		if (out_counter == 1)
		{
			std::size_t N_shared = FindThirdNeighbor(tetra0, tetra1);
			if (N_shared == 1)
			{
				std::size_t shared_loc=0;
				GetOppositePoint(tetras_[tetra0], b8s_temp_[0],shared_loc);
				flip32(tetra0, tetra1, p_loc, shared_loc,true);
				return;
			}
			else
			{
				if (flat_counter > 0)
				{
					for (std::size_t i = 0; i < 3; ++i)
					{
						std::size_t N3=0, N4=0;
						if (out_check[i] == 1 && Are44(T0, T1, (p_loc + i + 1) % 4, tetras_, N3, N4))
						{
							flip44(tetra0, tetra1, p_loc, N3, N4);
							return;
						}
					}
				}
			}
		}
		else
		{
			if (in_counter == 3) // tetra0 is flat
			{
				//is a 32 flip possible?
				std::size_t N_shared = FindThirdNeighbor(tetra0, tetra1);
				if (N_shared == 1)
				{
					std::size_t shared_loc=0;
					GetOppositePoint(tetras_[tetra0], b8s_temp_[0],shared_loc);
					flip32(tetra0, tetra1, p_loc, shared_loc,true);
					return;
				}
				else
					flip23(tetra0, tetra1, p_loc,true);
			}
		}
	}
}

void Delaunay3D::FindFlip(std::size_t tetra0,std::size_t tetra1,std::size_t p,size_t p_loc,size_t other_point_loc)
{
	size_t *tetcheck = tetras_[tetra0].points;
#ifdef __INTEL_COMPILER
#pragma ivdep
#endif
	for (std::size_t i = 0; i < 3;++i)
		//b3_temp_[i] = points_[tetras_[tetra0].points[(p_loc + i + 1) % 4]];
		b3_temp_[i] = points_[tetcheck[(p_loc + i + 1) % 4]];
	Vector3D intersection;
	bool good_intersection = PlaneLineIntersection(b3_temp_, points_[p],
		points_[tetras_[tetra1].points[other_point_loc]],intersection);
	std::pair<std::size_t, double> outside_intersection;
	if (!good_intersection)
		outside_intersection.second = 0;
	else
		outside_intersection= InTriangle(b3_temp_, intersection);
	if (outside_intersection.second<1e-6)
	{
		ExactFlip(tetra0, tetra1, p);
		return;
	}
	if (outside_intersection.first == 0)
	{
		flip23(tetra0, tetra1, p_loc,false);
		return;
	}
	if (outside_intersection.first == 1)
	{
		// Do we have a shared neighbor?
		std::size_t Nshared = FindThirdNeighbor(tetra0, tetra1);
		if (Nshared == 1)
		{
			std::size_t shared_loc=0;
			GetOppositePoint(tetras_[tetra0], b8s_temp_[0],shared_loc);
			flip32(tetra0, tetra1, p_loc, shared_loc,false);
		}
	}
}

void Delaunay3D::InsertPoint(std::size_t index)
{
	std::size_t to_split = Walk(index, last_checked_);
	last_checked_ = to_split;
	flip14(index, to_split);
	while (!to_check_.empty())
	{
		std::size_t cur_check = to_check_.back();
		to_check_.pop_back();
		size_t p_loc=0;
		GetPointLocationInTetra(tetras_[cur_check], index,p_loc);
		std::size_t to_flip = tetras_[cur_check].neighbors[p_loc];
		if (to_flip == outside_neighbor_ || (empty_tetras_.find(cur_check) != empty_tetras_.end()))
			continue;
		size_t other_point_loc=0;
		GetOppositePoint(tetras_[to_flip], cur_check,other_point_loc);
		b5_temp_[4] = points_[tetras_[to_flip].points[other_point_loc]];
		b5_temp_[0] = points_[tetras_[cur_check].points[0]];
		b5_temp_[1] = points_[tetras_[cur_check].points[1]];
		b5_temp_[2] = points_[tetras_[cur_check].points[2]];
		b5_temp_[3] = points_[tetras_[cur_check].points[3]];
		if (insphere(b5_temp_) < -0)
		{
			FindFlip(cur_check, to_flip,index,p_loc,other_point_loc);
		}
	}
}

std::size_t Delaunay3D::Walk(std::size_t point, std::size_t first_guess)
{
	bool good = false;
	std::size_t cur_facet = first_guess;
	std::size_t counter=0;
	b4_temp_[3] = points_[point];
	while (!good)
	{
		++counter;
		good = true;
		for (size_t i = 0; i < 4; ++i)
		{
			for (size_t j = 0; j < 3; j++)
				b4_temp_[j] = points_[tetras_[cur_facet].points[(i + j + 1) % 4]];
			int sign = 2 * static_cast<int>(i % 2) - 1;
			if ((orient3d(b4_temp_)*sign)>0)
			{
				good = false;
				size_t old_facet = cur_facet;
				cur_facet = tetras_[cur_facet].neighbors[i];
				if(cur_facet == outside_neighbor_)
				{
					std::cout<<"Walk wanted to goto outside neighbor"<<std::endl;
					std::cout << "Total of "<< Norg_ << " points" << std::endl;
					std::cout<<"point "<<point<<" "<<points_[point].x<<" "<<points_[point].y<<" "<<points_[point].z<<" "<<std::endl;
					std::cout<<"Big tetrahedron "<<points_[Norg_].x<<" "<<points_[Norg_].y<<" "<<points_[Norg_].z<<" "<<std::endl;
					std::cout<<"Big tetrahedron "<<points_[Norg_+1].x<<" "<<points_[Norg_+1].y<<" "<<points_[Norg_+1].z<<" "<<std::endl;
					std::cout<<"Big tetrahedron "<<points_[Norg_+2].x<<" "<<points_[Norg_+2].y<<" "<<points_[Norg_+2].z<<" "<<std::endl;
					std::cout<<"Big tetrahedron "<<points_[Norg_+3].x<<" "<<points_[Norg_+3].y<<" "<<points_[Norg_+3].z<<" "<<std::endl;
					std::cout << "Came from face " << old_facet << std::endl;
					for (size_t j = 0; j < 3; ++j)
						std::cout <<"point "<< tetras_[old_facet].points[(i + static_cast<size_t>(j) + 1) % 4]<<" "<< b4_temp_[j].x << " " << b4_temp_[j].y << " " << b4_temp_[j].z << std::endl;
					UniversalError eo("Bad Walk");
					throw eo;
				}
				break;
			}
		}
		assert(counter < 100000);
	}
	return cur_facet;
}

void Delaunay3D::flip14(std::size_t point, std::size_t tetra)
{
	Tetrahedron toadd;
	std::array<std::size_t, 3> Nloc;
	bool cleared_empty = false;
	if (empty_tetras_.size()>3)
	{
		cleared_empty = true;
		for (size_t i = 0; i < 3; i++)
		{
			Nloc[i] = *empty_tetras_.begin();
			empty_tetras_.erase(empty_tetras_.begin());
		}
	}
	else
	{
		size_t Ntet = tetras_.size();
		for (size_t i = 0; i < 3; i++)
			Nloc[i] = Ntet + i;
	}

	toadd.neighbors[0] = Nloc[1];
	toadd.neighbors[1] = Nloc[2];
	toadd.neighbors[2] = tetras_[tetra].neighbors[0];
	toadd.neighbors[3] = tetra;
	toadd.points[0] = tetras_[tetra].points[1];
	toadd.points[1] = tetras_[tetra].points[2];
	toadd.points[2] = point;
	toadd.points[3] = tetras_[tetra].points[3];
	if (toadd.neighbors[2] != outside_neighbor_)
	{
		size_t temploc = toadd.neighbors[2];
		size_t loctemp=0;
		GetOppositePoint(tetras_[temploc], tetra, loctemp);
		tetras_[toadd.neighbors[2]].neighbors[loctemp] = Nloc[0];
	}
	if (!cleared_empty)
		tetras_.push_back(toadd);
	else
		tetras_[Nloc[0]] = toadd;

	toadd.neighbors[0] = Nloc[0];
	toadd.neighbors[1] = Nloc[2];
	toadd.neighbors[2] = tetra;
	toadd.neighbors[3] = tetras_[tetra].neighbors[1];
	toadd.points[0] = tetras_[tetra].points[0];
	toadd.points[1] = tetras_[tetra].points[2];
	toadd.points[2] = tetras_[tetra].points[3];
	toadd.points[3] = point;
	if (toadd.neighbors[3] != outside_neighbor_)
	{
		size_t loctemp=0;
		GetOppositePoint(tetras_[toadd.neighbors[3]], tetra, loctemp);
		tetras_[toadd.neighbors[3]].neighbors[loctemp] = Nloc[1];
	}
	if (!cleared_empty)
		tetras_.push_back(toadd);
	else
		tetras_[Nloc[1]] = toadd;

	toadd.neighbors[0] = Nloc[0];
	toadd.neighbors[1] = tetra;
	toadd.neighbors[2] = Nloc[1];
	toadd.neighbors[3] = tetras_[tetra].neighbors[2];
	toadd.points[0] = tetras_[tetra].points[0];
	toadd.points[1] = tetras_[tetra].points[3];
	toadd.points[2] = tetras_[tetra].points[1];
	toadd.points[3] = point;
	if (toadd.neighbors[3] != outside_neighbor_)
	{
		size_t loctemp=0;
		GetOppositePoint(tetras_[toadd.neighbors[3]], tetra, loctemp);
		tetras_[toadd.neighbors[3]].neighbors[loctemp] = Nloc[2];
	}
	if (!cleared_empty)
		tetras_.push_back(toadd);
	else
		tetras_[Nloc[2]] = toadd;

	tetras_[tetra].neighbors[0] = Nloc[0];
	tetras_[tetra].neighbors[1] = Nloc[1];
	tetras_[tetra].neighbors[2] = Nloc[2];
	tetras_[tetra].points[3] = point;

	to_check_.push_back(tetra);
	to_check_.push_back(Nloc[0]);
	to_check_.push_back(Nloc[1]);
	to_check_.push_back(Nloc[2]);
#ifdef runcheks
	for (std::size_t i = 0; i < 4; ++i)
		b4_temp_[i] = points_[tetras_[tetras_.size() - 1].points[i]];
	assert(orient3d(b4_temp_) <= 0);
	for (std::size_t i = 0; i < 4; ++i)
		b4_temp_[i] = points_[tetras_[tetras_.size()-2].points[i]];
	assert(orient3d(b4_temp_) <= 0);
	for (std::size_t i = 0; i < 4; ++i)
		b4_temp_[i] = points_[tetras_[tetras_.size() - 3].points[i]];
	assert(orient3d(b4_temp_) <= 0);
	for (std::size_t i = 0; i < 4; ++i)
		b4_temp_[i] = points_[tetras_[tetra].points[i]];
	assert(orient3d(b4_temp_) <= 0);
#endif
}

bool Delaunay3D::CheckCorrect(void)
{
	std::size_t Ntetra = tetras_.size();

	for (std::size_t i = 0; i < Ntetra; ++i)
	{
		if (empty_tetras_.find(i) != empty_tetras_.end())
			continue;
		Tetrahedron const& T = tetras_[i];
		b5_temp_[0] = points_[T.points[0]];
		b5_temp_[1] = points_[T.points[1]];
		b5_temp_[2] = points_[T.points[2]];
		b5_temp_[3] = points_[T.points[3]];
		for (std::size_t j = 0; j < 4; ++j)
		{
			size_t loctemp=4;
			// Check same neighbors
			if (T.neighbors[j] != outside_neighbor_)
			{
				GetOppositePoint(tetras_[T.neighbors[j]], i, loctemp);
				assert(loctemp < 4);
			}
			// Check insphere
			if (T.neighbors[j] != outside_neighbor_)
			{
				std::size_t other=4;
				GetOppositePoint(tetras_[T.neighbors[j]], i,other);
				assert(other < 4);
				b5_temp_[4] = points_[tetras_[T.neighbors[j]].points[other]];
				assert(!(insphere(b5_temp_) < 0));
			}
		}
	}
	return true;
}

void Delaunay3D::Clean(void)
{
	tetras_.clear();
	points_.clear();
	empty_tetras_.clear();
}


#include "Kernels/TimeBase.h"
#include "Kernels/Time.h"

#ifndef NDEBUG
extern long long libxsmm_num_total_flops;
#endif

#include <Kernels/common.hpp>
#include <Kernels/denseMatrixOps.hpp>

#include <cstring>
#include <cassert>
#include <stdint.h>
#include <omp.h>
#include <Eigen/Dense>

#include "Equations/poroelastic/Model/PoroelasticSetup.h"

#include <yateto.h>

GENERATE_HAS_MEMBER(ET)
GENERATE_HAS_MEMBER(sourceMatrix)

seissol::kernels::TimeBase::TimeBase(){
  m_derivativesOffsets[0] = 0;
  for (int order = 0; order < CONVERGENCE_ORDER; ++order) {
    if (order > 0) {
      m_derivativesOffsets[order] = tensor::dQ::size(order-1) + m_derivativesOffsets[order-1];
    }
  }
}

void seissol::kernels::Time::setHostGlobalData(GlobalData const* global) {
  for (int n = 0; n < CONVERGENCE_ORDER; ++n) {
    if (n > 0) {
      for (int d = 0; d < 3; ++d) {
        m_krnlPrototype.kDivMTSub(d,n) = init::kDivMTSub::Values[tensor::kDivMTSub::index(d,n)];
      }
    }
    m_krnlPrototype.selectModes(n) = init::selectModes::Values[tensor::selectModes::index(n)];
  }
  for (int k = 0; k < NUMBER_OF_QUANTITIES; k++) {
    m_krnlPrototype.selectQuantity(k) = init::selectQuantity::Values[tensor::selectQuantity::index(k)];
    m_krnlPrototype.selectQuantityG(k) = init::selectQuantityG::Values[tensor::selectQuantityG::index(k)];
  }
  m_krnlPrototype.timeInt = init::timeInt::Values;
  m_krnlPrototype.wHat = init::wHat::Values;
}

void seissol::kernels::Time::setGlobalData(const CompoundGlobalData& global) {
  setHostGlobalData(global.onHost);

#ifdef ACL_DEVICE
  logError() << "Poroelasticity does not work on GPUs.";
#endif
}

void seissol::kernels::Time::executeSTP( double                      i_timeStepWidth,
                                         LocalData&                  data,
                                         real                        o_timeIntegrated[tensor::I::size()],
                                         real*                       stp )

{
  alignas(PAGESIZE_STACK) real stpRhs[tensor::spaceTimePredictorRhs::size()];
  assert( ((uintptr_t)stp) % PAGESIZE_STACK  == 0);
  std::fill(std::begin(stpRhs), std::end(stpRhs), 0);
  std::fill(stp, stp + tensor::spaceTimePredictor::size(), 0);
  kernel::spaceTimePredictor krnl = m_krnlPrototype;

  //libxsmm can not generate GEMMs with alpha!=1. As a workaround we multiply the
  //star matrices with dt before we execute the kernel.
  real A_values[init::star::size(0)];
  real B_values[init::star::size(1)];
  real C_values[init::star::size(2)];
  for (size_t i = 0; i < init::star::size(0); i++) {
    A_values[i] = i_timeStepWidth * data.localIntegration.starMatrices[0][i];
    B_values[i] = i_timeStepWidth * data.localIntegration.starMatrices[1][i];
    C_values[i] = i_timeStepWidth * data.localIntegration.starMatrices[2][i];
  }
  krnl.star(0) = A_values;
  krnl.star(1) = B_values;
  krnl.star(2) = C_values;

  //The matrix Zinv depends on the timestep
  //If the timestep is not as expected e.g. when approaching a sync point
  //we have to recalculate it
  if (i_timeStepWidth != data.localIntegration.specific.typicalTimeStepWidth) {
    auto sourceMatrix = init::ET::view::create(data.localIntegration.specific.sourceMatrix);
    real ZinvData[NUMBER_OF_QUANTITIES][CONVERGENCE_ORDER*CONVERGENCE_ORDER];
    model::zInvInitializerForLoop<0, NUMBER_OF_QUANTITIES, decltype(sourceMatrix)>(ZinvData, sourceMatrix, i_timeStepWidth);
    for (size_t i = 0; i < NUMBER_OF_QUANTITIES; i++) {
      krnl.Zinv(i) = ZinvData[i];
    }
  } else {
    for (size_t i = 0; i < NUMBER_OF_QUANTITIES; i++) {
      krnl.Zinv(i) = data.localIntegration.specific.Zinv[i];
    }
  }
  krnl.Gk = data.localIntegration.specific.G[10] * i_timeStepWidth;
  krnl.Gl = data.localIntegration.specific.G[11] * i_timeStepWidth;
  krnl.Gm = data.localIntegration.specific.G[12] * i_timeStepWidth;

  krnl.Q = const_cast<real*>(data.dofs);
  krnl.I = o_timeIntegrated;
  krnl.timestep = i_timeStepWidth;
  krnl.spaceTimePredictor = stp;
  krnl.spaceTimePredictorRhs = stpRhs;
  krnl.execute();
}

void seissol::kernels::Time::computeAder( double i_timeStepWidth,
                                          LocalData& data,
                                          LocalTmp& tmp,
                                          real o_timeIntegrated[tensor::I::size()],
                                          real* o_timeDerivatives,
                                          double startTime,
                                          bool updateDisplacement)
{
  /*
   * assert alignments.
   */
  assert( ((uintptr_t)data.dofs)              % ALIGNMENT == 0 );
  assert( ((uintptr_t)o_timeIntegrated )      % ALIGNMENT == 0 );
  assert( ((uintptr_t)o_timeDerivatives)      % ALIGNMENT == 0 || o_timeDerivatives == NULL );

  alignas(PAGESIZE_STACK) real temporaryBuffer[tensor::spaceTimePredictor::size()];
  real* stpBuffer = (o_timeDerivatives != nullptr) ? o_timeDerivatives : temporaryBuffer;
  executeSTP( i_timeStepWidth, data, o_timeIntegrated, stpBuffer );
}

void seissol::kernels::Time::flopsAder( unsigned int        &o_nonZeroFlops,
                                        unsigned int        &o_hardwareFlops ) {
  // reset flops
  o_nonZeroFlops = 0; o_hardwareFlops =0;

  o_nonZeroFlops = kernel::spaceTimePredictor::NonZeroFlops;
  o_hardwareFlops = kernel::spaceTimePredictor::HardwareFlops;
  //we multiply the star matrices with dt before we execute the kernel
  o_nonZeroFlops += 3*init::star::size(0);
  o_hardwareFlops += 3*init::star::size(0);
}

unsigned seissol::kernels::Time::bytesAder()
{
  unsigned reals = 0;

  // DOFs load, tDOFs load, tDOFs write
  reals += tensor::Q::size() + 2 * tensor::I::size();
  // star matrices, source matrix
  reals += yateto::computeFamilySize<tensor::star>();
  // Zinv
  reals += yateto::computeFamilySize<tensor::Zinv>();
  // G
  reals += 3;

  /// \todo incorporate derivatives

  return reals * sizeof(real);
}

void seissol::kernels::Time::computeIntegral( double                            i_expansionPoint,
                                              double                            i_integrationStart,
                                              double                            i_integrationEnd,
                                              const real*                       i_timeDerivatives,
                                              real                              o_timeIntegrated[tensor::I::size()])
{
  /*
   * assert alignments.
   */
  assert( ((uintptr_t)i_timeDerivatives)  % ALIGNMENT == 0 );
  assert( ((uintptr_t)o_timeIntegrated)   % ALIGNMENT == 0 );

  // assert that this is a forwared integration in time
  assert( i_integrationStart + (real) 1.E-10 > i_expansionPoint   );
  assert( i_integrationEnd                   > i_integrationStart );

  /*
   * compute time integral.
   */
  // compute lengths of integration intervals
  real l_deltaTLower = i_integrationStart - i_expansionPoint;
  real l_deltaTUpper = i_integrationEnd   - i_expansionPoint;

  // initialization of scalars in the taylor series expansion (0th term)
  real l_firstTerm  = (real) 1;
  real l_secondTerm = (real) 1;
  real l_factorial  = (real) 1;

  kernel::derivativeTaylorExpansion intKrnl;
  intKrnl.I = o_timeIntegrated;
  for (unsigned i = 0; i < yateto::numFamilyMembers<tensor::dQ>(); ++i) {
    intKrnl.dQ(i) = i_timeDerivatives + m_derivativesOffsets[i];
  }

  // iterate over time derivatives
  for(int der = 0; der < CONVERGENCE_ORDER; ++der ) {
    l_firstTerm  *= l_deltaTUpper;
    l_secondTerm *= l_deltaTLower;
    l_factorial  *= (real)(der+1);

    intKrnl.power  = l_firstTerm - l_secondTerm;
    intKrnl.power /= l_factorial;

    intKrnl.execute(der);
  }
}

void seissol::kernels::Time::computeTaylorExpansion( real         time,
                                                     real         expansionPoint,
                                                     real const*  timeDerivatives,
                                                     real         timeEvaluated[tensor::Q::size()] ) {
  /*
   * assert alignments.
   */
  assert( ((uintptr_t)timeDerivatives)  % ALIGNMENT == 0 );
  assert( ((uintptr_t)timeEvaluated)    % ALIGNMENT == 0 );

  // assert that this is a forward evaluation in time
  assert( time >= expansionPoint );

  real deltaT = time - expansionPoint;

  static_assert(tensor::I::size() == tensor::Q::size(), "Sizes of tensors I and Q must match");

  kernel::derivativeTaylorExpansion intKrnl;
  intKrnl.I = timeEvaluated;
  for (unsigned i = 0; i < yateto::numFamilyMembers<tensor::dQ>(); ++i) {
    intKrnl.dQ(i) = timeDerivatives + m_derivativesOffsets[i];
  }
  intKrnl.power = 1.0;

  // iterate over time derivatives
  for(int derivative = 0; derivative < CONVERGENCE_ORDER; ++derivative) {
    intKrnl.execute(derivative);
    intKrnl.power *= deltaT / real(derivative+1);
  }
}

void seissol::kernels::Time::flopsTaylorExpansion(long long& nonZeroFlops, long long& hardwareFlops) {
  // reset flops
  nonZeroFlops = 0; hardwareFlops = 0;

  // interate over derivatives
  for (unsigned der = 0; der < CONVERGENCE_ORDER; ++der) {
    nonZeroFlops  += kernel::derivativeTaylorExpansion::nonZeroFlops(der);
    hardwareFlops += kernel::derivativeTaylorExpansion::hardwareFlops(der);
  }
}


/*~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\
|  Phycas: Python software for phylogenetic analysis                          |
|  Copyright (C) 2006 Mark T. Holder, Paul O. Lewis and David L. Swofford     |
|                                                                             |
|  This program is free software; you can redistribute it and/or modify       |
|  it under the terms of the GNU General Public License as published by       |
|  the Free Software Foundation; either version 2 of the License, or          |
|  (at your option) any later version.                                        |
|                                                                             |
|  This program is distributed in the hope that it will be useful,            |
|  but WITHOUT ANY WARRANTY; without even the implied warranty of             |
|  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              |
|  GNU General Public License for more details.                               |
|                                                                             |
|  You should have received a copy of the GNU General Public License along    |
|  with this program; if not, write to the Free Software Foundation, Inc.,    |
|  51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.                |
\~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~*/

#if defined(_MSC_VER)
#	pragma warning(disable : 4267) // boost's builtin_converters.hpp casts size_t to int rather than unsigned
#endif

#if defined(USING_NUMARRAY)
#	define PY_ARRAY_UNIQUE_SYMBOL PyArrayHandle
#endif

#include <boost/python.hpp>

#include "basic_lot.hpp"
#include "probability_distribution.hpp"
#include "mvnormal_distribution.hpp"
#include "relative_rate_distribution.hpp"
#include "lognormal.hpp"
#include "stop_watch.hpp"
#include "slice_sampler.hpp"
#include "square_matrix.hpp"
#include "rectangular_matrix.hpp"
#include "xprobdist.hpp"

using namespace boost::python;
using namespace phycas;

double getEffectiveLnZero()
	{
	return -DBL_MAX;
	}

double lnGamma(double x)
	{
	return CDF().LnGamma(x);
	}

// The following wrapper struct is needed because we will potentially be deriving Python classes from AdHocDensity
// This struct is thus only necessary if we plan to do something like this in Python:
//		MyFunctor(AdhocDensity):
//			...
// See http://wiki.python.org/moin/boost.python/InternalDataStructures
//
struct AdHocDensityWrapper : AdHocDensity
	{
	AdHocDensityWrapper(PyObject * p) : self(p) {}
	virtual ~AdHocDensityWrapper()
		{
		//std::cerr << "(ProbDist)AdHocDensityWrapper dying..." << std::endl;
		}
	double operator()(double x) { return boost::python::call_method<double>(self, "__call__", x); }
    PyObject * self;
	};

void translateXProbDist(const XProbDist &e)
	{
    // Use the Python 'C' API to set up an exception object
    PyErr_SetString(PyExc_Exception, e.what());
    }

BOOST_PYTHON_MODULE(_ProbDistExt)
{
	def("getEffectiveLnZero", getEffectiveLnZero);
	def("lnGamma", lnGamma);

#if defined(USING_NUMARRAY)
	// these lines required by num_util
	import_array();
	numeric::array::set_module_and_type("numarray", "NDArray");
#endif

	class_<AdHocDensity, boost::noncopyable, boost::shared_ptr<AdHocDensityWrapper> >("AdHocDensityBase")
		;

	class_<ProbabilityDistribution, bases<AdHocDensity>, boost::shared_ptr<ProbabilityDistribution>, boost::noncopyable>("ProbabilityDistribution", no_init)
        .def("lnGamma", &ProbabilityDistribution::LnGamma)
		;

	class_<MultivariateProbabilityDistribution, boost::shared_ptr<MultivariateProbabilityDistribution>, boost::noncopyable>("MultivariateProbabilityDistribution", no_init)
		;

	class_<SliceSampler, boost::shared_ptr<SliceSampler> >("SliceSamplerBase")
		.def(init<boost::shared_ptr<phycas::Lot>, boost::shared_ptr<AdHocDensity> >())
		.def("attachFunc", &SliceSampler::AttachFunc)
		.def("sample", &SliceSampler::Sample)
		.def("debugSample", &SliceSampler::DebugSample)
		.def("overrelaxedSample", &SliceSampler::OverrelaxedSample)
		.def("debugOverrelaxedSample", &SliceSampler::DebugOverrelaxedSample)
		.def("attachRandomNumberGenerator", &SliceSampler::AttachRandomNumberGenerator)
		.def("adaptSimple", &SliceSampler::AdaptSimple)
		.def("adaptNeal", &SliceSampler::AdaptNeal)
		.def("getSliceUnitWidth", &SliceSampler::GetSliceUnitWidth)
		.def("setSliceUnitWidth", &SliceSampler::SetSliceUnitWidth)
		.def("getMinX", &SliceSampler::GetMinX)
		.def("getMaxX", &SliceSampler::GetMaxX)
		.def("setMaxUnits", &SliceSampler::SetMaxUnits)
		.def("getOrigLeftEdgeOfSlice", &SliceSampler::GetOrigLeftEdgeOfSlice)
		.def("getOrigRightEdgeOfSlice", &SliceSampler::GetOrigRightEdgeOfSlice)
		.def("getLeftEdgeOfSlice", &SliceSampler::GetLeftEdgeOfSlice)
		.def("getRightEdgeOfSlice", &SliceSampler::GetRightEdgeOfSlice)
		.def("getSliceYValue", &SliceSampler::GetSliceYValue)
		.def("getNumFuncEvals", &SliceSampler::GetNumFuncEvals)
		.def("getNumFailedSamples", &SliceSampler::GetNumFailedSamples)
		.def("getNumUnitsRequired", &SliceSampler::GetNumUnitsRequired)
		.def("getNumSamples", &SliceSampler::GetNumSamples)
		.def("resetDiagnostics", &SliceSampler::ResetDiagnostics)
		.def("adaptYConditional", &SliceSampler::AdaptYConditional)
		.def("calcW", &SliceSampler::CalcW)
		.def("getMode", &SliceSampler::GetMode)
		.def("getLnDensityAtMode", &SliceSampler::GetLnDensityAtMode)
		.def("getLastSampledXValue", &SliceSampler::GetLastSampledXValue)
		.def("getLastSampledYValue", &SliceSampler::GetLastSampledYValue)
		.def("setXValue", &SliceSampler::SetXValue)
		.def("useDoublingMethod", &SliceSampler::UseDoublingMethod)
		;

	class_<phycas::StopWatch, boost::shared_ptr<phycas::StopWatch>, boost::noncopyable>("StopWatchBase")
		.def("start", &phycas::StopWatch::start)
		.def("stop", &phycas::StopWatch::stop)
		.def("reset", &phycas::StopWatch::reset)
		.def("normalize", &phycas::StopWatch::normalize)
		.def("elapsedSeconds", &phycas::StopWatch::elapsedSeconds)
		.def("split", &phycas::StopWatch::split)
		.def("stopTicks", &phycas::StopWatch::stopTicks)
		//.def("doofus", &phycas::StopWatch::doofus)
		;

	class_<phycas::SubsetProportions, boost::shared_ptr<phycas::SubsetProportions>, boost::noncopyable>("SubsetProportionsBase")
		.def("getSubsetProportions", &phycas::SubsetProportions::getSubsetProportions, return_value_policy<copy_const_reference>())
		.def("setSubsetProportions", &phycas::SubsetProportions::setSubsetProportions)
		.def("setSubsetProportionsFromNumSites", &phycas::SubsetProportions::setSubsetProportionsFromNumSites)
        .def("getLogProdProportions", &phycas::SubsetProportions::getLogProdProportions)
		;

	class_<phycas::SquareMatrix, boost::shared_ptr<phycas::SquareMatrix>, boost::noncopyable>("SquareMatrixBase", init<unsigned, double>())
		.def(init<const phycas::SquareMatrix &>())
		.def("duplicate", &phycas::SquareMatrix::Duplicate, return_value_policy<manage_new_object>())
		.def("pow", &phycas::SquareMatrix::Power, return_value_policy<manage_new_object>())
		.def("inverse", &phycas::SquareMatrix::Inverse, return_value_policy<manage_new_object>())
		.def("LUDecomposition", &phycas::SquareMatrix::LUDecomposition, return_value_policy<manage_new_object>())
		.def("CholeskyDecomposition", &phycas::SquareMatrix::CholeskyDecomposition, return_value_policy<manage_new_object>())
		.def("rightMultiplyMatrix", &phycas::SquareMatrix::RightMultiplyMatrix, return_value_policy<manage_new_object>())
		.def("leftMultiplyMatrix", &phycas::SquareMatrix::LeftMultiplyMatrix, return_value_policy<manage_new_object>())
		.def("rightMultiplyVector", &phycas::SquareMatrix::RightMultiplyVector)
		.def("leftMultiplyVector", &phycas::SquareMatrix::LeftMultiplyVector)
		.def("identity", &phycas::SquareMatrix::Identity)
		.def("trace", &phycas::SquareMatrix::Trace)
		.def("logProdMainDiag", &phycas::SquareMatrix::LogProdMainDiag)
		.def("logDeterminant", &phycas::SquareMatrix::LogDeterminant)
		.def("getDimension", &phycas::SquareMatrix::GetDimension)
		.def("__repr__", &phycas::SquareMatrix::GetStringRepresentation)
		.def("addToElement", &phycas::SquareMatrix::AddToElement)
		.def("setElement", &phycas::SquareMatrix::SetElement)
		.def("getElement", &phycas::SquareMatrix::GetElement)
		.def("setMatrix", &phycas::SquareMatrix::SetMatrix)
		.def("getMatrix", &phycas::SquareMatrix::GetMatrix)
		;

	class_<phycas::RectangularMatrix, boost::shared_ptr<phycas::RectangularMatrix>, boost::noncopyable>("RectangularMatrixBase", init<unsigned, unsigned, double>())
		.def(init<const phycas::RectangularMatrix &>())
        .def("getDimensions", &phycas::RectangularMatrix::GetDimensions)
        .def("getNRows", &phycas::RectangularMatrix::GetNRows)
        .def("getNCols", &phycas::RectangularMatrix::GetNCols)
		.def("__repr__", &phycas::RectangularMatrix::GetStringRepresentation)
        .def("addToElement", &phycas::RectangularMatrix::AddToElement)
        .def("setElement", &phycas::RectangularMatrix::SetElement)
        .def("getElement", &phycas::RectangularMatrix::GetElement)
        .def("setMatrix", &phycas::RectangularMatrix::SetMatrix)
        .def("getMatrix", &phycas::RectangularMatrix::GetMatrix)
        .def("setRow", &phycas::RectangularMatrix::SetRow)
        .def("getRow", &phycas::RectangularMatrix::GetRow)
        .def("getMean", &phycas::RectangularMatrix::GetMean)
        .def("getVarCovMatrix", &phycas::RectangularMatrix::GetVarCovMatrix, return_value_policy<manage_new_object>())
		;

//We tell boost::python the smart pointer type we're using, like this:
//class_<DrawableInterface, DrawablePtr, boost::noncopyable>
//("DrawableInterface", no_init)
//    ;
//where DrawablePtr is a typedef for the smart pointer type.

	class_<phycas::Lot, boost::shared_ptr<phycas::Lot>, boost::noncopyable>("LotBase", init<unsigned>())
		.def("getSeed", &phycas::Lot::GetSeed)
		.def("setSeed", &phycas::Lot::SetSeed)
		.def("getInitSeed", &phycas::Lot::GetInitSeed)
		.def("uniform", &phycas::Lot::Uniform)
		.def("normal", &phycas::Lot::Normal)
		.def("getrandbits", &phycas::Lot::GetRandBits)
		.def("sampleUInt", &phycas::Lot::SampleUInt)
		;

	class_<MVNormalDistribution, bases<MultivariateProbabilityDistribution> >("MVNormalDistBase")
		.def(init<const std::vector<double> &, const std::vector<double> &>())
		.def(init<const MVNormalDistribution &>())
		.def("cloneAndSetLot", &MVNormalDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &MVNormalDistribution::Clone, return_value_policy<manage_new_object>())
		.def("fit", &MVNormalDistribution::Fit)
		.def("isDiscrete", &MVNormalDistribution::IsDiscrete)
		.def("getDistName", &MVNormalDistribution::GetDistributionName)
		.def("__str__", &MVNormalDistribution::GetDescriptionForPython)
		.def("__repr__", &MVNormalDistribution::GetDescriptionForPython)
		.def("setLot", &MVNormalDistribution::SetLot)
		.def("setSeed", &MVNormalDistribution::SetSeed)
		.def("resetLot", &MVNormalDistribution::ResetLot)
		.def("getMean", &MVNormalDistribution::GetMean)
		.def("getVar", &MVNormalDistribution::GetVar)
		.def("getStdDev", &MVNormalDistribution::GetStdDev)
		.def("approxCDF", &MVNormalDistribution::ApproxCDF)
		.def("sample", &MVNormalDistribution::Sample)
		.def("getLnPDF", &MVNormalDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &MVNormalDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &MVNormalDistribution::SetMeanAndVariance)
		.def("getVarCovarMatrix", &MVNormalDistribution::GetVarCovarMatrix)
		.def("getNParams", &MVNormalDistribution::GetNParams)
		.def("debugMVNorm", &MVNormalDistribution::DebugMVNorm)
		;

	class_<DirichletDistribution, bases<MultivariateProbabilityDistribution> >("DirichletDistBase")
		.def(init<const std::vector<double> &>())
		.def(init<const DirichletDistribution &>())
		.def("cloneAndSetLot", &DirichletDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &DirichletDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &DirichletDistribution::IsDiscrete)
		.def("getDistName", &DirichletDistribution::GetDistributionName)
		.def("__str__", &DirichletDistribution::GetDescriptionForPython)
		.def("__repr__", &DirichletDistribution::GetDescriptionForPython)
		.def("setLot", &DirichletDistribution::SetLot)
		.def("setSeed", &DirichletDistribution::SetSeed)
		.def("resetLot", &DirichletDistribution::ResetLot)
		.def("getMean", &DirichletDistribution::GetMean)
		.def("getVar", &DirichletDistribution::GetVar)
		.def("getStdDev", &DirichletDistribution::GetStdDev)
		.def("approxCDF", &DirichletDistribution::ApproxCDF)
		.def("sample", &DirichletDistribution::Sample)
		.def("getLnPDF", &DirichletDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &DirichletDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &DirichletDistribution::SetMeanAndVariance)
		.def("getVarCovarMatrix", &DirichletDistribution::GetVarCovarMatrix)
		.def("getNParams", &DirichletDistribution::GetNParams)
		;

	class_<RelativeRateDistribution, bases<MultivariateProbabilityDistribution> >("RelRateDistBase")
		.def(init<const std::vector<double> &, const std::vector<double> &>())
		.def(init<const RelativeRateDistribution &>())
		.def("cloneAndSetLot", &RelativeRateDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &RelativeRateDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &RelativeRateDistribution::IsDiscrete)
		.def("getDistName", &RelativeRateDistribution::GetDistributionName)
		.def("__str__", &RelativeRateDistribution::GetDescriptionForPython)
		.def("__repr__", &RelativeRateDistribution::GetDescriptionForPython)
		.def("setLot", &RelativeRateDistribution::SetLot)
		.def("setSeed", &RelativeRateDistribution::SetSeed)
		.def("resetLot", &RelativeRateDistribution::ResetLot)
		.def("getMean", &RelativeRateDistribution::GetMean)
		.def("getVar", &RelativeRateDistribution::GetVar)
		.def("getStdDev", &RelativeRateDistribution::GetStdDev)
		.def("approxCDF", &RelativeRateDistribution::ApproxCDF)
		.def("sample", &RelativeRateDistribution::Sample)
		.def("getLnPDF", &RelativeRateDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &RelativeRateDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &RelativeRateDistribution::SetMeanAndVariance)
		.def("getVarCovarMatrix", &RelativeRateDistribution::GetVarCovarMatrix)
		.def("getNParams", &RelativeRateDistribution::GetNParams)
		.def("setSubsetProportions", &RelativeRateDistribution::setSubsetProportions)
		//.def("setCoefficients", &RelativeRateDistribution::SetCoefficients)
		;

	class_<BetaDistribution, bases<ProbabilityDistribution, AdHocDensity> >("BetaDistBase")
		.def(init<double, double>())
		.def(init<const BetaDistribution &>())
		.def("cloneAndSetLot", &BetaDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &BetaDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &BetaDistribution::IsDiscrete)
		.def("getDistName", &BetaDistribution::GetDistributionName)
		.def("__str__", &BetaDistribution::GetDistributionDescription)
		.def("__repr__", &BetaDistribution::GetDistributionDescription)
		.def("setLot", &BetaDistribution::SetLot)
		.def("setSeed", &BetaDistribution::SetSeed)
		.def("resetLot", &BetaDistribution::ResetLot)
		.def("getMean", &BetaDistribution::GetMean)
		.def("getVar", &BetaDistribution::GetVar)
		.def("getStdDev", &BetaDistribution::GetStdDev)
		.def("getCDF", &BetaDistribution::GetCDF)
		.def("getQuantile", &BetaDistribution::GetQuantile)
		.def("sample", &BetaDistribution::Sample)
		.def("getLnPDF", &BetaDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &BetaDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &BetaDistribution::SetMeanAndVariance)
		;

	class_<BetaPrimeDistribution, bases<ProbabilityDistribution, AdHocDensity> >("BetaPrimeDistBase")
		.def(init<double, double>())
		.def(init<const BetaPrimeDistribution &>())
		.def("cloneAndSetLot", &BetaPrimeDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &BetaPrimeDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &BetaPrimeDistribution::IsDiscrete)
		.def("getDistName", &BetaPrimeDistribution::GetDistributionName)
		.def("__str__", &BetaPrimeDistribution::GetDistributionDescription)
		.def("__repr__", &BetaPrimeDistribution::GetDistributionDescription)
		.def("setLot", &BetaPrimeDistribution::SetLot)
		.def("setSeed", &BetaPrimeDistribution::SetSeed)
		.def("resetLot", &BetaPrimeDistribution::ResetLot)
		.def("getMean", &BetaPrimeDistribution::GetMean)
		.def("getVar", &BetaPrimeDistribution::GetVar)
		.def("getStdDev", &BetaPrimeDistribution::GetStdDev)
		.def("getCDF", &BetaPrimeDistribution::GetCDF)
		.def("getQuantile", &BetaPrimeDistribution::GetQuantile)
		.def("sample", &BetaPrimeDistribution::Sample)
		.def("getLnPDF", &BetaPrimeDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &BetaPrimeDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &BetaPrimeDistribution::SetMeanAndVariance)
		;
	class_<BernoulliDistribution, bases<ProbabilityDistribution> >("BernoulliDistBase")
		.def(init<double>())
		.def(init<const BernoulliDistribution &>())
		.def("cloneAndSetLot", &BernoulliDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &BernoulliDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &BernoulliDistribution::IsDiscrete)
		.def("getDistName", &BernoulliDistribution::GetDistributionName)
		.def("__str__", &BernoulliDistribution::GetDistributionDescription)
		.def("__repr__", &BernoulliDistribution::GetDistributionDescription)
		.def("setLot", &BernoulliDistribution::SetLot)
		.def("setSeed", &BernoulliDistribution::SetSeed)
		.def("resetLot", &BernoulliDistribution::ResetLot)
		.def("getMean", &BernoulliDistribution::GetMean)
		.def("getVar", &BernoulliDistribution::GetVar)
		.def("getStdDev", &BernoulliDistribution::GetStdDev)
		.def("getCDF", &BernoulliDistribution::GetCDF)
		.def("sample", &BernoulliDistribution::Sample)
		.def("getLnPDF", &BernoulliDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &BernoulliDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &BernoulliDistribution::SetMeanAndVariance)
		;
	class_<BinomialDistribution, bases<ProbabilityDistribution> >("BinomialDistBase")
		.def(init<double, double>())
		.def(init<const BinomialDistribution &>())
		.def("cloneAndSetLot", &BinomialDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &BinomialDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &BinomialDistribution::IsDiscrete)
		.def("getDistName", &BinomialDistribution::GetDistributionName)
		.def("__str__", &BinomialDistribution::GetDistributionDescription)
		.def("__repr__", &BinomialDistribution::GetDistributionDescription)
		.def("setLot", &BinomialDistribution::SetLot)
		.def("setSeed", &BinomialDistribution::SetSeed)
		.def("resetLot", &BinomialDistribution::ResetLot)
		.def("getMean", &BinomialDistribution::GetMean)
		.def("getVar", &BinomialDistribution::GetVar)
		.def("getStdDev", &BinomialDistribution::GetStdDev)
		.def("getCDF", &BinomialDistribution::GetCDF)
		.def("sample", &BinomialDistribution::Sample)
		.def("getLnPDF", &BinomialDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &BinomialDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &BernoulliDistribution::SetMeanAndVariance)
		;
	class_<ImproperUniformDistribution, bases<ProbabilityDistribution, AdHocDensity> >("ImproperUniformDistBase")
		.def(init<const ImproperUniformDistribution &>())
		.def("cloneAndSetLot", &ImproperUniformDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &ImproperUniformDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &ImproperUniformDistribution::IsDiscrete)
		.def("getDistName", &ImproperUniformDistribution::GetDistributionName)
		.def("__str__", &ImproperUniformDistribution::GetDistributionDescription)
		.def("__repr__", &ImproperUniformDistribution::GetDistributionDescription)
		.def("setLot", &ImproperUniformDistribution::SetLot)
		.def("setSeed", &ImproperUniformDistribution::SetSeed)
		.def("resetLot", &ImproperUniformDistribution::ResetLot)
		.def("getMean", &ImproperUniformDistribution::GetMean)
		.def("getVar", &ImproperUniformDistribution::GetVar)
		.def("getStdDev", &ImproperUniformDistribution::GetStdDev)
		.def("getCDF", &ImproperUniformDistribution::GetCDF)
		.def("sample", &ImproperUniformDistribution::Sample)
		.def("getLnPDF", &ImproperUniformDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &ImproperUniformDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &ImproperUniformDistribution::SetMeanAndVariance)
		;
	class_<UniformDistribution, bases<ProbabilityDistribution, AdHocDensity> >("UniformDistBase")
		.def(init<double, double>())
		.def(init<const UniformDistribution &>())
		.def("cloneAndSetLot", &UniformDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &UniformDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &UniformDistribution::IsDiscrete)
		.def("getDistName", &UniformDistribution::GetDistributionName)
		.def("__str__", &UniformDistribution::GetDistributionDescription)
		.def("__repr__", &UniformDistribution::GetDistributionDescription)
		.def("setLot", &UniformDistribution::SetLot)
		.def("setSeed", &UniformDistribution::SetSeed)
		.def("resetLot", &UniformDistribution::ResetLot)
		.def("getMean", &UniformDistribution::GetMean)
		.def("getVar", &UniformDistribution::GetVar)
		.def("getStdDev", &UniformDistribution::GetStdDev)
		.def("getCDF", &UniformDistribution::GetCDF)
		.def("sample", &UniformDistribution::Sample)
		.def("getLnPDF", &UniformDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &UniformDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &UniformDistribution::SetMeanAndVariance)
		;
	class_<GammaDistribution, bases<ProbabilityDistribution, AdHocDensity> >("GammaDistBase")
		.def(init<double, double>())
		.def(init<const GammaDistribution &>())
		.def("cloneAndSetLot", &GammaDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &GammaDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &GammaDistribution::IsDiscrete)
		.def("getDistName", &GammaDistribution::GetDistributionName)
		.def("__str__", &GammaDistribution::GetDistributionDescription)
		.def("__repr__", &GammaDistribution::GetDistributionDescription)
		.def("setLot", &GammaDistribution::SetLot)
		.def("setSeed", &GammaDistribution::SetSeed)
		.def("resetLot", &GammaDistribution::ResetLot)
		.def("getMean", &GammaDistribution::GetMean)
		.def("getVar", &GammaDistribution::GetVar)
		.def("getStdDev", &GammaDistribution::GetStdDev)
		.def("getCDF", &GammaDistribution::GetCDF)
		.def("sample", &GammaDistribution::Sample)
		.def("getLnPDF", &GammaDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &GammaDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &GammaDistribution::SetMeanAndVariance)
		;
	class_<ExponentialDistribution, bases<GammaDistribution, ProbabilityDistribution, AdHocDensity> >("ExponentialDistBase")
		.def(init<double>())
		.def(init<const ExponentialDistribution &>())
		.def("cloneAndSetLot", &ExponentialDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &ExponentialDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &ExponentialDistribution::IsDiscrete)
		.def("getDistName", &ExponentialDistribution::GetDistributionName)
		.def("__str__", &ExponentialDistribution::GetDistributionDescription)
		.def("__repr__", &ExponentialDistribution::GetDistributionDescription)
		.def("setLot", &ExponentialDistribution::SetLot)
		.def("setSeed", &ExponentialDistribution::SetSeed)
		.def("resetLot", &ExponentialDistribution::ResetLot)
		.def("getMean", &ExponentialDistribution::GetMean)
		.def("getVar", &ExponentialDistribution::GetVar)
		.def("getStdDev", &ExponentialDistribution::GetStdDev)
		.def("getCDF", &ExponentialDistribution::GetCDF)
		.def("sample", &ExponentialDistribution::Sample)
		.def("getLnPDF", &ExponentialDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &ExponentialDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &ExponentialDistribution::SetMeanAndVariance)
		;
	class_<InverseGammaDistribution, bases<ProbabilityDistribution, AdHocDensity> >("InverseGammaDistBase")
		.def(init<double, double>())
		.def(init<const InverseGammaDistribution &>())
		.def("cloneAndSetLot", &InverseGammaDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &InverseGammaDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &InverseGammaDistribution::IsDiscrete)
		.def("getDistName", &InverseGammaDistribution::GetDistributionName)
		.def("__str__", &InverseGammaDistribution::GetDistributionDescription)
		.def("__repr__", &InverseGammaDistribution::GetDistributionDescription)
		.def("setLot", &InverseGammaDistribution::SetLot)
		.def("setSeed", &InverseGammaDistribution::SetSeed)
		.def("resetLot", &InverseGammaDistribution::ResetLot)
		.def("getMean", &InverseGammaDistribution::GetMean)
		.def("getVar", &InverseGammaDistribution::GetVar)
		.def("getStdDev", &InverseGammaDistribution::GetStdDev)
		.def("getCDF", &InverseGammaDistribution::GetCDF)
		.def("sample", &InverseGammaDistribution::Sample)
		.def("getLnPDF", &InverseGammaDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &InverseGammaDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &InverseGammaDistribution::SetMeanAndVariance)
		;
	class_<NormalDistribution, bases<ProbabilityDistribution, AdHocDensity> >("NormalDistBase")
		.def(init<double, double>())
		.def(init<const NormalDistribution &>())
		.def("cloneAndSetLot", &NormalDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &NormalDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &NormalDistribution::IsDiscrete)
		.def("getDistName", &NormalDistribution::GetDistributionName)
		.def("__str__", &NormalDistribution::GetDistributionDescription)
		.def("__repr__", &NormalDistribution::GetDistributionDescription)
		.def("setLot", &NormalDistribution::SetLot)
		.def("setSeed", &NormalDistribution::SetSeed)
		.def("resetLot", &NormalDistribution::ResetLot)
		.def("getMean", &NormalDistribution::GetMean)
		.def("getVar", &NormalDistribution::GetVar)
		.def("getStdDev", &NormalDistribution::GetStdDev)
		.def("getCDF", &NormalDistribution::GetCDF)
		.def("sample", &NormalDistribution::Sample)
		.def("getLnPDF", &NormalDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &NormalDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &NormalDistribution::SetMeanAndVariance)
		;
	class_<LognormalDistribution, bases<ProbabilityDistribution, AdHocDensity> >("LognormalDistBase")
		.def(init<double, double>())
		.def(init<const LognormalDistribution &>())
		.def("cloneAndSetLot", &LognormalDistribution::cloneAndSetLot, return_value_policy<manage_new_object>())
		.def("clone", &LognormalDistribution::Clone, return_value_policy<manage_new_object>())
		.def("isDiscrete", &LognormalDistribution::IsDiscrete)
		.def("getDistName", &LognormalDistribution::GetDistributionName)
		.def("__str__", &LognormalDistribution::GetDistributionDescription)
		.def("__repr__", &LognormalDistribution::GetDistributionDescription)
		.def("setLot", &LognormalDistribution::SetLot)
		.def("setSeed", &LognormalDistribution::SetSeed)
		.def("resetLot", &LognormalDistribution::ResetLot)
		.def("getMean", &LognormalDistribution::GetMean)
		.def("getVar", &LognormalDistribution::GetVar)
		.def("getStdDev", &LognormalDistribution::GetStdDev)
		.def("getCDF", &LognormalDistribution::GetCDF)
		.def("sample", &LognormalDistribution::Sample)
		.def("getLnPDF", &LognormalDistribution::GetLnPDF)
		.def("getRelativeLnPDF", &LognormalDistribution::GetRelativeLnPDF)
		.def("setMeanAndVariance", &LognormalDistribution::SetMeanAndVariance)
		;
	register_exception_translator<XProbDist>(&translateXProbDist);
}