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

#include <fcntl.h>
#include <iomanip>
#include <random>
#include <fstream>
#include <filesystem>
#include <mutex>

#include <boost/program_options.hpp>
#include <boost/algorithm/string.hpp>

#include <zeep/http/daemon.hpp>
#include <zeep/http/server.hpp>
#include <zeep/http/html-controller.hpp>
#include <zeep/http/rest-controller.hpp>
#include <zeep/crypto.hpp>

#include "cif++/Secondary.hpp"
// #include "cif++/Statistics.hpp"
#include "cif++/CifUtils.hpp"
#include "cif++/Cif++.hpp"
#include "cif++/Structure.hpp"
#include "cif++/Compound.hpp"

#include <zeep/json/element.hpp>

#ifdef _MSC_VER
//MSVC stdlib.h definitions
#define STDIN_FILENO 0
#define STDOUT_FILENO 1
#define STDERR_FILENO 2
#endif

namespace po = boost::program_options;
namespace fs = std::filesystem;
namespace ba = boost::algorithm;

std::string VERSION_STRING;

using mmcif::Atom;
using mmcif::Point;
using mmcif::Structure;
using mmcif::Monomer;
using mmcif::Polymer;

using json = zeep::json::element;

// --------------------------------------------------------------------
// simple integer compression, based somewhat on MRS code

class OBitStream
{
  public:
	OBitStream(std::vector<uint8_t>& buffer)
		: m_buffer(buffer)
	{
		m_buffer.push_back(0);
	}

	OBitStream(const OBitStream&) = delete;
	OBitStream& operator=(const OBitStream&) = delete;

	void writebit(bool bit)
	{
		if (bit)
			m_buffer.back() |= 1 << m_bitOffset;

		if (--m_bitOffset < 0)
		{
			m_buffer.push_back(0);
			m_bitOffset = 7;
		}
	}

	// write fixed size
	void write(uint32_t value, int bits)
	{
		while (bits-- > 0)
		{
			if (value & (1UL << bits))
				m_buffer.back() |= 1 << m_bitOffset;

			if (--m_bitOffset < 0)
			{
				m_buffer.push_back(0);
				m_bitOffset = 7;
			}
		}
	}

	void sync()
	{
		writebit(0);

		while (m_bitOffset != 7)
			writebit(1);
	}

	const uint8_t* data() const					{ return m_buffer.data(); }
	size_t size() const							{ return m_buffer.size(); }

	friend void WriteArray(OBitStream& bs, const std::vector<uint32_t>& data);

  private:
	std::vector<uint8_t>& m_buffer;
	int m_bitOffset = 7;
};

class IBitStream
{
  public:
	IBitStream(const uint8_t* data)
		: m_data(data), m_byte(*m_data++), m_bitOffset(7) {}

	IBitStream(const OBitStream& bits)
		: IBitStream(bits.data()) {}

	IBitStream(const IBitStream&) = delete;
	IBitStream& operator=(const IBitStream&) = delete;

	uint32_t read(int bc)
	{
		uint32_t result = 0;

		while (bc > 0)
		{
			static const uint8_t kM[] = { 0x00, 0x01, 0x03, 0x07, 0x0F, 0x1F, 0x3F, 0x7F, 0xFF };

			int bw = m_bitOffset + 1;
			if (bw > bc)
				bw = bc;

			m_bitOffset -= bw;
			result = result << bw | (kM[bw] & (m_byte >> (m_bitOffset + 1)));

			if (m_bitOffset < 0)
			{
				m_byte = *m_data++;
				m_bitOffset = 7;
			}

			bc -= bw;
		}

		return result;
	}

	friend std::vector<uint32_t> ReadArray(IBitStream& bs);

  private:
	const uint8_t* m_data;
	uint8_t m_byte;
	int m_bitOffset;
};

// --------------------------------------------------------------------
//    Arrays
//    This is a simplified version of the array compression routines in MRS
//    Only supported datatype is uint32_t and only supported width it 24 bit.

struct Selector
{
	int32_t databits;
	uint32_t span;
} const kSelectors[16] = {
	{  0, 1 },
	{ -4, 1 },
	{ -2, 1 }, { -2, 2 },
	{ -1, 1 }, { -1, 2 }, { -1, 4 },
	{  0, 1 }, {  0, 2 }, {  0, 4 },
	{  1, 1 }, {  1, 2 }, {  1, 4 },
	{  2, 1 }, {  2, 2 },
	{  4, 1 }
};

// store ints of at most 24 bits, should be enough.
const uint32_t kStartWidth = 8, kMaxWidth = 24;

inline uint32_t bitWidth(uint32_t v)
{
	uint32_t result = 0;
	while (v > 0)
	{
		v >>= 1;
		++result;
	}
	return result;
}

void CompressSimpleArraySelector(OBitStream& inBits, const std::vector<uint32_t>& inArray)
{
	int32_t width = kStartWidth;

	int32_t bn[4];
	uint32_t dv[4];
	uint32_t bc = 0;
	auto a = inArray.begin(), e = inArray.end();

	while (a != e or bc > 0)
	{
		while (bc < 4 and a != e)
		{
			dv[bc] = *a++;
			bn[bc] = bitWidth(dv[bc]);
			++bc;
		}

		uint32_t s = 0;
		int32_t c = bn[0] - kMaxWidth;

		for (uint32_t i = 1; i < 16; ++i)
		{
			if (kSelectors[i].span > bc)
				continue;

			int32_t w = width + kSelectors[i].databits;

			if (static_cast<uint32_t>(w) > kMaxWidth)
				continue;

			bool fits = true;
			int32_t waste = 0;

			switch (kSelectors[i].span)
			{
				case 4:
					fits = fits and bn[3] <= w;
					waste += w - bn[3];
					[[fallthrough]];
				case 3:
					fits = fits and bn[2] <= w;
					waste += w - bn[2];
					[[fallthrough]];
				case 2:
					fits = fits and bn[1] <= w;
					waste += w - bn[1];
					[[fallthrough]];
				case 1:
					fits = fits and bn[0] <= w;
					waste += w - bn[0];
			}

			if (fits == false)
				continue;

			int32_t n = (kSelectors[i].span - 1) * 4 - waste;

			if (n > c)
			{
				s = i;
				c = n;
			}
		}

		if (s == 0)
			width = kMaxWidth;
		else
			width += kSelectors[s].databits;

		uint32_t n = kSelectors[s].span;

		inBits.write(s, 4);

		if (width > 0)
		{
			for (uint32_t i = 0; i < n; ++i)
				inBits.write(dv[i], width);
		}

		bc -= n;

		if (bc > 0)
		{
			for (uint32_t i = 0; i < (4 - n); ++i)
			{
				bn[i] = bn[i + n];
				dv[i] = dv[i + n];
			}
		}
	}
}

void DecompressSimpleArraySelector(IBitStream& inBits, std::vector<uint32_t>& outArray)
{
	uint32_t width = kStartWidth;
	uint32_t span = 0;

	// The array should be initilialized to the expected size!
	auto size = outArray.size();
	auto a = outArray.begin();

	while (size-- > 0)
	{
		if (span == 0)
		{
			uint32_t selector = inBits.read(4);
			span = kSelectors[selector].span;

			if (selector == 0)
				width = kMaxWidth;
			else
				width += kSelectors[selector].databits;
		}

		if (width > 0)
			*a++ = inBits.read(width);
		else
			*a++ = 0;

		--span;
	}
}

// --------------------------------------------------------------------

enum class SecStrType : char
{
	helix = 'H',
	strand = 'E',
	other = '.',
	cis = 'c',
	prepro = 'p'
};

std::ostream& operator<<(std::ostream& os, SecStrType ss)
{
	switch (ss)
	{
		case SecStrType::helix: os << "helix"; break;
		case SecStrType::strand: os << "strand"; break;
		case SecStrType::other: os << "other"; break;
		case SecStrType::cis: os << "cis"; break;
		case SecStrType::prepro: os << "prepro"; break;
	}

	return os;
}

// --------------------------------------------------------------------
// The header for the data blocks as written in de resource

struct StoredData
{
	char		aa[3];
	SecStrType	ss;
	float		mean, mean_vs_random, sd, sd_vs_random, binSpacing;
	uint32_t	offset;			// offset into compressed data area
};

class Data
{
	friend class DataTable;

  public:
	Data(Data&& d)
		: aa(d.aa), ss(d.ss), mean(d.mean), sd(d.sd)
		, mean_vs_random(d.mean_vs_random), sd_vs_random(d.sd_vs_random)
		, binSpacing(d.binSpacing), counts(move(d.counts))
		, dim(d.dim), d2(d.d2)
	{
	}

	Data(const Data&) = delete;
	Data& operator=(const Data&) = delete;

	Data(const char* type, const std::string& aa, SecStrType ss, std::istream& is);
	Data(bool torsion, const StoredData& data, const uint8_t* bits);

	void store(StoredData& data, std::vector<uint8_t>& databits);

	float interpolatedCount(float phi, float a2) const;
	float zscore(float a1, float a2) const
	{
		return (interpolatedCount(a1, a2) - mean) / sd;
	}

	void dump() const
	{
		for (size_t i = 0; i < counts.size(); ++i)
		{
			float a1, a2;
			std::tie(a1, a2) = angles(i);
			std::cout << a1 << ' ' << a2 << ' ' << counts[i] << std::endl;
		}
	}

  private:

	std::string aa;
	SecStrType ss;
	float mean, sd, mean_vs_random, sd_vs_random;
	float binSpacing;
	std::vector<uint32_t> counts;

	// calculated
	size_t dim;
	bool d2;

	float count(size_t a1Ix, size_t a2Ix) const
	{
		a1Ix %= dim;
		a2Ix %= dim;
		return d2 ? counts.at(a1Ix * dim + a2Ix) : counts.at(a1Ix);
	}

	size_t index(float a1, float a2 = 0) const
	{
		size_t x = 0, y = 0;

		if (d2)
		{
			x = static_cast<size_t>((a1 + 180) / binSpacing);
			y = static_cast<size_t>((a2 + 180) / binSpacing);
		}
		else
			y = static_cast<size_t>((a1 + 180) / binSpacing);

		return x * (360 / binSpacing) + y;
	}

	std::tuple<float,float> angles(size_t index) const
	{
		size_t x = index / dim;
		size_t y = index % dim;

		return std::make_tuple(x * binSpacing - 180, y * binSpacing - 180);
	}
};

Data::Data(const char* type, const std::string& aa, SecStrType ss, std::istream& is)
	: aa(aa), ss(ss)
{
	// example:
	// 14400 bins, aver 19.2878, sd 15.4453, binspacing 3
	// torsion vs random: 2.0553 2.8287
	static const std::regex
		kRX1(R"((\d+) bins, aver ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?), sd ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?), binspacing ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?))"),
		kRX2(R"((torsion|rama) vs random: ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?) ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?))");

	std::string line;
	getline(is, line);

	d2 = strcmp(type, "torsion") != 0 or std::set<std::string>{"CYS", "SER", "THR", "VAL"}.count(aa) == 0;

	std::smatch m;
	if (not std::regex_match(line, m, kRX1))
		throw std::runtime_error("Invalid file");

	size_t nBins = stoi(m[1]);
	mean = stof(m[2]);
	sd = stof(m[3]);
	binSpacing = stof(m[4]);

	dim = static_cast<size_t>(360 / binSpacing);
	if ((d2 and nBins != dim * dim) or (not d2 and nBins != dim))
		throw std::runtime_error("Unexpected number of bins");

	counts.resize(nBins);

	getline(is, line);

	if (not std::regex_match(line, m, kRX2) or m[1] != type)
		throw std::runtime_error("Invalid file");

	mean_vs_random = stof(m[2]);
	sd_vs_random = stof(m[3]);

	for (size_t i = 0; i < nBins; ++i)
	{
		float a1, a2;
		uint32_t count;

		if (d2)
			is >> a1 >> a2 >> count;
		else
			is >> a1 >> count;

		if (is.eof())
			throw std::runtime_error("truncated file?");

		counts.at(index(a1, a2)) = count;
	}
}

