#include <iostream>
#include <iomanip>
#include <vector>
#include <algorithm>
#include <numeric>
#include <stdexcept>
#include <cmath>
#include <locale.h>

#include <QApplication>

#include "common.h"
#include "time_utils.h"
#include "cli_parser.h"
#include "image_loader.h"
#include "Classifier.h"
#include "ClassificationDataset.h"
#include "FannClassificationDataset.h"
#include "NeuralNetworkPixelClassifiers.h"
#include "LibSVMClassificationDataset.h"
#include "SVMPixelClassifier.h"

#include "doublefann.h"

#include <tulip/TlpQtTools.h>
#include <tulip/PluginLoaderTxt.h>
#include <tulip/Graph.h>
#include <tulip/TlpTools.h>
#include <tulip/PluginLibraryLoader.h>
#include <tulip/StringCollection.h>
#include <tulip/DoubleProperty.h>
#include <tulip/BooleanProperty.h>

#include <boost/filesystem.hpp>

#include <itkImageSeriesWriter.h>
#include <itkNumericSeriesFileNames.h>

#include <itkBinaryThresholdImageFilter.h>

#include "LoggerPluginProgress.h"

#include "log4cxx/logger.h"
#include "log4cxx/consoleappender.h"
#include "log4cxx/patternlayout.h"
#include "log4cxx/basicconfigurator.h"

#include "callgrind.h"

using namespace tlp;
using namespace std;

namespace bfs = boost::filesystem;

//bool desc_comparator(const T a, const T b) { return a > b; }
template <typename T>
class desc_comparator {
public:
	desc_comparator(std::vector<T> const & values) :m_values(values) {}
	inline bool operator() (size_t a, size_t b) { return m_values[a] > m_values[b]; }
private:
	std::vector<T> const& m_values;
};

template <typename T>
std::vector<size_t> ordered(std::vector<T> const& values, desc_comparator<T> comparator) {
	std::vector<size_t> indices(values.size());
	//std::iota(begin(indices), end(indices), static_cast<size_t>(0)); // cxx11
	for (size_t i = 0; i != indices.size(); ++i) indices[i] = i;

	std::sort( indices.begin(), indices.end(), comparator );

	return indices;
}

class DirException : public std::runtime_error
{
private:
	DirException ( const std::string &err ) : std::runtime_error (err) {}
public:
	static DirException NotEmpty(const bfs::path &path) {     return DirException(path.native() + " is not empty."); }
	static DirException File(const bfs::path &path) {         return DirException(path.native() + " is a file."); }
	static DirException CannotCreate(const bfs::path &path) { return DirException(path.native() + " cannot be created."); }
};

void get_directory(const bfs::path &path, const bool mustBeEmpty = false) {
	if(bfs::exists(path)) {
		if(bfs::is_directory(path)) {
			if(mustBeEmpty && !bfs::is_empty(path))
				throw DirException::NotEmpty(path);
		} else
			throw DirException::File(path);
	} else {
		if(!bfs::create_directories(path))
			throw DirException::CannotCreate(path);
	}
}

std::string pad(const unsigned int i, const char c = '0', const unsigned int l = 6) {
	std::ostringstream os;
	os << std::setfill(c) << std::setw(l) << i;
	return os.str();
}

