#ifndef FSTSEARCHMONTECARLO_H
#define FSTSEARCHMONTECARLO_H

/*!======================================================================
   Feature Selection Toolbox 3 source code
   ---------------------------------------

   \file    search_monte_carlo.hpp
   \brief   Repeatedly samples random subsets to eventually yield the one with highest criterion value
   \author  Petr Somol (somol@utia.cas.cz) with collaborators, see Contacts at http://fst.utia.cz
   \date    March 2011
   \version 3.1.0.beta
   \note    FST3 was developed using gcc 4.3 and requires
   \note    \li Boost library (http://www.boost.org/, tested with versions 1.33.1 and 1.44),
   \note    \li (\e optionally) LibSVM (http://www.csie.ntu.edu.tw/~cjlin/libsvm/,
                tested with version 3.00)
   \note    Note that LibSVM is required for SVM related tools only,
            as demonstrated in demo12t.cpp, demo23.cpp, demo25t.cpp, demo32t.cpp, etc.

*/ /*
=========================================================================
Copyright:
  * FST3 software (with exception of any externally linked libraries)
    is copyrighted by Institute of Information Theory and Automation (UTIA),
    Academy of Sciences of the Czech Republic.
  * FST3 source codes as presented here do not contain code of third parties.
    FST3 may need linkage to external libraries to exploit its functionality
    in full. For details on obtaining and possible usage restrictions
    of external libraries follow their original sources (referenced from
    FST3 documentation wherever applicable).
  * FST3 software is available free of charge for non-commercial use.
    Please address all inquires concerning possible commercial use
    of FST3, or if in doubt, to FST3 maintainer (see http://fst.utia.cz).
  * Derivative works based on FST3 are permitted as long as they remain
    non-commercial only.
  * Re-distribution of FST3 software is not allowed without explicit
    consent of the copyright holder.
Disclaimer of Warranty:
  * FST3 software is presented "as is", without warranty of any kind,
    either expressed or implied, including, but not limited to, the implied
    warranties of merchantability and fitness for a particular purpose.
    The entire risk as to the quality and performance of the program
    is with you. Should the program prove defective, you assume the cost
    of all necessary servicing, repair or correction.
Limitation of Liability:
  * The copyright holder will in no event be liable to you for damages,
    including any general, special, incidental or consequential damages
    arising out of the use or inability to use the code (including but not
    limited to loss of data or data being rendered inaccurate or losses
    sustained by you or third parties or a failure of the program to operate
    with any other programs).
========================================================================== */

#include <boost/smart_ptr.hpp>
#include <iostream>
#include <sstream>
#include <ctime>
#include "error.hpp"
#include "global.hpp"
#include "stopwatch.hpp"
#include "search.hpp"
#include "result_tracker.hpp"

/*============== Template parameter type naming conventions ==============
--------- Numeric types: -------------------------------------------------
DATATYPE - data sample values - usually real numbers (but may be integers
          in text processing etc.)
REALTYPE - must be real numbers - for representing intermediate results of
          calculations like mean, covariance etc.
IDXTYPE - index values for enumeration of data samples - (nonnegative) integers,
          extent depends on numbers of samples in data
DIMTYPE - index values for enumeration of features (dimensions), or classes (not
          class sizes) - (nonnegative) integers, usually lower extent than IDXTYPE,
          but be aware of expressions like _classes*_features*_features !
          in linearized representations of feature matrices for all classes
BINTYPE - feature selection marker type - represents ca. <10 different feature
          states (selected, deselected, sel./desel. temporarily 1st nested loop, 2nd...)
RETURNTYPE - criterion value: real value, but may be extended in future to support
          multiple values
--------- Class types: ---------------------------------------------------
SUBSET       - class of class type Subset
CLASSIFIER   - class implementing interface defined in abstract class Classifier
EVALUATOR    - class implementing interface defined in abstract class Sequential_Step
DISTANCE     - class implementing interface defined in abstract class Distance
DATAACCESSOR - class implementing interface defined in abstract class Data_Accessor
INTERVALCONTAINER - class of class type TIntervaller
CONTAINER    - STL container of class type TInterval
========================================================================== */

namespace FST {

/*! \brief Repeatedly samples random subsets to eventually yield the one with highest criterion value

	\note Requires user-set terminating condition, either in form of the number of random
	      trials accomplished or in form of maximum time spent or both.

	\note offers two ways of random subset generation:
	      a) set_cardinality_randomization(const DIMTYPE d_from, const DIMTYPE d_to) invokes
	         the first option, i.e., when generating a random subset, the subset size
	         is first chosen randomly from [d_from,d_to], then features are randomly
	         selected until the required number is reached
	      b) set_cardinality_randomization(const float d_prob) invokes
	         the second option, where each feature is randomly included/excluded
	         with probability d_prob. The expected subset size is thus d_prob*no_of_all_features,
	         but can be actually anything from [0,no_of_all_features].
*/

template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION>
class Search_Monte_Carlo : public Search<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>{
public:
	typedef Search<RETURNTYPE,DIMTYPE,SUBSET,CRITERION> parent;
	typedef boost::shared_ptr<CRITERION> PCriterion;
	typedef boost::shared_ptr<SUBSET> PSubset;
	Search_Monte_Carlo(const unsigned long trials_limit=1000, const unsigned long time_limit=0, const unsigned int time_limit_check_freq=1):Search<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>() {notify("Search_Monte_Carlo constructor."); set_stopping_condition(trials_limit,time_limit,time_limit_check_freq); reset_cardinality_randomization();}
	virtual ~Search_Monte_Carlo() {notify("Search_Monte_Carlo destructor.");}

	// NOTE: crit is expected to be already initialize()d before use here
	virtual bool search(const DIMTYPE target_d, RETURNTYPE &result, const PSubset sub, const PCriterion crit, std::ostream& os=std::cout); //!< returns found subset of target_d features (optimizes cardinality if target_d==0) + criterion value

	//! set maximum number of random trials or maximum time in seconds or both
	void set_stopping_condition(const unsigned long trials_limit=1000, const unsigned long time_limit=0, const unsigned int time_limit_check_freq=100) {if(trials_limit==0 && time_limit==0) {_trials_limit=1000; _time_limit=0;} else {_trials_limit=trials_limit; _time_limit=time_limit;} if(time_limit_check_freq==0) _time_limit_check_freq=100; else _time_limit_check_freq=time_limit_check_freq; }

	// set the way of random subset (size) generation
	void set_cardinality_randomization(const DIMTYPE d_from, const DIMTYPE d_to) {if(d_from>0 && d_from<=d_to) {_d_from=d_from; _d_to=d_to;} else {_d_from=_d_to=0;}} //!< generate subset size first from [d_from,d_to], then randomly choose exactly that number of features
	void set_cardinality_randomization(const float d_prob) {if(d_prob>0.0 && d_prob<1.0) _d_prob=d_prob; else _d_prob=0.0;} //!< for each feature roll the dice and select it with probability d_prob
	void reset_cardinality_randomization() {_d_from=_d_to=0; _d_prob=0.0;} //!< equivalent to set_cardinality_randomization(1,no_of_all_features)

	virtual std::ostream& print(std::ostream& os) const;

protected:
	unsigned long _time_limit; //!< max time allowed to be spent in seconds
	unsigned long _trials_limit; //!< max no of candidates evaluated
	unsigned int _time_limit_check_freq; //!< number of trials before next time check
	DIMTYPE _d_from, _d_to; //!< generate random subsets of size within these limits
	float _d_prob; //!< active d_prob must be from (0,1]; generates random subset of random size where the mean size over a series of calls is d_prob*no_of_all_features
};

template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION>
std::ostream& Search_Monte_Carlo<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>::print(std::ostream& os) const
{
	os << "Random search  [Search_Monte_Carlo(";
	if(_trials_limit>0) os << "maxtrials="<<_trials_limit<<", ";
	if(_time_limit>0) os<<"seconds="<<_time_limit<<", ";
	if(_d_prob>0.0) os<<"d_prob="<<_d_prob; else os<<"d_from="<<_d_from<<", d_to="<<_d_to; os<<")";
	if(parent::result_tracker_active()) os << " with " << *parent::_tracker; os<<"]";
	return os;
}

template<class RETURNTYPE, typename DIMTYPE, class SUBSET, class CRITERION>
bool Search_Monte_Carlo<RETURNTYPE,DIMTYPE,SUBSET,CRITERION>::search(const DIMTYPE target_d, RETURNTYPE &result, const PSubset sub, const PCriterion crit, std::ostream& os) // returns found subset + criterion value
{
	StopWatch swatch;
	assert(sub);
	assert(crit);
	const DIMTYPE n=sub->get_n();
	assert(target_d>=0 && target_d<n);
	PSubset tmp_sub(new SUBSET(n));
	bool initialized=false;

	bool track=parent::result_tracker_active(); if(track) parent::_tracker->set_output_detail(parent::get_output_detail());

	if(parent::output_normal()) {
		std::ostringstream sos;
		sos << "---------------------------------------" << std::endl;
		sos << "Starting " << *this << std::endl;
		sos << "with Criterion: " << *crit << std::endl;
		if(target_d==0) sos << "Subset size to be optimized." << std::endl; else sos << "Target subset size set to: " << target_d << std::endl;
		sos << std::endl << std::flush;
		syncout::print(os,sos);
	}
	assert(_trials_limit!=0 || _time_limit!=0);
	assert(_time_limit_check_freq>0);
	unsigned long _trials=0;
	unsigned long _time=0;
	DIMTYPE _d_from_tmp=_d_from, _d_to_tmp=_d_to;
	if(_d_to_tmp<1 || _d_to_tmp>n) _d_to_tmp=n;
	if(_d_from_tmp<1 || _d_from_tmp>_d_to_tmp) _d_from_tmp=_d_to_tmp;
	assert(_d_from_tmp>=1 && _d_from_tmp<=_d_to_tmp && _d_to_tmp<=n);

	RETURNTYPE val;
	while( (_trials_limit==0 || _trials<_trials_limit) && (_time_limit==0 || _time<_time_limit) )
	{
		if(target_d==0) {
			if(_d_prob>0.0) do {tmp_sub->make_random_subset(_d_prob);} while(tmp_sub->get_d()==0);
			else tmp_sub->make_random_subset(_d_from_tmp,_d_to_tmp);
		} else tmp_sub->make_random_subset(target_d);

		if(!crit->evaluate(val,tmp_sub)) return false;
		if(track) parent::_tracker->add(val,tmp_sub);
		if(!initialized || val>result || (val==result && tmp_sub->get_d()<sub->get_d())) {
			result=val;
			sub->stateless_copy(*tmp_sub);
			initialized=true;
			if(parent::output_normal()) {std::ostringstream sos; sos << "new MAXCRIT="<<val<< ", trial=" << _trials << ", " << *tmp_sub << std::endl << swatch << std::endl << std::endl << std::flush; syncout::print(os,sos);}
		}
		_trials++;
		if(_trials%_time_limit_check_freq==0) _time=(unsigned long)swatch.time_elapsed();
	}
	assert(initialized);
	if(parent::output_normal()) {
		std::ostringstream sos;
		sos << "......................................." << std::endl;
		sos << "Search_Monte_Carlo() search finished after "<<_trials<<" trials. " << swatch << std::endl;
		sos << "Search result: "<< std::endl << *sub << std::endl << "Criterion value: " << result << std::endl << std::endl << std::flush;
		syncout::print(os,sos);
	}
	return true;
}

} // namespace
#endif // FSTSEARCHMONTECARLO_H ///:~


#include "Setup.hpp"

#include <cstdint>
#include <iostream>
#include <limits>
#include <memory>
#include <sstream>

#include <boost/date_time/time_duration.hpp>
#include <boost/format.hpp>

static IValueToken* numberConverter(const std::string& value) { return new DefaultValueType(std::stod(value) != 0.0); }