Data::Data(bool torsion, const StoredData& data, const uint8_t* databits)
{
	aa.assign(data.aa, data.aa + 3);
	ss = data.ss;
	mean = data.mean;
	mean_vs_random = data.mean_vs_random;
	sd = data.sd;
	sd_vs_random = data.sd_vs_random;
	binSpacing = data.binSpacing;

	d2 = not torsion or std::set<std::string>{"CYS", "SER", "THR", "VAL"}.count(aa) == 0;

	size_t nBins = static_cast<size_t>(360 / binSpacing);
	dim = nBins;

	if (d2)
		nBins *= nBins;

	counts.insert(counts.begin(), nBins, 0);

	IBitStream bits(databits + data.offset);
	DecompressSimpleArraySelector(bits, counts);
}

void Data::store(StoredData& data, std::vector<uint8_t>& databits)
{
	assert(aa.length() == 3);
	copy(aa.begin(), aa.end(), data.aa);
	data.ss = ss;
	data.mean = mean;
	data.sd = sd;
	data.mean_vs_random = mean_vs_random;
	data.sd_vs_random = sd_vs_random;
	data.offset = databits.size();
	data.binSpacing = binSpacing;

	OBitStream bits(databits);
	CompressSimpleArraySelector(bits, counts);
	bits.sync();
}

float Data::interpolatedCount(float a1, float a2) const
{
	const size_t N = dim;

	float result;

	if (d2)
	{
		size_t a1FloorIx = static_cast<size_t>(N * (a1 + 180) / 360);
		size_t a2FloorIx = static_cast<size_t>(N * (a2 + 180) / 360);

		size_t a1CeilIx = (a1FloorIx + 1);
		size_t a2CeilIx = (a2FloorIx + 1);

		float a1FloorAngle = (a1FloorIx * 360.0f) / N - 180;
		float a2FloorAngle = (a2FloorIx * 360.0f) / N - 180;

		float a1CeilAngle = (a1CeilIx * 360.0f) / N - 180;
		float a2CeilAngle = (a2CeilIx * 360.0f) / N - 180;

		float a1Factor = a1CeilIx > a1FloorIx ? (a1 - a1FloorAngle) / (a1CeilAngle - a1FloorAngle) : 1;
		float a2Factor = a2CeilIx > a2FloorIx ? (a2 - a2FloorAngle) / (a2CeilAngle - a2FloorAngle) : 1;

		float c1 = count(a1FloorIx, a2FloorIx) + (count(a1CeilIx, a2FloorIx) - count(a1FloorIx, a2FloorIx)) * a1Factor;
		float c2 = count(a1FloorIx, a2CeilIx) + (count(a1CeilIx, a2CeilIx) - count(a1FloorIx, a2CeilIx)) * a1Factor;

		result = c1 + (c2 - c1) * a2Factor;
	}
	else
	{
		size_t a1FloorIx = static_cast<size_t>(N * (a1 + 180) / 360);
		size_t a1CeilIx = (a1FloorIx + 1);

		float a1FloorAngle = (a1FloorIx * 360.0f) / N - 180;
		float a1CeilAngle = (a1CeilIx * 360.0f) / N - 180;

		float a1Factor = a1CeilIx > a1FloorIx ? (a1 - a1FloorAngle) / (a1CeilAngle - a1FloorAngle) : 1;

		result = count(a1FloorIx, 0) + (count(a1CeilIx, 0) - count(a1FloorIx, 0)) * a1Factor;
	}

	return result;
}

void buildDataFile(fs::path dir)
{
	using namespace std::literals;

	// first read the global mean and sd

	float mean_torsion, sd_torsion, mean_ramachandran, sd_ramachandran;

	std::ifstream in(dir / "zscores_proteins.txt");
	std::string line;
	const std::regex krx(R"((Rama|Rota): average ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?), sd ([-+]?\d+(?:\.\d+)?(?:[eE][-+]?\d+)?))");

	while (getline(in, line))
	{
		std::smatch m;
		if (not std::regex_match(line, m, krx))
			continue;

		if (m[1] == "Rama")
		{
			mean_ramachandran = stof(m[2]);
			sd_ramachandran = stof(m[3]);
		}
		else
		{
			mean_torsion = stof(m[2]);
			sd_torsion = stof(m[3]);
		}
	}

	std::vector<StoredData> data;
	std::vector<uint8_t> bits;

	// first ramachandran counts
	for (auto aa: mmcif::kAAMap)
	{
		for (std::pair<SecStrType, const char*> ss: {
				std::make_pair(SecStrType::helix, "helix"),
				std::make_pair(SecStrType::strand, "strand"),
				std::make_pair(SecStrType::other, "other") })
		{
			auto p = dir / ("rama_count_"s + ss.second + '_' + aa.first + ".txt");
			if (not fs::exists(p))
				continue;

			std::ifstream f(p);
			Data d("rama", aa.first, ss.first, f);

			StoredData sd = { };
			d.store(sd, bits);
			data.push_back(sd);
		}
	}

	for (std::tuple<SecStrType, const char*, const char*> ss: {
			std::make_tuple(SecStrType::cis, "PRO", "cis_PRO"),
			std::make_tuple(SecStrType::prepro, "***", "prepro_all_noGIV"),
			std::make_tuple(SecStrType::prepro, "GLY", "prepro_GLY"),
			std::make_tuple(SecStrType::prepro, "IV_", "prepro_ILEVAL") })
	{
		auto p = dir / ("rama_count_"s + std::get<2>(ss) + ".txt");
		if (not fs::exists(p))
			continue;

		std::ifstream f(p);
		Data d("rama", std::get<1>(ss), std::get<0>(ss), f);

		StoredData sd = { };
		d.store(sd, bits);
		data.push_back(sd);
	}

	data.push_back({});

	if (fs::exists("rama-data.bin"))
		fs::remove("rama-data.bin");
	std::ofstream out("rama-data.bin", std::ios::binary);
	if (not out.is_open())
		throw std::runtime_error("Could not create rama-data.bin file");
	out.write(reinterpret_cast<char*>(&mean_ramachandran), sizeof(mean_ramachandran));
	out.write(reinterpret_cast<char*>(&sd_ramachandran), sizeof(sd_ramachandran));
	out.write(reinterpret_cast<char*>(data.data()), data.size() * sizeof(StoredData));
	out.write(reinterpret_cast<char*>(bits.data()), bits.size());
	out.close();

	data.clear();
	bits.clear();

	// next torsion counts
	for (auto aa: mmcif::kAAMap)
	{
		for (std::pair<SecStrType, const char*> ss: {
				std::make_pair(SecStrType::helix, "helix"),
				std::make_pair(SecStrType::strand, "strand"),
				std::make_pair(SecStrType::other, "other") })
		{
			auto p = dir / ("torsion_count_"s + ss.second + '_' + aa.first + ".txt");
			if (not fs::exists(p))
				continue;

			std::ifstream f(p);
			Data d("torsion", aa.first, ss.first, f);

			StoredData sd = { };
			d.store(sd, bits);
			data.push_back(sd);
		}
	}

	data.push_back({});

	if (fs::exists("torsion-data.bin"))
		fs::remove("torsion-data.bin");
	out.open("torsion-data.bin", std::ios::binary);
	if (not out.is_open())
		throw std::runtime_error("Could not create torsion-data.bin file");
	out.write(reinterpret_cast<char*>(&mean_torsion), sizeof(mean_torsion));
	out.write(reinterpret_cast<char*>(&sd_torsion), sizeof(sd_torsion));
	out.write(reinterpret_cast<char*>(data.data()), data.size() * sizeof(StoredData));
	out.write(reinterpret_cast<char*>(bits.data()), bits.size());
	out.close();
}

// --------------------------------------------------------------------

class DataTable
{
  public:

	static DataTable& instance()
	{
		static DataTable sInstance;
		return sInstance;
	}

	const Data& loadTorsionData(const std::string& aa, SecStrType ss) const;
	const Data& loadRamachandranData(const std::string& aa, SecStrType ss) const;

	float mean_torsion() const		{ return m_mean_torsion; }
	float sd_torsion() const		{ return m_sd_torsion; }
	float mean_ramachandran() const	{ return m_mean_ramachandran; }
	float sd_ramachandran() const	{ return m_sd_ramachandran; }

  private:

	DataTable(const DataTable& ) = delete;
	DataTable& operator=(const DataTable&) = delete;

	DataTable();

	void load(const char* name, std::vector<Data>& table, float& mean, float& sd);

	std::vector<Data>	m_torsion, m_ramachandran;

	float m_mean_torsion, m_sd_torsion, m_mean_ramachandran, m_sd_ramachandran;
};

DataTable::DataTable()
{
	load("torsion-data.bin", m_torsion, m_mean_torsion, m_sd_torsion);
	load("rama-data.bin", m_ramachandran, m_mean_ramachandran, m_sd_ramachandran);
}

const Data& DataTable::loadTorsionData(const std::string& aa, SecStrType ss) const
{
	auto i = find_if(m_torsion.begin(), m_torsion.end(), [aa, ss](auto& d)
		{ return d.aa == aa and d.ss == ss; });
	if (i == m_torsion.end())
		throw std::runtime_error("Data missing for aa = " + aa + " and ss = '" + static_cast<char>(ss) + '\'');

	return *i;
}

const Data& DataTable::loadRamachandranData(const std::string& aa, SecStrType ss) const
{
	std::vector<Data>::const_iterator i;

	switch (ss)
	{
		case SecStrType::cis:
			i = find_if(m_ramachandran.begin(), m_ramachandran.end(), [](auto& d) { return d.ss == SecStrType::cis and d.aa == "PRO"; });
			break;

		case SecStrType::prepro:
			i = find_if(m_ramachandran.begin(), m_ramachandran.end(), [aa](auto& d) {
					bool result = false;
					if (d.ss == SecStrType::prepro)
					{
						if (aa == "GLY")
							result = d.aa == "GLY";
						else if (aa == "ILE" or aa == "VAL")
							result = d.aa == "IV_";
						else
							result = d.aa == "***";
					}
					return result;
				});
			break;

		default:
			i = find_if(m_ramachandran.begin(), m_ramachandran.end(), [aa, ss](auto& d)
				{ return d.aa == aa and d.ss == ss; });
			break;
	}

	if (i == m_ramachandran.end())
		throw std::runtime_error("Data missing for aa= " + aa + " and ss = '" + static_cast<char>(ss) + '\'');

	return *i;
}

void DataTable::load(const char* name, std::vector<Data>& table, float& mean, float& sd)
{
	using namespace std::literals;

	auto rfd = cif::loadResource(name);

	if (not rfd)
		throw std::runtime_error("Missing resource "s + name);

	rfd->seekg(0, rfd->end);
	auto size = rfd->tellg();
	rfd->seekg(0, rfd->beg);

	const float* fv = new float[size / sizeof(float) + 1];
	rfd->read(reinterpret_cast<char*>(const_cast<float*>(fv)), size);

	mean = fv[0];
	sd = fv[1];

	const StoredData* data = reinterpret_cast<const StoredData*>(fv + 2);
	size_t ix = 0;
	while (data[ix].aa[0] != 0)
		++ix;

	size_t n = ix;
	const uint8_t* bits = reinterpret_cast<const uint8_t*>(fv + 2) + (n + 1) * sizeof(StoredData);

	for (ix = 0; ix < n; ++ix)
		table.emplace_back(strcmp(name, "torsion-data.bin") == 0, data[ix], bits);
}

// --------------------------------------------------------------------

float jackknife(const std::vector<float>& zScorePerResidue)
{
	// jackknife variance estimate, see: https://en.wikipedia.org/wiki/Jackknife_resampling

	const size_t N = zScorePerResidue.size();
	double zScoreSum = accumulate(zScorePerResidue.begin(), zScorePerResidue.end(), 0.0);
	std::vector<double> scores(N);

	DataTable& tbl = DataTable::instance();

	double scoreSum = 0;
	for (size_t i = 0; i < zScorePerResidue.size(); ++i)
	{
		double score = (zScoreSum - zScorePerResidue[i]) / (N - 1);
		score = (score - tbl.mean_ramachandran()) / tbl.sd_ramachandran();
		scores[i] = score;
		scoreSum += score;
	}

	double avg = scoreSum / N;
	double sumD = accumulate(scores.begin(), scores.end(), 0.0, [avg](double a, double z) { return a + (z - avg) * (z - avg); });

	return sqrt((N - 1) * sumD / N);
}