int main(int argc, char **argv)
{
	QApplication app(argc,argv);
	setlocale(LC_NUMERIC,"C");

	log4cxx::BasicConfigurator::configure(
			log4cxx::AppenderPtr(new log4cxx::ConsoleAppender(
					log4cxx::LayoutPtr(new log4cxx::PatternLayout("\%-5p - [%c] - \%m\%n")),
					log4cxx::ConsoleAppender::getSystemErr()
					)
				)
			);

	log4cxx::LoggerPtr logger(log4cxx::Logger::getLogger("main"));

	CliParser cli_parser;
	try {
		if(cli_parser.parse_argv(argc, argv) != CliParser::CONTINUE)
			exit(0);
	} catch (CliException &err) {
		LOG4CXX_FATAL(logger, err.what());
		return -1;
	}

	timestamp_t last_timestamp;

	FeaturesImage::Pointer input_image;

	/*
	 * Loading the input image (if it exists).
	 */
	if(!cli_parser.get_input_image().empty()) {
		last_timestamp = get_timestamp();
		LOG4CXX_INFO(logger, "Loading features image");

		typename itk::ImageFileReader< FeaturesImage >::Pointer input_image_reader = itk::ImageFileReader< FeaturesImage >::New();
		input_image_reader->SetFileName(cli_parser.get_input_image());

		try {
			input_image_reader->Update();
		} catch( itk::ExceptionObject &ex ) {
			LOG4CXX_FATAL(logger, "ITK is unable to load the image \"" << cli_parser.get_input_image() << "\" (" << ex.what() << ")");
			exit(-1);
		}

		input_image = input_image_reader->GetOutput();

		LOG4CXX_INFO(logger, "Features image loaded in " << elapsed_time(last_timestamp, get_timestamp()) << "s");
	}

	boost::shared_ptr< Classifier<fann_type> > pixelClassifier;

	if(cli_parser.get_classifier_training_images_classes().empty()) {
		/*
		 * Loading the classifier from a stored configuration.
		 */
		try {
			if(cli_parser.get_classifier_type() == CliParser::ANN) {
				pixelClassifier = boost::shared_ptr< Classifier<fann_type> >(new NeuralNetworkPixelClassifiers);
			} else if(cli_parser.get_classifier_type() == CliParser::SVM) {
				pixelClassifier = boost::shared_ptr< Classifier<fann_type> >(new SVMPixelClassifier);
			}

			pixelClassifier->load(cli_parser.get_classifier_config_dir());
		} catch (std::runtime_error &err) {
			LOG4CXX_FATAL(logger, err.what());
			exit(-1);
		}
	} else {
		/*
		 * Loading the training classes.
		 */
		last_timestamp = get_timestamp();
		LOG4CXX_INFO(logger, "Loading training classes");

		boost::shared_ptr< ClassificationDataset<double> > trainingDataset;

		try {
			if(cli_parser.get_classifier_training_images().empty()) {
				/*
				 * No image provided for training the classifier, so we
				 * wil use the one that will be segmented.
				 */
				LOG4CXX_INFO(logger, "Loading training classes from input image");

				trainingDataset = boost::shared_ptr< ClassificationDataset<double> >(new ClassificationDataset<double>(input_image, cli_parser.get_classifier_training_images_classes()));
			} else {
				/*
				 * A list of image is available to train the classifier.
				 */
				LOG4CXX_INFO(logger, "Loading training classes from a list of images");

				trainingDataset = boost::shared_ptr< ClassificationDataset<double> >(
						new ClassificationDataset<double>(cli_parser.get_classifier_training_images(), cli_parser.get_classifier_training_images_classes())
				);
			}

			trainingDataset->checkValid();

			LOG4CXX_INFO(logger, "Training classes loaded in " << elapsed_time(last_timestamp, get_timestamp()) << "s");
		} catch (ClassificationDatasetException & ex) {
			LOG4CXX_FATAL(logger, "Unable to load the training classes: " << ex.what());
			exit(-1);
		}

		if(cli_parser.get_classifier_type() == CliParser::ANN)
		{
			/*
			 * Loading the validation classes.
			 */
			last_timestamp = get_timestamp();
			LOG4CXX_INFO(logger, "Loading training classes");

			boost::shared_ptr< ClassificationDataset<double> > validationDataset;

			if(cli_parser.get_ann_validation_images().size() > 0) {
				try {
					/*
					 * A list of image is available to build the validation-set.
					 */
					LOG4CXX_INFO(logger, "Loading validation-classes from a list of images");

					validationDataset = boost::shared_ptr< ClassificationDataset<fann_type> >(
							new ClassificationDataset<double>(cli_parser.get_ann_validation_images(), cli_parser.get_ann_validation_images_classes())
					);

					if(validationDataset->getInputSize() != trainingDataset->getInputSize()) {
						LOG4CXX_FATAL(logger, "The validation set do not have the same number of components per pixel than the training set.");
						exit(-1);
					}

					validationDataset->checkValid();
				} catch (ClassificationDatasetException & ex) {
					LOG4CXX_FATAL(logger, "Unable to load the validation classes: " << ex.what());
					exit(-1);
				}
			} else if(cli_parser.get_ann_validation_training_ratio() > 0) {
				/*
				 * From the training set.
				 */
				LOG4CXX_INFO(logger, "Generating the validation-set from the training-set with a ratio of " << cli_parser.get_ann_validation_training_ratio());

				try {
					trainingDataset->shuffle();

					std::pair< boost::shared_ptr< ClassificationDataset<fann_type> >, boost::shared_ptr< ClassificationDataset<fann_type> > > new_sets =
						trainingDataset->split(cli_parser.get_ann_validation_training_ratio());

					trainingDataset = new_sets.second;
					validationDataset = new_sets.first;

				} catch (FannClassificationDatasetException &ex) {
					LOG4CXX_FATAL(logger, "Cannot generate validation-set from training-set: " << ex.what());
					exit(-1);
				}
			}

			LOG4CXX_INFO(logger, "Validation classes loaded in " << elapsed_time(last_timestamp, get_timestamp()) << "s");

			boost::shared_ptr< FannClassificationDataset > fannTrainingDatasets(new FannClassificationDataset(*trainingDataset)),
			                                               fannValidationDatasets(new FannClassificationDataset(*validationDataset));

			// TODO Supprimer le training set et le validation-set
			// They are not needed now that we have the FannClassificationDatasets
			trainingDataset.reset();
			validationDataset.reset();

			fannTrainingDatasets->shuffle();

			NeuralNetworkPixelClassifiers *ann = new NeuralNetworkPixelClassifiers();
			pixelClassifier = boost::shared_ptr< Classifier<fann_type> >(ann);

			/*
			 * Training of neural networks
			 */
			last_timestamp = get_timestamp();
			LOG4CXX_INFO(logger, "Training neural networks");

			ann->create_neural_networks(fannTrainingDatasets->getInputSize(), fannTrainingDatasets->getNumberOfDatasets(), cli_parser.get_ann_hidden_layers(), cli_parser.get_ann_learning_rate());
			ann->train_neural_networks(fannTrainingDatasets.get(), cli_parser.get_ann_max_epoch(), cli_parser.get_ann_mse_target(), fannValidationDatasets.get());

			LOG4CXX_INFO(logger, "Neural networks trained in " << elapsed_time(last_timestamp, get_timestamp()) << "s");
		} else if(cli_parser.get_classifier_type() == CliParser::SVM) {
			boost::shared_ptr< LibSVMClassificationDataset > svmTrainingDataset(new LibSVMClassificationDataset(*trainingDataset));
			trainingDataset.reset();

			SVMPixelClassifier *svm = new SVMPixelClassifier();
			pixelClassifier = boost::shared_ptr< Classifier<fann_type> >(svm);

			last_timestamp = get_timestamp();
			LOG4CXX_INFO(logger, "Training the SVM");
			if(!svm->train(svmTrainingDataset.get())) {
				exit(-1);
			}
			LOG4CXX_INFO(logger, "SVM trained in " << elapsed_time(last_timestamp, get_timestamp()) << "s");
		}

		/*
		 * Saving the classifier (if required).
		 */
		if(!cli_parser.get_classifier_config_dir().empty()) {
			bfs::path classifier_config_dir(cli_parser.get_classifier_config_dir());

			try {
				get_directory(classifier_config_dir);
				pixelClassifier->save(cli_parser.get_classifier_config_dir());
			} catch (std::runtime_error &err) {
				LOG4CXX_FATAL(logger, err.what());
				exit(-1);
			}
		}
	}

	/*
	 *
	 *
	 *
	 * The classifier is ready!
	 *
	 *
	 *
	 */

	if(cli_parser.get_input_image().empty())
		exit(0);

	if((cli_parser.get_classifier_type() == CliParser::ANN) && (pixelClassifier->getInputSize() != input_image->GetNumberOfComponentsPerPixel())) {
		LOG4CXX_FATAL(logger, "The classifier is configured to work on pixels with " << pixelClassifier->getInputSize() << " components per pixel, "
		                   << "but the input image has " << input_image->GetNumberOfComponentsPerPixel() << " components per pixel.");
		exit(-1);
	}

	bfs::path export_dir_path(cli_parser.get_export_dir());
	try {
		get_directory(export_dir_path);
	} catch (DirException &err) {
		LOG4CXX_FATAL(logger, err.what());
		exit(-1);
	}

	const unsigned int number_of_classifiers = pixelClassifier->getNumberOfClasses() == 2 ? 1 : pixelClassifier->getNumberOfClasses();

	/*
	 * Creation of the export folders for each class
	 */
	if(cli_parser.get_export_interval() > 0) {
		try {
			for(int i = 0; i < number_of_classifiers; ++i)
				get_directory(export_dir_path / pad(i));
		} catch (DirException &err) {
			LOG4CXX_FATAL(logger, err.what());
			exit(-1);
		}
	}

	if(cli_parser.get_debug()) {
		PluginLoaderTxt txtLoader;
		tlp::initTulipSoftware(&txtLoader);
	} else {
		tlp::initTulipSoftware(NULL);
	}

	/*
	 * Creation of the graph structure
	 */
	last_timestamp = get_timestamp();
	LOG4CXX_INFO(logger, "Generating graph structure");

	tlp::DataSet data;
	data.set("Width",               input_image->GetLargestPossibleRegion().GetSize()[0]);
	data.set("Height",              input_image->GetLargestPossibleRegion().GetSize()[1]);
	data.set("Depth",               input_image->GetLargestPossibleRegion().GetSize()[2]);
	data.set("Neighborhood radius", 1.0);
	data.set("Neighborhood type",   tlp::StringCollection("Circular"));
	data.set("Positionning",        true);
	data.set("Spacing",             1.0);

	tlp::Graph *graph = tlp::importGraph("Grid 3D", data);

	tlp::BooleanProperty *everything = graph->getLocalProperty<tlp::BooleanProperty>("everything");
	everything->setAllNodeValue(true);
	everything->setAllEdgeValue(true);

	tlp::BooleanProperty *roi = graph->getLocalProperty<tlp::BooleanProperty>("ROI");

	LOG4CXX_INFO(logger, "Importing region of interest");
	if(cli_parser.get_region_of_interest().empty()) {
		LOG4CXX_INFO(logger, "No region of interest specified");
		roi->setAllNodeValue(true);
	} else {
		tlp::DataSet data;
		std::string error;
		data.set("file::Image",          cli_parser.get_region_of_interest());
		data.set("Property",             roi);
		data.set("Convert to grayscale", false);

		if(!graph->applyAlgorithm("Load image data", error, &data)) {
			LOG4CXX_FATAL(logger, "Unable to import region of interest: " << error);
			return -1;
		}
		LOG4CXX_INFO(logger, "Region of interest successfully imported");
	}

	tlp::DoubleProperty *weight = graph->getLocalProperty<tlp::DoubleProperty>("Weight");
	weight->setAllEdgeValue(1);

	tlp::DoubleVectorProperty *features_property = graph->getLocalProperty<tlp::DoubleVectorProperty>("features");

	{ // Copy of the texture features into the graph
		tlp::Iterator<tlp::node> *itNodes = graph->getNodes();
		tlp::node u;

		const FeaturesImage::PixelType::ValueType *features_tmp;
		std::vector<double> features(input_image->GetNumberOfComponentsPerPixel());

		while(itNodes->hasNext())
		{
			u = itNodes->next();
			FeaturesImage::PixelType texture = input_image->GetPixel(input_image->ComputeIndex(u.id));

			features_tmp = texture.GetDataPointer();
			features.assign(features_tmp, features_tmp + input_image->GetNumberOfComponentsPerPixel());
			features_property->setNodeValue(u, features);
		}
		delete itNodes;
	}

	LOG4CXX_INFO(logger, "Graph structure generated in " << elapsed_time(last_timestamp, get_timestamp()) << "s");

	last_timestamp = get_timestamp();
	LOG4CXX_INFO(logger, "Classifying the pixels");

	std::vector< tlp::DoubleProperty* > regularized_segmentations(number_of_classifiers);

	std::vector< tlp::Graph* > subgraphs;
	std::vector< tlp::DoubleProperty* > f0_properties;
	std::vector< tlp::DoubleProperty* > seed_properties;

	/*
	 * Classification of the pixels
	 */
	for(unsigned int i = 0; i < number_of_classifiers; ++i)
	{
		tlp::Graph *subgraph = graph->addSubGraph(everything, pad(i));

		subgraphs.push_back(subgraph);
		seed_properties.push_back(subgraph->getLocalProperty<tlp::DoubleProperty>("Seed"));
		f0_properties.push_back(subgraph->getLocalProperty<tlp::DoubleProperty>("f0"));
	}

	{
		tlp::Iterator<tlp::node> *itNodes = graph->getNodes();
		tlp::node u;
		while(itNodes->hasNext())
		{
			u = itNodes->next();
			if(roi->getNodeValue(u))
			{
				std::vector<float> probabilities = pixelClassifier->classify(features_property->getNodeValue(u));

				for(unsigned int i = 0; i < number_of_classifiers; ++i)
				{
					f0_properties[i]->setNodeValue(u, probabilities[i]);
					seed_properties[i]->setNodeValue(u, probabilities[i]);
				}
			}
		}
		delete itNodes;
	}

	LOG4CXX_INFO(logger, "Pixels classified in " << elapsed_time(last_timestamp, get_timestamp()) << "s");

	for(unsigned int i = 0; i < number_of_classifiers; ++i)
	{
		LOG4CXX_INFO(logger, "Data classification done for image #" << i);

		/*****************************************************/
		/* Application of the graph regularisation algorithm */
		/*****************************************************/
		//LOG4CXX_INFO(logger, "Applying CV Regularization algorithm on image #" << i);
		LOG4CXX_INFO(logger, "Applying ROF Regularization algorithm on image #" << i);

		bfs::path export_dir = export_dir_path / pad(i);

		DoubleProperty* fn = subgraphs[i]->getLocalProperty< DoubleProperty >("fn");
		BooleanProperty* segmentation = subgraphs[i]->getLocalProperty< BooleanProperty >("viewSelection");

		DataSet data4;
		data4.set("seed",                  seed_properties[i]);
		data4.set("result",                fn);
		data4.set("segmentation result",   segmentation);
		data4.set("data",                  f0_properties[i]);
		data4.set("similarity measure",    weight);
		data4.set("number of iterations",  cli_parser.get_num_iter());
		data4.set("lambda",               cli_parser.get_lambda());
		data4.set("export interval",       cli_parser.get_export_interval());
		data4.set("dir::export directory", export_dir.native());

		LoggerPluginProgress pp("main.cv_ta");

		string error4;
		//bool reg_applied = subgraph->applyAlgorithm("ChanVese Regularization", error4, &data4, &pp);
		bool reg_applied = subgraphs[i]->applyAlgorithm("Rudin-Osher-Fatemi Regularization", error4, &data4, &pp);
		if(!reg_applied) {
			LOG4CXX_FATAL(logger, "Unable to apply the ROF Regularization algorithm: " << error4);
			return -1;
		}

		regularized_segmentations[i] = fn;

		LOG4CXX_INFO(logger, "Regularization done for image #" << i);
	}

	if(cli_parser.get_debug()) {
		bfs::path output_graph = export_dir_path / "graph.tlp";
	}

	ImageType::Pointer classification_image = ImageType::New();
	classification_image->SetRegions(input_image->GetLargestPossibleRegion());
	classification_image->Allocate();
	ImageType::IndexType index;

	int width, height, depth;
	unsigned int id;

	graph->getAttribute<int>("width", width);
	graph->getAttribute<int>("height", height);
	graph->getAttribute<int>("depth", depth);

	tlp::Iterator<tlp::node> *itNodes = graph->getNodes();
	tlp::node u;
	std::vector< double > values(number_of_classifiers);

	std::vector< double >::iterator max_it;
	unsigned int max_pos;

	while(itNodes->hasNext())
	{
		u = itNodes->next();
		id = u.id;
		index[0] =  id % width;
		id /= width;
		index[1] = id % height;
		id /= height;
		index[2] = id;

		if(roi->getNodeValue(u))
		{
			if(number_of_classifiers > 1)
			{
				for(unsigned int i = 0; i < number_of_classifiers; ++i)
				{
					values[i] = regularized_segmentations[i]->getNodeValue(u);
				}

				std::vector< size_t > ordered_indices = ordered(values, desc_comparator<double>(values));

				//if( (values[ordered_indices[0]] > 0.5) && ( (0.9 * values[ordered_indices[0]]) > values[ordered_indices[1]]) ) {
				/*
				if( values[ordered_indices[0]] > 0.5 ) {
					max_pos = ordered_indices[0] + 1;
				} else {
					max_pos = 0;
				}
				*/
				max_pos = ordered_indices[0] + 1;
			} else {
				max_pos = (regularized_segmentations[0]->getNodeValue(u) > 0.5 ? 1 : 2); // No rejected class
			}
		} else {
			max_pos = 0; // XXX Do we set it to 0 when we are supposed to ignore the pixel?
		}

		classification_image->SetPixel(index, max_pos);

		//copy( &values[0], &values[networks->size()], std::ostream_iterator< double >(std::cout, ", "));
		//std::cout << std::endl;
	}
	delete itNodes;

	bfs::path final_export_dir_path = export_dir_path / "final_export";

	try {
		get_directory(final_export_dir_path);
	} catch (DirException &err) {
		LOG4CXX_FATAL(logger, err.what());
		exit(-1);
	}

	{
		bfs::path classmap_export_dir_path = final_export_dir_path / "classmap";
		try {
			get_directory(classmap_export_dir_path);
		} catch (DirException &err) {
			LOG4CXX_FATAL(logger, err.what());
			exit(-1);
		}

		bfs::path export_dir_pattern = classmap_export_dir_path / "%06d.bmp";
		itk::NumericSeriesFileNames::Pointer outputNames = itk::NumericSeriesFileNames::New();
		outputNames->SetSeriesFormat(export_dir_pattern.native());
		outputNames->SetStartIndex(0);
		outputNames->SetEndIndex(depth - 1);

		typedef itk::ImageSeriesWriter< ImageType, itk::Image< unsigned char, 2 > > WriterType;
		WriterType::Pointer writer = WriterType::New();
		writer->SetInput(classification_image);
		writer->SetFileNames(outputNames->GetFileNames());
		writer->Update();
	}

	for(int i = 0; i <= number_of_classifiers; ++i)
	{
		bfs::path final_class_export_dir_path = final_export_dir_path / (i == 0 ? "rejected" : pad(i));

		try {
			get_directory(final_class_export_dir_path);
		} catch (DirException &err) {
			LOG4CXX_FATAL(logger, err.what());
			exit(-1);
		}

		bfs::path final_class_export_dir_pattern = final_class_export_dir_path / "%06d.bmp";
		itk::NumericSeriesFileNames::Pointer outputNames = itk::NumericSeriesFileNames::New();
		outputNames->SetSeriesFormat(final_class_export_dir_pattern.native());
		outputNames->SetStartIndex(0);
		outputNames->SetEndIndex(depth - 1);

		typedef itk::BinaryThresholdImageFilter< ImageType, ImageType > Thresholder;
		Thresholder::Pointer thresholder = Thresholder::New();
		thresholder->SetLowerThreshold(i);
		thresholder->SetUpperThreshold(i);
		thresholder->SetInput(classification_image);

		typedef itk::ImageSeriesWriter< ImageType, itk::Image< unsigned char, 2 > > WriterType;
		WriterType::Pointer writer = WriterType::New();
		writer->SetInput(thresholder->GetOutput());
		writer->SetFileNames(outputNames->GetFileNames());
		writer->Update();
	}

	delete graph;

	return 0;
}