static void addUnaryOperator(const UnaryOperatorToken::CallbackType& callback,
                             char identifier,
                             int precedence,
                             Associativity associativity,
                             const std::string& title       = "",
                             const std::string& description = "")
{
  auto tmpNew                           = std::make_unique<UnaryOperatorToken>(identifier, callback, precedence, associativity);
  auto tmp                              = tmpNew.get();
  defaultUnaryOperatorCache[identifier] = std::move(tmpNew);
  defaultUnaryOperators[identifier]     = tmp;

  defaultUnaryOperatorInfoMap.push_back(std::make_tuple(tmp, title, description));
}

static void addBinaryOperator(const BinaryOperatorToken::CallbackType& callback,
                              const std::string& identifier,
                              int precedence,
                              Associativity associativity,
                              const std::string& title       = "",
                              const std::string& description = "")
{
  auto tmpNew                            = std::make_unique<BinaryOperatorToken>(identifier, callback, precedence, associativity);
  auto tmp                               = tmpNew.get();
  defaultBinaryOperatorCache[identifier] = std::move(tmpNew);
  defaultBinaryOperators[identifier]     = tmp;

  defaultBinaryOperatorInfoMap.push_back(std::make_tuple(tmp, title, description));
}

static void addFunction(const FunctionToken::CallbackType& callback,
                        const std::string& identifier,
                        std::size_t minArgs            = 0u,
                        std::size_t maxArgs            = FunctionToken::GetArgumentCountMaxLimit(),
                        const std::string& title       = "",
                        const std::string& description = "")
{
  auto tmpNew                      = std::make_unique<FunctionToken>(identifier, callback, minArgs, maxArgs);
  auto tmp                         = tmpNew.get();
  defaultFunctionCache[identifier] = std::move(tmpNew);
  defaultFunctions[identifier]     = tmp;

  defaultFunctionInfoMap.push_back(std::make_tuple(tmp, title, description));
}

template<class T>
static void addVariable(const T& value, const std::string& identifier, const std::string& title = "", const std::string& description = "")
{
  auto tmpNew                                 = std::make_unique<DefaultVariableType>(identifier, value);
  auto tmp                                    = tmpNew.get();
  defaultInitializedVariableCache[identifier] = std::move(tmpNew);
  defaultVariables[identifier]                = tmp;

  defaultVariableInfoMap.push_back(std::make_tuple(tmp, title, description));
}

static DefaultValueType* addNewVariable(const std::string& identifier)
{
  auto tmpNew = std::make_unique<DefaultVariableType>(identifier);
  auto result = tmpNew.get();
  defaultUninitializedVariableCache.push_back(std::move(tmpNew));
  defaultVariables[identifier] = result;
  return result;
}

#ifndef __REGION__UNOPS
#ifndef __REGION__UNOPS__BITWISE
static IValueToken* UnaryOperator_Not(IValueToken* rhs) { return new DefaultValueType(!rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()); }
#endif // __REGION__UNOPS__BITWISE
#endif // __REGION__UNOPS