// --------------------------------------------------------------------

json calculateZScores(const Structure& structure)
{
	mmcif::DSSP dssp(structure, 3, false);
	auto& tbl = DataTable::instance();

	double ramaZScoreSum = 0;
	size_t ramaZScoreCount = 0;
	double torsZScoreSum = 0;
	size_t torsZScoreCount = 0;

	json residues;
	std::vector<float> ramaZScorePerResidue, torsZScorePerResidue;

	for (auto& poly: structure.polymers())
	{
		for (size_t i = 1; i + 1 < poly.size(); ++i)
		{
			auto& res = poly[i];

			auto phi = res.phi();
			auto psi = res.psi();

			if (phi == 360 or psi == 360)
				continue;

			std::tuple<std::string,int,std::string,std::string> pdbID = structure.MapLabelToPDB(res.asymID(), res.seqID(), res.compoundID(), res.authSeqID());

			json residue = {
				{ "asymID", res.asymID() },
				{ "seqID", res.seqID() },
				{ "compID", res.compoundID() },
				{ "pdb", {
					{ "strandID", std::get<0>(pdbID) },
					{ "seqNum", std::get<1>(pdbID) },
					{ "compID", std::get<2>(pdbID) },
					{ "insCode", std::get<3>(pdbID) }
				}}
			};

			std::string aa = res.compoundID();

			// remap some common modified amino acids
			if (aa == "MSE")
			{
				if (cif::VERBOSE > 1)
					std::cerr << "Replacing MSE with MET" << std::endl;
				aa = "MET";
			}
			else if (aa == "HYP")
			{
				if (cif::VERBOSE > 1)
					std::cerr << "Replacing HYP with PRO" << std::endl;

				aa = "PRO";
			}
			else if (aa == "ASX")
			{
				if (cif::VERBOSE > 1)
					std::cerr << "Replacing ASX with ASP" << std::endl;

				aa = "ASP";
			}
			else if (aa == "GLX")
			{
				if (cif::VERBOSE > 1)
					std::cerr << "Replacing GLX with GLU" << std::endl;

				aa = "GLU";
			}
			else if (mmcif::kAAMap.find(aa) == mmcif::kAAMap.end())
			{
				if (cif::VERBOSE)
					std::cerr << "Replacing " << aa << " with ALA" << std::endl;

				aa = "ALA";
			}

			SecStrType tors_ss, rama_ss;

			switch (dssp(res))
			{
				case mmcif::ssAlphahelix:	tors_ss = SecStrType::helix; break;
				case mmcif::ssStrand:		tors_ss = SecStrType::strand; break;
				default:					tors_ss = SecStrType::other; break;
			}

			if (aa != "PRO" and poly[i + 1].compoundID() == "PRO")
				rama_ss = SecStrType::prepro;
			else if (aa == "PRO" && res.isCis())
				rama_ss = SecStrType::cis;
			else
				rama_ss = tors_ss;

#pragma warning "todo"
			auto& rd = tbl.loadRamachandranData(aa, rama_ss);

			auto zr = rd.zscore(phi, psi);

			residue["ramachandran"] =
			{
				{ "ss-type", boost::lexical_cast<std::string>(rama_ss) },
				{ "z-score", zr }
			};

			ramaZScorePerResidue.push_back(zr);

			ramaZScoreSum += zr;
			++ramaZScoreCount;

			try
			{
				float zt = nan("1");

				auto chiCount = res.nrOfChis();
				if (chiCount)
				{
					float chi1 = res.chi(0);
					float chi2 = chiCount > 1 ? res.chi(1) : 0;

					auto& td = tbl.loadTorsionData(aa, tors_ss);

					zt = td.zscore(chi1, chi2);

					torsZScoreSum += zt;
					++torsZScoreCount;

					torsZScorePerResidue.push_back(zt);

					residue["torsion"] =
					{
						{ "ss-type", boost::lexical_cast<std::string>(tors_ss) },
						{ "z-score", zt }
					};
				}
			}
			catch (const std::exception& e)
			{
				if (cif::VERBOSE)
					std::cerr << e.what() << '\n';
			}

			residues.push_back(residue);
		}
	}

	float ramaVsRand = ramaZScoreSum / ramaZScoreCount;
	float torsVsRand = torsZScoreSum / torsZScoreCount;

	float jackknifeRama = jackknife(ramaZScorePerResidue);
	float jackknifeTors = jackknife(torsZScorePerResidue);

	return {
		{ "ramachandran-z", ((ramaVsRand - tbl.mean_ramachandran()) / tbl.sd_ramachandran()) },
		{ "ramachandran-jackknife-sd", jackknifeRama },
		{ "torsion-z", ((torsVsRand - tbl.mean_torsion()) / tbl.sd_torsion()) },
		{ "torsion-jackknife-sd", jackknifeTors },
		{ "residues", residues },
	};
}

// --------------------------------------------------------------------
#if WEBSERVICE

class tortoize_html_controller : public zeep::http::html_controller
{
  public:
	tortoize_html_controller()
		: zeep::http::html_controller("tortoize")
	{
		mount("{css,scripts,fonts,images,favicon}/", &tortoize_html_controller::handle_file);
		mount("{favicon.ico,browserconfig.xml,manifest.json}", &tortoize_html_controller::handle_file);
		mount("", &tortoize_html_controller::index);
	}

	void index(const zeep::http::request& request, const zeep::http::scope& scope, zeep::http::reply& reply)
	{
		get_template_processor().create_reply_from_template("index", scope, reply);
	}
};

// --------------------------------------------------------------------

class tortoize_rest_controller : public zeep::http::rest_controller
{
  public:
	tortoize_rest_controller()
		: zeep::http::rest_controller("")
		, m_tempdir(fs::temp_directory_path() / "tortoize-ws")
	{
		fs::create_directories(m_tempdir);

		map_post_request("tortoize", &tortoize_rest_controller::calculate, "data", "dict");
	}

	json calculate(const std::string& file, const std::string& dict)
	{
		// First store dictionary, just in case

		fs::path dictFile;

		if (not dict.empty())\
		{
			dictFile = m_tempdir / ("dict-" + std::to_string(m_next_dict_nr++));
			std::ofstream tmpFile(dictFile);
			tmpFile << dict;

			mmcif::CompoundFactory::instance().pushDictionary(dictFile);
		}

		try
		{
			// --------------------------------------------------------------------

			json data{
				{ "software",
					{
						{ "name", "tortoize" },
						{ "version", VERSION_STRING },
						{ "reference", "Sobolev et al. A Global Ramachandran Score Identifies Protein Structures with Unlikely Stereochemistry, Structure (2020)" },
						{ "reference-doi", "https://doi.org/10.1016/j.str.2020.08.005" }
					}
				}
			};

			// --------------------------------------------------------------------

			mmcif::File f(file.data(), file.length());

			std::set<uint32_t> models;
			for (auto r: f.data()["atom_site"])
			{
				if (not r["pdbx_PDB_model_num"].empty())
					models.insert(r["pdbx_PDB_model_num"].as<uint32_t>());
			}

			if (models.empty())
				models.insert(0);

			for (auto model: models)
			{
				Structure structure(f, model);
				data["model"][std::to_string(model)] = calculateZScores(structure);
			}

			if (not dictFile.empty())
			{
				mmcif::CompoundFactory::instance().popDictionary();
				fs::remove(dictFile);
			}

			return data;
		}
		catch (...)
		{
			std::error_code ec;

			if (not dictFile.empty())
			{
				mmcif::CompoundFactory::instance().popDictionary();
				fs::remove(dictFile);
			}

			throw;
		}
	}

	fs::path m_tempdir;
	size_t m_next_dict_nr = 1;
};

int start_server(int argc, char* argv[])
{
	using namespace std::literals;
	namespace zh = zeep::http;

	mmcif::CompoundFactory::init(true);

	int result = 0;

	po::options_description visible_options(PACKAGE_NAME " [options] input [output]");
	visible_options.add_options()
		("log",		po::value<std::string>(),	"Write log to this file")

		("help,h",								"Display help message")
		("version",								"Print version")

		("address",	po::value<std::string>()->default_value("0.0.0.0"),		"External address")
		("port",	po::value<uint16_t>()->default_value(10350),			"Port to listen to")
		("user,u",	po::value<std::string>()->default_value("www-data"),	"User to run the daemon")

		("no-daemon,F",							"Do not fork into background" )

		("verbose,v",							"verbose output")
		;

	po::options_description hidden_options("hidden options");
	hidden_options.add_options()
		("command", po::value<std::string>(),	"The command to execute")
		("debug,d",	po::value<int>(),			"Debug level (for even more verbose output)")
		;

	po::options_description cmdline_options;
	cmdline_options.add(visible_options).add(hidden_options);

	po::positional_options_description p;
	p.add("command", 1);

	po::variables_map vm;
	po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(p).run(), vm);

	po::notify(vm);

	// --------------------------------------------------------------------

	std::string secret;
	if (vm.count("secret"))
		secret = vm["secret"].as<std::string>();
	else
	{
		secret = zeep::encode_base64(zeep::random_hash());
		std::cerr << "starting with created secret " << secret << std::endl;
	}

	std::string user = vm["user"].as<std::string>();
	std::string address = vm["address"].as<std::string>();
	uint16_t port = vm["port"].as<uint16_t>();

	zh::daemon server([&]()
	{
		auto s = new zeep::http::server();

#if DEBUG
		s->set_template_processor(new zeep::http::file_based_html_template_processor("docroot"));
#else
		s->set_template_processor(new zeep::http::rsrc_based_html_template_processor());
#endif
		s->add_controller(new tortoize_rest_controller());
		s->add_controller(new tortoize_html_controller());
		return s;
	}, PACKAGE_NAME );

	std::string command = vm["command"].as<std::string>();

	if (command == "start")
	{
		std::cout << "starting server at http://" << address << ':' << port << '/' << std::endl;

		if (vm.count("no-daemon"))
			result = server.run_foreground(address, port);
		else
			result = server.start(address, port, 2, 2, user);
		// server.start(vm.count("no-daemon"), address, port, 2, user);
		// // result = daemon::start(vm.count("no-daemon"), port, user);
	}
	else if (command == "stop")
		result = server.stop();
	else if (command == "status")
		result = server.status();
	else if (command == "reload")
		result = server.reload();
	else
	{
		std::cerr << "Invalid command" << std::endl;
		result = 1;
	}

	return result;
}
#endif

// --------------------------------------------------------------------