#ifndef STAN_MATH_PRIM_MAT_PROB_LKJ_CORR_LOG_HPP
#define STAN_MATH_PRIM_MAT_PROB_LKJ_CORR_LOG_HPP

#include <stan/math/prim/meta.hpp>
#include <stan/math/prim/mat/fun/Eigen.hpp>
#include <stan/math/prim/mat/prob/lkj_corr_lpdf.hpp>
#include <boost/math/tools/promotion.hpp>

namespace stan {
namespace math {

/**
 * @deprecated use lkj_corr_lpdf
 */
template <bool propto, typename T_y, typename T_shape>
return_type_t<T_y, T_shape> lkj_corr_log(
    const Eigen::Matrix<T_y, Eigen::Dynamic, Eigen::Dynamic>& y,
    const T_shape& eta) {
  return lkj_corr_lpdf<propto, T_y, T_shape>(y, eta);
}

/**
 * @deprecated use lkj_corr_lpdf
 */
template <typename T_y, typename T_shape>
inline return_type_t<T_y, T_shape> lkj_corr_log(
    const Eigen::Matrix<T_y, Eigen::Dynamic, Eigen::Dynamic>& y,
    const T_shape& eta) {
  return lkj_corr_lpdf<T_y, T_shape>(y, eta);
}

}  // namespace math
}  // namespace stan
#endif


#include "exodus/mdex.h"

#include "exodus/errors.h"
#include "exodus/fees.h"
#include "exodus/log.h"
#include "exodus/exodus.h"
#include "exodus/rules.h"
#include "exodus/sp.h"
#include "exodus/tx.h"
#include "exodus/uint256_extensions.h"

#include "arith_uint256.h"
#include "chain.h"
#include "main.h"
#include "tinyformat.h"
#include "uint256.h"

#include <univalue.h>

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

#include <openssl/sha.h>

#include <assert.h>
#include <stdint.h>

#include <fstream>
#include <limits>
#include <map>
#include <set>
#include <string>

typedef boost::multiprecision::cpp_dec_float_100 dec_float;
typedef boost::multiprecision::checked_int128_t int128_t;

using namespace exodus;

//! Number of digits of unit price
#define DISPLAY_PRECISION_LEN  50

//! Global map for price and order data
md_PropertiesMap exodus::metadex;

md_PricesMap* exodus::get_Prices(uint32_t prop)
{
    md_PropertiesMap::iterator it = metadex.find(prop);

    if (it != metadex.end()) return &(it->second);

    return (md_PricesMap*) NULL;
}

md_Set* exodus::get_Indexes(md_PricesMap* p, rational_t price)
{
    md_PricesMap::iterator it = p->find(price);

    if (it != p->end()) return &(it->second);

    return (md_Set*) NULL;
}

enum MatchReturnType
{
    NOTHING = 0,
    TRADED = 1,
    TRADED_MOREINSELLER,
    TRADED_MOREINBUYER,
    ADDED,
    CANCELLED,
};

static const std::string getTradeReturnType(MatchReturnType ret)
{
    switch (ret) {
        case NOTHING: return "NOTHING";
        case TRADED: return "TRADED";
        case TRADED_MOREINSELLER: return "TRADED_MOREINSELLER";
        case TRADED_MOREINBUYER: return "TRADED_MOREINBUYER";
        case ADDED: return "ADDED";
        case CANCELLED: return "CANCELLED";
        default: return "* unknown *";
    }
}

// Used by rangeInt64, xToInt64
static bool rangeInt64(const int128_t& value)
{
    return (std::numeric_limits<int64_t>::min() <= value && value <= std::numeric_limits<int64_t>::max());
}

// Used by xToString
static bool rangeInt64(const rational_t& value)
{
    return (rangeInt64(value.numerator()) && rangeInt64(value.denominator()));
}

// Used by CMPMetaDEx::displayUnitPrice
static int64_t xToRoundUpInt64(const rational_t& value)
{
    // for integer rounding up: ceil(num / denom) => 1 + (num - 1) / denom
    int128_t result = int128_t(1) + (value.numerator() - int128_t(1)) / value.denominator();

    assert(rangeInt64(result));

    return result.convert_to<int64_t>();
}

std::string xToString(const dec_float& value)
{
    return value.str(DISPLAY_PRECISION_LEN, std::ios_base::fixed);
}

std::string xToString(const int128_t& value)
{
    return strprintf("%s", boost::lexical_cast<std::string>(value));
}

std::string xToString(const rational_t& value)
{
    if (rangeInt64(value)) {
        int64_t num = value.numerator().convert_to<int64_t>();
        int64_t denom = value.denominator().convert_to<int64_t>();
        dec_float x = dec_float(num) / dec_float(denom);
        return xToString(x);
    } else {
        return strprintf("%s / %s", xToString(value.numerator()), xToString(value.denominator()));
    }
}

// find the best match on the market
// NOTE: sometimes I refer to the older order as seller & the newer order as buyer, in this trade
// INPUT: property, desprop, desprice = of the new order being inserted; the new object being processed
// RETURN:
static MatchReturnType x_Trade(CMPMetaDEx* const pnew)
{
    const uint32_t propertyForSale = pnew->getProperty();
    const uint32_t propertyDesired = pnew->getDesProperty();
    MatchReturnType NewReturn = NOTHING;
    bool bBuyerSatisfied = false;

    if (exodus_debug_metadex1) PrintToLog("%s(%s: prop=%d, desprop=%d, desprice= %s);newo: %s\n",
        __FUNCTION__, pnew->getAddr(), propertyForSale, propertyDesired, xToString(pnew->inversePrice()), pnew->ToString());

    md_PricesMap* const ppriceMap = get_Prices(propertyDesired);

    // nothing for the desired property exists in the market, sorry!
    if (!ppriceMap) {
        PrintToLog("%s()=%d:%s NOT FOUND ON THE MARKET\n", __FUNCTION__, NewReturn, getTradeReturnType(NewReturn));
        return NewReturn;
    }

    // within the desired property map (given one property) iterate over the items looking at prices
    for (md_PricesMap::iterator priceIt = ppriceMap->begin(); priceIt != ppriceMap->end(); ++priceIt) { // check all prices
        const rational_t sellersPrice = priceIt->first;

        if (exodus_debug_metadex2) PrintToLog("comparing prices: desprice %s needs to be GREATER THAN OR EQUAL TO %s\n",
            xToString(pnew->inversePrice()), xToString(sellersPrice));

        // Is the desired price check satisfied? The buyer's inverse price must be larger than that of the seller.
        if (pnew->inversePrice() < sellersPrice) {
            continue;
        }

        md_Set* const pofferSet = &(priceIt->second);

        // at good (single) price level and property iterate over offers looking at all parameters to find the match
        md_Set::iterator offerIt = pofferSet->begin();
        while (offerIt != pofferSet->end()) { // specific price, check all properties
            const CMPMetaDEx* const pold = &(*offerIt);
            assert(pold->unitPrice() == sellersPrice);

            if (exodus_debug_metadex1) PrintToLog("Looking at existing: %s (its prop= %d, its des prop= %d) = %s\n",
                xToString(sellersPrice), pold->getProperty(), pold->getDesProperty(), pold->ToString());

            // does the desired property match?
            if (pold->getDesProperty() != propertyForSale) {
                ++offerIt;
                continue;
            }

            if (exodus_debug_metadex1) PrintToLog("MATCH FOUND, Trade: %s = %s\n", xToString(sellersPrice), pold->ToString());

            // match found, execute trade now!
            const int64_t seller_amountForSale = pold->getAmountRemaining();
            const int64_t buyer_amountOffered = pnew->getAmountRemaining();

            if (exodus_debug_metadex1) PrintToLog("$$ trading using price: %s; seller: forsale=%d, desired=%d, remaining=%d, buyer amount offered=%d\n",
                xToString(sellersPrice), pold->getAmountForSale(), pold->getAmountDesired(), pold->getAmountRemaining(), pnew->getAmountRemaining());
            if (exodus_debug_metadex1) PrintToLog("$$ old: %s\n", pold->ToString());
            if (exodus_debug_metadex1) PrintToLog("$$ new: %s\n", pnew->ToString());

            ///////////////////////////

            // preconditions
            assert(0 < pold->getAmountRemaining());
            assert(0 < pnew->getAmountRemaining());
            assert(pnew->getProperty() != pnew->getDesProperty());
            assert(pnew->getProperty() == pold->getDesProperty());
            assert(pold->getProperty() == pnew->getDesProperty());
            assert(pold->unitPrice() <= pnew->inversePrice());
            assert(pnew->unitPrice() <= pold->inversePrice());

            ///////////////////////////

            // First determine how many representable (indivisible) tokens Alice can
            // purchase from Bob, using Bob's unit price
            // This implies rounding down, since rounding up is impossible, and would
            // require more tokens than Alice has
            arith_uint256 iCouldBuy = (ConvertTo256(pnew->getAmountRemaining()) * ConvertTo256(pold->getAmountForSale())) / ConvertTo256(pold->getAmountDesired());

            int64_t nCouldBuy = 0;
            if (iCouldBuy < ConvertTo256(pold->getAmountRemaining())) {
                nCouldBuy = ConvertTo64(iCouldBuy);
            } else {
                nCouldBuy = pold->getAmountRemaining();
            }

            if (nCouldBuy == 0) {
                if (exodus_debug_metadex1) PrintToLog(
                        "-- buyer has not enough tokens for sale to purchase one unit!\n");
                ++offerIt;
                continue;
            }

            // If the amount Alice would have to pay to buy Bob's tokens at his price
            // is fractional, always round UP the amount Alice has to pay
            // This will always be better for Bob. Rounding in the other direction
            // will always be impossible, because ot would violate Bob's accepted price
            arith_uint256 iWouldPay = DivideAndRoundUp((ConvertTo256(nCouldBuy) * ConvertTo256(pold->getAmountDesired())), ConvertTo256(pold->getAmountForSale()));
            int64_t nWouldPay = ConvertTo64(iWouldPay);

            // If the resulting adjusted unit price is higher than Alice' price, the
            // orders shall not execute, and no representable fill is made
            const rational_t xEffectivePrice(nWouldPay, nCouldBuy);

            if (xEffectivePrice > pnew->inversePrice()) {
                if (exodus_debug_metadex1) PrintToLog(
                        "-- effective price is too expensive: %s\n", xToString(xEffectivePrice));
                ++offerIt;
                continue;
            }

            const int64_t buyer_amountGot = nCouldBuy;
            const int64_t seller_amountGot = nWouldPay;
            const int64_t buyer_amountLeft = pnew->getAmountRemaining() - seller_amountGot;
            const int64_t seller_amountLeft = pold->getAmountRemaining() - buyer_amountGot;

            if (exodus_debug_metadex1) PrintToLog("$$ buyer_got= %d, seller_got= %d, seller_left_for_sale= %d, buyer_still_for_sale= %d\n",
                buyer_amountGot, seller_amountGot, seller_amountLeft, buyer_amountLeft);

            ///////////////////////////

            // postconditions
            assert(xEffectivePrice >= pold->unitPrice());
            assert(xEffectivePrice <= pnew->inversePrice());
            assert(0 <= seller_amountLeft);
            assert(0 <= buyer_amountLeft);
            assert(seller_amountForSale == seller_amountLeft + buyer_amountGot);
            assert(buyer_amountOffered == buyer_amountLeft + seller_amountGot);

            ///////////////////////////

            int64_t buyer_amountGotAfterFee = buyer_amountGot;
            int64_t tradingFee = 0;

            // strip a 0.05% fee from non-EXODUS pairs if fees are activated
            if (IsFeatureActivated(FEATURE_FEES, pnew->getBlock())) {
                if (pold->getProperty() > EXODUS_PROPERTY_TEXODUS && pold->getDesProperty() > EXODUS_PROPERTY_TEXODUS) {
                    int64_t feeDivider = 2000; // 0.05%
                    tradingFee = buyer_amountGot / feeDivider;

                    // subtract the fee from the amount the seller will receive
                    buyer_amountGotAfterFee = buyer_amountGot - tradingFee;

                    // add the fee to the fee cache
                    p_feecache->AddFee(pnew->getDesProperty(), pnew->getBlock(), tradingFee);
                } else {
                    if (exodus_debug_fees) PrintToLog("Skipping fee reduction for trade match %s:%s as one of the properties is Omni\n", pold->getHash().GetHex(), pnew->getHash().GetHex());
                }
            }

            // transfer the payment property from buyer to seller
            assert(update_tally_map(pnew->getAddr(), pnew->getProperty(), -seller_amountGot, BALANCE));
            assert(update_tally_map(pold->getAddr(), pold->getDesProperty(), seller_amountGot, BALANCE));

            // transfer the market (the one being sold) property from seller to buyer
            assert(update_tally_map(pold->getAddr(), pold->getProperty(), -buyer_amountGot, METADEX_RESERVE));
            assert(update_tally_map(pnew->getAddr(), pnew->getDesProperty(), buyer_amountGotAfterFee, BALANCE));

            NewReturn = TRADED;

            CMPMetaDEx seller_replacement = *pold; // < can be moved into last if block
            seller_replacement.setAmountRemaining(seller_amountLeft, "seller_replacement");

            pnew->setAmountRemaining(buyer_amountLeft, "buyer");

            if (0 < buyer_amountLeft) {
                NewReturn = TRADED_MOREINBUYER;
            }

            if (0 == buyer_amountLeft) {
                bBuyerSatisfied = true;
            }

            if (0 < seller_amountLeft) {
                NewReturn = TRADED_MOREINSELLER;
            }

            if (exodus_debug_metadex1) PrintToLog("==== TRADED !!! %u=%s\n", NewReturn, getTradeReturnType(NewReturn));

            // record the trade in MPTradeList
            t_tradelistdb->recordMatchedTrade(pold->getHash(), pnew->getHash(), // < might just pass pold, pnew
                pold->getAddr(), pnew->getAddr(), pold->getDesProperty(), pnew->getDesProperty(), seller_amountGot, buyer_amountGotAfterFee, pnew->getBlock(), tradingFee);

            if (exodus_debug_metadex1) PrintToLog("++ erased old: %s\n", offerIt->ToString());
            // erase the old seller element
            pofferSet->erase(offerIt++);

            // insert the updated one in place of the old
            if (0 < seller_replacement.getAmountRemaining()) {
                PrintToLog("++ inserting seller_replacement: %s\n", seller_replacement.ToString());
                pofferSet->insert(seller_replacement);
            }

            if (bBuyerSatisfied) {
                assert(buyer_amountLeft == 0);
                break;
            }
        } // specific price, check all properties

        if (bBuyerSatisfied) break;
    } // check all prices

    PrintToLog("%s()=%d:%s\n", __FUNCTION__, NewReturn, getTradeReturnType(NewReturn));

    return NewReturn;
}