#ifndef __REGION__BINOPS
#ifndef __REGION__BINOPS__COMPARISON
static IValueToken* BinaryOperator_Equals(IValueToken* lhs, IValueToken* rhs)
{
  return new DefaultValueType(DefaultArithmeticType(lhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() ==
                                                    rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}

static IValueToken* BinaryOperator_NotEquals(IValueToken* lhs, IValueToken* rhs)
{
  return new DefaultValueType(DefaultArithmeticType(lhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() !=
                                                    rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}
#endif // __REGION__BINOPS__COMPARISON

#ifndef __REGION__BINOPS__BITWISE
static IValueToken* BinaryOperator_BitwiseOr(IValueToken* lhs, IValueToken* rhs)
{
  return new DefaultValueType(
      DefaultArithmeticType(lhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() | rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}

static IValueToken* BinaryOperator_BitwiseAnd(IValueToken* lhs, IValueToken* rhs)
{
  return new DefaultValueType(
      DefaultArithmeticType(lhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() & rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}

static IValueToken* BinaryOperator_BitwiseXor(IValueToken* lhs, IValueToken* rhs)
{
  return new DefaultValueType(
      DefaultArithmeticType(lhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() ^ rhs->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}
#endif // __REGION__BINOPS__BITWISE
#endif // __REGION__BINOPS

#ifndef __REGION__FUNCTIONS
#ifndef __REGION__FUNCTIONS__BITWISE
static IValueToken* Function_Not(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(!args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>());
}

static IValueToken* Function_Or(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() |
                              args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>());
}

static IValueToken* Function_And(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() &
                              args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>());
}

static IValueToken* Function_Xor(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() &
                              args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>());
}

static IValueToken* Function_Nor(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(
      !(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() | args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}

static IValueToken* Function_Nand(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(
      !(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() & args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}

static IValueToken* Function_Xnor(const std::vector<IValueToken*>& args)
{
  return new DefaultValueType(
      !(args[0]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>() ^ args[1]->As<DefaultValueType*>()->GetValue<DefaultArithmeticType>()));
}
#endif // __REGION__FUNCTIONS__BITWISE
#endif // __REGION__FUNCTIONS

static std::unique_ptr<BinaryOperatorToken> juxtapositionOperator;

void InitTruthTable(ExpressionParserBase& instance)
{
  if(options.jpo_precedence != 0)
  {
    juxtapositionOperator = std::make_unique<BinaryOperatorToken>("&", BinaryOperator_BitwiseAnd, 2 + options.jpo_precedence, Associativity::Left);
  }

  instance.SetOnParseNumberCallback(numberConverter);
  instance.SetOnUnknownIdentifierCallback(addNewVariable);
  instance.SetJuxtapositionOperator(juxtapositionOperator.get());

  instance.SetUnaryOperators(&defaultUnaryOperators);
  instance.SetBinaryOperators(&defaultBinaryOperators);
  instance.SetFunctions(&defaultFunctions);
  instance.SetVariables(&defaultVariables);

  addUnaryOperator(UnaryOperator_Not, '!', 5, Associativity::Right, "Not", "!x");
  addUnaryOperator(UnaryOperator_Not, '~', 5, Associativity::Right, "Not", "~x");

  addBinaryOperator(BinaryOperator_Equals, "==", 4, Associativity::Left, "Equals", "x == y");
  addBinaryOperator(BinaryOperator_NotEquals, "!=", 4, Associativity::Left, "Not equals", "x != y");
  defaultBinaryOperatorInfoMap.push_back(std::make_tuple(nullptr, "", ""));
  addBinaryOperator(BinaryOperator_BitwiseOr, "|", 2, Associativity::Left, "Bitwise OR", "x | y");
  addBinaryOperator(BinaryOperator_BitwiseAnd, "&", 2, Associativity::Left, "Bitwise AND", "x & y");
  addBinaryOperator(BinaryOperator_BitwiseXor, "^", 2, Associativity::Left, "Bitwise XOR", "x ^ y");
  defaultBinaryOperatorInfoMap.push_back(std::make_tuple(nullptr, "", ""));
  addBinaryOperator(BinaryOperator_BitwiseOr, "+", 2, Associativity::Left, "Bitwise OR", "x + y");
  addBinaryOperator(BinaryOperator_BitwiseAnd, "*", 2, Associativity::Left, "Bitwise AND", "x * y");
  addBinaryOperator(BinaryOperator_BitwiseXor, "/", 2, Associativity::Left, "Bitwise XOR", "x / y");

  addFunction(Function_Not, "NOT", 1u, 1u, "Not", "NOT x");
  defaultFunctionInfoMap.push_back(std::make_tuple(nullptr, "", ""));
  addFunction(Function_Or, "OR", 2u, 2u, "Or", "x OR y");
  addFunction(Function_And, "AND", 2u, 2u, "Or", "x AND y");
  addFunction(Function_Xor, "XOR", 2u, 2u, "Or", "x XOR y");
  defaultFunctionInfoMap.push_back(std::make_tuple(nullptr, "", ""));
  addFunction(Function_Nor, "NOR", 2u, 2u, "Or", "x NOR y");
  addFunction(Function_Nand, "NAND", 2u, 2u, "Or", "x NAND y");
  addFunction(Function_Xnor, "XNOR", 2u, 2u, "Or", "x XNOR y");

  addVariable(true, "true", "True", "Boolean value");
  addVariable(true, "T", "True", "Boolean value");
  addVariable(false, "false", "False", "Boolean value");
  addVariable(false, "F", "False", "Boolean value");
  defaultVariableInfoMap.push_back(std::make_tuple(nullptr, "", ""));
  addVariable(true, "high", "High", "Boolean value");
  addVariable(true, "H", "High", "Boolean value");
  addVariable(false, "low", "Low", "Boolean value");
  addVariable(false, "L", "Low", "Boolean value");
}


#include "storage_engine/column_store.h"
#include "perftest_tools.h"
#include "datetime.h"

#include <iostream>
#include <fstream>
#include <sstream>
#include <cstdlib>
#include <algorithm>
#include <zlib.h>
#include <cstring>
#include <map>

#include <boost/filesystem.hpp>

namespace fs = boost::filesystem;

using namespace Akumuli;

//! Generate time-series from random walk
struct RandomWalk {
    std::random_device                  randdev;
    std::mt19937                        generator;
    std::normal_distribution<double>    distribution;
    size_t                              N;
    std::vector<double>                 values;

    RandomWalk(double start, double mean, double stddev, size_t N)
        : generator(randdev())
        , distribution(mean, stddev)
        , N(N)
    {
        values.resize(N, start);
    }

    double generate(aku_ParamId id) {
        values.at(id) += 0.01;// distribution(generator);
        return sin(values.at(id));
    }

    void add_anomaly(aku_ParamId id, double value) {
        values.at(id) += value;
    }
};

UncompressedChunk read_data(fs::path path) {
    UncompressedChunk res;
    std::fstream in(path.c_str());
    std::string line;
    aku_ParamId base_pid = 1;
    std::map<std::string, aku_ParamId> pid_map;
    while(std::getline(in, line)) {
        std::istringstream lstr(line);
        std::string series, timestamp, value;
        std::getline(lstr, series, ',');
        std::getline(lstr, timestamp, ',');
        std::getline(lstr, value, ',');

        aku_ParamId id;
        auto pid_it = pid_map.find(series);
        if (pid_it == pid_map.end()) {
            pid_map[series] = base_pid;
            id = base_pid;
            base_pid++;
        } else {
            id = pid_it->second;
        }
        res.paramids.push_back(id);

        res.timestamps.push_back(DateTimeUtil::from_iso_string(timestamp.c_str()));

        res.values.push_back(std::stod(value));

    }
    return res;
}

struct TestRunResults {
    // Akumuli stats
    std::string file_name;
    size_t uncompressed;
    size_t compressed;
    size_t nelements;

    double bytes_per_element;
    double compression_ratio;

    // Gzip stats
    double gz_bytes_per_element;
    double gz_compression_ratio;
    double gz_compressed;

    // Performance
    std::vector<double> perf;
    std::vector<double> gz_perf;
};

TestRunResults run_tests(fs::path path) {
    TestRunResults runresults;
    runresults.file_name = fs::basename(path);

    auto header = read_data(path);

    const size_t UNCOMPRESSED_SIZE = header.paramids.size()*8    // Didn't count lengths and offsets
                                   + header.timestamps.size()*8  // because because this arrays contains
                                   + header.values.size()*8;     // no information and should be compressed
                                                                 // to a few bytes

    auto bstore = StorageEngine::BlockStoreBuilder::create_memstore();
    auto cstore = std::make_shared<StorageEngine::ColumnStore>(bstore);

    aku_ParamId previd = 0;
    std::vector<u64> rpoints;
    for (size_t i = 0; i < header.paramids.size(); i++) {
        aku_Sample sample = {};
        sample.payload.type = AKU_PAYLOAD_FLOAT;
        sample.paramid = header.paramids[i];
        sample.timestamp = header.timestamps[i];
        sample.payload.float64 = header.values[i];
        if (previd != sample.paramid) {
            cstore->create_new_column(sample.paramid);
        }
        cstore->write(sample, &rpoints, nullptr);
    }
    auto store_stats = bstore->get_stats();
    auto uncommitted = cstore->_get_uncommitted_memory();
    cstore->close();
    //std::cout << "Block store: " << store_stats.nblocks <<
    //             " blocks used, uncommitted size: " << uncommitted << std::endl;

    // Compress using zlib

    const size_t COMPRESSED_SIZE = store_stats.nblocks*store_stats.block_size + uncommitted;
    const float BYTES_PER_EL = float(COMPRESSED_SIZE)/header.paramids.size();
    const float COMPRESSION_RATIO = float(UNCOMPRESSED_SIZE)/COMPRESSED_SIZE;

    // Save compression stats

    runresults.uncompressed         = UNCOMPRESSED_SIZE;
    runresults.compressed           = COMPRESSED_SIZE;
    runresults.nelements            = header.timestamps.size();
    runresults.bytes_per_element    = BYTES_PER_EL;
    runresults.compression_ratio    = COMPRESSION_RATIO;

    // Try to decompress
    // TBD
    return runresults;
}

int main(int argc, char** argv) {
    if (argc < 2) {
        std::cout << "Path to dataset required" << std::endl;
        exit(1);
    }

    // Iter directory
    fs::path dir{argv[1]};
    fs::directory_iterator begin(dir), end;
    std::vector<fs::path> files;
    std::list<TestRunResults> results;
    for (auto it = begin; it != end; it++) {
        if (it->path().extension() == ".csv") {
            files.push_back(*it);
        }
    }
    std::sort(files.begin(), files.end());
    for (auto fname: files) {
        //std::cout << "Run tests for " << fs::basename(fname) << std::endl;
        results.push_back(run_tests(fname));
    }

    // Write table
    std::cout << "| File name | num elements | uncompressed | compressed | ratio | bytes/el |" << std::endl;
    std::cout << "| ----- | ---- | ----- | ---- | ----- | ---- | " << std::endl;
    for (auto const& run: results) {
        std::cout << run.file_name << " | " <<
                     run.nelements << " | " <<
                     run.uncompressed << " | " <<
                     run.compressed << " | " <<
                     run.compression_ratio << " | " <<
                     run.bytes_per_element << " | " <<
                     std::endl;
    }

}


/* Copyright (c) 2013 Y. William Yu. Released under CC0 1.0 Universal */
/* sparsify.cpp
 * Sparsifies the quality vector for a read based on k-mer distance from dictionary
*/

#if !defined(NDEBUG)
#define BOOST_MULTI_INDEX_ENABLE_INVARIANT_CHECKING
#define BOOST_MULTI_INDEX_ENABLE_SAFE_MODE
#endif

#define BOOST_MULT_INDEX_DISABLE_SERIALIZATION

//#define VERBOSE

#include "global.h"

#include <boost/multi_index_container.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <boost/multi_index/member.hpp>
#include <iterator>
#include <fstream>

#include <ctime>

using boost::multi_index_container;
using namespace boost::multi_index;

struct read_entry
{
public:
	readseq encoded;
	unsigned long m1, m2;
	unsigned long m3, m4;
	read_entry() {};
	read_entry(
		const readseq encoded_):
		encoded(encoded_)
	{
//		m1 = encoded & 0x0000FFFFFFFFFFFF;
//		m2 = encoded & 0xFFFF0000FFFFFFFF;
//		m3 = encoded & 0xFFFFFFFF0000FFFF;
//		m4 = encoded & 0xFFFFFFFFFFFF0000;

		m1 = encoded & 0x3F3F3F3F3F3F3F3F;
		m2 = encoded & 0xCFCFCFCFCFCFCFCF;
		m3 = encoded & 0xF3F3F3F3F3F3F3F3;
		m4 = encoded & 0xFCFCFCFCFCFCFCFC;
	}

	friend std::ostream& operator<<(std::ostream& os,const read_entry& e)
	{
		os << decode_read(e.encoded);
		os << std::endl;
		return os;
	}
};

//struct id{};
struct m1{};
struct m2{};
struct m3{};
struct m4{};

typedef multi_index_container<
	read_entry,
	indexed_by<
		//hashed_non_unique<tag<encoded>,member<read_entry,std::bitset,&read_entry::encoded::data> >
		//hashed_unique<tag<id>,member<read_entry,uint64_t,&read_entry::encoded> >,
		hashed_non_unique<tag<m1>,member<read_entry,uint64_t,&read_entry::m1> >,
		hashed_non_unique<tag<m2>,member<read_entry,uint64_t,&read_entry::m2> >,
		hashed_non_unique<tag<m3>,member<read_entry,uint64_t,&read_entry::m3> >,
		hashed_non_unique<tag<m4>,member<read_entry,uint64_t,&read_entry::m4> >//,
	>
> read_entry_database;

template<typename Tag,typename MultiIndexContainer>
void print_out_by(
	const MultiIndexContainer& s
)
{
	const typename boost::multi_index::index<MultiIndexContainer,Tag>::type& i=get<Tag>(s);
	typedef typename MultiIndexContainer::value_type value_type;

	/* dump the elements of the index to cout */
	std::copy(i.begin(),i.end(),std::ostream_iterator<value_type>(std::cout));
}

// Function returns a "readseq" with "A/00" in all positions except those which
// are SNPs (i.e. the database has an entry exactly one substitution away from
// tested), which are coded as "G/10" instead.
//
// In the event that there are no hits in the database, return -1=1111...111
readseq check_red(const read_entry_database &red, const std::unordered_set<readseq> &red_whole, const readseq tested)
{
	std::set<readseq> tmp;
	readseq ans = 0;
	int curr;
	const readseq constant_one = 1;
	const readseq constant_max = -1;

	read_entry work = {tested};
	//assert(work.encoded == tested);
	//std::cerr  << decode_read(tested) << std::endl;

	int numhits = red.get<m1>().count(work.m1) + red.get<m2>().count(work.m2) + red.get<m3>().count(work.m3) + red.get<m4>().count(work.m4);
	if (numhits == 0) {
		return constant_max;
	} else if (numhits < 96) {
		typedef read_entry_database::index<m1>::type read_by_m1;
		std::pair <read_by_m1::iterator, read_by_m1::iterator> range_m1 = red.get<m1>().equal_range(work.m1);
		while (range_m1.first != range_m1.second) {
			tmp.insert((*range_m1.first).encoded);
			range_m1.first++;
		}

		typedef read_entry_database::index<m2>::type read_by_m2;
		std::pair <read_by_m2::iterator, read_by_m2::iterator> range_m2 = red.get<m2>().equal_range(work.m2);
		while (range_m2.first != range_m2.second) {
			tmp.insert((*range_m2.first).encoded);
			range_m2.first++;
		}

		typedef read_entry_database::index<m3>::type read_by_m3;
		std::pair <read_by_m3::iterator, read_by_m3::iterator> range_m3 = red.get<m3>().equal_range(work.m3);
		while (range_m3.first != range_m3.second) {
			tmp.insert((*range_m3.first).encoded);
			range_m3.first++;
		}

		typedef read_entry_database::index<m4>::type read_by_m4;
		std::pair <read_by_m4::iterator, read_by_m4::iterator> range_m4 = red.get<m4>().equal_range(work.m4);
		while (range_m4.first != range_m4.second) {
			tmp.insert((*range_m4.first).encoded);
			range_m4.first++;
		}

		bool one_subst = false;
		bool exact_match = false;
		for (auto itr = tmp.begin(); itr != tmp.end(); ++itr) {
			if ((curr = subst_find((*itr),(work.encoded))) == -1) {
				exact_match = true;
			}
			else if (curr >= 0) {
				ans |= (constant_one<<(2*curr));
				one_subst = true;
			}
		}
		if (!(one_subst || exact_match))
			return constant_max;
		return ans;
	} else {
		const readseq orig = work.encoded;
		readseq x, y;
		unsigned int curr_value;
		for (int i=0; i<32; ++i) {
			x = orig & ~(3UL << (2*i));
			curr_value = (orig >> (2*i) & 3);
			for (unsigned long j=0; j<4; ++j) {
				if (curr_value == j) {
					// silently do nothing
				} else {
					y = x | (j << (2*i));
					if (red_whole.count(y)==1) {
						ans |= (constant_one << (2*(31-i)));
					}
				}
			}
		}
		if ((ans != 0) || (red_whole.count(orig)==1))
			return ans;
		else
			return constant_max;
	}

}

// Constructs the read_entry_database hash table
void construct_red(read_entry_database &red, std::unordered_set<readseq> &red_whole, std::string dict_filename) {
	std::deque<readseq> mer_list;
	char line[4096];
	std::string line2;
	{
		FILE * dictionary;
		dictionary = fopen (dict_filename.c_str(), "r");
		setvbuf (dictionary, NULL, _IOFBF, BUFSIZ);

		readseq *array;
		unsigned long arraysize = 1024;
		array = (readseq*) malloc(arraysize*sizeof(readseq));
		unsigned long arraycounter = 0;
		std::string encoded;
		readseq reversed;

		if (dictionary == NULL) perror ("Error opening file");
		else
		{

			printf("Reading database file: %016d",0);
			while ( fgets (line, 4096, dictionary) != NULL ) {
				encoded=line;
				encoded.resize(32);
				mer_list = encode_read(encoded.c_str());
				array[arraycounter++] = mer_list[0];
				if (arraycounter == arraysize) {
					array = (readseq*) realloc(array,arraysize*2*sizeof(readseq));
					arraysize *= 2;
				}
				if (!(arraycounter%4096)) {
					printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%016ld", arraycounter);
					fflush(stdout);
				}
			}
			printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%016ld", arraycounter);
			printf("\n");

			red.rehash(2*arraycounter);
			red_whole.reserve(2*arraycounter);
			printf("Generating keys table: %016d",0);
			unsigned long i;
			for ( i = 0; i<arraycounter; ++i) {
				red.insert(read_entry(array[i]));
				red_whole.insert(array[i]);
				reversed = rev_compl(array[i]);
				red.insert(reversed);
				red_whole.insert(reversed);
				if (!(i%4096)) {
					printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%016ld", i);
					fflush(stdout);
				}
			}
			printf("\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b\b%016ld", i);
			printf("\n");
			free(array);

		}
		fclose(dictionary);
	}

}

int main( int argc, char *argv[])
{
	int64checker();
	if (argc < 3) {
		std::cerr << "Discards non-SNP quality values for known reads." << std::endl;
		std::cerr << "Usage: " << argv[0] << " dictionary_file input_file(s)" << std::endl;
		std::cerr << "\tInput is assumed to be SAM file without headers. Because this program" << std::endl
				  << "\tonly modifies column 11 if column 10 contains a valid read, however," << std::endl
				  << "\tit may or may not work on SAM files with headers. This behaviour" << std::endl
				  << "\tshould *not* be depended on." << std::endl << std::endl
				  << "\tOutput will be modified SAM files named [input_file].filtered," <<std::endl
				  << "\tidentical to the original except in column 11." << std::endl << std::endl
				  << "\tThe quality of nearly all bases corresponding to a 32-mer listed in the 1st" << std::endl
				  << "\tcolumn of [dictionary_file] will be set to '~'. Correspondance shall be" << std::endl
				  << "\tdefined as a Hamming distance of less than or equal to 1 in the 32-mer." << std::endl
				  << "\tNote that a 32-mer can correspond to multiple 32-mers in the dictionary." << std::endl
				  << "\tThe exceptions to the setting of quality values above will be all bases" << std::endl
				  << "\tthat are different to any of the corresponding 32-mers in the dictionary," << std::endl
				  << "\twhich will retain their original quality value data." << std::endl;
		exit(-1);
	}

	std::string dict_filename = argv[1];
	read_entry_database red;
	std::unordered_set<readseq> red_whole;
	construct_red(red, red_whole, dict_filename);

	std::FILE * inFile;
	std::FILE * outFile;
	char outfile_name[4096];
	char infile_name[4096];
	unsigned int name_length;
	int col=1;
	char str[4096];
	char timestr[4096];

	//int c; // character input
	int j; // string counter
	std::vector<readseq> mer_list;
	for (int argwalker = 2; argwalker < argc; ++argwalker) {
		strcpy(infile_name,argv[argwalker]);
		name_length = strlen(infile_name);
		if (name_length>4000) {
			std::cerr << "Filepaths cannot be longer than 4000 characters." << std::endl;
			exit(-2);
		}
		strcpy(outfile_name,infile_name);
		outfile_name[name_length++]='.';
		outfile_name[name_length++]='f';
		outfile_name[name_length++]='i';
		outfile_name[name_length++]='l';
		outfile_name[name_length++]='t';
		outfile_name[name_length++]='e';
		outfile_name[name_length++]='r';
		outfile_name[name_length++]='e';
		outfile_name[name_length++]='d';
		outfile_name[name_length++]='\0';

		std::cout << infile_name << " ==> " << outfile_name << ":\t";
		// Timer
		std::time_t result = std::time(NULL);
		strcpy(timestr,std::asctime(std::localtime(&result)));
		timestr[strlen(timestr)-1]='\0';
		std::cerr << timestr << " --> " ;

		inFile=fopen(infile_name,"r");
		if (inFile==NULL) {
			perror ("Error opening input file.");
			exit(-3);
		}
		setvbuf (inFile, NULL, _IOFBF, BUFSIZ);
		outFile=fopen(outfile_name,"w");
		if (outFile==NULL) {
			perror ("Error opening output file.");
			exit(-4);
		}
		setvbuf (outFile, NULL, _IOFBF, BUFSIZ );

		const readseq constant_max = -1;
		readseq a;
		readseq b;
		//int counter = 0;
		int lasty;
		bool alter_this[4096] = { 0 };
		bool pass_through_line;
		char line[4096];
		char *c;
        while ( fgets (line, 4096, inFile) != NULL ) {
			c = &line[0];
			pass_through_line = false;
			j = 0;
			col = 1;
			for (int x=0; x<4096; ++x)
				alter_this[x]=false;
			while ( *c != '\0' )
			{
				if (pass_through_line == true) {
					break;
				} else if (col == 10) {
					switch (*c) {
						case 'A': case 'a':
						case 'C': case 'c':
						case 'G': case 'g':
						case 'T': case 't':
							str[j++]=(char) *c;
							break;
						case '\t': case '\0':
							str[j]='\0';
							mer_list = encode_read_vector(str);
							lasty=-100;
							for (unsigned int y=0; y<mer_list.size(); ++y) {
								a = mer_list[y];
								if (((alter_this[y])||(y-lasty<16))&&(!(y==(mer_list.size()-1)))) {
									// Silently do nothing
								} else {
									lasty=y;
									b = check_red(red,red_whole,a);
									if (b==constant_max) {
										// Silently do nothing
									} else {
										for (int x=0; x<32; ++x) {
											alter_this[x+y] = alter_this[x+y] || !(b&1);
											b>>=2;
										}
									}

								/*	b = check_red(red,red_whole,rev_compl(a));
									if (b==constant_max) {
										// Silently do nothing
									} else {
										for (int x=0; x<32; ++x) {
											alter_this[31-x+y] = alter_this[31-x+y] || !(b&1);
											b>>=2;
										}
									} */
								}
							}
							j=0;
							++col;
							break;
						case '\n':
							// This should NOT happen
							std::cerr << "Malformed read. Not enough columns." << std::endl;
							// Deliberate fall-through here.
						default:
							// If something other than A,T,C,G (perhaps N) appears, just skip line.
							pass_through_line = true;
							break;
					}
				} else if (col == 11) {
					switch (*c) {
						case '\t':
							pass_through_line = true;
							break;
						case '#':
							break;
						default:
							if ((alter_this[j++]))
								*c = 126;
							break;
					}

				} else {
					if (*c == '\t')
						++col;
				}
				c++;
			}
			fputs(line, outFile);
		}

		// Timer
		std::time_t result2 = std::time(NULL);
		std::cerr << std::asctime(std::localtime(&result2));

		fclose(inFile);
		fclose(outFile);
	}
	return 0;
};


#include "../Outer_Parameters.hxx"

#include <boost/program_options.hpp>
#include <boost/filesystem/fstream.hpp>

namespace po = boost::program_options;

Outer_Parameters::Outer_Parameters(int argc, char *argv[])
{
  int int_verbosity;
  std::string write_solution_string;
  using namespace std::string_literals;

  po::options_description required_options("Required options");
  required_options.add_options()(
    "functions",
    po::value<boost::filesystem::path>(&functions_path)->required(),
    "Mathematica, JSON, or NSV file with SDP functions evaluated at Chebyshev "
    "zeros.");
  required_options.add_options()(
    "points", po::value<boost::filesystem::path>(&points_path)->required(),
    "JSON or NSV file with initial points.");

  po::options_description cmd_line_options;
  cmd_line_options.add(required_options);

  po::options_description basic_options("Basic options");
  basic_options.add_options()("help,h", "Show this helpful message.");
  basic_options.add_options()("version",
                              "Show version and configuration info.");
  basic_options.add_options()(
    "paramFile,p", po::value<boost::filesystem::path>(&param_path),
    "Any parameter can optionally be set via this file in key=value "
    "format. Command line arguments override values in the parameter "
    "file.");
  basic_options.add_options()(
    "out,o", po::value<boost::filesystem::path>(&output_path),
    "The optimal solution is saved to this file in json "
    "format. Defaults to 'functions' with the ending '_out.json'.");
  basic_options.add_options()("verbosity",
                              po::value<int>(&int_verbosity)->default_value(1),
                              "Verbosity.  0 -> no output, 1 -> regular "
                              "output, 2 -> debug output");

  cmd_line_options.add(basic_options);
  po::options_description solver_options(solver.options());
  solver_options.add_options()(
    "dualityGapReduction",
    po::value<El::BigFloat>(&duality_gap_reduction)
      ->default_value(El::BigFloat("1024", 10)),
    "Shrink the duality gap threshold by this factor during each outer "
    "iteration.  Smaller means slower convergence.");
  cmd_line_options.add(solver_options);

  po::variables_map variables_map;
  try
    {
      po::store(po::parse_command_line(argc, argv, cmd_line_options),
                variables_map);

      if(variables_map.count("help") != 0)
        {
          if(El::mpi::Rank() == 0)
            {
              std::cout << cmd_line_options << '\n';
            }
        }
      else if(variables_map.count("version") != 0)
        {
          if(El::mpi::Rank() == 0)
            {
              std::cout << "SDPB " << SDPB_VERSION_STRING << "\n";
            }
          El::PrintVersion();
          El::PrintConfig();
          El::PrintCCompilerInfo();
          El::PrintCxxCompilerInfo();
        }
      else
        {
          if(variables_map.count("paramFile") != 0)
            {
              boost::filesystem::ifstream param_file(param_path);
              if(!param_file.good())
                {
                  throw std::runtime_error("Could not open '"
                                           + param_path.string() + "'");
                }

              po::store(po::parse_config_file(param_file, cmd_line_options),
                        variables_map);
            }

          po::notify(variables_map);

          if(!boost::filesystem::exists(functions_path))
            {
              throw std::runtime_error("functions path '" + functions_path.string()
                                       + "' does not exist");
            }
          if(boost::filesystem::is_directory(functions_path))
            {
              throw std::runtime_error("functions path '" + functions_path.string()
                                       + "' is a directory, not a file.");
            }

          if(variables_map.count("out") == 0)
            {
              boost::filesystem::path filename(functions_path);
              filename.replace_extension();
              output_path = filename.string() + "_out.json";
            }

          if(variables_map.count("checkpointDir") == 0)
            {
              solver.checkpoint_out = functions_path;
              if(solver.checkpoint_out.filename() == ".")
                {
                  solver.checkpoint_out = solver.checkpoint_out.parent_path();
                }
              solver.checkpoint_out.replace_extension("ck");
            }

          if(variables_map.count("initialCheckpointDir") == 0)
            {
              solver.checkpoint_in = solver.checkpoint_out;
            }
          else
            {
              require_initial_checkpoint = true;
            }

          write_solution = Write_Solution(write_solution_string);

          if(El::mpi::Rank() == 0)
            {
              boost::filesystem::ofstream ofs(output_path);
              if(!ofs.good())
                {
                  throw std::runtime_error("Cannot write to outDir: "
                                           + output_path.string());
                }
            }

          if(int_verbosity != 0 && int_verbosity != 1 && int_verbosity != 2)
            {
              throw std::runtime_error(
                "Invalid number for Verbosity.  Only 0, 1 or 2 are allowed\n");
            }
          else
            {
              verbosity = static_cast<Verbosity>(int_verbosity);
            }
        }
    }
  catch(po::error &e)
    {
      El::ReportException(e);
      El::mpi::Abort(El::mpi::COMM_WORLD, 1);
    }
}


//==============================================================================
//         Copyright 2003 - 2011 LASMEA UMR 6602 CNRS/Univ. Clermont II
//         Copyright 2009 - 2011 LRI    UMR 8623 CNRS/Univ Paris Sud XI
//
//          Distributed under the Boost Software License, Version 1.0.
//                 See accompanying file LICENSE.txt or copy at
//                     http://www.boost.org/LICENSE_1_0.txt
//==============================================================================
#ifndef NT2_TRIGONOMETRIC_FUNCTIONS_CONTAINER_SINCOS_HPP_INCLUDED
#define NT2_TRIGONOMETRIC_FUNCTIONS_CONTAINER_SINCOS_HPP_INCLUDED
#include <nt2/trigonometric/functions/sincos.hpp>
#include <nt2/include/functions/tie.hpp>
#include <nt2/core/container/dsl.hpp>
#include <boost/dispatch/meta/terminal_of.hpp>

namespace nt2 { namespace ext
{
  BOOST_DISPATCH_IMPLEMENT  ( sincos_, tag::cpu_
                            , (A0)(A1)(A2)
                            , ((ast_<A0, nt2::container::domain>))
                              (unspecified_<A1>)
                              (unspecified_<A2>)
                            )
  {
    typedef void result_type;
    BOOST_FORCEINLINE result_type operator()(A0 const& a0,A1 & r0,A2& r1) const
    {
      nt2::tie(r0,r1) = nt2::sincos(a0);
    }
  };

  BOOST_DISPATCH_IMPLEMENT  ( sincos_, tag::cpu_
                            , (A0)(A1)
                            , ((ast_<A0, nt2::container::domain>))
                              (unspecified_<A1>)
                            )
  {
    typedef typename boost::dispatch::meta::terminal_of<A0>::type result_type;
    BOOST_FORCEINLINE result_type operator()(A0 const& a0,A1& r1) const
    {
      result_type r0;
      nt2::tie(r0,r1) = nt2::sincos(a0);
      return r0;
    }
  };
} }

#endif


//
// OpenTissue, A toolbox for physical based simulation and animation.
// Copyright (C) 2007 Department of Computer Science, University of Copenhagen
//
#include <OpenTissue/configuration.h>

#include <OpenTissue/core/math/math_basic_types.h>
#include <OpenTissue/core/containers/mesh/mesh.h>

#define BOOST_AUTO_TEST_MAIN
#include <OpenTissue/utility/utility_push_boost_filter.h>
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/unit_test_suite.hpp>
#include <boost/test/floating_point_comparison.hpp>
#include <boost/test/test_tools.hpp>
#include <OpenTissue/utility/utility_pop_boost_filter.h>

#include <cmath>

void polymesh_compile_test()
{
  typedef OpenTissue::math::BasicMathTypes<double, size_t> math_types;
  typedef math_types::vector3_type                         vector3_type;
  typedef math_types::matrix3x3_type                       matrix3x3_type;
  typedef math_types::real_type                            real_type;

  OpenTissue::polymesh::PolyMesh<math_types> mesh;

  typedef OpenTissue::polymesh::PolyMesh<> mesh_type;
  mesh_type   m_mesh;

  std::list<mesh_type> m_pieces;

  typedef OpenTissue::trimesh::TriMesh<> mesh_type2;
  mesh_type2   m_mesh2;

  {
    //--- Test Mesh                //
    //---                          //
    //---    6     5               //
    //---     +---+                //
    //---    /4\1/5\               //
    //---  1+---+0--+ 4            //
    //---    \2/6\3/               //
    //---     +---+                //
    //---    2     3               //
    //---                          //
    //--- v0 = (  0,  0, 0)        //
    //--- v1 = (-10,  0, 0)        //
    //--- v2 = ( -5,-10, 0)        //
    //--- v3 = (  5, 10, 0)        //
    //--- v4 = ( 10,  0, 0)        //
    //--- v5 = (  5, 10, 0)        //
    //--- v6 = ( -5, 10, 0)        //
    //---                          //
    m_mesh.clear();

    mesh_type::vertex_handle v0 = m_mesh.add_vertex(vector3_type(0,0,0));
    mesh_type::vertex_handle v1 = m_mesh.add_vertex(vector3_type(-10,0,0));
    mesh_type::vertex_handle v2 = m_mesh.add_vertex(vector3_type(-5,-10,0));
    mesh_type::vertex_handle v3 = m_mesh.add_vertex(vector3_type(5,-10,0));
    mesh_type::vertex_handle v4 = m_mesh.add_vertex(vector3_type(10,0,0));
    mesh_type::vertex_handle v5 = m_mesh.add_vertex(vector3_type(5,10,0));
    mesh_type::vertex_handle v6 = m_mesh.add_vertex(vector3_type(-5,10,0));
    std::vector<mesh_type::vertex_handle> vhandles(3);

    vhandles[0] = 5;
    vhandles[1] = 6;
    vhandles[2] = 0;
    mesh_type::face_handle f0 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    vhandles[0] = 1;
    vhandles[1] = 2;
    vhandles[2] = 0;
    mesh_type::face_handle f1 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    vhandles[0] = 3;
    vhandles[1] = 4;
    vhandles[2] = 0;
    mesh_type::face_handle f2 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    vhandles[0] = 6;
    vhandles[1] = 1;
    vhandles[2] = 0;
    mesh_type::face_handle f3 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    vhandles[0] = 4;
    vhandles[1] = 5;
    vhandles[2] = 0;
    mesh_type::face_handle f4 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    vhandles[0] = 2;
    vhandles[1] = 3;
    vhandles[2] = 0;
    mesh_type::face_handle f5 = m_mesh.add_face(vhandles.begin(),vhandles.end());

    m_mesh.remove_face(f5);

    m_mesh.remove_face(f4);

    m_mesh.remove_face(f3);

    m_mesh.remove_face(f2);

    m_mesh.remove_face(f1);

    m_mesh.remove_face(f0);

    m_mesh.remove_vertex(v6);

    m_mesh.remove_vertex(v5);

    m_mesh.remove_vertex(v4);

    m_mesh.remove_vertex(v3);

    m_mesh.remove_vertex(v2);

    m_mesh.remove_vertex(v1);

    m_mesh.remove_vertex(v0);

    m_mesh.clear();
  }
  {
    //--- Test Mesh                //
    //---                          //
    //---    6     5               //
    //---     +---+                //
    //---    /4\1/5\               //
    //---  1+---+0--+ 4            //
    //---    \2/6\3/               //
    //---     +---+                //
    //---    2     3               //
    //---                          //
    //--- v0 = (  0,  0, 0)        //
    //--- v1 = (-10,  0, 0)        //
    //--- v2 = ( -5,-10, 0)        //
    //--- v3 = (  5, 10, 0)        //
    //--- v4 = ( 10,  0, 0)        //
    //--- v5 = (  5, 10, 0)        //
    //--- v6 = ( -5, 10, 0)        //
    //---                          //
    m_mesh.clear();

    mesh_type::vertex_handle v0 = m_mesh.add_vertex(vector3_type(0,0,0));
    mesh_type::vertex_handle v1 = m_mesh.add_vertex(vector3_type(-10,0,0));
    mesh_type::vertex_handle v2 = m_mesh.add_vertex(vector3_type(-5,-10,0));
    mesh_type::vertex_handle v3 = m_mesh.add_vertex(vector3_type(5,-10,0));
    mesh_type::vertex_handle v4 = m_mesh.add_vertex(vector3_type(10,0,0));
    mesh_type::vertex_handle v5 = m_mesh.add_vertex(vector3_type(5,10,0));
    mesh_type::vertex_handle v6 = m_mesh.add_vertex(vector3_type(-5,10,0));
    std::vector<mesh_type::vertex_handle> vhandles(3);

    vhandles[0] = 5;
    vhandles[1] = 6;
    vhandles[2] = 0;
    mesh_type::face_handle f0 = m_mesh.add_face(vhandles.begin(),vhandles.end());
    vhandles[0] = 1;
    vhandles[1] = 2;
    vhandles[2] = 0;
    mesh_type::face_handle f1 = m_mesh.add_face(vhandles.begin(),vhandles.end());
    vhandles[0] = 3;
    vhandles[1] = 4;
    vhandles[2] = 0;
    mesh_type::face_handle f2 = m_mesh.add_face(vhandles.begin(),vhandles.end());
    vhandles[0] = 6;
    vhandles[1] = 1;
    vhandles[2] = 0;
    mesh_type::face_handle f3 = m_mesh.add_face(vhandles.begin(),vhandles.end());
    vhandles[0] = 4;
    vhandles[1] = 5;
    vhandles[2] = 0;
    mesh_type::face_handle f4 = m_mesh.add_face(vhandles.begin(),vhandles.end());
    vhandles[0] = 2;
    vhandles[1] = 3;
    vhandles[2] = 0;
    mesh_type::face_handle f5 = m_mesh.add_face(vhandles.begin(),vhandles.end());
  }
  mesh_type tmp(m_mesh);  //--- copy constructor test
  m_mesh.clear();
  m_mesh = tmp;           //--- assignment operator test

  m_mesh.clear();

  OpenTissue::mesh::make_box(1.0,1.0,1.0,m_mesh);

  std::vector<vector3_type> profile;
  profile.push_back(vector3_type(0.0,0.0,0.0));
  profile.push_back(vector3_type(5.0,0.0,5.0));
  profile.push_back(vector3_type(5.0,0.0,10.0));
  profile.push_back(vector3_type(0.0,0.0,15.0));
  OpenTissue::mesh::profile_sweep(profile.begin(),profile.end(),2*OpenTissue::math::detail::pi<real_type>(),32,m_mesh);

  OpenTissue::mesh::make_disk(5.0,2.0,12,4,m_mesh);
  OpenTissue::mesh::make_cylinder(5.0,2.0,12,m_mesh);
  OpenTissue::mesh::make_sphere(5.0,12,5,m_mesh);
  OpenTissue::mesh::translate(m_mesh,vector3_type(1,0,0));
  OpenTissue::mesh::uniform_scale(m_mesh,0.5);
  OpenTissue::mesh::scale(m_mesh,vector3_type(1,1,2));
  OpenTissue::mesh::twist(m_mesh,vector3_type(0,1,0),2.0);
  OpenTissue::mesh::bend(m_mesh,vector3_type(0,1,0),vector3_type(1,0,0),1.0);
  OpenTissue::mesh::spherical_bend(m_mesh,vector3_type(0,0,1),1.0);
  OpenTissue::mesh::make_unit(m_mesh);

  std::vector<vector3_type> points(20);
  for(unsigned int i=0;i<20;++i)
    random(points[i]);
  OpenTissue::mesh::convex_hull(points.begin(),points.end(),m_mesh);
  mesh_type::vertex_iterator end    = m_mesh.vertex_end();
  mesh_type::vertex_iterator vertex = m_mesh.vertex_begin();
  for(;vertex!=end;++vertex)
    random(vertex->m_color);

  OpenTissue::mesh::compute_angle_weighted_vertex_normals(m_mesh);

  OpenTissue::mesh::compute_mean_vertex_normals(m_mesh);
  {
    OpenTissue::geometry::Plane<math_types> Q;
    Q.n() = vector3_type(0,0,1);
    Q.w() = 0;

    mesh_type tmp(m_mesh);
    mesh_type tmp2;

    OpenTissue::mesh::plane_clipper(tmp,Q,m_mesh,tmp2);
  }

  {
    mesh_type::vertex_iterator end    = m_mesh.vertex_end();
    mesh_type::vertex_iterator vertex = m_mesh.vertex_begin();
    for(;vertex!=end;++vertex)
      random(vertex->m_color);
  }

  naive_patcher(m_mesh);

  OpenTissue::mesh::convert(m_mesh,m_mesh2);
  {
    mesh_type tmp(m_mesh);
    OpenTissue::mesh::flip(tmp,m_mesh);
  }
  {
    mesh_type::edge_iterator e = m_mesh.edge_begin();
    while(e!=m_mesh.edge_end() && !edge_flip(*e)) ++e;
  }
  {
    mesh_type::face_iterator f0 = m_mesh.face_begin();
    mesh_type::face_iterator f1 = f0->get_border_halfedge_iterator()->get_twin_iterator()->get_face_iterator();
    face_merge(*f0,*f1);
  }
  {
    mesh_type::face_iterator f = m_mesh.face_begin();
    while(f!=m_mesh.face_end() && valency(*f)==3 ) ++f;

    mesh_type::vertex_iterator v0 = f->get_border_halfedge_iterator()->get_destination_iterator();
    mesh_type::vertex_iterator v1 = f->get_border_halfedge_iterator()->get_next_iterator()->get_next_iterator()->get_destination_iterator();
    face_split(*f,*v0,*v1);
  }
  {
    mesh_type::face_iterator f = m_mesh.face_begin();
    face_subdivide(*f);
  }
  {
    mesh_type::vertex_iterator v = m_mesh.vertex_begin();
    vertex_expand(*v);
  }
  {
    mesh_type::face_iterator f = m_mesh.face_begin();
    face_collapse(*f);
  }
  {
    OpenTissue::mesh::VolumeIntegrator<mesh_type> integrator(m_mesh);

    real_type V;
    integrator.get_volume(V);

    real_type density=1.0;
    real_type M;
    integrator.get_mass(density,M);

    vector3_type r;
    integrator.get_center_of_mass(density,r);

    matrix3x3_type I;
    integrator.get_inertia_tensor(density,I);
  }
  {
    vector3_type mean;
    matrix3x3_type cov;
    OpenTissue::mesh::compute_surface_covariance(m_mesh,mean,cov);
  }

  OpenTissue::polymesh::laplacian_smooth(m_mesh,.5);
  OpenTissue::polymesh::taubin_smooth(m_mesh,1);

  {
    std::string data_path = opentissue_path;
    std::string filename = data_path + "/demos/data/obj/teapot.obj";
    OpenTissue::mesh::obj_read(filename,m_mesh);
    if(!is_manifold(m_mesh))
    {
      mesh_type manifold;
      OpenTissue::mesh::remove_redundant_vertices(m_mesh,manifold);
      if(is_manifold(manifold))
      {
        OpenTissue::mesh::default_write(filename,manifold);
      }
    }
  }
  OpenTissue::mesh::default_write("test.msh",m_mesh);
  OpenTissue::mesh::tetgen_write("test.poly",m_mesh);
  OpenTissue::mesh::obj_write("test.obj",m_mesh);
  OpenTissue::mesh::vrml_write("test.vrml",m_mesh);
  OpenTissue::mesh::obj_read("test.obj",m_mesh);
  OpenTissue::polymesh::reflex_convex_decomposition(m_mesh,m_pieces);
  OpenTissue::mesh::make_plane(.25,.25,5,5,m_mesh);

  {
    mesh_type::face_iterator fend = m_mesh.face_end();
    mesh_type::face_iterator face = m_mesh.face_begin();
    for(;face!=fend;++face)
    {
      vector3_type c,n;
      OpenTissue::mesh::compute_face_center(*face,c);
      OpenTissue::polymesh::compute_face_normal(*face,n);
    }
  }

}

BOOST_AUTO_TEST_SUITE(opentissue_mesh_polymesh);

BOOST_AUTO_TEST_CASE(compile_testing)
{
  void (*ptr)() = &polymesh_compile_test;
  ptr = 0;
}

BOOST_AUTO_TEST_SUITE_END();


#include "attack.h"

#include "../Log.h"
#include "../Config.h"

#include <cmath>

#include <boost/foreach.hpp>
#define foreach BOOST_FOREACH

int AttackPlanet(const GameState& state, PlanetPtr p, const DefenceExclusions& defence_exclusions, Player player);
int AttackPlanet(const GameState& state, PlanetPtr p, const DefenceExclusions& defence_exclusions, std::vector<Fleet>& orders, Player player);
int ScoreEdge(const GameState& state, PlanetPtr dest, PlanetPtr source, int available_ships, int source_ships, int& cost, std::vector<Fleet>& orders, Player player);
std::vector<PlanetPtr> FindTargets(const GameState& state, const DefenceExclusions& defence_exclusions, Player player);
void GreedyAttack(GameState& state, const DefenceExclusions& defence_exclusions, std::vector<PlanetPtr>& targets, Player player);
int CombinationAttack(GameState& state, const DefenceExclusions& defence_exclusions, std::vector<PlanetPtr>& targets, Player player, unsigned int=0);
int EvalState( const GameState& state );

void Attack(GameState& state, DefenceExclusions& defence_exclusions, Player player) {
    static bool attack = Config::Value<bool>("attack");
    if ( ! attack  ) return;

    LOG("Attack phase");

    std::vector<PlanetPtr> targets = FindTargets(state, defence_exclusions, player);

    CombinationAttack(state, defence_exclusions, targets, player);
}

int AttackPlanet(const GameState& state, PlanetPtr p, const DefenceExclusions& defence_exclusions, Player player) {
    std::vector<Fleet> orders;
    return AttackPlanet(state, p, defence_exclusions, orders, player);
}

// Does a cost analysis for taking over planet p and populates orders with the orders required
// to do so.
// Returns score
int AttackPlanet(const GameState& state, PlanetPtr p, const DefenceExclusions& defence_exclusions, std::vector<Fleet>& orders, Player player) {
    int p_id = p->id;

    // sort player planets in order of distance the target planet
    const std::vector<int>& all_sorted = Map::PlanetsByDistance( p_id );
    std::vector<int> my_sorted;
    foreach ( int source_id, all_sorted ) {
        if ( state.Planet(source_id)->Owner() == player ) {
            my_sorted.push_back( source_id );
        }
    }

    // determine distance to closest enemy
    PlanetPtr closest_enemy = state.ClosestPlanetByOwner( p, -player );
    int closest_enemy_distance = closest_enemy ? Map::Distance(closest_enemy->id, p_id) : INF;
    int future_owner = p->FutureOwner();

    int score = INF;
    int cost = INF;

    int available_ships = 0;
    foreach ( int source_id, my_sorted ) {
        if ( cost <= available_ships ) break;

        const PlanetPtr source = state.Planet(source_id);
        int source_ships = source->Ships(true);

        if ( future_owner == NEUTRAL ) {
            // Don't attack neutral planets closer to the enemy
            int distance = Map::Distance( source_id, p_id );
            // if ( closest_enemy_distance <= distance && p->FutureDays() < distance ) {
            if ( closest_enemy_distance <= distance ) {
                // We do not want this move to be considered AT ALL
                // So give it the highest score
                cost = INF;
                score = INF;
                break;
            }
        }

        // determine if we have any ship locking exclusion for this
        //   source/dest pair
        DefenceExclusions::const_iterator d_it = defence_exclusions.find(source_id);
        if ( d_it != defence_exclusions.end() && d_it->second.first == p_id ) {
            LOG( " Lifting defence exclusion for " << d_it->second.second << " ships on planet " << source_id );
            source_ships += d_it->second.second;
        }

        available_ships += source_ships;

        score = ScoreEdge(state, p, source, available_ships, source_ships, cost, orders, player);
    }

    // If the cost is too large then we can generate no orders
    if ( cost > available_ships ) {
        orders.clear();
    }

    if ( orders.size() > 0 ) {
        LOG( "  score of planet " << p_id << " = " << score << " (" <<  cost << ") after " << orders.back().launch << " days" );
    }
    else {
        LOG( "  not enough ships to attack " << p_id );
    }

    return score;
}

// Score one source -> dest fleet
int ScoreEdge(const GameState& state, PlanetPtr dest, PlanetPtr source, int available_ships, int source_ships, int& cost, std::vector<Fleet>& orders, Player player) {
    static double distance_scale = Config::Value<double>("cost.distance_scale");
    static double growth_scale   = Config::Value<double>("cost.growth_scale");
    static int    cost_offset    = Config::Value<int>("cost.offset");

    int source_id = source->id;
    int dest_id = dest->id;

    int future_days = dest->FutureDays();
    int future_owner = dest->FutureOwner();
    int growth_rate = dest->GrowthRate();

    int distance = Map::Distance( dest_id, source_id );
    cost = INF;
    int score = INF;
    int extra_delay = 0;

    int ships_within_range = state.ShipsWithinRange(dest, distance, -player);
    if ( distance > future_days ) {
        // easy case: we arrive after all the other fleets
        PlanetState prediction = dest->FutureState( distance );
        cost = prediction.ships;

        if ( future_owner ) {

            int score_cost = cost + ships_within_range;
            if ( dest->Owner() == -player ) cost = score_cost;

            // TODO: determine the best factor for distance
            score = (int)((double)score_cost/growth_rate/growth_scale + distance*distance_scale);
            // score = distance + distance/2;
            LOG( "  to attack " << dest_id << " from " << source_id << ": cost = " << cost << ", score_cost = " << score_cost << ", score = " << score );
        }
        else {
            // For a neutral planet:
            //   the number of days to travel to the planet
            //   + time to regain units spent
            int score_cost = cost + ships_within_range;
            score = ceil((double)score_cost/growth_rate) + distance;
        }
    }
    else {
        // hard case: we can arrive before some other ships
        // we know that this planet is (or will be) eventually be owned
        // by an enemy
        int best_score = INF;
        int best_cost = 0;

        // determine the best day to arrive on, we search up to 1 day AFTER the last fleet arrives
        for ( int arrive = future_days+1; arrive >= distance; --arrive ) {
            // TODO: Another magic param
            int cost = dest->Cost( arrive, player );
            int score_cost = cost + ships_within_range;
            if ( dest->Owner() == -player ) cost = score_cost;

            // int score = arrive + arrive/2;
            int score = (int)((double)score_cost/growth_rate/growth_scale + arrive*distance_scale);
            if ( score < best_score ) {
                best_score = score;
                extra_delay = arrive - distance;
                best_cost = cost;
            }
        }

        score = best_score;
        cost = best_cost;
    }

    cost += cost_offset;
    if ( cost < 0 ) {
        cost = 0;
    }

    int required_ships = 0;
    if ( cost > available_ships ) {
        required_ships = source_ships;
    }
    else {
        required_ships = source_ships - ( available_ships - cost );
    }

    if ( required_ships < 0 ) {
        // Fix the WTF
        LOG_ERROR( "WTF: attacking " << dest_id << ": " << cost << " " << available_ships << " " << source_ships );
    }

    orders.push_back( Fleet(player, source_id, dest_id, required_ships, extra_delay) );

    return score;
}

// Find potential targets in order of score
std::vector<PlanetPtr> FindTargets(const GameState& state, const DefenceExclusions& defence_exclusions, Player player) {
    LOG(" Finding targets");

    std::vector<PlanetPtr> planets = state.Planets();
    std::vector< std::pair<int,int> > scores;

    std::map<int,bool> targets_1 = state.FrontierPlanets(-player);
    std::map<int,bool> targets_2 = state.FutureFrontierPlanets(-player);
    for (unsigned int p_id=0; p_id<planets.size(); ++p_id) {
        const PlanetPtr p = planets[p_id];
        int growth_rate = Map::GrowthRate(p_id);

        // Ignore 0 growth planets for now
        // LATER: put it back in FOR ENEMIES ONLY
        // Beware of divide by 0
        if ( growth_rate == 0 ) {
            continue;
        }

        // Don't need to do anything if we will own the planet
        int future_owner = p->FutureOwner();
        if ( future_owner == player) {
            continue;
        }

        // If a neutral planet is closer to an enemy then ignore it
        if ( future_owner == NEUTRAL ) {

            PlanetPtr closest_enemy = state.ClosestPlanetByOwner( p, -player );
            PlanetPtr closest_me = state.ClosestPlanetByOwner( p, player );

            if ( closest_enemy && closest_me && Map::Distance( closest_enemy->id, p_id ) <= Map::Distance( closest_me->id, p_id) ) {
                continue;
            }
        }

        // If the planet is owned by an enemy and it is NOT a frontier planet then ignore
        if ( p->Owner() == -player && ! ( targets_1[p_id] || targets_2[p_id] ) ) {
            continue;
        }

        // Don't try to neutral steal planets that we would not otherwise attack
        if ( future_owner == -player && p->Owner() == NEUTRAL && ! targets_2[p_id] ) {
            continue;
        }

        int score = AttackPlanet(state, p, defence_exclusions, player);
        scores.push_back( std::pair<int,int>(score, p_id) );
    }

    // sort scores
    std::sort(scores.begin(), scores.end());

    // create the result
    std::vector<PlanetPtr> result;
    std::pair<int,int> s;
    foreach ( s, scores ) {
        result.push_back(state.Planet(s.second));
    }

    return result;
}

void GreedyAttack(GameState& state, const DefenceExclusions& defence_exclusions, std::vector<PlanetPtr>& targets, Player player) {
    LOG(" Starting attacks (greedy)");

    // start attacking planets based on score
    foreach ( PlanetPtr p, targets ) {
        // list of the orders that we will want to execute
        std::vector<Fleet> orders;

        AttackPlanet(state, p, defence_exclusions, orders, player);

        if ( orders.empty() ) {
            // This case happens when cost > available_ships

            // If we don't have enough ships to capture the next best planet then WAIT
            //  This serves a few purposes:
            //  1. Prevent us overextending our forces
            //  2. If we wait we might get the required resources later
            //  3. This helps against Rage tactics
            // Harass(state, p->id, orders);
            break;
        }

        // update delays so all fleet arrive at once
        // TODO: Try leaving this out (Might harm neutral attack/overtakes)
        const Fleet& last_order = orders.back();
        int last_arrival = last_order.launch + Map::Distance( last_order.source, last_order.dest );
        foreach ( Fleet& order, orders ) {
            order.launch = last_arrival - Map::Distance( order.source, order.dest );
        }

        // If we reached here we want to actually execute the orders
        foreach ( Fleet& order, orders ) {
            state.IssueOrder(order);
        }

    }
}

int CombinationAttack(GameState& state, const DefenceExclusions& defence_exclusions, std::vector<PlanetPtr>& targets, Player player, unsigned int i) {
    if ( i == 0 ) {
        LOG(" Starting attacks (combination)");
    }

    if ( i >= targets.size() ) {
        return EvalState(state);
    }

    GameState attack_state = state;

    int attack_eval = -INF;

    // Try attacking targets[i]
    std::vector<Fleet> orders;
    AttackPlanet(attack_state, targets[i], defence_exclusions, orders, player);
    if ( ! orders.empty() ) {
        const Fleet& last_order = orders.back();

        // update delays so that all fleets arrive at a neutral at the same time
        if ( attack_state.Planet(last_order.dest)->FutureOwner() == NEUTRAL ) {
            int last_arrival = last_order.launch + Map::Distance( last_order.source, last_order.dest );
            foreach ( Fleet& order, orders ) {
                order.launch = last_arrival - Map::Distance( order.source, order.dest );
            }
        }

        // If we reached here we want to actually execute the orders
        foreach ( Fleet& order, orders ) {
            attack_state.IssueOrder(order);
        }

        attack_eval = CombinationAttack(attack_state, defence_exclusions, targets, player, i+1);
    }

    // Try not attacking targets[i]
    int eval = CombinationAttack(state, defence_exclusions, targets, player, i+1);

    LOG( "  " << i << ": attack_state " << attack_eval << ", state " << eval );
    // update with most valuable state
    if ( attack_eval > eval ) {
        state = attack_state;
        eval = attack_eval;
    }

    return eval;
}

int EvalState( const GameState& state ) {
    int score = 0;
    foreach ( const PlanetPtr& p, state.Planets() ) {
        PlanetState s = p->FutureState(50);
        score += s.owner * s.ships;
    }
    return score;
}


#define PY_ARRAY_UNIQUE_SYMBOL pbcvt_ARRAY_API

#define NUMPY_IMPORT_ARRAY_RETVAL NULL

#include <boost/python.hpp>
#include <pyboostcvconverter/pyboostcvconverter.hpp>
#include <opencv2/features2d.hpp>
#include <opencv2/highgui.hpp>
#include "sift.simd.hpp"
#include <iostream>

#include <algorithm>

const float sigma = 1.6;
const int nOctaveLayers = 3;

std::string getImageType(int number)
{
    // find type
    int imgTypeInt = number%8;
    std::string imgTypeString;

    switch (imgTypeInt)
    {
        case 0:
            imgTypeString = "8U";
            break;
        case 1:
            imgTypeString = "8S";
            break;
        case 2:
            imgTypeString = "16U";
            break;
        case 3:
            imgTypeString = "16S";
            break;
        case 4:
            imgTypeString = "32S";
            break;
        case 5:
            imgTypeString = "32F";
            break;
        case 6:
            imgTypeString = "64F";
            break;
        default:
            break;
    }

    // find channel
    int channel = (number/8) + 1;

    std::stringstream type;
    type<<"CV_"<<imgTypeString<<"C"<<channel;

    return type.str();
}

static cv::Mat createInitialImage( const cv::Mat& img, bool doubleImageSize, float sigma )
{
    CV_TRACE_FUNCTION();

    cv::Mat gray, gray_fpt;
    if( img.channels() == 3 || img.channels() == 4 )
    {
        cv::cvtColor(img, gray, cv::COLOR_BGR2GRAY);
        gray.convertTo(gray_fpt, cv::DataType<cv::sift_wt>::type, cv::SIFT_FIXPT_SCALE, 0);
    }
    else
        img.convertTo(gray_fpt, cv::DataType<cv::sift_wt>::type, cv::SIFT_FIXPT_SCALE, 0);

    float sig_diff;

    if( doubleImageSize )
    {
        sig_diff = sqrtf( std::max(sigma * sigma - cv::SIFT_INIT_SIGMA * cv::SIFT_INIT_SIGMA * 4, 0.01f) );
        cv::Mat dbl;
#if DoG_TYPE_SHORT
        cv::resize(gray_fpt, dbl, cv::Size(gray_fpt.cols*2, gray_fpt.rows*2), 0, 0, cv::INTER_LINEAR_EXACT);
#else
        cv::resize(gray_fpt, dbl, cv::Size(gray_fpt.cols*2, gray_fpt.rows*2), 0, 0, cv::INTER_LINEAR);
#endif
        cv::Mat result;
        cv::GaussianBlur(dbl, result, cv::Size(), sig_diff, sig_diff);
        return result;
    }
    else
    {
        sig_diff = sqrtf( std::max(sigma * sigma - cv::SIFT_INIT_SIGMA * cv::SIFT_INIT_SIGMA, 0.01f) );
        cv::Mat result;
        cv::GaussianBlur(gray_fpt, result, cv::Size(), sig_diff, sig_diff);
        return result;
    }
}

std::vector<cv::Mat>
build_image_pyramid(cv::Mat im, int firstOctave, int nOctaves) {

    // Copyright (c) 2006-2010, Rob Hess <hess@eecs.oregonstate.edu>
    // Copyright (C) 2009, Willow Garage Inc., all rights reserved.
    // Copyright (C) 2020, Intel Corporation, all rights reserved.
    cv::Mat base = createInitialImage(im, firstOctave<0, sigma);
    printf("%i, %i\n", base.rows, base.cols);
    CV_TRACE_FUNCTION();
    std::vector<cv::Mat> gauss_pyr;

    std::vector<double> sig(nOctaveLayers + 3);
    gauss_pyr.resize(nOctaves*(nOctaveLayers + 3));

    // precompute Gaussian sigmas using the following formula:
    //  \sigma_{total}^2 = \sigma_{i}^2 + \sigma_{i-1}^2
    sig[0] = sigma;
    double k = std::pow( 2., 1. / nOctaveLayers );
    for( int i = 1; i < nOctaveLayers + 3; i++ )
    {
        double sig_prev = std::pow(k, (double)(i-1))*sigma;
        double sig_total = sig_prev*k;
        sig[i] = std::sqrt(sig_total*sig_total - sig_prev*sig_prev);
    }

    for( int o = 0; o < nOctaves; o++ )
    {
        for( int i = 0; i < nOctaveLayers + 3; i++ )
        {
            cv::Mat& dst = gauss_pyr[o*(nOctaveLayers + 3) + i];
            if( o == 0  &&  i == 0 )
                dst = base;
            // base of new octave is halved image from end of previous octave
            else if( i == 0 )
            {
                const cv::Mat& src = gauss_pyr[(o-1)*(nOctaveLayers + 3) + nOctaveLayers];
                cv::resize(src, dst, cv::Size(src.cols/2, src.rows/2),
                       0, 0, cv::INTER_NEAREST);
            }
            else
            {
                const cv::Mat& src = gauss_pyr[o*(nOctaveLayers + 3) + i-1];
                cv::GaussianBlur(src, dst, cv::Size(), sig[i], sig[i]);
            }
        }
    }
    return gauss_pyr;
}

namespace pbcvt {

  using namespace boost::python;
  PyObject *dog_pyramid(PyObject *impy, int firstOctave, int nOctaves) {
    cv::Mat im;
    im = pbcvt::fromNDArrayToMat(impy);
    std::vector<cv::Mat> dogpyr;
    dogpyr.resize(nOctaves * (nOctaveLayers + 2));
    std::vector<cv::Mat> gauss_pyr = build_image_pyramid(im, firstOctave, nOctaves);
    for( int a=0; a < nOctaves * (nOctaveLayers + 2); a++ ) {
      const int o = a / (nOctaveLayers + 2);
      const int i = a % (nOctaveLayers + 2);

      const cv::Mat& src1 = gauss_pyr[o*(nOctaveLayers + 3) + i];
      const cv::Mat& src2 = gauss_pyr[o*(nOctaveLayers + 3) + i + 1];
      cv::Mat& dst = dogpyr[o*(nOctaveLayers + 2) + i];
      cv::subtract(src2, src1, dst, noArray(), DataType<sift_wt>::type);
    }
    PyObject *ret = PyList_New(dogpyr.size());
    for (int i=0; i<dogpyr.size(); i++) {
      PyObject* layer = pbcvt::fromMatToNDArray(dogpyr.at(i));
      PyList_SetItem(ret, i, layer);
    }
    return ret;
  }

  PyObject *image_pyramid(PyObject *impy, int firstOctave, int nOctaves) {
    cv::Mat im;
    im = pbcvt::fromNDArrayToMat(impy);
    std::vector<cv::Mat> gauss_pyr = build_image_pyramid(im, firstOctave, nOctaves);
    // cv::Mat out_kpts_t;
    // cv::vconcat(out_kpts, out_kpts_t);
    // PyObject *ret = pbcvt::fromMatToNDArray(out_kpts_t);
    PyObject *ret = PyList_New(gauss_pyr.size());
    for (int i=0; i<gauss_pyr.size(); i++) {
      PyObject* layer = pbcvt::fromMatToNDArray(gauss_pyr.at(i));
      PyList_SetItem(ret, i, layer);
    }
    return ret;
  }

  PyObject *sift_desc(PyObject *impy, PyObject *keypoints, int firstOctave, int nOctaves) {
    // IDK if this is clear but there is a lot of opencv code in here

    cv::Mat im, kpts;
    im = pbcvt::fromNDArrayToMat(impy);
    kpts = pbcvt::fromNDArrayToMat(keypoints);
    // First, build a gauss_pyr
    std::vector<cv::Mat> gauss_pyr = build_image_pyramid(im, firstOctave, nOctaves);

    std::vector<cv::Mat> out_kpts;

    // Compute orientation of keypoints
    for (int i=0; i<kpts.rows; i++) {
      // Get attributes of keypoint
      float x = kpts.at<float>(i, 0);
      float y = kpts.at<float>(i, 1);
      float size = kpts.at<float>(i, 2);
      int layer = kpts.at<float>(i, 3);
      int octave = kpts.at<float>(i, 4) - firstOctave;
      float c = x / (1 << octave);
      float r = y / (1 << octave);
      if (firstOctave < 0) {
        size *= (1 << -firstOctave);
        c *= (1 << -firstOctave);
        r *= (1 << -firstOctave);
      }

      // Init variables
      int n = cv::SIFT_ORI_HIST_BINS;
      float hist[n];
      float scl_octv = size*0.5f/(1 << octave);

      // std::cout << gauss_pyr[octave*(nOctaveLayers+3) + layer].depth() << std::endl;
      // cv::imshow("HI", gauss_pyr[octave*(nOctaveLayers+3) + layer]);
      // cv::waitKey(0);
      float omax = cv::opt_CV_CPU_DISPATCH_MODE::calcOrientationHist(
          gauss_pyr[octave*(nOctaveLayers+3) + layer],
          cv::Point(c, r),
          cvRound(cv::SIFT_ORI_RADIUS * scl_octv),
          cv::SIFT_ORI_SIG_FCTR * scl_octv,
          hist, n);

      float mag_thr = (float)(omax * cv::SIFT_ORI_PEAK_RATIO);
      for( int j = 0; j < n; j++ )
      {
        int l = j > 0 ? j - 1 : n - 1;
        int r2 = j < n-1 ? j + 1 : 0;

        if( hist[j] > hist[l]  &&  hist[j] > hist[r2]  &&  hist[j] >= mag_thr )
        {
          float bin = j + 0.5f * (hist[l]-hist[r2]) / (hist[l] - 2*hist[j] + hist[r2]);
          bin = bin < 0 ? n + bin : bin >= n ? bin - n : bin;
          float angle = 360.f - (float)((360.f/n) * bin);
          if(std::abs(angle - 360.f) < FLT_EPSILON)
            angle = 0.f;

          cv::Mat kpt = cv::Mat::zeros(1, 6, CV_32F);
          kpt.at<float>(0, 0) = x;
          kpt.at<float>(0, 1) = y;
          kpt.at<float>(0, 2) = size;
          kpt.at<float>(0, 3) = layer;
          kpt.at<float>(0, 4) = octave;
          kpt.at<float>(0, 5) = angle;
          if (firstOctave < 0) {
            kpt.at<float>(0, 2) /= (1 << -firstOctave);
            kpt.at<float>(0, 4) += firstOctave;
          }
          out_kpts.push_back(kpt);
        }
      }
    }

    cv::Mat out_kpts_t;
    cv::vconcat(out_kpts, out_kpts_t);
    PyObject *ret = pbcvt::fromMatToNDArray(out_kpts_t);
    return ret;
  }

#if (PY_VERSION_HEX >= 0x03000000)

  static void *init_ar() {
#else
    static void init_ar(){
#endif
      Py_Initialize();

      import_array();
      return NUMPY_IMPORT_ARRAY_RETVAL;
    }

    BOOST_PYTHON_MODULE (sift_ori) {
      //using namespace XM;
      init_ar();

      //initialize converters
      to_python_converter<cv::Mat,pbcvt::matToNDArrayBoostConverter>();
      matFromNDArrayBoostConverter();

      //expose module-level functions
      def("sift_desc", sift_desc);
      def("image_pyramid", image_pyramid);
      def("dog_pyramid", dog_pyramid);
    }

  } //end namespace pbcvt


/*  This file is part of libDAI - http://www.libdai.org/
 *
 *  Copyright (c) 2006-2011, The libDAI authors. All rights reserved.
 *
 *  Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
 */

#include <dai/util.h>
#include <boost/random.hpp>

#ifdef WINDOWS
    #include <windows.h>
    #include <boost/math/special_functions/atanh.hpp>  // for atanh
    #include <boost/math/special_functions/log1p.hpp>  // for log1p
    #include <float.h>  // for _isnan
#else
    // Assume POSIX compliant system. We need the following for querying the system time
    #include <sys/time.h>
#endif

#ifdef WINDOWS
double atanh( double x ) {
    return boost::math::atanh( x );
}
double log1p( double x ) {
    return boost::math::log1p( x );
}
#endif

namespace dai {

#if defined CYGWIN
bool isnan( Real x ) {
    return __isnand( x );  // isnan() is a macro in Cygwin (as required by C99)
}
#elif defined WINDOWS
bool isnan( Real x ) {
    return _isnan( x );
}
#else
bool isnan( Real x ) {
    return std::isnan( x );
}
#endif

// Returns user+system time in seconds
double toc() {
#ifdef WINDOWS
    SYSTEMTIME tbuf;
    GetSystemTime(&tbuf);
    return( (double)(tbuf.wHour*3600.0+tbuf.wMinute*60.0+tbuf.wSecond + (double)tbuf.wMilliseconds / 1000.0) );
#else
    struct timeval tv;
    struct timezone tz;
    gettimeofday( &tv, &tz );
    return( (double)(tv.tv_sec + (double)tv.tv_usec / 1000000.0) );
#endif
}

/// Type of global random number generator
typedef boost::mt19937 _rnd_gen_type;

/// Global random number generator
_rnd_gen_type _rnd_gen(42U);

/// Uniform distribution with values between 0 and 1 (0 inclusive, 1 exclusive).
boost::uniform_real<Real> _uni_dist(0,1);

/// Normal distribution with mean 0 and standard deviation 1.
boost::normal_distribution<Real> _normal_dist;

/// Global uniform random random number
boost::variate_generator<_rnd_gen_type&, boost::uniform_real<Real> > _uni_rnd(_rnd_gen, _uni_dist);

/// Global random number generator with standard normal distribution
boost::variate_generator<_rnd_gen_type&, boost::normal_distribution<Real> > _normal_rnd(_rnd_gen, _normal_dist);

void rnd_seed( size_t seed ) {
    _rnd_gen.seed( static_cast<unsigned int>(seed) );
    _normal_rnd.distribution().reset(); // needed for clearing the cache used in boost::normal_distribution
}

Real rnd_uniform() {
    return _uni_rnd();
}

Real rnd_stdnormal() {
    return _normal_rnd();
}

int rnd_int( int min, int max ) {
    return (int)floor(_uni_rnd() * (max + 1 - min) + min);
}

std::vector<std::string> tokenizeString( const std::string& s, bool singleDelim, const std::string& delim ) {
    using namespace std;
    vector<string> tokens;

    string::size_type start = 0;
    while( start <= s.size() ) {
        string::size_type end = s.find_first_of( delim, start );
        if( end == string::npos )
            end = s.size();

        if( end == start && !singleDelim ) {
            // skip to next non-delimiter
            start = s.find_first_not_of( delim, start );
            if( start == string::npos )
                break;
        } else { // we found a token
            tokens.push_back( s.substr(start, end - start) );
            start = end + 1;
        }
    }

    return tokens;
}

} // end of namespace dai


#pragma once
#include <geometry_msgs/Point.h>
#include <mil_blueview_driver/BlueViewPing.h>
#include <nav_msgs/OccupancyGrid.h>
#include <ros/ros.h>

#include <tf/transform_listener.h>
#include <tf2/convert.h>
#include <tf2_geometry_msgs/tf2_geometry_msgs.h>
#include <tf2_msgs/TFMessage.h>
#include <tf2_ros/transform_listener.h>
#include <tf2_sensor_msgs/tf2_sensor_msgs.h>

#include <opencv2/core/core.hpp>
#include <stdexcept>
#include "opencv2/opencv.hpp"

#include <pcl/common/common.h>
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl_ros/point_cloud.h>
#include <visualization_msgs/Marker.h>
#include <visualization_msgs/MarkerArray.h>

#include <boost/circular_buffer.hpp>

#include <sub8_msgs/Bounds.h>

#include <waypoint_validity.hpp>

#include <Classification.hpp>

#include <mil_msgs/ObjectDBQuery.h>
#include <mil_msgs/PerceptionObject.h>
#include <mil_msgs/PerceptionObjectArray.h>
#include <mil_msgs/RangeStamped.h>
#include <std_srvs/Trigger.h>
#include <ros_alarms/listener.hpp>

extern struct ogrid_param
{
  bool ogrid;
  // Statistical Outlier Removal
  float statistical_mean_k;
  float statistical_stddev_mul_thresh;
  // Euclidian Clustering
  float cluster_tolerance_m;
  float cluster_min_num_points;
  float cluster_max_num_points;
  // Remmove points below threshold
  float nearby_threshold;
  // Remove points below depth in map frame
  float depth;
  // Debugging
  bool debug;
} params;

class OGridGen

{
public:
  OGridGen();
  void publish_big_pointcloud(const ros::TimerEvent &);

  void callback(const mil_blueview_driver::BlueViewPingPtr &ping_msg);

  void dvl_callback(const mil_msgs::RangeStampedConstPtr &dvl);

  bool clear_ogrid_callback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res);

  bool clear_pcl_callback(std_srvs::Trigger::Request &req, std_srvs::Trigger::Response &res);

  bool get_objects_callback(mil_msgs::ObjectDBQuery::Request &req, mil_msgs::ObjectDBQuery::Response &res);

  // Publish mat_ogrid
  void publish_ogrid();
  // Project point_cloud and make a persistant ogrid
  void process_persistant_ogrid(pcl::PointCloud<pcl::PointXYZI>::Ptr point_cloud_plane);
  // Convert persistant ogrid to a mat_ogrid
  void populate_mat_ogrid();

  mil_msgs::PerceptionObjectArray cluster(pcl::PointCloud<pcl::PointXYZI>::Ptr pc);

private:
  ros::NodeHandle nh_;
  ros::Subscriber sub_to_imaging_sonar_;
  ros::Subscriber sub_to_dvl_;

  tf::TransformListener listener_;

  ros_alarms::AlarmListener<> kill_listener_;
  // remember if the sub was killed or not in order to reset ogrid origin
  bool was_killed_;
  // where the ogrid is with respect to map frame
  cv::Point mat_origin_;

  // Publish ogrid and pointclouds
  ros::Publisher pub_grid_;
  ros::Publisher pub_point_cloud_filtered_;
  ros::Publisher pub_point_cloud_raw_;
  ros::Publisher pub_point_cloud_plane_;
  ros::Publisher pub_markers_;
  ros::Publisher pub_objects_;

  ros::ServiceServer clear_pcl_service_;
  ros::ServiceServer clear_ogrid_service_;
  ros::ServiceServer get_objects_service_;
  ros::Timer timer_;

  // A CV_32F Mat to store probability of occupied/unoccupied spaces
  cv::Mat persistant_ogrid_;
  float hit_prob_;

  cv::Mat mat_ogrid_;
  float ogrid_size_;
  float resolution_;
  double dvl_range_;
  int min_intensity_;

  ros::ServiceClient service_get_bounds_;
  tf::StampedTransform transform_;

  // Storage container for the pointcloud
  boost::circular_buffer<pcl::PointXYZI> point_cloud_buffer_;
  pcl::PointCloud<pcl::PointXYZI>::Ptr pointCloud_;

  std::vector<cv::Point> bounds_;

  Classification classification_;
};


//==================================================================================================
/*!
  @file

  @copyright 2016 NumScale SAS
  @copyright 2016 J.T. Lapreste

  Distributed under the Boost Software License, Version 1.0.
  (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)
*/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_GROUPS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_COMMON_SIMD_FUNCTION_GROUPS_HPP_INCLUDED
#include <boost/simd/detail/overload.hpp>

#include <boost/simd/meta/hierarchy/simd.hpp>
#include <boost/simd/function/simd/extract.hpp>
#include <boost/simd/function/simd/group.hpp>
#include <boost/simd/function/simd/saturate.hpp>
#include <boost/simd/function/simd/slice.hpp>
#include <boost/dispatch/meta/downgrade.hpp>
#include <boost/mpl/not.hpp>

namespace boost { namespace simd { namespace ext
{
   namespace bd = boost::dispatch;
   namespace bs = boost::simd;
//    BOOST_DISPATCH_OVERLOAD(groups_

//                           , (typename A0, typename X)
//                           , bd::cpu_
//                           , bs::pack_<bd::arithmetic_<A0>, X>
//                           )
//    {
//   using  = ;
//       BOOST_FORCEINLINE result operator()( const A0& a0) const BOOST_NOEXCEPT
//       {
//         return eval(a0, boost::is_same<A0, result>());
//       }
//       BOOST_FORCEINLINE
//       result eval(A0 const& a0, boost::mpl::false_ const&) const
//       {
//         return eval2(a0, boost::mpl::bool_<(A0::static_size>=4)>());
//       }
//       BOOST_FORCEINLINE
//       result eval(A0 const& a0, boost::mpl::true_ const&) const
//       {
//         return a0;
//       }
//       BOOST_FORCEINLINE
//       result eval2(A0 const& a0, boost::mpl::true_ const&) const
//       {
//         typename simd::meta::vector_of< typename A0::value_type
//                                       , A0::static_size/2
//                                       >::type a00,a01;
//         bs::slice(a0,a00,a01);
//         return bs::groups(a00,a01);
//       }
//       BOOST_FORCEINLINE
//       result eval2(A0 const& a0, boost::mpl::false_ const&) const
//       {
//         return make<result>( groups( extract<0>(a0) )
//                                 , groups( extract<1>(a0) )
//                                 );
//       }
//    };

//    BOOST_DISPATCH_OVERLOAD_IF(groups_
//                              , (typename A0, typename X)
//                              , bd::cpu_
//                              , bs::pack_<bd::arithmetic_<A0>, X>
//                              , bs::pack_<bd::arithmetic_<A0>, X>
//                              )
//    {
//       using result = bd::downgrade_t<A0>;
//       BOOST_FORCEINLINE result operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
//       {
//         return group(saturate<result>(a0), saturate<result>(a1));
//       }
//    };

//    BOOST_DISPATCH_OVERLOAD_IF(groups_
//                              , (typename A0, typename X)
//                              , bd::cpu_
//                              , bs::pack_<bd::floating_<A0>, X>
//                              , bs::pack_<bd::floating_<A0>, X>
//                              )
//    {
//       using result = bd::downgrade_t<A0>;
//       BOOST_FORCEINLINE result operator()( const A0& a0, const A0& a1) const BOOST_NOEXCEPT
//       {
//         return group(a0, a1);
//       }
//    };

//    BOOST_DISPATCH_OVERLOAD(groups_
//                           , (typename A0, typename X)
//                           , bd::cpu_
//                           , bs::pack_<bd::floating_<A0>, X>
//                           )
//    {
//   using  = ;
//       BOOST_FORCEINLINE result operator()( const A0& a0) const BOOST_NOEXCEPT
//       {
//         return group(a0);
//       }
//    };

} } }

#endif