int pr_main(int argc, char* argv[])
{
	using namespace std::literals;

#if WEBSERVICE
	if (argc > 2 and argv[1] == "server"s)
		return start_server(argc - 1, argv + 1);
#endif

	po::options_description visible_options(fs::path(argv[0]).filename().string() + " [options] input [output]");
	visible_options.add_options()
		("log",		po::value<std::string>(),	"Write log to this file")

		("dict",	po::value<std::vector<std::string>>(),
												"Dictionary file containing restraints for residues in this specific target, can be specified multiple times.")

		("help,h",								"Display help message")
		("version",								"Print version")

		("verbose,v",							"verbose output")
		;

	po::options_description hidden_options("hidden options");
	hidden_options.add_options()
		("xyzin",	po::value<std::string>(),	"coordinates file")
		("output",	po::value<std::string>(),	"Output to this file")
		("debug,d",	po::value<int>(),			"Debug level (for even more verbose output)")
		("build",	po::value<std::string>(),	"Build a binary data table")
		;

	po::options_description cmdline_options;
	cmdline_options.add(visible_options).add(hidden_options);

	po::positional_options_description p;
	p.add("xyzin", 1);
	p.add("output", 1);

	po::variables_map vm;
	po::store(po::command_line_parser(argc, argv).options(cmdline_options).positional(p).run(), vm);

	po::notify(vm);

	// --------------------------------------------------------------------

	if (vm.count("version"))
	{
		std::cout << argv[0] << " version " << VERSION_STRING << std::endl;
		exit(0);
	}

	if (vm.count("help"))
	{
		std::cout << visible_options << std::endl
			 << std::endl
			 << R"(Tortoize validates protein structure models by checking the
Ramachandran plot and side-chain rotamer distributions. Quality
Z-scores are given at the residue level and at the model level
(ramachandran-z and torsions-z). Higher scores are better. To compare
models or to describe the reliability of the model Z-scores jackknife-
based standard deviations are also reported (ramachandran-jackknife-sd
and torsion-jackknife-sd).

References:
- Sobolev et al. A Global Ramachandran Score Identifies Protein
  Structures with Unlikely Stereochemistry, Structure (2020),
  DOI: https://doi.org/10.1016/j.str.2020.08.005
- Van Beusekom et al. Homology-based loop modeling yields more complete
  crystallographic  protein structures, IUCrJ (2018),
  DOI: https://doi.org/10.1107/S2052252518010552
- Hooft et al. Objectively judging the quality of a protein structure
  from a Ramachandran plot, CABIOS (1993),
  DOI: https://doi.org/10.1093/bioinformatics/13.4.425
)" << std::endl
			 << std::endl;
		exit(0);
	}

	if (vm.count("build"))
	{
		buildDataFile(vm["build"].as<std::string>());
		exit(0);
	}

	if (vm.count("xyzin") == 0)
	{
		std::cerr << "Input file not specified" << std::endl;
		exit(1);
	}

	cif::VERBOSE = vm.count("verbose") != 0;
	if (vm.count("debug"))
		cif::VERBOSE = vm["debug"].as<int>();

	if (vm.count("log"))
	{
		if (not vm.count("output"))
		{
			std::cerr << "If you specify a log file, you should also specify an output file" << std::endl;
			exit(1);
		}

		std::string logFile = vm["log"].as<std::string>();

		// open the log file
		int fd = open(logFile.c_str(), O_CREAT|O_RDWR, 0644);
		if (fd < 0)
			throw std::runtime_error("Opening log file " + logFile + " failed: " + strerror(errno));

		// redirect stdout and stderr to the log file
		dup2(fd, STDOUT_FILENO);
		dup2(fd, STDERR_FILENO);
		close(fd);
	}

	if (vm.count("dict"))
	{
		for (auto dict: vm["dict"].as<std::vector<std::string>>())
			mmcif::CompoundFactory::instance().pushDictionary(dict);
	}

	// --------------------------------------------------------------------

	json data{
		{ "software",
			{
				{ "name", "tortoize" },
				{ "version", VERSION_STRING },
				{ "reference", "Sobolev et al. A Global Ramachandran Score Identifies Protein Structures with Unlikely Stereochemistry, Structure (2020)" },
				{ "reference-doi", "https://doi.org/10.1016/j.str.2020.08.005" }
			}
		}
	};

	// --------------------------------------------------------------------

	mmcif::File f(vm["xyzin"].as<std::string>());

	std::set<uint32_t> models;
	for (auto r: f.data()["atom_site"])
	{
		if (not r["pdbx_PDB_model_num"].empty())
			models.insert(r["pdbx_PDB_model_num"].as<uint32_t>());
	}

	if (models.empty())
		models.insert(0);

	for (auto model: models)
	{
		Structure structure(f, model);

		data["model"][std::to_string(model)] = calculateZScores(structure);
	}

	if (vm.count("output"))
	{
		std::ofstream of(vm["output"].as<std::string>());
		if (not of.is_open())
		{
			std::cerr << "Could not open output file" << std::endl;
			exit(1);
		}
		of << data;
	}
	else
		std::cout << data << std::endl;

	return 0;
}

// --------------------------------------------------------------------

namespace {
	std::string gVersionNr, gVersionDate;
}

void load_version_info()
{
	const std::regex
		rxVersionNr(R"(build-(\d+)-g[0-9a-f]{7}(-dirty)?)"),
		rxVersionDate(R"(Date: +(\d{4}-\d{2}-\d{2}).*)"),
		rxVersionNr2(R"(tortoize-version: (\d+(?:\.\d+)+))");

#include "revision.hpp"

	struct membuf : public std::streambuf
	{
		membuf(char* data, size_t length)       { this->setg(data, data, data + length); }
	} buffer(const_cast<char*>(kRevision), sizeof(kRevision));

	std::istream is(&buffer);

	std::string line;

	while (getline(is, line))
	{
		std::smatch m;

		if (std::regex_match(line, m, rxVersionNr))
		{
			gVersionNr = m[1];
			if (m[2].matched)
				gVersionNr += '*';
			continue;
		}

		if (std::regex_match(line, m, rxVersionDate))
		{
			gVersionDate = m[1];
			continue;
		}

		// always the first, replace with more specific if followed by the other info
		if (std::regex_match(line, m, rxVersionNr2))
		{
			gVersionNr = m[1];
			continue;
		}
	}

	if (not VERSION_STRING.empty())
		VERSION_STRING += "\n";
	VERSION_STRING += gVersionNr + " " + gVersionDate;
}

std::string get_version_nr()
{
	return gVersionNr/* + '/' + cif::get_version_nr()*/;
}

std::string get_version_date()
{
	return gVersionDate;
}

// --------------------------------------------------------------------

// recursively print exception whats:
void print_what (const std::exception& e)
{
	std::cerr << e.what() << std::endl;
	try
	{
		std::rethrow_if_nested(e);
	}
	catch (const std::exception& nested)
	{
		std::cerr << " >> ";
		print_what(nested);
	}
}

#if not defined(TORTOIZE_TEST_NO_MAIN)

int main(int argc, char* argv[])
{
	int result = -1;

	try
	{
		load_version_info();

		result = pr_main(argc, argv);
	}
	catch (std::exception& ex)
	{
		print_what(ex);
		exit(1);
	}

	return result;
}

#endif


// Copyright (c) 2018, Ryo Currency Project
// Portions copyright (c) 2014-2018, The Monero Project
//
// Portions of this file are available under BSD-3 license. Please see ORIGINAL-LICENSE for details
// All rights reserved.
//
// Authors and copyright holders give permission for following:
//
// 1. Redistribution and use in source and binary forms WITHOUT modification.
//
// 2. Modification of the source form for your own personal use.
//
// As long as the following conditions are met:
//
// 3. You must not distribute modified copies of the work to third parties. This includes
//    posting the work online, or hosting copies of the modified work for download.
//
// 4. Any derivative version of this work is also covered by this license, including point 8.
//
// 5. Neither the name of the copyright holders nor the names of the authors may be
//    used to endorse or promote products derived from this software without specific
//    prior written permission.
//
// 6. You agree that this licence is governed by and shall be construed in accordance
//    with the laws of England and Wales.
//
// 7. You agree to submit all disputes arising out of or in connection with this licence
//    to the exclusive jurisdiction of the Courts of England and Wales.
//
// Authors and copyright holders agree that:
//
// 8. This licence expires and the work covered by it is released into the
//    public domain on 1st of February 2019
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

/*!
 * \file electrum-words.cpp
 *
 * \brief Mnemonic seed generation and wallet restoration from them.
 *
 * This file and its header file are for translating Electrum-style word lists
 * into their equivalent byte representations for cross-compatibility with
 * that method of "backing up" one's wallet keys.
 */

#include "mnemonics/electrum-words.h"
#include "crypto/crypto.h" // for declaration of crypto::secret_key
#include <boost/algorithm/string.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/crc.hpp>
#include <boost/filesystem.hpp>
#include <boost/locale.hpp>
#include "common/boost_locale.hpp"
#include <cassert>
#include <cstdint>
#include <fstream>
#include <map>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>

#include "chinese_simplified.h"
#include "dutch.h"
#include "english.h"
#include "esperanto.h"
#include "french.h"
#include "german.h"
#include "italian.h"
#include "japanese.h"
#include "language_base.h"
#include "lojban.h"
#include "portuguese.h"
#include "russian.h"
#include "singleton.h"
#include "spanish.h"

/*!
 * \namespace crypto
 *
 * \brief crypto namespace.
 */