/**
 * Used for display of unit prices to 8 decimal places at UI layer.
 *
 * Automatically returns unit or inverse price as needed.
 */
std::string CMPMetaDEx::displayUnitPrice() const
{
     rational_t tmpDisplayPrice;
     if (getDesProperty() == EXODUS_PROPERTY_EXODUS || getDesProperty() == EXODUS_PROPERTY_TEXODUS) {
         tmpDisplayPrice = unitPrice();
         if (isPropertyDivisible(getProperty())) tmpDisplayPrice = tmpDisplayPrice * COIN;
     } else {
         tmpDisplayPrice = inversePrice();
         if (isPropertyDivisible(getDesProperty())) tmpDisplayPrice = tmpDisplayPrice * COIN;
     }

     // offers with unit prices under 0.00000001 will be excluded from UI layer - TODO: find a better way to identify sub 0.00000001 prices
     std::string tmpDisplayPriceStr = xToString(tmpDisplayPrice);
     if (!tmpDisplayPriceStr.empty()) { if (tmpDisplayPriceStr.substr(0,1) == "0") return "0.00000000"; }

     // we must always round up here - for example if the actual price required is 0.3333333344444
     // round: 0.33333333 - price is insufficient and thus won't result in a trade
     // round: 0.33333334 - price will be sufficient to result in a trade
     std::string displayValue = FormatDivisibleMP(xToRoundUpInt64(tmpDisplayPrice));
     return displayValue;
}

/**
 * Used for display of unit prices with 50 decimal places at RPC layer.
 *
 * Note: unit price is no longer always shown in EXODUS and/or inverted
 */
std::string CMPMetaDEx::displayFullUnitPrice() const
{
    rational_t tempUnitPrice = unitPrice();

    /* Matching types require no action (divisible/divisible or indivisible/indivisible)
       Non-matching types require adjustment for display purposes
           divisible/indivisible   : *COIN
           indivisible/divisible   : /COIN
    */
    if ( isPropertyDivisible(getProperty()) && !isPropertyDivisible(getDesProperty()) ) tempUnitPrice = tempUnitPrice*COIN;
    if ( !isPropertyDivisible(getProperty()) && isPropertyDivisible(getDesProperty()) ) tempUnitPrice = tempUnitPrice/COIN;

    std::string unitPriceStr = xToString(tempUnitPrice);
    return unitPriceStr;
}

rational_t CMPMetaDEx::unitPrice() const
{
    rational_t effectivePrice;
    if (amount_forsale) effectivePrice = rational_t(amount_desired, amount_forsale);
    return effectivePrice;
}

rational_t CMPMetaDEx::inversePrice() const
{
    rational_t inversePrice;
    if (amount_desired) inversePrice = rational_t(amount_forsale, amount_desired);
    return inversePrice;
}

int64_t CMPMetaDEx::getAmountToFill() const
{
    // round up to ensure that the amount we present will actually result in buying all available tokens
    arith_uint256 iAmountNeededToFill = DivideAndRoundUp((ConvertTo256(amount_remaining) * ConvertTo256(amount_desired)), ConvertTo256(amount_forsale));
    int64_t nAmountNeededToFill = ConvertTo64(iAmountNeededToFill);
    return nAmountNeededToFill;
}

int64_t CMPMetaDEx::getBlockTime() const
{
    CBlockIndex* pblockindex = chainActive[block];
    return pblockindex->GetBlockTime();
}

void CMPMetaDEx::setAmountRemaining(int64_t amount, const std::string& label)
{
    amount_remaining = amount;
    PrintToLog("update remaining amount still up for sale (%ld %s):%s\n", amount, label, ToString());
}

std::string CMPMetaDEx::ToString() const
{
    return strprintf("%s:%34s in %d/%03u, txid: %s , trade #%u %s for #%u %s",
        xToString(unitPrice()), addr, block, idx, txid.ToString().substr(0, 10),
        property, FormatMP(property, amount_forsale), desired_property, FormatMP(desired_property, amount_desired));
}

void CMPMetaDEx::saveOffer(std::ofstream& file, SHA256_CTX* shaCtx) const
{
    std::string lineOut = strprintf("%s,%d,%d,%d,%d,%d,%d,%d,%s,%d",
        addr,
        block,
        amount_forsale,
        property,
        amount_desired,
        desired_property,
        subaction,
        idx,
        txid.ToString(),
        amount_remaining
    );

    // add the line to the hash
    SHA256_Update(shaCtx, lineOut.c_str(), lineOut.length());

    // write the line
    file << lineOut << std::endl;
}

bool MetaDEx_compare::operator()(const CMPMetaDEx &lhs, const CMPMetaDEx &rhs) const
{
    if (lhs.getBlock() == rhs.getBlock()) return lhs.getIdx() < rhs.getIdx();
    else return lhs.getBlock() < rhs.getBlock();
}

bool exodus::MetaDEx_INSERT(const CMPMetaDEx& objMetaDEx)
{
    // Create an empty price map (to use in case price map for this property does not already exist)
    md_PricesMap temp_prices;
    // Attempt to obtain the price map for the property
    md_PricesMap *p_prices = get_Prices(objMetaDEx.getProperty());

    // Create an empty set of metadex objects (to use in case no set currently exists at this price)
    md_Set temp_indexes;
    md_Set *p_indexes = NULL;

    // Prepare for return code
    std::pair <md_Set::iterator, bool> ret;

    // Attempt to obtain a set of metadex objects for this price from the price map
    if (p_prices) p_indexes = get_Indexes(p_prices, objMetaDEx.unitPrice());
    // See if the set was populated, if not no set exists at this price level, use the empty set that we created earlier
    if (!p_indexes) p_indexes = &temp_indexes;

    // Attempt to insert the metadex object into the set
    ret = p_indexes->insert(objMetaDEx);
    if (false == ret.second) return false;

    // If a prices map did not exist for this property, set p_prices to the temp empty price map
    if (!p_prices) p_prices = &temp_prices;

    // Update the prices map with the new set at this price
    (*p_prices)[objMetaDEx.unitPrice()] = *p_indexes;

    // Set the metadex map for the property to the updated (or new if it didn't exist) price map
    metadex[objMetaDEx.getProperty()] = *p_prices;

    return true;
}

// pretty much directly linked to the ADD TX21 command off the wire
int exodus::MetaDEx_ADD(const std::string& sender_addr, uint32_t prop, int64_t amount, int block, uint32_t property_desired, int64_t amount_desired, const uint256& txid, unsigned int idx)
{
    int rc = METADEX_ERROR -1;

    // Create a MetaDEx object from paremeters
    CMPMetaDEx new_mdex(sender_addr, block, prop, amount, property_desired, amount_desired, txid, idx, CMPTransaction::ADD);
    if (exodus_debug_metadex1) PrintToLog("%s(); buyer obj: %s\n", __FUNCTION__, new_mdex.ToString());

    // Ensure this is not a badly priced trade (for example due to zero amounts)
    if (0 >= new_mdex.unitPrice()) return METADEX_ERROR -66;

    // Match against existing trades, remainder of the order will be put into the order book
    if (exodus_debug_metadex3) MetaDEx_debug_print();
    x_Trade(&new_mdex);
    if (exodus_debug_metadex3) MetaDEx_debug_print();

    // Insert the remaining order into the MetaDEx maps
    if (0 < new_mdex.getAmountRemaining()) { //switch to getAmountRemaining() when ready
        if (!MetaDEx_INSERT(new_mdex)) {
            PrintToLog("%s() ERROR: ALREADY EXISTS, line %d, file: %s\n", __FUNCTION__, __LINE__, __FILE__);
            return METADEX_ERROR -70;
        } else {
            // move tokens into reserve
            assert(update_tally_map(sender_addr, prop, -new_mdex.getAmountRemaining(), BALANCE));
            assert(update_tally_map(sender_addr, prop, new_mdex.getAmountRemaining(), METADEX_RESERVE));

            if (exodus_debug_metadex1) PrintToLog("==== INSERTED: %s= %s\n", xToString(new_mdex.unitPrice()), new_mdex.ToString());
            if (exodus_debug_metadex3) MetaDEx_debug_print();
        }
    }

    rc = 0;
    return rc;
}