namespace crypto
{
// Polynomial chosen using https://users.ece.cmu.edu/~koopman/crc/
// Params width=12  poly=0x987  init=0x000  refin=false  refout=false  xorout=0x000  check=0xf1a  residue=0x000
const uint16_t crc12_table[256] = {
	0x000, 0x987, 0xa89, 0x30e, 0xc95, 0x512, 0x61c, 0xf9b, 0x0ad, 0x92a, 0xa24, 0x3a3, 0xc38, 0x5bf, 0x6b1, 0xf36,
	0x15a, 0x8dd, 0xbd3, 0x254, 0xdcf, 0x448, 0x746, 0xec1, 0x1f7, 0x870, 0xb7e, 0x2f9, 0xd62, 0x4e5, 0x7eb, 0xe6c,
	0x2b4, 0xb33, 0x83d, 0x1ba, 0xe21, 0x7a6, 0x4a8, 0xd2f, 0x219, 0xb9e, 0x890, 0x117, 0xe8c, 0x70b, 0x405, 0xd82,
	0x3ee, 0xa69, 0x967, 0x0e0, 0xf7b, 0x6fc, 0x5f2, 0xc75, 0x343, 0xac4, 0x9ca, 0x04d, 0xfd6, 0x651, 0x55f, 0xcd8,
	0x568, 0xcef, 0xfe1, 0x666, 0x9fd, 0x07a, 0x374, 0xaf3, 0x5c5, 0xc42, 0xf4c, 0x6cb, 0x950, 0x0d7, 0x3d9, 0xa5e,
	0x432, 0xdb5, 0xebb, 0x73c, 0x8a7, 0x120, 0x22e, 0xba9, 0x49f, 0xd18, 0xe16, 0x791, 0x80a, 0x18d, 0x283, 0xb04,
	0x7dc, 0xe5b, 0xd55, 0x4d2, 0xb49, 0x2ce, 0x1c0, 0x847, 0x771, 0xef6, 0xdf8, 0x47f, 0xbe4, 0x263, 0x16d, 0x8ea,
	0x686, 0xf01, 0xc0f, 0x588, 0xa13, 0x394, 0x09a, 0x91d, 0x62b, 0xfac, 0xca2, 0x525, 0xabe, 0x339, 0x037, 0x9b0,
	0xad0, 0x357, 0x059, 0x9de, 0x645, 0xfc2, 0xccc, 0x54b, 0xa7d, 0x3fa, 0x0f4, 0x973, 0x6e8, 0xf6f, 0xc61, 0x5e6,
	0xb8a, 0x20d, 0x103, 0x884, 0x71f, 0xe98, 0xd96, 0x411, 0xb27, 0x2a0, 0x1ae, 0x829, 0x7b2, 0xe35, 0xd3b, 0x4bc,
	0x864, 0x1e3, 0x2ed, 0xb6a, 0x4f1, 0xd76, 0xe78, 0x7ff, 0x8c9, 0x14e, 0x240, 0xbc7, 0x45c, 0xddb, 0xed5, 0x752,
	0x93e, 0x0b9, 0x3b7, 0xa30, 0x5ab, 0xc2c, 0xf22, 0x6a5, 0x993, 0x014, 0x31a, 0xa9d, 0x506, 0xc81, 0xf8f, 0x608,
	0xfb8, 0x63f, 0x531, 0xcb6, 0x32d, 0xaaa, 0x9a4, 0x023, 0xf15, 0x692, 0x59c, 0xc1b, 0x380, 0xa07, 0x909, 0x08e,
	0xee2, 0x765, 0x46b, 0xdec, 0x277, 0xbf0, 0x8fe, 0x179, 0xe4f, 0x7c8, 0x4c6, 0xd41, 0x2da, 0xb5d, 0x853, 0x1d4,
	0xd0c, 0x48b, 0x785, 0xe02, 0x199, 0x81e, 0xb10, 0x297, 0xda1, 0x426, 0x728, 0xeaf, 0x134, 0x8b3, 0xbbd, 0x23a,
	0xc56, 0x5d1, 0x6df, 0xf58, 0x0c3, 0x944, 0xa4a, 0x3cd, 0xcfb, 0x57c, 0x672, 0xff5, 0x06e, 0x9e9, 0xae7, 0x360};

inline void update_crc12(uint32_t &crcv, uint8_t data)
{
	uint8_t idx = uint8_t((crcv >> 4) ^ data);
	crcv = crc12_table[idx] ^ (crcv << 8);
}

uint32_t calc_crc12(const crypto::secret_key_16 &key, uint8_t extra)
{
	uint32_t crcv = 0;
	for(size_t i = 0; i < 16; i++)
		update_crc12(crcv, tools::unwrap(key).data[i]);
	update_crc12(crcv, extra);
	return crcv & 0xfff;
}

/*!
   * \namespace crypto::ElectrumWords
   *
   * \brief Mnemonic seed word generation and wallet restoration helper functions.
   */
/*!
   * \brief Finds the word list that contains the seed words and puts the indices
   *        where matches occured in matched_indices.
   * \param  seed            List of words to match.
   * \param  has_checksum    The seed has a checksum word (maybe not checked).
   * \param  matched_indices The indices where the seed words were found are added to this.
   * \param  language        Language instance pointer to write to after it is found.
   * \return                 true if all the words were present in some language false if not.
   */
bool find_seed_language(const std::vector<std::string> &seed, Language::Base **language)
{
	// Yes, I don't like std::pair
	struct sort_pair
	{
		Language::Base *inst;
		size_t score;

		inline bool operator<(const sort_pair &oth) const { return score < oth.score; }
	};

	// If there's a new language added, add an instance of it here.
	std::vector<sort_pair> language_instances({{Language::Singleton<Language::Chinese_Simplified>::instance(), 0},
											   {Language::Singleton<Language::English>::instance(), 0},
											   {Language::Singleton<Language::Dutch>::instance(), 0},
											   {Language::Singleton<Language::French>::instance(), 0},
											   {Language::Singleton<Language::Spanish>::instance(), 0},
											   {Language::Singleton<Language::German>::instance(), 0},
											   {Language::Singleton<Language::Italian>::instance(), 0},
											   {Language::Singleton<Language::Portuguese>::instance(), 0},
											   {Language::Singleton<Language::Japanese>::instance(), 0},
											   {Language::Singleton<Language::Russian>::instance(), 0},
											   {Language::Singleton<Language::Esperanto>::instance(), 0},
											   {Language::Singleton<Language::Lojban>::instance(), 0}});

	// Assumption here is that the end user will spell more words correctly than get at random in a foreign lang
	for(sort_pair &pair : language_instances)
	{
		const std::unordered_map<std::string, uint32_t> &word_map = pair.inst->get_word_map();
		for(const std::string &word : seed)
		{
			if(word_map.count(word) > 0)
				pair.score++;
		}
	}

	std::sort(language_instances.begin(), language_instances.end());

	if(language_instances.back().score > 0)
	{
		*language = language_instances.back().inst;
		return true;
	}

	return false;
}

bool match_words(const Language::Base &lang, const std::vector<std::string> &seed, std::vector<uint32_t> &matched_indices)
{
	size_t trim_size = lang.get_unique_prefix_length();
	const std::unordered_map<std::string, uint32_t> &trimmed_word_map = lang.get_trimmed_word_map();
	matched_indices.reserve(seed.size());

	for(const std::string &word : seed)
	{
		std::string trimmed_word = Language::utf8prefix(word, trim_size);
		if(trimmed_word_map.count(trimmed_word) > 0)
			matched_indices.push_back(trimmed_word_map.at(trimmed_word));
		else
			return false;
	}
	return true;
}

namespace Electrum
{

/*!
     * \brief Gets a list of seed languages that are supported.
     * \param languages The vector is set to the list of languages.
     */
const std::vector<const Language::Base *> &get_language_list()
{
	static const std::vector<const Language::Base *> language_instances({Language::Singleton<Language::German>::instance(),
																		 Language::Singleton<Language::English>::instance(),
																		 Language::Singleton<Language::Spanish>::instance(),
																		 Language::Singleton<Language::French>::instance(),
																		 Language::Singleton<Language::Italian>::instance(),
																		 Language::Singleton<Language::Dutch>::instance(),
																		 Language::Singleton<Language::Portuguese>::instance(),
																		 Language::Singleton<Language::Russian>::instance(),
																		 Language::Singleton<Language::Japanese>::instance(),
																		 Language::Singleton<Language::Chinese_Simplified>::instance(),
																		 Language::Singleton<Language::Esperanto>::instance(),
																		 Language::Singleton<Language::Lojban>::instance()});
	return language_instances;
}

void get_language_list(std::vector<std::string> &languages, bool english)
{
	const std::vector<const Language::Base *> language_instances = get_language_list();
	for(std::vector<const Language::Base *>::const_iterator it = language_instances.begin();
		it != language_instances.end(); it++)
	{
		languages.push_back(english ? (*it)->get_english_language_name() : (*it)->get_language_name());
	}
}

std::string get_english_name_for(const std::string &name)
{
	const std::vector<const Language::Base *> language_instances = get_language_list();
	for(std::vector<const Language::Base *>::const_iterator it = language_instances.begin();
		it != language_instances.end(); it++)
	{
		if((*it)->get_language_name() == name)
			return (*it)->get_english_language_name();
	}
	return "<language not found>";
}

std::string verify_language_input(std::string input)
{
	boost_locale_init();
	input = boost::locale::to_title(input);

	for(const Language::Base* lng : get_language_list())
	{
		if(lng->get_language_name() == input)
			return lng->get_language_name();

		if(lng->get_english_language_name() == input)
			return lng->get_language_name();
	}
	return "";
}
}

namespace Electrum14Words
{
bool words_to_bytes(std::string words, crypto::secret_key_16 &dst, uint8_t &extra, std::string &language_name)
{
	std::vector<std::string> seed;

	boost::algorithm::trim(words);
	boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on);

	if(seed.size() != 14 && seed.size() != 12)
		return false;

	Language::Base *language;
	if(!find_seed_language(seed, &language))
		return false;

	std::vector<uint32_t> matched_indices;
	if(!match_words(*language, seed, matched_indices))
		return false;

	language_name = language->get_language_name();
	uint32_t word_list_length = language->get_word_list().size();

	uint32_t *out = (uint32_t *)tools::unwrap(dst).data;
	for(unsigned int i = 0; i < seed.size() / 3; i++)
	{
		uint32_t val;
		uint32_t w1, w2, w3;
		w1 = matched_indices[i * 3];
		w2 = matched_indices[i * 3 + 1];
		w3 = matched_indices[i * 3 + 2];

		val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) +
			  word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length);

		if(!(val % word_list_length == w1))
			return false;
		out[i] = val;
	}

	if(seed.size() == 14)
	{
		uint32_t val;
		uint32_t w1, w2;
		w1 = matched_indices[12];
		w2 = matched_indices[13];

		val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length);
		val &= 0xfffff; // 20 bytes

		extra = uint8_t(val & 0xff);
		val >>= 8;

		if(calc_crc12(dst, extra) != val)
			return false;
	}

	return true;
}

bool bytes_to_words(const crypto::secret_key_16 &src, uint8_t extra, std::string &words, const std::string &language_name)
{
	const std::vector<const Language::Base *> &lng_insts = Electrum::get_language_list();
	const Language::Base *language = nullptr;
	for(const Language::Base *b : lng_insts)
	{
		if(b->get_language_name() == language_name || b->get_english_language_name() == language_name)
		{
			language = b;
			break;
		}
	}

	if(language == nullptr)
		return false;

	const std::vector<std::string> &word_list = language->get_word_list();

	uint32_t word_list_length = word_list.size();
	const uint32_t *psrc = (const uint32_t *)tools::unwrap(src).data;
	for(unsigned int i = 0; i < 4; i++, words += ' ')
	{
		uint32_t w1, w2, w3;
		uint32_t val = psrc[i];

		w1 = val % word_list_length;
		w2 = ((val / word_list_length) + w1) % word_list_length;
		w3 = (((val / word_list_length) / word_list_length) + w2) % word_list_length;

		words += word_list[w1];
		words += ' ';
		words += word_list[w2];
		words += ' ';
		words += word_list[w3];
	}

	uint32_t w1, w2;
	uint32_t checksum = calc_crc12(src, extra);
	checksum <<= 8;
	checksum |= extra;

	w1 = checksum % word_list_length;
	w2 = ((checksum / word_list_length) + w1) % word_list_length;
	words += word_list[w1];
	words += ' ';
	words += word_list[w2];

	return true;
}
}

namespace Electrum25Words
{
/*!
   * \brief Creates a checksum index in the word list array on the list of words.
   * \param  word_list            Vector of words
   * \param unique_prefix_length  the prefix length of each word to use for checksum
   * \return                      Checksum index
   */
uint32_t create_checksum_index(const std::vector<std::string> &word_list, uint32_t unique_prefix_length)
{
	std::string trimmed_words = "";

	for(std::vector<std::string>::const_iterator it = word_list.begin(); it != word_list.end(); it++)
	{
		if(it->length() > unique_prefix_length)
		{
			trimmed_words += Language::utf8prefix(*it, unique_prefix_length);
		}
		else
		{
			trimmed_words += *it;
		}
	}
	boost::crc_32_type result;
	result.process_bytes(trimmed_words.data(), trimmed_words.length());
	return result.checksum() % seed_length;
}

/*!
   * \brief Does the checksum test on the seed passed.
   * \param seed                  Vector of seed words
   * \param unique_prefix_length  the prefix length of each word to use for checksum
   * \return                      True if the test passed false if not.
   */
bool checksum_test(std::vector<std::string> seed, uint32_t unique_prefix_length)
{
	if(seed.empty())
		return false;
	// The last word is the checksum.
	std::string last_word = seed.back();
	seed.pop_back();

	std::string checksum = seed[create_checksum_index(seed, unique_prefix_length)];

	std::string trimmed_checksum = checksum.length() > unique_prefix_length ? Language::utf8prefix(checksum, unique_prefix_length) : checksum;
	std::string trimmed_last_word = last_word.length() > unique_prefix_length ? Language::utf8prefix(last_word, unique_prefix_length) : last_word;
	return trimmed_checksum == trimmed_last_word;
}

/*!
     * \brief Converts seed words to bytes (secret key).
     * \param  words           String containing the words separated by spaces.
     * \param  dst             To put the secret data restored from the words.
     * \param  len             The number of bytes to expect, 0 if unknown
     * \param  duplicate       If true and len is not zero, we accept half the data, and duplicate it
     * \param  language_name   Language of the seed as found gets written here.
     * \return                 false if not a multiple of 3 words, or if word is not in the words list
     */
bool words_to_bytes(std::string words, std::string &dst, size_t len, std::string &language_name)
{
	std::vector<std::string> seed;

	boost::algorithm::trim(words);
	boost::split(seed, words, boost::is_any_of(" "), boost::token_compress_on);

	if(len % 4)
		return false;

	if(seed.size() == 26) //Last word is a funky sumo attempt at a second checksum, ignore it
		seed.pop_back();

	bool has_checksum = true;
	if(len)
	{
		// error on non-compliant word list
		const size_t expected = len * 8 * 3 / 32;
		if(seed.size() != expected / 2 && seed.size() != expected && seed.size() != expected + 1)
			return false;

		// If it is seed with a checksum.
		has_checksum = seed.size() == (expected + 1);
	}

	Language::Base *language;
	if(!find_seed_language(seed, &language))
		return false;

	std::vector<uint32_t> matched_indices;
	if(!match_words(*language, seed, matched_indices))
		return false;

	language_name = language->get_language_name();
	uint32_t word_list_length = language->get_word_list().size();

	if(has_checksum)
	{
		if(!checksum_test(seed, language->get_unique_prefix_length()))
		{
			// Checksum fail
			return false;
		}
		seed.pop_back();
	}

	for(unsigned int i = 0; i < seed.size() / 3; i++)
	{
		uint32_t val;
		uint32_t w1, w2, w3;
		w1 = matched_indices[i * 3];
		w2 = matched_indices[i * 3 + 1];
		w3 = matched_indices[i * 3 + 2];

		val = w1 + word_list_length * (((word_list_length - w1) + w2) % word_list_length) +
			  word_list_length * word_list_length * (((word_list_length - w2) + w3) % word_list_length);

		if(!(val % word_list_length == w1))
			return false;

		dst.append((const char *)&val, 4); // copy 4 bytes to position
	}

	return true;
}

/*!
     * \brief Converts seed words to bytes (secret key).
     * \param  words           String containing the words separated by spaces.
     * \param  dst             To put the secret key restored from the words.
     * \param  language_name   Language of the seed as found gets written here.
     * \return                 false if not a multiple of 3 words, or if word is not in the words list
     */
bool words_to_bytes(std::string words, crypto::secret_key &dst, std::string &language_name)
{
	std::string s;
	if(!words_to_bytes(words, s, sizeof(dst), language_name))
		return false;
	if(s.size() != sizeof(dst))
		return false;
	dst = *(const crypto::secret_key *)s.data();
	return true;
}

/*!
     * \brief Converts bytes (secret key) to seed words.
     * \param  src           Secret key
     * \param  words         Space delimited concatenated words get written here.
     * \param  language_name Seed language name
     * \return               true if successful false if not. Unsuccessful if wrong key size.
     */
bool bytes_to_words(const char *src, size_t len, std::string &words, const std::string &language_name)
{
	if(len % 4 != 0 || len == 0)
		return false;

	const std::vector<const Language::Base *> &lng_insts = Electrum::get_language_list();
	const Language::Base *language = nullptr;
	for(const Language::Base *b : lng_insts)
	{
		if(b->get_language_name() == language_name || b->get_english_language_name() == language_name)
		{
			language = b;
			break;
		}
	}

	if(language == nullptr)
		return false;

	const std::vector<std::string> &word_list = language->get_word_list();
	// To store the words for random access to add the checksum word later.
	std::vector<std::string> words_store;

	uint32_t word_list_length = word_list.size();
	// 4 bytes -> 3 words.  8 digits base 16 -> 3 digits base 1626
	for(unsigned int i = 0; i < len / 4; i++, words += ' ')
	{
		uint32_t w1, w2, w3;

		uint32_t val;

		memcpy(&val, src + (i * 4), 4);

		w1 = val % word_list_length;
		w2 = ((val / word_list_length) + w1) % word_list_length;
		w3 = (((val / word_list_length) / word_list_length) + w2) % word_list_length;

		words += word_list[w1];
		words += ' ';
		words += word_list[w2];
		words += ' ';
		words += word_list[w3];

		words_store.push_back(word_list[w1]);
		words_store.push_back(word_list[w2]);
		words_store.push_back(word_list[w3]);
	}

	words.pop_back();
	words += (' ' + words_store[create_checksum_index(words_store, language->get_unique_prefix_length())]);
	return true;
}

bool bytes_to_words(const crypto::secret_key &src, std::string &words, const std::string &language_name)
{
	return bytes_to_words(src.data, sizeof(src), words, language_name);
}
}
}