int exodus::MetaDEx_CANCEL_AT_PRICE(const uint256& txid, unsigned int block, const std::string& sender_addr, uint32_t prop, int64_t amount, uint32_t property_desired, int64_t amount_desired)
{
    int rc = METADEX_ERROR -20;
    CMPMetaDEx mdex(sender_addr, 0, prop, amount, property_desired, amount_desired, uint256(), 0, CMPTransaction::CANCEL_AT_PRICE);
    md_PricesMap* prices = get_Prices(prop);
    const CMPMetaDEx* p_mdex = NULL;

    if (exodus_debug_metadex1) PrintToLog("%s():%s\n", __FUNCTION__, mdex.ToString());

    if (exodus_debug_metadex2) MetaDEx_debug_print();

    if (!prices) {
        PrintToLog("%s() NOTHING FOUND for %s\n", __FUNCTION__, mdex.ToString());
        return rc -1;
    }

    // within the desired property map (given one property) iterate over the items
    for (md_PricesMap::iterator my_it = prices->begin(); my_it != prices->end(); ++my_it) {
        rational_t sellers_price = my_it->first;

        if (mdex.unitPrice() != sellers_price) continue;

        md_Set* indexes = &(my_it->second);

        for (md_Set::iterator iitt = indexes->begin(); iitt != indexes->end();) {
            p_mdex = &(*iitt);

            if (exodus_debug_metadex3) PrintToLog("%s(): %s\n", __FUNCTION__, p_mdex->ToString());

            if ((p_mdex->getDesProperty() != property_desired) || (p_mdex->getAddr() != sender_addr)) {
                ++iitt;
                continue;
            }

            rc = 0;
            PrintToLog("%s(): REMOVING %s\n", __FUNCTION__, p_mdex->ToString());

            // move from reserve to main
            assert(update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), -p_mdex->getAmountRemaining(), METADEX_RESERVE));
            assert(update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), p_mdex->getAmountRemaining(), BALANCE));

            // record the cancellation
            bool bValid = true;
            p_txlistdb->recordMetaDExCancelTX(txid, p_mdex->getHash(), bValid, block, p_mdex->getProperty(), p_mdex->getAmountRemaining());

            indexes->erase(iitt++);
        }
    }

    if (exodus_debug_metadex2) MetaDEx_debug_print();

    return rc;
}

int exodus::MetaDEx_CANCEL_ALL_FOR_PAIR(const uint256& txid, unsigned int block, const std::string& sender_addr, uint32_t prop, uint32_t property_desired)
{
    int rc = METADEX_ERROR -30;
    md_PricesMap* prices = get_Prices(prop);
    const CMPMetaDEx* p_mdex = NULL;

    PrintToLog("%s(%d,%d)\n", __FUNCTION__, prop, property_desired);

    if (exodus_debug_metadex3) MetaDEx_debug_print();

    if (!prices) {
        PrintToLog("%s() NOTHING FOUND\n", __FUNCTION__);
        return rc -1;
    }

    // within the desired property map (given one property) iterate over the items
    for (md_PricesMap::iterator my_it = prices->begin(); my_it != prices->end(); ++my_it) {
        md_Set* indexes = &(my_it->second);

        for (md_Set::iterator iitt = indexes->begin(); iitt != indexes->end();) {
            p_mdex = &(*iitt);

            if (exodus_debug_metadex3) PrintToLog("%s(): %s\n", __FUNCTION__, p_mdex->ToString());

            if ((p_mdex->getDesProperty() != property_desired) || (p_mdex->getAddr() != sender_addr)) {
                ++iitt;
                continue;
            }

            rc = 0;
            PrintToLog("%s(): REMOVING %s\n", __FUNCTION__, p_mdex->ToString());

            // move from reserve to main
            assert(update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), -p_mdex->getAmountRemaining(), METADEX_RESERVE));
            assert(update_tally_map(p_mdex->getAddr(), p_mdex->getProperty(), p_mdex->getAmountRemaining(), BALANCE));

            // record the cancellation
            bool bValid = true;
            p_txlistdb->recordMetaDExCancelTX(txid, p_mdex->getHash(), bValid, block, p_mdex->getProperty(), p_mdex->getAmountRemaining());

            indexes->erase(iitt++);
        }
    }

    if (exodus_debug_metadex3) MetaDEx_debug_print();

    return rc;
}

/**
 * Scans the orderbook and remove everything for an address.
 */
int exodus::MetaDEx_CANCEL_EVERYTHING(const uint256& txid, unsigned int block, const std::string& sender_addr, unsigned char ecosystem)
{
    int rc = METADEX_ERROR -40;

    PrintToLog("%s()\n", __FUNCTION__);

    if (exodus_debug_metadex2) MetaDEx_debug_print();

    PrintToLog("<<<<<<\n");

    for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) {
        unsigned int prop = my_it->first;

        // skip property, if it is not in the expected ecosystem
        if (isMainEcosystemProperty(ecosystem) && !isMainEcosystemProperty(prop)) continue;
        if (isTestEcosystemProperty(ecosystem) && !isTestEcosystemProperty(prop)) continue;

        PrintToLog(" ## property: %u\n", prop);
        md_PricesMap& prices = my_it->second;

        for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) {
            rational_t price = it->first;
            md_Set& indexes = it->second;

            PrintToLog("  # Price Level: %s\n", xToString(price));

            for (md_Set::iterator it = indexes.begin(); it != indexes.end();) {
                PrintToLog("%s= %s\n", xToString(price), it->ToString());

                if (it->getAddr() != sender_addr) {
                    ++it;
                    continue;
                }

                rc = 0;
                PrintToLog("%s(): REMOVING %s\n", __FUNCTION__, it->ToString());

                // move from reserve to balance
                assert(update_tally_map(it->getAddr(), it->getProperty(), -it->getAmountRemaining(), METADEX_RESERVE));
                assert(update_tally_map(it->getAddr(), it->getProperty(), it->getAmountRemaining(), BALANCE));

                // record the cancellation
                bool bValid = true;
                p_txlistdb->recordMetaDExCancelTX(txid, it->getHash(), bValid, block, it->getProperty(), it->getAmountRemaining());

                indexes.erase(it++);
            }
        }
    }
    PrintToLog(">>>>>>\n");

    if (exodus_debug_metadex2) MetaDEx_debug_print();

    return rc;
}

/**
 * Scans the orderbook and removes every all-pair order
 */
int exodus::MetaDEx_SHUTDOWN_ALLPAIR()
{
    int rc = 0;
    PrintToLog("%s()\n", __FUNCTION__);
    for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) {
        md_PricesMap& prices = my_it->second;
        for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) {
            md_Set& indexes = it->second;
            for (md_Set::iterator it = indexes.begin(); it != indexes.end();) {
                if (it->getDesProperty() > EXODUS_PROPERTY_TEXODUS && it->getProperty() > EXODUS_PROPERTY_TEXODUS) { // no EXODUS/TEXODUS side to the trade
                    PrintToLog("%s(): REMOVING %s\n", __FUNCTION__, it->ToString());
                    // move from reserve to balance
                    assert(update_tally_map(it->getAddr(), it->getProperty(), -it->getAmountRemaining(), METADEX_RESERVE));
                    assert(update_tally_map(it->getAddr(), it->getProperty(), it->getAmountRemaining(), BALANCE));
                    indexes.erase(it++);
                }
            }
        }
    }
    return rc;
}

/**
 * Scans the orderbook and removes every order
 */
int exodus::MetaDEx_SHUTDOWN()
{
    int rc = 0;
    PrintToLog("%s()\n", __FUNCTION__);
    for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) {
        md_PricesMap& prices = my_it->second;
        for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) {
            md_Set& indexes = it->second;
            for (md_Set::iterator it = indexes.begin(); it != indexes.end();) {
                PrintToLog("%s(): REMOVING %s\n", __FUNCTION__, it->ToString());
                // move from reserve to balance
                assert(update_tally_map(it->getAddr(), it->getProperty(), -it->getAmountRemaining(), METADEX_RESERVE));
                assert(update_tally_map(it->getAddr(), it->getProperty(), it->getAmountRemaining(), BALANCE));
                indexes.erase(it++);
            }
        }
    }
    return rc;
}

// searches the metadex maps to see if a trade is still open
// allows search to be optimized if propertyIdForSale is specified
bool exodus::MetaDEx_isOpen(const uint256& txid, uint32_t propertyIdForSale)
{
    for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) {
        if (propertyIdForSale != 0 && propertyIdForSale != my_it->first) continue;
        md_PricesMap & prices = my_it->second;
        for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) {
            md_Set & indexes = (it->second);
            for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) {
                CMPMetaDEx obj = *it;
                if( obj.getHash().GetHex() == txid.GetHex() ) return true;
            }
        }
    }
    return false;
}

/**
 * Returns a string describing the status of a trade
 *
 */
std::string exodus::MetaDEx_getStatusText(int tradeStatus)
{
    switch (tradeStatus) {
        case TRADE_OPEN: return "open";
        case TRADE_OPEN_PART_FILLED: return "open part filled";
        case TRADE_FILLED: return "filled";
        case TRADE_CANCELLED: return "cancelled";
        case TRADE_CANCELLED_PART_FILLED: return "cancelled part filled";
        case TRADE_INVALID: return "trade invalid";
        default: return "unknown";
    }
}

/**
 * Returns the status of a MetaDEx trade
 *
 */
int exodus::MetaDEx_getStatus(const uint256& txid, uint32_t propertyIdForSale, int64_t amountForSale, int64_t totalSold)
{
    // NOTE: If the calling code is already aware of the total amount sold, pass the value in to this function to avoid duplication of
    //       work.  If the calling code doesn't know the amount, leave default (-1) and we will calculate it from levelDB lookups.
    if (totalSold == -1) {
        UniValue tradeArray(UniValue::VARR);
        int64_t totalReceived;
        t_tradelistdb->getMatchingTrades(txid, propertyIdForSale, tradeArray, totalSold, totalReceived);
    }

    // Return a "trade invalid" status if the trade was invalidated at parsing/interpretation (eg insufficient funds)
    if (!getValidMPTX(txid)) return TRADE_INVALID;

    // Calculate and return the status of the trade via the amount sold and open/closed attributes.
    if (MetaDEx_isOpen(txid, propertyIdForSale)) {
        if (totalSold == 0) {
            return TRADE_OPEN;
        } else {
            return TRADE_OPEN_PART_FILLED;
        }
    } else {
        if (totalSold == 0) {
            return TRADE_CANCELLED;
        } else if (totalSold < amountForSale) {
            return TRADE_CANCELLED_PART_FILLED;
        } else {
            return TRADE_FILLED;
        }
    }
}

void exodus::MetaDEx_debug_print(bool bShowPriceLevel, bool bDisplay)
{
    PrintToLog("<<<\n");
    for (md_PropertiesMap::iterator my_it = metadex.begin(); my_it != metadex.end(); ++my_it) {
        uint32_t prop = my_it->first;

        PrintToLog(" ## property: %u\n", prop);
        md_PricesMap& prices = my_it->second;

        for (md_PricesMap::iterator it = prices.begin(); it != prices.end(); ++it) {
            rational_t price = it->first;
            md_Set& indexes = it->second;

            if (bShowPriceLevel) PrintToLog("  # Price Level: %s\n", xToString(price));

            for (md_Set::iterator it = indexes.begin(); it != indexes.end(); ++it) {
                const CMPMetaDEx& obj = *it;

                if (bDisplay) PrintToConsole("%s= %s\n", xToString(price), obj.ToString());
                else PrintToLog("%s= %s\n", xToString(price), obj.ToString());
            }
        }
    }
    PrintToLog(">>>\n");
}