//
// Created by veyry_p on 7/11/15.
//

#include <iostream>
#include <chrono>
#include <numeric>
#include <SFML/Graphics/Sprite.hpp>
#include <SFML/Graphics/Texture.hpp>
#include <SFML/Graphics/RectangleShape.hpp>
#include <SFML/Graphics/RenderTexture.hpp>
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include "strong-classifier.h"
#include "../features/four-rectangles-feature.h"
#include "../features/three-horizontal-rectangles-feature.h"
#include "../config.h"
#include "../features/two-vertical-rectangles-feature.h"
#include "../features/two-horizontal-rectangles-feature.h"
#include "../features/three-vertical-rectangles-feature.h"

extern void select_best_gpu(int featureNum, int testNum, bool * valids, double * weights, double validweight, int * featureIndex,
    int* index, bool* good, double* error);

namespace violajones
{
  StrongClassifier::StrongClassifier(std::vector<WeakClassifier> classifiers)
          : classifiers_{classifiers}
  {
    long double galpha = 0;
    for (auto& classifier : classifiers)
      galpha += classifier.alpha_;

    global_alpha_ = galpha;
  }

  bool StrongClassifier::check(Window win, std::shared_ptr<IntegralImage> image)
  {
    long double sumvalues = 0.0;
    for (auto& weakclass : classifiers_)
      sumvalues += weakclass.get_value(win, image);

    bool tmp = sumvalues >= (global_alpha_ / 2.0);
    if (Config::debug_classifier_check)
      std::cout << "StrongClassifier::check " << tmp
      << " - sumvalues: " << sumvalues
      << " global_alpha: " << global_alpha_ << std::endl;

    return tmp;
  }

  void StrongClassifier::save(std::string path)
  {
    std::ofstream fs;
    fs.open(path);
    for (auto& classif : classifiers_)
      fs << classif.alpha_ << " "
      << classif.threshold_ << " "
      << (int)classif.parity_ << " "
      << classif.feature_->get_type() << " "
      << classif.feature_->frame.top_left.x << " "
      << classif.feature_->frame.top_left.y << " "
      << classif.feature_->frame.width << " "
      << classif.feature_->frame.height << std::endl;
    fs.close();
  }

  StrongClassifier StrongClassifier::load_from_file(std::string path)
  {
    std::function<WeakClassifier(std::string&)> restore_classifier =
            [](std::string& s) {
              std::vector<std::string> tokens;
              boost::split(tokens, s, boost::is_any_of(" ;"));
              double alpha = std::stod(tokens[0]);
              int threshold = std::stoi(tokens[1]);
              char parity = (char) std::stoi(tokens[2]);
              std::string feature_type = tokens[3];

              int featurex = std::stoi(tokens[4]);
              int featurey = std::stoi(tokens[5]);
              int featurewidth = std::stoi(tokens[6]);
              int featureheight = std::stoi(tokens[7]);

              Rectangle feature_frame(Point(featurex, featurey), featurewidth, featureheight);
              if (feature_type == "FourRectanglesFeature")
                return WeakClassifier(alpha, threshold, parity, std::make_shared<FourRectanglesFeature>(feature_frame));
              else if (feature_type == "ThreeHorizontalRectanglesFeature")
                return WeakClassifier(alpha, threshold, parity,
                                      std::make_shared<ThreeHorizontalRectanglesFeature>(feature_frame));
              else if (feature_type == "ThreeVerticalRectanglesFeature")
                return WeakClassifier(alpha, threshold, parity,
                                      std::make_shared<ThreeVerticalRectanglesFeature>(feature_frame));
              else if (feature_type == "TwoHorizontalRectanglesFeature")
                return WeakClassifier(alpha, threshold, parity,
                                      std::make_shared<TwoHorizontalRectanglesFeature>(feature_frame));
              else if (feature_type == "TwoVerticalRectanglesFeature")
                return WeakClassifier(alpha, threshold, parity,
                                      std::make_shared<TwoVerticalRectanglesFeature>(feature_frame));

              std::cerr << "Fatal Error: feature_type " << feature_type << std::endl;
              return WeakClassifier(alpha, threshold, parity, std::make_shared<FourRectanglesFeature>(feature_frame));
            };

    std::ifstream infile(path);
    std::string line;
    std::vector<WeakClassifier> classifiers;
    while (std::getline(infile, line))
      classifiers.push_back(restore_classifier(line));
    return StrongClassifier(classifiers);
  }

  StrongClassifier StrongClassifier::train(std::string tests_dir)
  {
    std::cout << "Initializing Trainer ...\n"
    << "Loading trainer tests ..." << std::endl;
    auto start = std::chrono::steady_clock::now();
    auto tests_set = load_tests_set(tests_dir);

    auto& tests = tests_set.first;
    auto& features_values = tests_set.second;
    int ncached_features = 0;

    std::cout << "loading the features\n";
    /*
    if (Config::parallelized)
      tbb::parallel_for_each(features_values.begin(), features_values.end(),
                             [&ncached_features](FeatureValues& ftvalues) {
                               if (ftvalues.values_.size())
                                 ++ncached_features;
                             });
    else
    */
      for (FeatureValues& ftvalues : features_values)
        if (ftvalues.values_.size())
          ++ncached_features;

    std::cout << "loading the features completed\n";

    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;

    std::cout << "Tests loaded in " << diff.count() << " seconds ("
    << ncached_features * 100 / features_values.size()
    << "% cached)\n Launching training..." << std::endl;

    std::vector<WeakClassifier> classifiers;
    unsigned ipass = 1;
    auto global_start = std::chrono::steady_clock::now();

    std::cout<<"Need "<<Config::learn_pass<<" pass"<<std::endl;

    // Bo Li
    int featureNum = features_values.size();
    int testNum = tests.size();
    int * featureIndexfeatures_values = (int *)malloc(featureNum * testNum * sizeof(int));
    //std::cout << " Feature test index in gpu is ";
    for(int i=0; i<featureNum; ++i){
      for(int j=0; j<testNum; ++j){
        featureIndexfeatures_values[i*testNum + j] = features_values[i].values_[j].test_index_;
        //std::cout<<featureIndexfeatures_values[i*testNum + j]<<" ";
      }
      //std::cout<<std::endl;
    }

    //Read Only Data
    size_t bytesV = testNum * sizeof( bool );
    bool * valids = (bool*)malloc(bytesV);
    size_t bytesF = testNum*sizeof(double);
    double* weights = (double*)malloc(bytesF);

    //Each Iteration Best
      int* indexResult = (int*)malloc(featureNum*sizeof(int));
      bool* goodResult = (bool*)malloc(featureNum*sizeof(bool));
      double* errorResult = (double*)malloc(featureNum*sizeof(double));

    while (ipass <= Config::learn_pass)
    {
      start = std::chrono::steady_clock::now();
      std::cout << ipass << "/" << Config::learn_pass << " trainer pass..." << std::endl;
      double weightsum = std::accumulate(tests.begin(), tests.end(), 0.0,
                                         [](double acc, TestImage& t) { return t.weight_ + acc; });

      double validweight = 0.0;
      for (size_t i = 0; i < tests.size(); ++i)
      {
        tests[i].weight_ = tests[i].weight_ / weightsum;
        if (tests[i].valid_)
          validweight += tests[i].weight_;
      }

      TestWeakClassifier best(features_values[0], 0, 1, std::numeric_limits<double>::max());
      /*
      if (Config::parallelized)
      {
        tbb::parallel_for_each(features_values.begin(), features_values.end(),
                               [&](FeatureValues& fv) {
                                 auto new_classifier = TestWeakClassifier::train(tests, validweight, fv);
                                 tbb::mutex::scoped_lock lock;
                                 lock.acquire(mutex);
                                 if (best.errors_ > new_classifier.errors_)
                                   best = new_classifier;
                                 lock.release();
                               });
      }
      else
      */

      for(int i=0; i<testNum; ++i){
          valids[i] = tests[i].valid_;
          weights[i] = tests[i].weight_;
          //std::cout << valids[i] << " with "<< weights[i] << std::endl;
      }

      auto GPUstart = std::chrono::steady_clock::now();
      select_best_gpu(featureNum, testNum, valids, weights, validweight, featureIndexfeatures_values,
         indexResult, goodResult, errorResult);
      auto GPUend = std::chrono::steady_clock::now();

      int b_index = 0;
      bool b_good = 0;
      double b_error = std::numeric_limits<double>::max();
      int f_index = 0;
      for(int i=0; i<featureNum; ++i) {
        //std::cout << i << "th training result is " << errorResult[i] << " with index " << indexResult[i] <<std::endl;
        if(errorResult[i] < b_error){
          b_error = errorResult[i];
          b_good = goodResult[i];
          b_index = indexResult[i];
          f_index = i;
        }
      }
      std::chrono::duration<double> GPUdiff = GPUend - GPUstart;
      TestWeakClassifier bestGPU(features_values[f_index],
          features_values[f_index].values_[b_index].value_ + ((b_good)?1:-1), b_good, b_error);
      std::cout << "GPU New weak classifier selected in " << GPUdiff.count() << " seconds (error score : "
      << bestGPU.errors_ << ")\n"
      << "X: " << bestGPU.feature_.feature_->frame.top_left.x
      << " Y: " << bestGPU.feature_.feature_->frame.top_left.y
      << " - Width: " << bestGPU.feature_.feature_->frame.width
      << " Height: " << bestGPU.feature_.feature_->frame.height << std::endl;

      //Bo Li CPU
      /*
      auto CPUstart = std::chrono::steady_clock::now();
      std::for_each(features_values.begin(), features_values.end(),
                      [&](FeatureValues& fv) {

                        auto new_classifier = TestWeakClassifier::train(tests, validweight, fv);
                        //std::cout << " New Featrue CPU is " << new_classifier.errors_ << " errors \n";
                        if (best.errors_ > new_classifier.errors_)
                          best = new_classifier;
                      });
      auto CPUend = std::chrono::steady_clock::now();
      std::chrono::duration<double> CPUdiff = CPUend - CPUstart;

      std::cout << "CPU New weak classifier selected in " << CPUdiff.count() << " seconds (error score : "
      << best.errors_ << ")\n"
      << "X: " << best.feature_.feature_->frame.top_left.x
      << " Y: " << best.feature_.feature_->frame.top_left.y
      << " - Width: " << best.feature_.feature_->frame.width
      << " Height: " << best.feature_.feature_->frame.height << std::endl;
      */
      best = bestGPU;
      double beta = best.errors_ / (1.0 - best.errors_);
      if (beta < 1.0 / 100000000)
        beta = 1.0 / 100000000;

      for (FeatureValue& featurevalue : best.feature_.values_)
        if (best.check(featurevalue.value_) == tests[featurevalue.test_index_].valid_)
          tests[featurevalue.test_index_].weight_ *= beta;

      auto alpha = std::log(1.0 / beta);
      classifiers.push_back(best.get_classifier(alpha));
      ++ipass;
    }

    free(valids);
    free(weights);
    free(indexResult);
    free(goodResult);
    free(errorResult);
    auto global_end = std::chrono::steady_clock::now();
    diff = global_end - global_start;
    std::cout << "Training finished in " << diff.count()
    << " secs." << std::endl;
    return StrongClassifier(classifiers);
  }