/**
 * Locates a trade in the MetaDEx maps via txid and returns the trade object
 *
 */
const CMPMetaDEx* exodus::MetaDEx_RetrieveTrade(const uint256& txid)
{
    for (md_PropertiesMap::iterator propIter = metadex.begin(); propIter != metadex.end(); ++propIter) {
        md_PricesMap & prices = propIter->second;
        for (md_PricesMap::iterator pricesIter = prices.begin(); pricesIter != prices.end(); ++pricesIter) {
            md_Set & indexes = pricesIter->second;
            for (md_Set::iterator tradesIter = indexes.begin(); tradesIter != indexes.end(); ++tradesIter) {
                if (txid == (*tradesIter).getHash()) return &(*tradesIter);
            }
        }
    }
    return (CMPMetaDEx*) NULL;
}


#include <iostream>
#include <string>
#include <math.h>
#include <cstdlib>
#include <stdbool.h>
#include <vector>
//#include <boost/thread/thread.hpp>
#include <unistd.h>
#include <thread>
#include <time.h>
#include <fstream>
//#include <boost/system/error_code.hpp>
////////////////////////////////////////////////////////////////////////////////
// MAC: Comment in srandomdev(); comment out rand() (one occurrence)
// LINUX: Comment out srandomdev(); comment in ran()
///////////////////////////////////////
// TO RUN WITH C++11
// -Comment out all boost:: lines, commenting in the std:: version next to them.
// -Comment out boost in #include statements, and comment in #include <thread>

// TO RUN WITHOUT C++11
// -Install boost thread and system libraries
// -Comment in all places using boost::, while commenting out the adjacent std:: versions.
// -Comment in the two boost #include lines, and comment out the #include <thread> line.
///////////////////////////////////////////////////////////////////////////////

#define DIVISION 600 //number of clock pulses per quarter note; for header

//#include <unistd.h>
//#define SLEEP( milliseconds ) usleep( (unsigned long) (milliseconds * 1000.0) )

std::vector<int> factors;
std::vector<int> sorted_factors;
std::vector<int> dupes_out;
std::vector<int> to_run (13, 0);

int low;
int med;
int high;

int chords[3][7];

int this_key = 21;
int this_chord = 0;

void randomize(){
    //srandomdev();
    //srand (time(NULL));
    int number = random() % 100;
    //std::cout<<"Random number: "<<number<<"\n";
    int tenth = number/10;
    if(tenth<2){
        this_chord = 0;
    }
    else if(tenth==2 || tenth==3){
        this_chord = 1;
    }
    else if(tenth==4){
        this_chord = 2;
    }
    else if(tenth==5){
        this_chord = 3;
    }
    else if(tenth==6){
        //this_chord = 4;
        this_chord = 6;
    }
    else if(tenth==7 || tenth==8){
        this_chord = 5;
    }
    else{
        this_chord = 6;
    }
    //std::cout<<"This chord: "<<this_chord<<"\n";
    low = this_key+chords[0][this_chord];
    med = this_key+chords[1][this_chord];
    high = this_key+chords[2][this_chord];

}
void one(){//channel 11 in 0-index, 12 in 1-index
    std::fstream channel_12 ("ch12.csv", std::fstream::out | std::fstream::trunc);//These are one-indexed!

    //for A: use 33 or 45
    if(to_run[1]==1){

        int iterations = 0;
        int elapsed = 0;
        channel_12<<"11, 0, Start_track\n11, 0, Program_c, 11, "<<35<<"\n";
        //std::cout<<"in one\n";

        while(iterations<23){
            //std::cout<<"bass iterations: "<<iterations<<"\n";
            randomize();
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 0\n";
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<med<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
            elapsed += 200;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";

            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 80\n";
            elapsed += 400;
            channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<high<<", 0\n";

            iterations++;
        }
        low = this_key+12;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
        elapsed += 400;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
        elapsed += 400;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
        elapsed += 400;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
        elapsed += 400;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 80\n";
        elapsed += 1600;
        channel_12<<"11, "<<elapsed<<", Note_on_c, 11, "<<low<<", 0\n";

        channel_12<<"11, "<<elapsed<<", End_track\n";
    }
    channel_12.close();

}
void two(){//channel 1 in 0-index, 2 in 1-index
    std::fstream channel_3 ("ch3.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[2]==1){
        //std::cout<<"two should go...";
        int iterations = 0;
        int elapsed = 0;
        //elapsed_1.push_back(elapsed);
        channel_3<<"2, 0, Start_track\n2, 0, Program_c, 2, "<<11<<"\n";

        //channel_3<<"3, 0, Control_c, 2, 7, 89\n3, 0, Control_c, 2, 10, 64\n3, 0, Control_c, 2, 91, 70\n";

        //std::cout<<"in one\n";
        elapsed += 4800*2;
        int key = this_key;
        while(iterations<6){
            key = this_key;
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<36+key<<", 60\n";
            elapsed += 2000;
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<36+key<<", 0\n";
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<43+key<<", 60\n";
            elapsed += 400;
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<43+key<<", 0\n";
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<34+key<<", 60\n";
            elapsed += 50;
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<41+key<<", 60\n";
            elapsed += 50;
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<38+key<<", 60\n";
            elapsed += 7100;//2300+4800
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<34+key<<", 0\n";
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<41+key<<", 0\n";
            channel_3<<"2, "<<elapsed<<", Note_on_c, 2, "<<38+key<<", 0\n";

            iterations++;
        }
        channel_3<<"2, "<<elapsed<<", End_track\n";
    }
    channel_3.close();
}
void three(){
    std::fstream channel_4 ("ch4.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[3]==1){
        int iterations = 0;
        int elapsed = 0;
        channel_4<<"3, 0, Start_track\n3, 0, Program_c, 3, "<<50<<"\n";
        elapsed += 4800*3;
        int key = this_key;
        int chord = this_chord;
        while(iterations<8){
            key = this_key;
            chord = this_chord;
            channel_4<<"3, "<<elapsed<<", Note_on_c, 3, "<<24+key<<", 80\n";
            elapsed += 2400;
            channel_4<<"3, "<<elapsed<<", Note_on_c, 3, "<<24+key<<", 0\n";

            channel_4<<"3, "<<elapsed<<", Note_on_c, 3, "<<12+chords[2][chord]+key<<", 80\n";
            elapsed +=7200;
            channel_4<<"3, "<<elapsed<<", Note_on_c, 3, "<<12+chords[2][chord]+key<<", 0\n";

            iterations++;
        }
        channel_4<<"3, "<<elapsed<<", End_track\n";
    }
    channel_4.close();
}
void nine(){
    std::fstream channel_5 ("ch5.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[9]==1){
        int iterations = 0;
        int elapsed = 0;
        channel_5<<"4, 0, Start_track\n4, 0, Program_c, 4, "<<26<<"\n";
        elapsed += 4800*4;
        while(iterations<8){
            for(int a=0; a<4; a++){
                channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<59<<", 60\n";
                elapsed += 300;
                channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<59<<", 0\n";
            }
            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<55<<", 60\n";
            elapsed += 300;
            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<59<<", 60\n";
            elapsed += 300;
            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<55<<", 0\n";
            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<59<<", 0\n";

            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<62<<", 60\n";
            elapsed += 600;
            channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<62<<", 0\n";

            for(int b=0; b<8; b++){
                channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<55<<", 60\n";
                elapsed += 300;
                channel_5<<"4, "<<elapsed<<", Note_on_c, 4, "<<55<<", 0\n";
            }
            elapsed += 300;

            iterations++;
        }
        channel_5<<"4, "<<elapsed<<", End_track\n";
    }
    channel_5.close();
}
void five(){
    std::fstream channel_6 ("ch6.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[5]==1){
        int elapsed = 0;
        int iterations = 0;
        channel_6<<"5, 0, Start_track\n5, 0, Program_c, 5, "<<77<<"\n";
        elapsed += 4800*5;
        int key = this_key;
        int chord = this_chord;
        int value, new_value;
        while(iterations<8){
            value = random() % 3;
            new_value = value+1;
            if(new_value>3){
                new_value=0;
            }
            key = this_key;
            chord = this_chord;
            channel_6<<"5, "<<elapsed<<", Note_on_c, 5, "<<key+chords[value][chord]+12<<", 80\n";
            channel_6<<"5, "<<elapsed<<", Note_on_c, 5, "<<key+chords[new_value][chord]+12<<", 80\n";
            int interval = random () % 3;
            interval = (interval+1)*400;
            elapsed += interval;

            channel_6<<"5, "<<elapsed<<", Note_on_c, 5, "<<key+chords[value][chord]+12<<", 0\n";
            channel_6<<"5, "<<elapsed<<", Note_on_c, 5, "<<key+chords[new_value][chord]+12<<", 0\n";
            elapsed += 3600;

            iterations++;
        }
        channel_6<<"5, "<<elapsed<<", End_track\n";
    }
    channel_6.close();
}
void twelve(){
    std::fstream channel_7 ("ch7.csv", std::fstream::out | std::fstream::trunc);
    //a lot like 5; just vary the values longer. (gives a distribution determined by the chord)
    if(to_run[12]==1){
        int iterations = 0;
        int elapsed = 0;
        channel_7<<"6, 0, Start_track\n6, 0, Program_c, 6, "<<61<<"\n";
        elapsed += 4800*3;
        int key = this_key;
        int chord = this_chord;
        while(iterations<8){
            int value = random() % 3;
            int new_value = (value+1)%3;
            //if(new_value==4){new_value=0;}
            key = this_key;
            chord = this_chord;
            channel_7<<"6, "<<elapsed<<", Note_on_c, 6, "<<key+chords[value][chord]+12<<", 80\n";
            elapsed += 300;
            channel_7<<"6, "<<elapsed<<", Note_on_c, 6, "<<key+chords[new_value][chord]+12<<", 80\n";
            int interval = random () % 3;
            interval = (interval+1)*200;
            elapsed += interval;

            channel_7<<"6, "<<elapsed<<", Note_on_c, 6, "<<key+chords[value][chord]+12<<", 0\n";
            elapsed += (interval*3);
            channel_7<<"6, "<<elapsed<<", Note_on_c, 6, "<<key+chords[new_value][chord]+12<<", 0\n";
            elapsed += 3600;

            iterations++;
        }
        channel_7<<"6, "<<elapsed<<", End_track\n";
    }
    channel_7.close();
}
void ten(){
    std::fstream channel_8 ("ch8.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[10]==1){
        int iterations = 0;
        int elapsed = 0;
        channel_8<<"7, 0, Start_track\n7, 0, Program_c, 7, "<<26<<"\n";
        elapsed += 4800*6;
        int key = this_key;
        while(iterations<3){
            key = this_key;
            for(int a=0; a<5; a++){
                channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
                elapsed += 200;
                channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            }
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";

            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<36+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<36+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";

            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 0\n";

            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 0\n";

            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 200;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";

            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 100\n";
            elapsed += 400;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<43+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<36+key<<", 100\n";
            elapsed += 400;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<36+key<<", 0\n";
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 100\n";
            elapsed += 400;
            channel_8<<"7, "<<elapsed<<", Note_on_c, 7, "<<40+key<<", 0\n";
            elapsed += 14400;

            iterations++;
        }
        channel_8<<"7, "<<elapsed<<", End_track\n";
    }
    channel_8.close();
}
void eight(){
    std::fstream channel_11 ("ch11.csv", std::fstream::out | std::fstream::trunc);
    if(to_run[8]==1){
        int iterations = 0;
        int elapsed = 0;
        channel_11<<"10, 0, Start_track\n10, 0, Program_c, 10, "<<116<<"\n";
        elapsed += 2400*5;
        int key = this_key;
        int chord = this_chord;
        while(iterations<18){
            int value = random() % 3;
            int new_value = (value+1)%3;
            //if(new_value==4){new_value=0;}
            key = this_key;
            chord = this_chord;
            channel_11<<"10, "<<elapsed<<", Note_on_c, 10, "<<key+chords[value][chord]+12<<", 80\n";
            channel_11<<"10, "<<elapsed<<", Note_on_c, 10, "<<key+chords[new_value][chord]+12<<", 80\n";
            elapsed += 400;

            iterations++;
        }
        channel_11<<"10, "<<elapsed<<", End_track\n";
    }
    channel_11.close();
}