  std::pair<std::vector<TestImage>,
          std::vector<FeatureValues> > StrongClassifier::load_tests_set(std::string tests_dir)
  {
    std::string gooddir = tests_dir + "/good";
    std::string baddir = tests_dir + "/bad";
    auto good = load_images(gooddir);
    auto bad = load_images(baddir);

    double goodweight = 1.0 / (2 * good.size());
    double badweight = 1.0 / (2 * bad.size());

    std::cout << "Image size is " <<  good[0]->width << " * " << good[0]->height << std::endl;

    std::cout << " Computing IntegralImage\n";
    auto startI = std::chrono::steady_clock::now();

    std::vector<TestImage> tests;
    for (size_t i = 0; i < good.size(); ++i)
      tests.push_back(TestImage(*good[i], goodweight, true));

    for (auto i = good.size(); i < good.size() + bad.size(); ++i)
      tests.push_back(TestImage(*bad[i - good.size()], badweight, false));

    std::cout << " completed IntegralImage\n";
    auto endI = std::chrono::steady_clock::now();
    std::chrono::duration<double> diffI = endI - startI;
    std::cout << "Computing IntegralImage in " << diffI.count() << " seconds."<< std::endl;

    std::cout << " Computing feature Values\n";
    auto start = std::chrono::steady_clock::now();

    auto features_values = compute_features_values(tests);
    std::cout << " completed feature Values\n";
    auto end = std::chrono::steady_clock::now();
    std::chrono::duration<double> diff = end - start;
    std::cout << "Computing Featrues in " << diff.count() << " seconds."<< std::endl;

    return std::pair<std::vector<TestImage>, std::vector<FeatureValues>>(tests, features_values);
  }

  std::vector<FeatureValues> StrongClassifier::compute_features_values(std::vector<TestImage> tests)
  {
    std::vector<std::shared_ptr<Feature> > features = Window::list_features();
    std::vector<FeatureValues> features_values;
    std::cout << "We have "<<features.size() << " featrues to compute\n";
    for (size_t i = 0; i < features.size(); ++i)
    {
      if(i%100==0)
        std::cout << "Computing "<< i<< "th feature Values\n";
      auto values = FeatureValue::compute_all_values_sorted(tests, *features[i]);
      features_values.push_back(FeatureValues(features[i], values));
    }
    return features_values;
  }

  std::shared_ptr<GreyImage> StrongClassifier::load_image(std::string imagepath)
  {
    sf::Image sfimage;
    sfimage.loadFromFile(imagepath);
    if (sfimage.getSize().x != Config::window_width || sfimage.getSize().y != Config::window_height)
    {
      std::cerr << "Invalid image size (must be "
      << Config::window_width << "*"
      << Config::window_height << "): "
      << imagepath << std::endl;
      exit(1);
    }
    return std::make_shared<GreyImage>(GreyImage(sfimage));
  }

  std::vector <std::shared_ptr<GreyImage>> StrongClassifier::load_images(std::string dir)
  {
    namespace fs = boost::filesystem;
    fs::path path(dir);
    fs::directory_iterator end_itr;
    std::vector<std::shared_ptr<GreyImage>> images;

    size_t number_loaded = 0;
    /*
    if (Config::parallelized)
    {
      std::vector<std::string> strings;
      for (fs::directory_iterator iter(path);
           iter != end_itr && (number_loaded < Config::number_load || !Config::number_load);
           ++iter, ++number_loaded)
        if (fs::is_regular_file(iter->status()))
          strings.push_back(iter->path().string());

      tbb::parallel_for_each(strings.begin(), strings.end(),
                             [&](std::string string) {
                               images.push_back(load_image(string));
                             });
    }
    else
    */
    if (fs::exists(path) && fs::is_directory(path))
      for (fs::directory_iterator iter(path);
           iter != end_itr && (number_loaded < Config::number_load || !Config::number_load);
           ++iter, ++number_loaded)
        if (fs::is_regular_file(iter->status()))
          images.push_back(load_image(iter->path().string()));

    std::cout << "Loaded " << images.size() << " in " << dir << std::endl;
    if (!images.size())
    {
      std::cerr << "Error: no images has been loaded from: " << dir << std::endl;
      exit(0);
    }

    return images;
  }
}


///////////////////////////////////////////////////////////////////////////////
//
// This file is a part of the PadallelFDTD Finite-Difference Time-Domain
// simulation library. It is released under the MIT License. You should have
// received a copy of the MIT License along with ParallelFDTD.  If not, see
// http://www.opensource.org/licenses/mit-license.php
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// For details, see the LICENSE file
//
// (C) 2013-2014 Jukka Saarelma
// Aalto University School of Science
//
///////////////////////////////////////////////////////////////////////////////

#include <algorithm>
#include <Python.h>
#include <boost/cstdint.hpp>
#include <boost/python.hpp>
#include <boost/python/extract.hpp>
#include <boost/python/numpy.hpp>
#include <boost/python/stl_iterator.hpp>
#include <boost/python/suite/indexing/vector_indexing_suite.hpp>
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
#include <numpy/arrayobject.h>
#include "App.h"
#include "kernels/visualizationUtils.h"
#include "kernels/voxelizationUtils.h"

namespace GLOBAL {
  static FDTD::App* currentApp;
  static volatile bool interrupt = false;
}

using namespace boost::python;

boost::python::object toNumpyArray(unsigned int* data, long int size) {
      boost::python::numpy::dtype dt = boost::python::numpy::dtype::get_builtin<unsigned int>();
      boost::python::tuple shape = boost::python::make_tuple(size);
      boost::python::tuple stride = boost::python::make_tuple(size);
      boost::python::object owner;
      boost::python::numpy::ndarray array =
      boost::python::numpy::from_data(data, dt, shape, stride, owner);

    /* The problem of returning arr is twofold: firstly the user can modify
      the data which will betray the const-correctness
      Secondly the lifetime of the data is managed by the C++ API and not the
      lifetime of the numpy array whatsoever. But we have a simple solution..
     */

       return array.copy(); // copy the object. numpy owns the copy now.
}

boost::python::object toNumpyArray(float* data, long int size) {
      boost::python::numpy::dtype dt = boost::python::numpy::dtype::get_builtin<float>();
      boost::python::tuple shape = boost::python::make_tuple(size);
      boost::python::tuple stride = boost::python::make_tuple(size);
      boost::python::object owner;
      boost::python::numpy::ndarray array =
      boost::python::numpy::from_data(data, dt, shape, stride, owner);

      return array.copy(); // copy the object. numpy owns the copy now.
}

boost::python::object toNumpyArray(double* data, long int size) {
      boost::python::numpy::dtype dt = boost::python::numpy::dtype::get_builtin<double>();
      boost::python::tuple shape = boost::python::make_tuple(size);
      boost::python::tuple stride = boost::python::make_tuple(size);
      boost::python::object owner;
      boost::python::numpy::ndarray array =
      boost::python::numpy::from_data(data, dt, shape, stride, owner);

    // create a PyObject * from pointer and data
      return array.copy(); // copy the object. numpy owns the copy now.
}

boost::python::object toNumpyArray(unsigned char* data, long int size) {
    boost::python::numpy::dtype dt = boost::python::numpy::dtype::get_builtin<char>();
    boost::python::tuple shape = boost::python::make_tuple(size);
    boost::python::tuple stride = boost::python::make_tuple(size);
    boost::python::object owner;
    boost::python::numpy::ndarray array =
    boost::python::numpy::from_data(data, dt, shape, stride, owner);

      return array.copy(); // copy the object. numpy owns the copy now.
}

void FDTD::App::initializeGeometryPy(boost::python::list indices,
                                     boost::python::list vertices) {
  int v_len = (int)boost::python::len(vertices);
  int i_len = (int)boost::python::len(indices);
  std::vector<float> std_vertices(v_len, 0.f);
  std::vector<unsigned int> std_indices(i_len, 0);

  for(int i = 0; i < v_len; i++)
    std_vertices.at(i) = boost::python::extract<float>(vertices[i]);

  for(int i = 0; i < i_len; i++)
    std_indices.at(i) = boost::python::extract<unsigned int>(indices[i]);

  this->m_geometry.initialize(std_indices, std_vertices);
  this->has_geometry_ = true;
}

boost::python::object FDTD::App::getVoxelization(boost::python::list indices,
                                                 boost::python::list vertices,
                                                 boost::python::list material_coefficients,
                                                 int voxelization_type,
                                                 float dx) {
  unsigned char* d_position_idx = (unsigned char*)NULL;
  unsigned char* d_material_idx = (unsigned char*)NULL;
  uint3 voxelization_dim = make_uint3(0,0,0);

  this->initializeGeometryPy(indices, vertices);

  unsigned int number_of_triangles = this->m_geometry.getNumberOfTriangles();

  this->addSurfaceMaterials(material_coefficients,
                            number_of_triangles,
                            20);

  if(voxelization_type == 0)
    voxelizeGeometry_solid(m_geometry.getVerticePtr(),
                           m_geometry.getIndexPtr(),
                           m_materials.getMaterialIdxPtr(),
                           m_geometry.getNumberOfTriangles(),
                           m_geometry.getNumberOfVertices(),
                           m_materials.getNumberOfUniqueMaterials(),
                           (double)dx,
                           &d_position_idx,
                           &d_material_idx,
                           &voxelization_dim);

  if(voxelization_type == 1) {
    voxelizeGeometry_surface_6_separating(m_geometry.getVerticePtr(),
                                         m_geometry.getIndexPtr(),
                                         m_materials.getMaterialIdxPtr(),
                                         m_geometry.getNumberOfTriangles(),
                                         m_geometry.getNumberOfVertices(),
                                         m_materials.getNumberOfUniqueMaterials(),
                                         (double) m_parameters.getDx(),
                                         &d_position_idx,
                                         &d_material_idx,
                                         &voxelization_dim,
                                         0,voxelization_dim.z);
  }

  unsigned int num_elems = voxelization_dim.x*voxelization_dim.y*voxelization_dim.z;
  unsigned char* h_position_idx;
  unsigned char* h_material_idx;
  boost::python::list result;
  h_position_idx = fromDevice<unsigned char>(num_elems, d_position_idx, 0);
  h_material_idx = fromDevice<unsigned char>(num_elems, d_material_idx, 0);
  result.append(toNumpyArray(h_position_idx, num_elems));
  result.append(toNumpyArray(h_material_idx, num_elems));
  result.append(toNumpyArray((unsigned int*)&voxelization_dim, 3));
  free(h_position_idx);
  free(h_material_idx);
  destroyMem(d_position_idx);
  destroyMem(d_material_idx);
  return result;
}