void mandatory(){
    std::fstream channel_9 ("ch9.csv", std::fstream::out | std::fstream::trunc);
    //std::cout<<"in mandatory thread\n";
    //shifts the key
    int elapsed = 0;
    elapsed += 2400*26;
    //std::cout<<"key changed\n";
    //this_key += 7;
    elapsed += 2400*8;
    //this_key -= 7;
    //std::cout<<"key changed again\n";
    elapsed += 2400;

    channel_9<<"8, 0, Start_track\n8, 0, Program_c, 8, "<<119<<"\n";

    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 30\n";
    elapsed += 200;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 40\n";
    elapsed += 150;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 50\n";
    elapsed += 120;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 60\n";
    elapsed += 90;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 70\n";
    elapsed += 90;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 80\n";
    elapsed += 90;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", 90\n";
    elapsed += 90;
    for(int b=100; b>19; b-=20){
        for(int a=0; a<4; a++){
            channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<59<<", "<<b<<"\n";
            elapsed += 100;
        }
    }
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<46<<", 80\n";
    elapsed += 100;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<46<<", 0\n";
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<51<<", 80\n";
    elapsed += 100;
    channel_9<<"8, "<<elapsed<<", Note_on_c, 8, "<<51<<", 0\n";

    channel_9<<"8, "<<elapsed<<", End_track\n";
    channel_9.close();
}
void percussion(){
    std::fstream channel_10 ("ch10.csv", std::fstream::out | std::fstream::trunc);
    int elapsed = 0;
    elapsed += 2400;
    channel_10<<"9, 0, Start_track\n";
    int iterations = 0;
    while(iterations < 30){
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<36<<", 60\n";
        elapsed += 400;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<36<<", 60\n";
        elapsed += 400;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<38<<", 60\n";
        elapsed += 400;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<36<<", 60\n";
        elapsed += 400;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<36<<", 60\n";
        elapsed += 400;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<38<<", 60\n";
        elapsed += 200;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<38<<", 60\n";
        elapsed += 200;
        channel_10<<"9, "<<elapsed<<", Note_on_c, 9, "<<38<<", 60\n";
        elapsed += 400;

        iterations ++;
    }
    channel_10<<"9, "<<elapsed<<", End_track\n";
    channel_10.close();
}
void chord_init(){//based from the octave below the desired one, for the -1s.
    //chords[3][7]
    chords[0][0] =12;
    chords[1][0] =16;
    chords[2][0] =19;

    chords[0][1] =12;
    chords[1][1] =17;
    chords[2][1] =21;

    chords[0][2] =11;
    chords[1][2] =17;
    chords[2][2] =19;

    chords[0][3] =11;
    chords[1][3] =14;
    chords[2][3] =19;

    chords[0][4] =12;
    chords[1][4] =15;
    chords[2][4] =19;

    chords[0][5] =12;
    chords[1][5] =16;
    chords[2][5] =22;

    chords[0][6] =14;
    chords[1][6] =17;
    chords[2][6] =21;

    low = 45;
    med = low;
    high = low;

}
void sort(){
    for(int y = 0; y<100; y++){
        for(unsigned int x=0; x<factors.size(); x++){
            if(factors[x] == y){
                sorted_factors.push_back(y);
            }
        }
    }
}
void remove_dupes(){
    dupes_out.push_back(sorted_factors[0]);
    for(unsigned int x=1; x<sorted_factors.size(); x++){
        if(sorted_factors[x]!=sorted_factors[x-1]){
            dupes_out.push_back(sorted_factors[x]);
        }
    }
}
int main(){
    chord_init();
    srand (time(NULL));

    bool input_done = false;
    int input;
    while(!input_done){
        std::cout<<"Input a number of 6 digits or less\n";
        std::string in;
        std::getline(std::cin, in);
        //int input = stoi(in, NULL, 10);
        input = atoi(in.c_str());
        if(input/1000000 == 0){
            input_done = true;
        }
    }
    std::cout<<input<<"\nFactors:\n";
    bool factoring_done = false;
    int divisor = 1;
    int count_factors = 0;
    while(!factoring_done){
        if (input % divisor == 0) {
            std::cout<<divisor<<"\n";
            factors.push_back(divisor);
            divisor ++;
            count_factors++;
        }
        else{
            std::cout<<"\t"<<divisor<<" is not a factor.\n";
            divisor++;
        }
        if (divisor > sqrt(input)) {
        //if(divisor>input/2){
            factoring_done = true;
        }
    }
    std::cout<<"Number of factors for "<<input<<": "<<count_factors<<"\n";
    std::cout<<"Factors: ";
    for (unsigned int i = 0; i < factors.size(); i++){
        //std::cout<<factors[i]<<", ";
        //factors[i] = factors[i] % 100;
        std::cout<<factors[i]<<", ";
    }
    std::cout<<"\n";
    sort();
    std::cout<<"Sorted: ";

    for (unsigned int j = 0; j < sorted_factors.size(); j++){
        std::cout<<sorted_factors[j]<<", ";
    }
    std::cout<<"\n";
    remove_dupes();
    std::cout<<"Duplicates removed.\n ";
    fflush(stdout);

    //boost::thread t_mandatory(mandatory);
    std::thread t_mandatory(mandatory);
    //std::cout<<"thread initiated\n";

    for (unsigned int k = 0; k < dupes_out.size(); k++){
        std::cout<<dupes_out[k]<<", ";
        to_run[dupes_out[k]] = 1;
    }
    std::cout<<"\n";
    for(unsigned int n = 0; n<to_run.size(); n++){
        //std::cout<<to_run[n]<<",";
    }
    //std::cout<<"\n";

    //boost::thread t_one(one);
    std::thread t_one(one);

    //boost::thread t_two(two);
    std::thread t_two(two);

    //boost::thread t_three(three);
    std::thread t_three(three);

    //boost::thread t_five(five);
    std::thread t_five(five);

    //boost::thread t_eight(eight);
    std::thread t_eight(eight);

    //boost::thread t_nine(nine);
    std::thread t_nine(nine);

    //boost::thread t_ten(ten);
    std::thread t_ten(ten);

    //boost::thread t_twelve(twelve);
    std::thread t_twelve(twelve);

    //boost::thread t_else(percussion, dupes_out[k]);
    std::thread t_else(percussion);

    t_mandatory.join();//9
    t_one.join();//12 CHANNEL (1-index)
    t_two.join();//3
    t_three.join();//4
    t_five.join();//6
    t_eight.join();//11
    t_nine.join();//5
    t_ten.join();//8
    t_twelve.join();//7
    t_else.join();//10

    int count_threads = 2;
    for(int a=0; a<13; a++){
        if(to_run[a]==1){
            if(a==1 || a==2 || a==3 || a==5 || a==8 || a==9 || a==10 || a==12){
                count_threads++;
                //std::cout<<"count_threads: "<<count_threads<<"\n";
            }
        }
    }

    std::cout<<"\n";
    std::cout<<"Number of unique factors after mod 100: "<<dupes_out.size()<<"\n";

    //std::cout<<"back to file ops\n";
    std::fstream main_file ("final_test.csv", std::fstream::in | std::fstream::out | std::fstream::trunc);

    main_file << "0, 0, Header, 1, "<< 1+count_threads <<", " << DIVISION << std::endl;

    main_file<<"1, 0, Start_track\n";
    main_file<<"1, 0, Title_t, \"Test program\"\n";
    main_file<<"1, 0, Time_signature, 4, 2, 24, 8\n";
    main_file<<"1, 0, Tempo, 600000\n";
    main_file<<"1, 0, End_track\n";

    std::ifstream ch_3 ("ch3.csv", std::fstream::in);
    if(to_run[2]==1){
        main_file << ch_3.rdbuf();
    }
    ch_3.close();

    std::fstream ch_4 ("ch4.csv", std::fstream::in);
    if(to_run[3]==1){
        main_file << ch_4.rdbuf();
    }
    ch_4.close();

    std::fstream ch_5 ("ch5.csv", std::fstream::in);
    if(to_run[9]==1){
        main_file << ch_5.rdbuf();
    }
    ch_5.close();

    std::fstream ch_6 ("ch6.csv", std::fstream::in);
    if(to_run[5]==1){
        main_file << ch_6.rdbuf();
    }
    ch_6.close();

    std::fstream ch_7 ("ch7.csv", std::fstream::in);
    if(to_run[12]==1){
        main_file << ch_7.rdbuf();
    }
    ch_7.close();

    std::fstream ch_8 ("ch8.csv", std::fstream::in);
    if(to_run[10]==1){
        main_file << ch_8.rdbuf();
    }
    ch_8.close();

    std::fstream ch_9 ("ch9.csv", std::fstream::in);
    main_file << ch_9.rdbuf();
    ch_9.close();

    std::fstream ch_10 ("ch10.csv", std::fstream::in);
    main_file << ch_10.rdbuf();
    ch_10.close();

    std::fstream ch_11 ("ch11.csv", std::fstream::in);
    if(to_run[8]==1){
        main_file << ch_11.rdbuf();
    }
    ch_11.close();

    std::fstream ch_12 ("ch12.csv", std::fstream::in);
    if(to_run[1]==1){
        main_file << ch_12.rdbuf();
    }
    ch_12.close();

    main_file << "0, 0, End_of_file\n";
    main_file.close();

    return 0;
}


/*

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

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

This file is part of Chaste.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
 * Redistributions of source code must retain the above copyright notice,
   this list of conditions and the following disclaimer.
 * Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.
 * Neither the name of the University of Oxford nor the names of its
   contributors may be used to endorse or promote products derived from this
   software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

*/

#ifndef _CML_noble_varghese_kohl_noble_1998_basic_with_sac_
#define _CML_noble_varghese_kohl_noble_1998_basic_with_sac_

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

#include <cmath>
#include <cassert>
#include "AbstractCardiacCell.hpp"
#include "Exception.hpp"
#include "AbstractStimulusFunction.hpp"
#include "OdeSystemInformation.hpp"

/**
 *  The Noble98 'Basic' model, but hand-altered to add a stretch activation channel ionic
 *  current, which is dependent on the stretch the cell is under (an additional member variable
 *  which can be set be the user/mechanics solver
 */
class CML_noble_varghese_kohl_noble_1998_basic_with_sac : public AbstractCardiacCell
{
    friend class boost::serialization::access;
    /**
     * Checkpointing.
     * @param archive
     * @param version
     */
    template<class Archive>
    void serialize(Archive & archive, const unsigned int version)
    {
        archive & mStretch;
        archive & boost::serialization::base_object<AbstractCardiacCell>(*this);
    }
private:

    /** The stretch the cell is under - affects the stretch-activated-channel ionic current */
    double mStretch;

public:
    /**
     * Constructor.
     * @param pSolver  ODE solver to use
     * @param pIntracellularStimulus  intracellular stimulus to apply
     */
    CML_noble_varghese_kohl_noble_1998_basic_with_sac(boost::shared_ptr<AbstractIvpOdeSolver> pSolver,
                                                      boost::shared_ptr<AbstractStimulusFunction> pIntracellularStimulus);

    /** Destructor. */
    ~CML_noble_varghese_kohl_noble_1998_basic_with_sac();

    /** @return the total ionic current
     * @param pStateVariables  state variable vector; if not given, use cell's internal state
     */
    double GetIIonic(const std::vector<double>* pStateVariables=NULL);

    /**
     * Evaluate the RHS of this cell's ODE system.
     * @param var_environment__time  current simulation time
     * @param rY  state variable vector
     * @param rDY  will be filled in with the derivatives
     */
    void EvaluateYDerivatives(double var_environment__time,
                              const std::vector<double> &rY,
                              std::vector<double> &rDY);

    /**
     *  Set the stretch (overloaded)
     *  @param stretch stretch
     */
    void SetStretch(double stretch)
    {
        assert(stretch > 0.0);
        mStretch = stretch;
    }

    /**
     *  @return the stretch
     */
    double GetStretch()
    {
        return mStretch;
    }

    /**
     * Get the intracellular calcium concentration
     *
     * @return the intracellular calcium concentration
     */
    double GetIntracellularCalciumConcentration()
    {
        return mStateVariables[16];
    }
};

#include "SerializationExportWrapper.hpp"
CHASTE_CLASS_EXPORT(CML_noble_varghese_kohl_noble_1998_basic_with_sac)

namespace boost
{
    namespace serialization
    {
        /**
         * Avoid the need for a default constructor.
         * @param ar
         * @param t
         * @param fileVersion
         */
        template<class Archive>
        inline void save_construct_data(
            Archive & ar, const CML_noble_varghese_kohl_noble_1998_basic_with_sac * t, const unsigned int fileVersion)
        {
            const boost::shared_ptr<AbstractIvpOdeSolver> p_solver = t->GetSolver();
            const boost::shared_ptr<AbstractStimulusFunction> p_stimulus = t->GetStimulusFunction();
            ar << p_solver;
            ar << p_stimulus;
        }

        /**
         * Avoid the need for a default constructor.
         * @param ar
         * @param t
         * @param fileVersion
         */
        template<class Archive>
        inline void load_construct_data(
            Archive & ar, CML_noble_varghese_kohl_noble_1998_basic_with_sac * t, const unsigned int fileVersion)
        {
            boost::shared_ptr<AbstractIvpOdeSolver> p_solver;
            boost::shared_ptr<AbstractStimulusFunction> p_stimulus;
            ar >> p_solver;
            ar >> p_stimulus;
            ::new(t)CML_noble_varghese_kohl_noble_1998_basic_with_sac(p_solver, p_stimulus);
        }
    }
}

#endif


#pragma once

#include "FaceClassificationResult.hpp"
#include "Persistence/FlsDatabase.hpp"
#include "Resource/UserResource.hpp"

#include <memory>
#include <opencv2/core/hal/interface.h>
#include <dlib/svm/kernel.h>
#include <dlib/svm/function.h>

namespace Fls
{
    struct UserResource;

    class FaceClassifier
    {
    public:
        void init(const std::shared_ptr<FlsDatabase>& database);

        std::vector<FaceClassificationResult> classify(const UserResource* userResource,
            const std::vector<std::vector<float>>& embeddedFaces);
        int classify(const std::vector<float>& faceEmbeddings) const;
    private:
        typedef dlib::matrix<double, 0, 1> Sample;
        typedef dlib::linear_kernel<Sample> LinearKernel;
        typedef dlib::vector_normalizer<Sample> Normalizer;
        typedef dlib::multiclass_linear_decision_function<LinearKernel, int> DecisionFunction;
        typedef dlib::normalized_function<DecisionFunction> Classifier;

        void train();
        std::vector<FaceClassificationResult> namePredictions(const std::vector<int>& predictions) const;

        std::atomic_bool mInitialized{ false };
        std::future<void> mInitializedFuture;

        std::shared_ptr<FlsDatabase> mDatabase;
        Classifier mClassifier;
        float mConfidenceThreshold{ 0.4f };

        int64 mLastFrameId{ 0 };
        std::vector<FaceClassificationResult> mLastFrameClassificationResult;
    };
}


#ifndef HOPS_LINEARPROGRAMCLPIMPL_HPP
#define HOPS_LINEARPROGRAMCLPIMPL_HPP

#include <Eigen/Core>
#include "LinearProgram.hpp"

#ifdef HOPS_CLP_FOUND

#include <coin/ClpSimplex.hpp>

namespace hops {
    class LinearProgramClpImpl : public LinearProgram {
    public:
        LinearProgramClpImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b);

        LinearProgramClpImpl(const LinearProgramClpImpl &other);

        LinearProgramClpImpl &operator=(const LinearProgramClpImpl &other);

        LinearProgramSolution solve(const Eigen::VectorXd &objective) const override;

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd> removeRedundantConstraints(double tolerance) override;

        LinearProgramSolution computeChebyshevCenter() const override;

        std::vector<long> computeUnconstrainedDimensions() const override;

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd>
        addBoxConstraintsToUnconstrainedDimensions(double lb, double ub) override;

    private:
        mutable ClpSimplex model;
    };
}

#else //HOPS_CLP_FOUND

namespace hops {
    class LinearProgramClpImpl : public LinearProgram {
    public:
        LinearProgramClpImpl(const Eigen::MatrixXd &A, const Eigen::VectorXd &b) : LinearProgram(A, b) {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }

        [[nodiscard]] LinearProgramSolution solve(const Eigen::VectorXd &) const override {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd> removeRedundantConstraints(double) override {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }

        [[nodiscard]] LinearProgramSolution computeChebyshevCenter() const override {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }

        [[nodiscard]] std::vector<long> computeUnconstrainedDimensions() const override {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }

        std::tuple<Eigen::MatrixXd, Eigen::VectorXd>
        addBoxConstraintsToUnconstrainedDimensions(double, double) override {
            throw std::runtime_error("HOPS did not find CLP during compilation.");
        }
    };
}

#endif //HOPS_CLP_FOUND
#endif //HOPS_LINEARPROGRAMCLPIMPL_HPP


#include "graph_map/ndt/ndt_map_type.h"
#include <boost/serialization/export.hpp>
BOOST_CLASS_EXPORT(perception_oru::libgraphMap::NDTMapType)
namespace perception_oru{
namespace libgraphMap{
  using namespace std;

  using namespace perception_oru;

  NDTMapType::NDTMapType( MapParamPtr paramptr) : MapType(paramptr){
    NDTMapParamPtr param = boost::dynamic_pointer_cast< NDTMapParam >(paramptr);//Should not be NULL
    if(param!=NULL){
      resolution_=param ->resolution_;
      map_ = new perception_oru::NDTMap(new perception_oru::LazyGrid(resolution_));
      map_->initialize(0.0,0.0,0.0,param->sizex_,param->sizey_,param->sizez_);
    }
    else
      cerr<<"Cannot create instance of NDTmapHMT"<<std::endl;
  }
  NDTMapType::~NDTMapType(){}

  void NDTMapType::update(const Eigen::Affine3d &Tsensor,pcl::PointCloud<pcl::PointXYZ> &cloud, bool simple){//update map, cloud is the scan, Tsensor is the pose where the scan was aquired.

    if(initialized_ && enable_mapping_){
      Eigen::Vector3d localMapSize(2*max_range_,2*max_range_,sizez_);
      if (!simple) {
        map_->addPointCloudMeanUpdate(Tsensor.translation(),cloud,localMapSize, 1e5, 25, sizez_, 0.06);
      }
      else {
        map_->addPointCloudSimple(cloud, sizez_);
        map_->computeNDTCells();
      }
    }
    else if(!initialized_){
      InitializeMap(Tsensor,cloud, simple);
      initialized_ = true;
    }
  }

  void NDTMapType::update(const Eigen::Affine3d &Tsensor,pcl::PointCloud<velodyne_pointcloud::PointXYZIR> &cloud, bool simple){//update map, cloud is the scan, Tsensor is the pose where the scan was aquired.

    cerr << "TODO: implement update for point type PointXYZIR - will convert to PointXYZ for now" << endl;
    pcl::PointCloud<pcl::PointXYZ> cloud_xyz;
    pcl::copyPointCloud(cloud, cloud_xyz);
    update(Tsensor, cloud_xyz, simple);
  }

  void NDTMapType::InitializeMap(const Eigen::Affine3d &Tsensor,pcl::PointCloud<pcl::PointXYZ> &cloud, bool simple){
    cout<<"initialize map"<<endl;
    if (!simple) {
      map_->addPointCloud(Tsensor.translation(),cloud, 0.1, 100.0, 0.1);
      map_->computeNDTCells(CELL_UPDATE_MODE_SAMPLE_VARIANCE, 1e5, 255, Tsensor.translation(), 0.1);
    }
    else {
      map_->addPointCloudSimple(cloud, sizez_);
      map_->computeNDTCells();
    }
  }
  bool NDTMapType::CompoundMapsByRadius(MapTypePtr target,const Affine3d &T_source,const Affine3d &T_target, double radius){

    Affine3d Tdiff=Affine3d::Identity();
    Tdiff=T_source.inverse()*T_target;
    pcl::PointXYZ center_pcl(Tdiff.translation()(0),Tdiff.translation()(1),Tdiff.translation()(2));
    if( NDTMapPtr targetPtr=boost::dynamic_pointer_cast<NDTMapType>(target) ){
      cout<<"dynamic casted pointer"<<endl;
      if(resolution_!=targetPtr->resolution_)//checking if source and target have same resolution, they shoould have.
        return false;

      if(radius==-1)//if radius is not defined, match rcenter_pcladius to size of new map
        radius=targetPtr->sizex_<targetPtr->sizey_? targetPtr->sizex_/2:targetPtr->sizey_/2;

      int neighboors=radius/resolution_;
      cout<<"neighboors cells to search through="<<neighboors<<endl;
      std::vector<NDTCell*>cells= map_->getCellsForPoint(center_pcl,neighboors,true);
      cout<<"cells to transfer:"<<cells.size()<<endl;
      Tdiff=T_source.inverse()*T_target;
      cout<<"centerpoint in prev map frame=\n"<<Tdiff.translation()<<endl;

      for(int i=0;i<cells.size();i++){
        Eigen::Matrix3d cov=Tdiff.inverse().linear()*cells[i]->getCov()*Tdiff.linear();
        Eigen::Vector3d mean=Tdiff.inverse()*cells[i]->getMean();
        targetPtr->GetNDTMap()->addDistributionToCell(cov,mean,cells[i]->getN());
      }

    }

  }
  std::string NDTMapType::ToString(){
    stringstream ss;
    ss<<MapType::ToString()<<"NDT Map Type:"<<endl;
    ss<<"resolution:"<<resolution_<<endl;
    ss<<"resolution local factor:"<<resolution_local_factor_<<endl;
  // TODO sensor_range_ is not used at the moment.
    ss<<"maximum sensor range (not used):"<<sensor_range_<<endl;
    ss<<"nb active cells:"<<map_->numberOfActiveCells() << endl;
    //ss<<"NDTMap:"<<map_->ToString()<<endl;
    return ss.str();
  }

}
}