void FDTD::App::initializeDomainPy(boost::python::numpy::ndarray position_idx,
                                   boost::python::numpy::ndarray material_idx,
                                   unsigned int dim_x,
                                   unsigned int dim_y,
                                   unsigned int dim_z) {
  log_msg<LOG_INFO>(L"App::initializeDomainPy - begin");
  mesh_size_t p_len = (mesh_size_t)boost::python::len(position_idx);
  mesh_size_t m_len = (mesh_size_t)boost::python::len(material_idx);

  if(p_len != dim_x*dim_y*dim_z || m_len !=dim_x*dim_y*dim_z) {
    log_msg<LOG_WARNING>(L"App::initializeGeometryPy, dimensions do not match"
                         L"with the size of the domain, x: %u y:%u z:%u, "
                         L"num pos: %u num mat: %u")
                         % dim_x % dim_y % dim_z % p_len % m_len;
   }
  clock_t start_t;
  clock_t end_t;
  start_t = clock();
  std::vector<unsigned char> std_pos(p_len, 0);
  unsigned char* ps = (unsigned char*)PyArray_GETPTR1(((PyArrayObject*)position_idx.ptr()), 0);

  for(mesh_size_t i = 0; i < p_len; i++)
    std_pos.at(i) = ps[i];

  end_t = clock()-start_t;
  log_msg<LOG_INFO>(L"App::initializeDomainPy - time copy pos: %f seconds")
          % ((float)end_t/CLOCKS_PER_SEC);

  start_t = clock();
  std::vector<unsigned char> std_mat(m_len, 0);
  unsigned char* ms = (unsigned char*)PyArray_GETPTR1(((PyArrayObject*)material_idx.ptr()), 0);

  for(mesh_size_t i = 0; i < m_len; i++)
    std_mat.at(i) = ms[i];

  end_t = clock()-start_t;
  log_msg<LOG_INFO>(L"App::initializeDomainPy - time copy mat: %f seconds")
            % ((float)end_t/CLOCKS_PER_SEC);

  this->initializeDomain(&std_pos[0], &std_mat[0], dim_x, dim_y, dim_z);
}

void FDTD::App::setSliceXY(boost::python::numpy::ndarray slice,
                           unsigned int dim_x, unsigned int dim_y,
                           int slice_idx) {
  int s_len = (int)boost::python::len(slice);
  if(s_len != dim_x*dim_y) {
    std::cout<<"FDTD::App::setSliceXY: slice size does not match "
               "the given dimensions"<<std::endl;
    return;
  }

  if(this->m_mesh.isDouble()) {
    int dev_i = 0;
    mesh_size_t element = 0;
    this->m_mesh.getElementIdxAndDevice(0,0,slice_idx, &dev_i, &element);
    double* d_dest = this->m_mesh.getPressureDoublePtrAt(dev_i)+element;
    //double* h_src = (double*)slice.ptr()->data;
    double* h_src = (double*)PyArray_DATA((PyArrayObject*)slice.ptr());
    copyHostToDevice<double>(s_len, d_dest, h_src, dev_i);
  }
  else {
    int dev_i = 0;
    mesh_size_t element = 0;
    this->m_mesh.getElementIdxAndDevice(0,0,slice_idx, &dev_i, &element);
    //float* h_src = (float*)((PyArrayObject*)slice.ptr())->data;
    float* h_src = (float*)PyArray_DATA((PyArrayObject*)slice.ptr());
    float* d_dest = this->m_mesh.getPressurePtrAt(dev_i)+element;
    copyHostToDevice<float>(s_len, d_dest, h_src, dev_i);
  }
}

void FDTD::App::initializeSimulationPy() {
  GLOBAL::currentApp = this;

  if(this->has_geometry_)
    this->initializeMesh(2);

  unsigned int step = this->m_parameters.getNumSteps();
  this->responses_.assign(m_parameters.getNumSteps()*m_parameters.getNumReceivers(), 0.f);
}

void FDTD::App::setLayerIndicesPy(boost::python::list indices,
                                  std::string name) {
  int i_len = (int)boost::python::len(indices);
  std::vector<int> std_indices(i_len, 0.f);
  for(int i = 0; i < i_len; i++)
    std_indices.at(i) = boost::python::extract<int>(indices[i]);

  this->m_geometry.setLayerIndices(std_indices, name);
}

void FDTD::App::addSurfaceMaterials(boost::python::list material_coefficients,
                                    unsigned int number_of_surfaces,
                                    unsigned int number_of_coefficients) {
  int m_len = (int)boost::python::len(material_coefficients);
  std::vector<float> std_mat(m_len, 0.f);
  for(int i = 0; i < m_len; i++) {
    std_mat.at(i) = boost::python::extract<float>(material_coefficients[i]);
  }
  this->m_materials.addMaterials(&std_mat[0],
                                 number_of_surfaces,
                                 number_of_coefficients);
}

void FDTD::App::addSourceDataFloat(boost::python::list src_data,
                                   int num_steps,
                                   int num_sources) {
  std::vector<float> std_src_data(num_steps*num_sources, 0.0);
  for(int j = 0; j < num_sources; j++) {
    for(int i = 0; i < num_steps; i++) {
      int idx =j*num_steps+i;
      std_src_data.at(idx) = boost::python::extract<float>(src_data[idx]);
    }
  }
  this->m_parameters.addInputData(std_src_data);
}

void FDTD::App::addSourceDataDouble(boost::python::list src_data,
                              int num_steps,
                              int num_sources) {
  std::vector<double> std_src_data(num_steps*num_sources, 0.0);
  for(int j = 0; j < num_sources; j++) {
    for(int i = 0; i < num_steps; i++) {
      int idx =j*num_steps+i;
      std_src_data.at(idx) = boost::python::extract<double>(src_data[idx]);
    }
  }
  this->m_parameters.addInputDataDouble(std_src_data);
}

boost::python::object FDTD::App::getSlice(int orientation,
                                          int slice_idx) {
  float* pressure_data = NULL;
  unsigned char* position_data = NULL;
  unsigned int d_x = 0;
  unsigned int d_y = 0;
  if(orientation == 0) {d_x = this->m_mesh.getDimX(); d_y = this->m_mesh.getDimY();};
  if(orientation == 1) {d_x = this->m_mesh.getDimX(); d_y = this->m_mesh.getDimZ();};
  if(orientation == 2) {d_x = this->m_mesh.getDimY(); d_y = this->m_mesh.getDimZ();};

  captureCurrentSlice(&this->m_mesh, &pressure_data, &position_data,
                      slice_idx, orientation);

  boost::python::object ret = toNumpyArray(pressure_data, d_x*d_y);
  free(pressure_data);
  free(position_data);
  return ret;
}

boost::python::list FDTD::App::getMeshDimensionsPy() {
  boost::python::list result;
  mesh_size_t dim_x = this->m_mesh.getDimX();
  mesh_size_t dim_y = this->m_mesh.getDimY();
  mesh_size_t dim_z = this->m_mesh.getDimZ();
  result.append(dim_x);
  result.append(dim_y);
  result.append(dim_z);
  return result;
}

/// Following convertion code taken from:
/// http://stackoverflow.com/questions/26595350/extracting-unsigned-char-from-array-of-numpy-uint8
/// @brief Converter type that enables automatic conversions between NumPy
///        scalars and C++ types.
template <typename T, NPY_TYPES NumPyScalarType>
struct enable_numpy_scalar_converter
{
  enable_numpy_scalar_converter() {
    // Required NumPy call in order to use the NumPy C API within another
    // extension module.
    _import_array();

    boost::python::converter::registry::push_back(
      &convertible,
      &construct,
      boost::python::type_id<T>());
  }

  static void* convertible(PyObject* object) {
    // The object is convertible if all of the following are true:
    // - is a valid object.
    // - is a numpy array scalar.
    // - its descriptor type matches the type for this converter.
    return (
      object &&                                                    // Valid
      PyArray_CheckScalar(object) &&                               // Scalar
      PyArray_DescrFromScalar(object)->type_num == NumPyScalarType // Match
    )
      ? object // The Python object can be converted.
      : NULL;
  }

  static void construct(
    PyObject* object,
    boost::python::converter::rvalue_from_python_stage1_data* data) {
    // Obtain a handle to the memory block that the converter has allocated
    // for the C++ type.
    namespace python = boost::python;
    typedef python::converter::rvalue_from_python_storage<T> storage_type;
    void* storage = reinterpret_cast<storage_type*>(data)->storage.bytes;

    // Extract the array scalar type directly into the storage.
    PyArray_ScalarAsCtype(object, storage);

    // Set convertible to indicate success.
    data->convertible = storage;
  }
};

BOOST_PYTHON_MODULE(liblibPyFDTD) {
  //boost::python::numpy::set_module_and_type("numpy", "ndarray");

  enable_numpy_scalar_converter<boost::uint8_t, NPY_UBYTE>();

  class_< std::vector<float> >("std_vec_float")
    .def(vector_indexing_suite< std::vector<float> >() )
    ;

  class_< std::vector<double> >("std_vec_double")
    .def(vector_indexing_suite< std::vector<double> >() )
    ;

  class_<FDTD::App>("App")
    .def("initializeDevices", &FDTD::App::initializeDevices)
    .def("initializeGeometryFromFile", &FDTD::App::initializeGeometryFromFile)
    .def("initializeGeometryPy", &FDTD::App::initializeGeometryPy)
    .def("initializeSimulationPy", &FDTD::App::initializeSimulationPy)
    .def("initializeDomainPy", &FDTD::App::initializeDomainPy)
    .def("setLayerIndices", &FDTD::App::setLayerIndicesPy)
    .def("setVoxelizationType", &FDTD::App::setVoxelizationType)
    .def("setDouble", &FDTD::App::setDouble)
    .def("setCapturedB", &FDTD::App::setCapturedB)
    .def("setCaptureId", &FDTD::App::setCaptureId)
    .def("setSpatialFs", &FDTD::App::setSpatialFs)
    .def("setNumSteps", &FDTD::App::setNumSteps )
    .def("setUpdateType", &FDTD::App::setUpdateType)
    .def("setUniformMaterial", &FDTD::App::setUniformMaterial)
    .def("setSliceXY", &FDTD::App::setSliceXY)
    .def("addSource", &FDTD::App::addSource)
    .def("addSourceDataFloat", &FDTD::App::addSourceDataFloat)
    .def("addSourceDataDouble", &FDTD::App::addSourceDataDouble)
    .def("addReceiver", &FDTD::App::addReceiver)
    .def("addSurfaceMaterials", &FDTD::App::addSurfaceMaterials)
    .def("addSliceToCapture", &FDTD::App::addSliceToCapture)
    .def("getSlice", &FDTD::App::getSlice)
    .def("getResponse", &FDTD::App::getResponse)
    .def("getResponseDouble", &FDTD::App::getResponseDouble)
    .def("getMvox", &FDTD::App::getMvoxPerSec)
    .def("getNumElems", &FDTD::App::getNumElements)
    .def("getMeshDimensions", &FDTD::App::getMeshDimensionsPy)
    .def("getEyring", &FDTD::App::getEyring)
    .def("getSabine", &FDTD::App::getSabine)
    .def("getVolume", &FDTD::App::getVolume)
    .def("getVoxelization", &FDTD::App::getVoxelization)
    .def("forcePartitionTo", &FDTD::App::setForcePartitionTo)
    .def("runSimulation", &FDTD::App::runSimulation)
    .def("runCapture", &FDTD::App::runCapture)
    .def("executeStep", &FDTD::App::executeStep)
    .def("close", &FDTD::App::close)
    #ifdef COMPILE_VISUALIZATION
    .def("runVisualization", &FDTD::App::runVisualization)
    #endif
    ;
}