/**
 * @file   test_MISColoring.cpp
 * @brief  test MIS based coloring algorithm @ref limbo::algorithms::coloring::MISColoring
 * @author Yibo Lin
 * @date   Feb 2015
 */

#include <iostream>
#include <fstream>
#include <string>
#include <boost/graph/graphviz.hpp>
#include <boost/graph/graph_utility.hpp>
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/undirected_graph.hpp>
#include <limbo/algorithms/coloring/MISColoring.h>
#include <boost/graph/erdos_renyi_generator.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/graph/random.hpp>
#include <boost/graph/iteration_macros.hpp>

#include <boost/version.hpp>
#if BOOST_VERSION <= 14601
#include <boost/graph/detail/is_same.hpp>
#else
#include <boost/type_traits/is_same.hpp>
#endif

using std::cout;
using std::endl;
using std::ifstream;
using std::ofstream;
using std::string;
using std::pair;
using namespace boost;

/// @nowarn
// do not use setS, it does not compile for subgraph
// do not use custom property tags, it does not compile for most utilities
typedef adjacency_list<vecS, vecS, undirectedS,
		property<vertex_index_t, std::size_t, property<vertex_color_t, int> >,
		property<edge_index_t, std::size_t, property<edge_weight_t, int> >,
		property<graph_name_t, string> > graph_type;
typedef subgraph<graph_type> subgraph_type;
typedef property<vertex_index_t, std::size_t> VertexId;
typedef property<edge_index_t, std::size_t> EdgeID;
typedef graph_traits<graph_type>::vertex_descriptor vertex_descriptor;
typedef graph_traits<graph_type>::edge_descriptor edge_descriptor;
typedef property_map<graph_type, edge_weight_t>::type edge_weight_map_type;
typedef property_map<graph_type, vertex_color_t>::type vertex_color_map_type;
/// @endnowarn

/// test 1: a random graph
void randomGraph()
{
	mt19937 gen;
	graph_type g;
	int N = 40;
	std::vector<vertex_descriptor> vertex_set;
	std::vector< std::pair<vertex_descriptor, vertex_descriptor> > edge_set;
	generate_random_graph(g, N, N * 2, gen,
			std::back_inserter(vertex_set),
			std::back_inserter(edge_set));
	BOOST_AUTO(edge_weight_map, get(edge_weight, g));
	unsigned int i = 0;
	graph_traits<graph_type>::edge_iterator eit, eit_end;
	for (tie(eit, eit_end) = edges(g); eit != eit_end; ++eit, ++i)
	{
        edge_weight_map[*eit] = 1;
	}

	//test relaxed LP based coloring
	limbo::algorithms::coloring::MISColoring<graph_type> lc (g);
	lc.stitch_weight(0.1);
	// THREE or FOUR
	lc.color_num(limbo::algorithms::coloring::MISColoring<graph_type>::THREE);
	double cost = lc();
    cout << "final cost = " << cost << endl;
}

/// test 2: a real graph from input
/// @param filename input file in graphviz format
void realGraph(string const& filename)
{
	ifstream in (filename.c_str());

	// the graphviz reader in boost cannot specify vertex_index_t
	// I have to create a temporary graph and then construct the real graph
	typedef adjacency_list<vecS, vecS, undirectedS,
			property<vertex_index_t, std::size_t, property<vertex_color_t, int, property<vertex_name_t, std::size_t> > >,
			property<edge_index_t, std::size_t, property<edge_weight_t, int> >,
			property<graph_name_t, string> > tmp_graph_type;
	tmp_graph_type tmpg;
	dynamic_properties tmpdp;
	tmpdp.property("node_id", get(vertex_name, tmpg));
	tmpdp.property("label", get(vertex_name, tmpg));
	tmpdp.property("weight", get(edge_weight, tmpg));
	tmpdp.property("label", get(edge_weight, tmpg));
	assert(read_graphviz(in, tmpg, tmpdp, "node_id"));

	// real graph
	graph_type g (num_vertices(tmpg));
	graph_traits<tmp_graph_type>::vertex_iterator vit, vit_end;
	for (tie(vit, vit_end) = vertices(tmpg); vit != vit_end; ++vit)
	{
		size_t name  = get(vertex_name, tmpg, *vit);
		int color = get(vertex_color, tmpg, *vit);
		put(vertex_color, g, (vertex_descriptor)name, color);
	}

	graph_traits<tmp_graph_type>::edge_iterator eit, eit_end;
	for (tie(eit, eit_end) = edges(tmpg); eit != eit_end; ++eit)
	{
		size_t s_name = get(vertex_name, tmpg, source(*eit, tmpg));
		size_t t_name = get(vertex_name, tmpg, target(*eit, tmpg));
		pair<edge_descriptor, bool> pe = add_edge(s_name, t_name, g);
		assert(pe.second);
		int weight = get(edge_weight, g, *eit);
		put(edge_weight, g, pe.first, weight);
	}

#ifdef DEBUG_MISCOLORING
	dynamic_properties dp;
	dp.property("id", get(vertex_index, g));
	dp.property("node_id", get(vertex_index, g));
	dp.property("label", get(vertex_index, g));
	dp.property("weight", get(edge_weight, g));
	dp.property("label", get(edge_weight, g));
	ofstream out ("graph_init.gv");
	write_graphviz_dp(out, g, dp, string("id"));
	out.close();
	system("dot -Tpdf graph_init.gv -o graph_init.pdf");
#endif

	//test relaxed LP based coloring
	limbo::algorithms::coloring::MISColoring<graph_type> lc (g);
    // stitch is not supported
	// THREE or FOUR
	lc.color_num(limbo::algorithms::coloring::MISColoring<graph_type>::FOUR);
	double cost = lc();
    cout << "final cost = " << cost << endl;

	in.close();
}

/// main function \n
/// test either on random graph or real graph
/// @param argc number of arguments
/// @param argv values of arguments
/// @return 0
int main(int argc, char** argv)
{
	if (argc < 2)
	{
		randomGraph();
	}
	else realGraph(argv[1]);

	return 0;
}


// Copyright David Stone 2020.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <tm/clients/pokemon_online/read_team_file.hpp>

#include <tm/clients/pokemon_online/conversion.hpp>
#include <tm/clients/pokemon_online/invalid_team_file.hpp>

#include <tm/stat/generic_stats.hpp>
#include <tm/stat/ingame_id_to_nature.hpp>
#include <tm/stat/ev.hpp>
#include <tm/stat/iv.hpp>

#include <tm/constant_generation.hpp>
#include <tm/generation.hpp>
#include <tm/team.hpp>

#include <containers/integer_range.hpp>

#include <numeric_traits/min_max_value.hpp>

#include <operators/arrow.hpp>

#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/xml_parser.hpp>

#include <filesystem>

namespace technicalmachine::po {
namespace {
using namespace std::string_view_literals;

struct CheckedIterator : operators::arrow<CheckedIterator> {
	explicit CheckedIterator(boost::property_tree::ptree const & pt):
		m_it(pt.begin()),
		m_last(pt.end())
	{
	}

	auto const & advance(std::string_view const expected_key) & {
		++m_it;
		if (m_it == m_last) {
			throw std::runtime_error("Incomplete PO team file\n");
		}
		auto const & value = *m_it;
		if (value.first != expected_key) {
			throw InvalidTeamFile(expected_key, value.first);
		}
		return value.second;
	}
private:
	boost::property_tree::ptree::const_iterator m_it;
	boost::property_tree::ptree::const_iterator m_last;
};

auto parse_species(boost::property_tree::ptree const & pt) -> bounded::optional<SpeciesIDs::ID> {
	// Pokemon Online gives Missingno. the ID 0, and uses that to represent the
	// empty slots in teams smaller than 6 Pokemon.
	try {
		return pt.get<SpeciesIDs::ID>("<xmlattr>.Num");
	} catch (std::range_error const &) {
		return bounded::none;
	}
}

auto number_of_pokemon(boost::property_tree::ptree const & pt) -> TeamSize {
	auto pokemon_count = TeamSize(0_bi);
	for (auto const & value : pt) {
		if (value.first == "Pokemon" and parse_species(value.second)) {
			if (pokemon_count == numeric_traits::max_value<TeamSize>) {
				throw std::runtime_error("Attempted to add too many Pokemon");
			}
			++pokemon_count;
		}
	}
	return pokemon_count;
}

auto parse_moves(Generation const generation, CheckedIterator it) {
	struct Parsed {
		RegularMoves moves;
		CheckedIterator it;
	};
	auto moves = RegularMoves();
	for (auto const n [[maybe_unused]] : containers::integer_range(4_bi)) {
		auto const & value = it.advance("Move");
		// TODO: return optional
		using ReadMoveID = bounded::integer<0, static_cast<int>(numeric_traits::max_value<MoveID>)>;
		auto const move_id = value.get_value<ReadMoveID>();
		if (move_id != 0_bi) {
			moves.push_back(Move(generation, id_to_move(MoveID(move_id))));
		}
	}
	return Parsed{moves, it};
}

template<typename Type>
auto parse_stats(std::string_view const name, CheckedIterator it) {
	struct Parsed {
		GenericStats<Type> stats;
		CheckedIterator it;
	};
	auto get_next = [&]{
		auto const & value = it.advance(name);
		return Type(value.get_value<typename Type::value_type>());
	};
	return Parsed{
		{
			get_next(),
			get_next(),
			get_next(),
			get_next(),
			get_next(),
			get_next(),
		},
		it
	};
}

template<Generation generation>
auto parse_dvs_or_ivs(CheckedIterator it) {
	constexpr auto name = std::string_view("DV");
	if constexpr (generation <= Generation::two) {
		struct Parsed {
			DVs stats;
			CheckedIterator it;
		};
		auto get_next = [&]{
			auto const & value = it.advance(name);
			return DV(value.get_value<DV::value_type>());
		};
		auto const hp = get_next();
		auto const atk = get_next();
		auto const def = get_next();
		auto const spc = get_next();
		get_next();
		auto const spe = get_next();
		auto const dvs = DVs(atk, def, spe, spc);
		if (dvs.hp() != hp) {
			throw std::runtime_error("Stored HP DV does not match calculated HP DV.");
		}
		return Parsed{dvs, it};
	} else {
		return parse_stats<IV>(name, it);
	}
}

template<Generation generation>
auto parse_evs(CheckedIterator it) {
	constexpr auto name = std::string_view("EV");
	if constexpr (generation <= Generation::two) {
		struct Parsed {
			OldGenEVs stats;
			CheckedIterator it;
		};
		auto get_next = [&]{
			auto const & value = it.advance(name);
			return EV(value.get_value<EV::value_type>());
		};
		auto const hp = get_next();
		auto const atk = get_next();
		auto const def = get_next();
		auto const spc = get_next();
		get_next();
		auto const spe = get_next();
		auto const evs = OldGenEVs(hp, atk, def, spe, spc);
		return Parsed{evs, it};
	} else {
		return parse_stats<EV>(name, it);
	}
}

template<Generation generation>
auto parse_pokemon(boost::property_tree::ptree const & pt, SpeciesIDs::ID species_id) {
	auto const species = id_to_species({ species_id, pt.get<SpeciesIDs::Forme>("<xmlattr>.Forme")} );
	// auto const nickname = pt.get<std::string>("<xmlattr>.Nickname");
	auto const gender = id_to_gender(pt.get<GenderID>("<xmlattr>.Gender"));
	auto const level = Level(pt.get<Level::value_type>("<xmlattr>.Lvl"));
	auto const happiness = Happiness(pt.get<Happiness::value_type>("<xmlattr>.Happiness"));
	auto const item = id_to_item(pt.get<ItemID>("<xmlattr>.Item"));
	auto const ability = id_to_ability(pt.get<AbilityID>("<xmlattr>.Ability"));
	auto const nature = ingame_id_to_nature(pt.get<IngameNatureID>("<xmlattr>.Nature"));

	auto const it = CheckedIterator(pt.get_child(""));
	auto const parsed_moves = parse_moves(generation, it);

	auto const dvs_or_ivs = parse_dvs_or_ivs<generation>(parsed_moves.it);
	auto const evs = parse_evs<generation>(dvs_or_ivs.it);

	return Pokemon<generation>(
		species,
		level,
		gender,
		item,
		ability,
		CombinedStats<generation>{nature, dvs_or_ivs.stats, evs.stats},
		parsed_moves.moves,
		happiness
	);
}

template<Generation generation>
auto parse_team(boost::property_tree::ptree const & pt) {
	constexpr bool is_me = true;
	auto team = Team<generation>(number_of_pokemon(pt), is_me);
	for (auto const & value : pt) {
		if (value.first != "Pokemon") {
			continue;
		}
		if (auto const species = parse_species(value.second)) {
			team.add_pokemon(parse_pokemon<generation>(value.second, *species));
		}
	}
	team.all_pokemon().set_index(0_bi);
	return team;
}

} // namespace

auto read_team_file(std::filesystem::path const & team_file) -> GenerationGeneric<Team> {
	try {
		auto pt = boost::property_tree::ptree();
		read_xml(team_file.string(), pt);

		auto const all_pokemon = pt.get_child("Team");
		using GenerationInteger = bounded::integer<1, 7>;
		auto const parsed_generation = static_cast<Generation>(all_pokemon.get<GenerationInteger>("<xmlattr>.gen"));
		return constant_generation(parsed_generation, [&]<Generation generation>(constant_gen_t<generation>) {
			return GenerationGeneric<Team>(parse_team<generation>(all_pokemon));
		});
	} catch (std::exception const & ex) {
		throw std::runtime_error(containers::concatenate<std::string>("Failed to parse Pokemon Online team file \""sv, team_file.string(), "\" -- "sv, std::string_view(ex.what())));
	}
}

} // namespace technicalmachine::po


/******************************************************************************
 * Copyright (C) 2013 by Paul Furgale and Jerome Maye                         *
 * jerome.maye@gmail.com                                                      *
 *                                                                            *
 * This program is free software; you can redistribute it and/or modify       *
 * it under the terms of the Lesser GNU General Public License as published by*
 * the Free Software Foundation; either version 3 of the License, or          *
 * (at your option) any later version.                                        *
 *                                                                            *
 * This program is distributed in the hope that it will be useful,            *
 * but WITHOUT ANY WARRANTY; without even the implied warranty of             *
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the              *
 * Lesser GNU General Public License for more details.                        *
 *                                                                            *
 * You should have received a copy of the Lesser GNU General Public License   *
 * along with this program. If not, see <http://www.gnu.org/licenses/>.       *
 ******************************************************************************/

/** \file IncrementalEstimator.cpp
    \brief This file defines the python exports for the IncrementalEstimator
           class.
  */

#include <boost/shared_ptr.hpp>

#include <numpy_eigen/boost_python_headers.hpp>

#include <aslam/backend/CompressedColumnMatrix.hpp>
#include <aslam/backend/Optimizer2Options.hpp>

#include <aslam/calibration/core/IncrementalEstimator.h>
#include <aslam/calibration/core/IncrementalOptimizationProblem.h>
#include <aslam/calibration/core/LinearSolverOptions.h>

using namespace boost::python;
using namespace aslam::backend;
using namespace aslam::calibration;

/// This functions gets rid of the reference
Eigen::MatrixXd getNobsBasis(const IncrementalEstimator* ie) { return ie->getNobsBasis(); }

/// This functions gets rid of the reference
Eigen::MatrixXd getNobsBasisScaled(const IncrementalEstimator* ie) { return ie->getNobsBasis(true); }

/// This functions gets rid of the reference
Eigen::MatrixXd getObsBasis(const IncrementalEstimator* ie) { return ie->getObsBasis(); }

/// This functions gets rid of the reference
Eigen::MatrixXd getObsBasisScaled(const IncrementalEstimator* ie) { return ie->getObsBasis(true); }

/// This functions gets rid of the reference
Eigen::MatrixXd getSigma2Theta(const IncrementalEstimator* ie) { return ie->getSigma2Theta(); }

/// This functions gets rid of the reference
Eigen::MatrixXd getSigma2ThetaScaled(const IncrementalEstimator* ie) { return ie->getSigma2Theta(true); }

/// This functions gets rid of the reference
Eigen::MatrixXd getSigma2ThetaObs(const IncrementalEstimator* ie) { return ie->getSigma2ThetaObs(); }

/// This functions gets rid of the reference
Eigen::MatrixXd getSigma2ThetaObsScaled(const IncrementalEstimator* ie) { return ie->getSigma2ThetaObs(true); }

/// This functions gets rid of the reference
Eigen::MatrixXd getSingularValues(const IncrementalEstimator* ie) { return ie->getSingularValues(); }

/// This functions gets rid of the reference
Eigen::MatrixXd getScaledSingularValues(const IncrementalEstimator* ie) { return ie->getSingularValues(true); }

void exportIncrementalEstimator() {
    /// Export options for the IncrementalEstimator class
    class_<IncrementalEstimator::Options>("IncrementalEstimatorOptions", init<>())
        .def_readwrite("infoGainDelta", &IncrementalEstimator::Options::infoGainDelta)
        .def_readwrite("checkValidity", &IncrementalEstimator::Options::checkValidity)
        .def_readwrite("verbose", &IncrementalEstimator::Options::verbose);

    /// Export return value for the IncrementalEstimator class
    class_<IncrementalEstimator::ReturnValue>("IncrementalEstimatorReturnValue", init<>())
        .def_readwrite("batchAccepted", &IncrementalEstimator::ReturnValue::batchAccepted)
        .def_readwrite("informationGain", &IncrementalEstimator::ReturnValue::informationGain)
        .def_readwrite("rankPsi", &IncrementalEstimator::ReturnValue::rankPsi)
        .def_readwrite("rankPsiDeficiency", &IncrementalEstimator::ReturnValue::rankPsiDeficiency)
        .def_readwrite("rankTheta", &IncrementalEstimator::ReturnValue::rankTheta)
        .def_readwrite("rankThetaDeficiency", &IncrementalEstimator::ReturnValue::rankThetaDeficiency)
        .def_readwrite("svdTolerance", &IncrementalEstimator::ReturnValue::svdTolerance)
        .def_readwrite("qrTolerance", &IncrementalEstimator::ReturnValue::qrTolerance)
        .def_readwrite("nobsBasis", &IncrementalEstimator::ReturnValue::nobsBasis)
        .def_readwrite("nobsBasisScaled", &IncrementalEstimator::ReturnValue::nobsBasisScaled)
        .def_readwrite("obsBasis", &IncrementalEstimator::ReturnValue::obsBasis)
        .def_readwrite("obsBasisScaled", &IncrementalEstimator::ReturnValue::obsBasisScaled)
        .def_readwrite("sigma2Theta", &IncrementalEstimator::ReturnValue::sigma2Theta)
        .def_readwrite("sigma2ThetaScaled", &IncrementalEstimator::ReturnValue::sigma2ThetaScaled)
        .def_readwrite("sigma2ThetaObs", &IncrementalEstimator::ReturnValue::sigma2ThetaObs)
        .def_readwrite("sigma2ThetaObsScaled", &IncrementalEstimator::ReturnValue::sigma2ThetaObsScaled)
        .def_readwrite("singularValues", &IncrementalEstimator::ReturnValue::singularValues)
        .def_readwrite("singularValuesScaled", &IncrementalEstimator::ReturnValue::singularValuesScaled)
        .def_readwrite("numIterations", &IncrementalEstimator::ReturnValue::numIterations)
        .def_readwrite("JStart", &IncrementalEstimator::ReturnValue::JStart)
        .def_readwrite("JFinal", &IncrementalEstimator::ReturnValue::JFinal)
        .def_readwrite("elapsedTime", &IncrementalEstimator::ReturnValue::elapsedTime);

    /// Functions for querying the options
    IncrementalEstimator::Options& (IncrementalEstimator::*getOptions)() = &IncrementalEstimator::getOptions;
    Optimizer2Options& (IncrementalEstimator::*getOptimizerOptions)() = &IncrementalEstimator::getOptimizerOptions;
    LinearSolverOptions& (IncrementalEstimator::*getLinearSolverOptions)() =
        &IncrementalEstimator::getLinearSolverOptions;

    /// Removes a measurement batch from the estimator
    void (IncrementalEstimator::*removeBatch1)(size_t) = &IncrementalEstimator::removeBatch;
    void (IncrementalEstimator::*removeBatch2)(const IncrementalEstimator::BatchSP&) =
        &IncrementalEstimator::removeBatch;

    /// Export IncrementalEstimator class
    class_<IncrementalEstimator, boost::shared_ptr<IncrementalEstimator>, boost::noncopyable>(
        "IncrementalEstimator",
        init<size_t, const IncrementalEstimator::Options&, const LinearSolverOptions&, const Optimizer2Options&>(
            "IncrementalEstimator(groupId, Options, "
            "LinearSolverOptions, OptimizerOptions) -- The group id should identify "
            "the calibration parameters"))
        .def(init<size_t>("IncrementalEstimator(groupId) -- The group id should "
                          "identify the calibration parameters"))
        .def("getOptions", getOptions, return_internal_reference<>())
        .def("getOptimizerOptions", getOptimizerOptions, return_internal_reference<>())
        .def("getLinearSolverOptions", getLinearSolverOptions, return_internal_reference<>())
        .def("addBatch", &IncrementalEstimator::addBatch)
        .def("reoptimize", &IncrementalEstimator::reoptimize)
        .def("getNumBatches", &IncrementalEstimator::getNumBatches)
        .def("removeBatch", removeBatch1)
        .def("removeBatch", removeBatch2)
        .def("getMargGroupId", &IncrementalEstimator::getMargGroupId)
        .def("getInformationGain", &IncrementalEstimator::getInformationGain)
        .def("getJacobianTranspose", &IncrementalEstimator::getJacobianTranspose, return_internal_reference<>())
        .def("getRankPsi", &IncrementalEstimator::getRankPsi)
        .def("getRankPsiDeficiency", &IncrementalEstimator::getRankPsiDeficiency)
        .def("getRankTheta", &IncrementalEstimator::getRankTheta)
        .def("getRankThetaDeficiency", &IncrementalEstimator::getRankThetaDeficiency)
        .def("getSVDTolerance", &IncrementalEstimator::getSVDTolerance)
        .def("getQRTolerance", &IncrementalEstimator::getQRTolerance)
        .def("getPeakMemoryUsage", &IncrementalEstimator::getPeakMemoryUsage)
        .def("getMemoryUsage", &IncrementalEstimator::getMemoryUsage)
        .def("getNumFlops", &IncrementalEstimator::getNumFlops)
        .def("getNobsBasis", &getNobsBasis)
        .def("getNobsBasisScaled", &getNobsBasisScaled)
        .def("getObsBasis", &getObsBasis)
        .def("getObsBasisScaled", &getObsBasisScaled)
        .def("getSigma2Theta", &getSigma2Theta)
        .def("getSigma2ThetaScaled", &getSigma2ThetaScaled)
        .def("getSigma2ThetaObs", &getSigma2ThetaObs)
        .def("getSigma2ThetaObsScaled", &getSigma2ThetaObsScaled)
        .def("getSingularValues", &getSingularValues)
        .def("getScaledSingularValues", &getScaledSingularValues)
        .def("getProblem", &IncrementalEstimator::getProblem, boost::python::return_internal_reference<>());
}


/*
    Copyright 2012 Andrew Perrault and Saurav Kumar.

    This file is part of DetectText.

    DetectText is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    DetectText is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with DetectText.  If not, see <http://www.gnu.org/licenses/>.
*/
#include <boost/graph/adjacency_list.hpp>
#include <boost/graph/graph_traits.hpp>
#include <boost/graph/connected_components.hpp>
#include <boost/unordered_map.hpp>
#include <boost/graph/floyd_warshall_shortest.hpp>
#include <boost/numeric/ublas/matrix.hpp>
#include <boost/numeric/ublas/io.hpp>

/*#include <graph/adjacency_list.hpp>
#include <graph/graph_traits.hpp>
#include <graph/connected_components.hpp>
#include <unordered_map.hpp>
#include <graph/floyd_warshall_shortest.hpp>
#include <numeric/ublas/matrix.hpp>
#include <numeric/ublas/io.hpp> */
#include <cassert>
#include <cmath>
#include <iostream>
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include  <opencv2/text.hpp>
#include <math.h>
#include <time.h>
#include <utility>
#include <algorithm>
#include <vector>

using namespace cv;

#include "TextDetection.h"

#define PI 3.14159265

namespace DetectText {

const Scalar BLUE (255, 0, 0);
const Scalar GREEN(0, 255, 0);
const Scalar RED  (0, 0, 255);

std::vector<SWTPointPair2i > findBoundingBoxes( std::vector<std::vector<SWTPoint2d> > & components,
                                                           std::vector<Chain> & chains,
                                                           std::vector<SWTPointPair2d > & compBB,
                                                           Mat& output) {
    std::vector<SWTPointPair2i > bb;
    bb.reserve(chains.size());
    for (auto& chainit : chains) {
        int minx = output.cols;
        int miny = output.rows;
        int maxx = 0;
        int maxy = 0;
        for (std::vector<int>::const_iterator cit = chainit.components.begin(); cit != chainit.components.end(); cit++) {
                miny = std::min(miny,compBB[*cit].first.y);
                minx = std::min(minx,compBB[*cit].first.x);
                maxy = std::max(maxy,compBB[*cit].second.y);
                maxx = std::max(maxx,compBB[*cit].second.x);
        }
        Point2i p0(minx,miny);
        Point2i p1(maxx,maxy);
        SWTPointPair2i pair(p0,p1);
        bb.push_back(pair);
    }
    return bb;
}

std::vector<SWTPointPair2i > findBoundingBoxes( std::vector<std::vector<SWTPoint2d> > & components,
                                                           Mat& output) {
    std::vector<SWTPointPair2i > bb;
    bb.reserve(components.size());
    for (auto& compit : components) {
        int minx = output.cols;
        int miny = output.rows;
        int maxx = 0;
        int maxy = 0;
        for (auto& it : compit) {
            miny = std::min(miny,it.y);
            minx = std::min(minx,it.x);
            maxy = std::max(maxy,it.y);
            maxx = std::max(maxx,it.x);
        }
        Point2i p0(minx,miny);
        Point2i p1(maxx,maxy);
        SWTPointPair2i pair(p0,p1);
        bb.push_back(pair);
    }
    return bb;
}

void normalizeImage (const Mat& input, Mat& output) {
    assert ( input.depth() == CV_32F );
    assert ( input.channels() == 1 );
    assert ( output.depth() == CV_32F );
    assert ( output.channels() == 1 );

    float maxVal = 0;
    float minVal = 1e100;
    for ( int row = 0; row < input.rows; row++ ){
        const float* ptr = (const float*)input.ptr(row);
        for ( int col = 0; col < input.cols; col++ ){
            if (*ptr < 0) { }
            else {
                maxVal = std::max(*ptr, maxVal);
                minVal = std::min(*ptr, minVal);
            }
            ptr++;
        }
    }

    float difference = maxVal - minVal;
    for ( int row = 0; row < input.rows; row++ ) {
        const float* ptrin = (const float*)input.ptr(row);
        float* ptrout = (float*)output.ptr(row);
        for ( int col = 0; col < input.cols; col++ ) {
            if (*ptrin < 0) {
                *ptrout = 1;
            } else {
                *ptrout = ((*ptrin) - minVal)/difference;
            }
            ptrout++;
            ptrin++;
        }
    }
}

void renderComponents (const Mat& SWTImage, std::vector<std::vector<SWTPoint2d> > & components, Mat& output) {
    output.setTo(0);

	for (auto& component : components) {
        for (auto& pit : component) {
            output.at<float>(pit.y, pit.x) = SWTImage.at<float>(pit.y, pit.x);
        }
    }
    for( int row = 0; row < output.rows; row++ ){
        float* ptr = (float*)output.ptr(row);
        for ( int col = 0; col < output.cols; col++ ){
            if (*ptr == 0) {
                *ptr = -1;
            }
            ptr++;
        }
    }
    float maxVal = 0;
    float minVal = 1e100;
    for( int row = 0; row < output.rows; row++ ){
        const float* ptr = (const float*)output.ptr(row);
        for ( int col = 0; col < output.cols; col++ ){
            if (*ptr == 0) { }
            else {
                maxVal = std::max(*ptr, maxVal);
                minVal = std::min(*ptr, minVal);
            }
            ptr++;
        }
    }
    float difference = maxVal - minVal;
    for( int row = 0; row < output.rows; row++ ){
        float* ptr = (float*)output.ptr(row);
        for ( int col = 0; col < output.cols; col++ ){
            if (*ptr < 1) {
                *ptr = 1;
            } else {
                *ptr = ((*ptr) - minVal)/difference;
            }
            ptr++;
        }
    }

}

void renderComponentsWithBoxes (Mat& SWTImage, std::vector<std::vector<SWTPoint2d> > & components,
                                std::vector<SWTPointPair2d > & compBB, Mat& output) {
    Mat outTemp( output.size(), CV_32FC1 );

    renderComponents(SWTImage, components, outTemp);

    std::vector<SWTPointPair2i> bb;
    bb.reserve(compBB.size());
    for (auto& it : compBB) {
        Point2i p0 = cvPoint(it.first.x,  it.first.y);
        Point2i p1 = cvPoint(it.second.x, it.second.y);
        SWTPointPair2i pair(p0, p1);
        bb.push_back(pair);
    }

    Mat out( output.size(), CV_8UC1 );
    outTemp.convertTo(out, CV_8UC1, 255.);
    cvtColor (out, output, CV_GRAY2RGB);

    int count = 0;
    for (auto it : bb) {
        Scalar c;
        if (count % 3 == 0) {
            c = BLUE;
        }
        else if (count % 3 == 1) {
            c = GREEN;
        }
        else {
            c = RED;
        }
        count++;
        rectangle(output, it.first, it.second, c, 2);
    }
}

void renderChainsWithBoxes (Mat& SWTImage,
                   std::vector<std::vector<SWTPoint2d> > & components,
                   std::vector<Chain> & chains,
                   std::vector<SWTPointPair2d > & compBB,
                   Mat& output) {
    // keep track of included components
    std::vector<bool> included;
    included.reserve(components.size());
    for (unsigned int i = 0; i != components.size(); i++) {
        included.push_back(false);
    }
    for (Chain& it : chains) {
        for (std::vector<int>::iterator cit = it.components.begin(); cit != it.components.end(); cit++) {
            included[*cit] = true;
        }
    }
    std::vector<std::vector<SWTPoint2d> > componentsRed;
    for (unsigned int i = 0; i != components.size(); i++ ) {
        if (included[i]) {
            componentsRed.push_back(components[i]);
        }
    }
    Mat outTemp( output.size(), CV_32FC1 );

    std::cout << componentsRed.size() << " components after chaining" << std::endl;
    renderComponents(SWTImage, componentsRed, outTemp);
    std::vector<SWTPointPair2i > bb;
    bb = findBoundingBoxes(components, chains, compBB, outTemp);

    Mat out( output.size(), CV_8UC1 );
    outTemp.convertTo(out, CV_8UC1, 255);
    cvtColor (out, output, CV_GRAY2RGB);

    int count = 0;
    for (auto& it : bb) {
        CvScalar c;
        if (count % 3 == 0) {
            c = BLUE;
        }
        else if (count % 3 == 1) {
            c = GREEN;
        }
        else {
            c = RED;
        }
        count++;
        rectangle(output, it.first, it.second, c, 2);
    }
}

void renderChains (Mat& SWTImage,
                   std::vector<std::vector<SWTPoint2d> > & components,
                   std::vector<Chain> & chains,
                   Mat& output) {
    // keep track of included components
    std::vector<bool> included;
    included.reserve(components.size());
    for (unsigned int i = 0; i != components.size(); i++) {
        included.push_back(false);
    }
    for (std::vector<Chain>::iterator it = chains.begin(); it != chains.end();it++) {
        for (std::vector<int>::iterator cit = it->components.begin(); cit != it->components.end(); cit++) {
            included[*cit] = true;
        }
    }
    std::vector<std::vector<SWTPoint2d> > componentsRed;
    for (unsigned int i = 0; i != components.size(); i++ ) {
        if (included[i]) {
            componentsRed.push_back(components[i]);
        }
    }
    std::cout << componentsRed.size() << " components after chaining" << std::endl;
    Mat outTemp( output.size(), CV_32FC1 );
    renderComponents(SWTImage,componentsRed,outTemp);
    outTemp.convertTo(output, CV_8UC1, 255);

}

Mat SWT(const Mat &input, bool dark_on_light) {
    assert ( input.depth() == CV_8U );
    assert ( input.channels() == 3 );

    std::cout << "Running SWT with dark_on_light " << dark_on_light << std::endl;

    // Convert to grayscale
    Mat grayImage( input.size(), CV_8UC1 );
    cvtColor ( input, grayImage, CV_RGB2GRAY );
    // Create Canny Image
    double threshold_low = 175;
    double threshold_high = 320;
    Mat edgeImage( input.size(),CV_8UC1 );
    Canny(grayImage, edgeImage, threshold_low, threshold_high, 3) ;
    //todo: remove
    imwrite ( "canny.png", edgeImage);

    // Create gradient X, gradient Y
    // 将8位整数转化为 0~1 范围内的浮点数
    Mat gaussianImage( input.size(), CV_32FC1);
    grayImage.convertTo(gaussianImage, CV_32FC1, 1./255.);
    GaussianBlur( gaussianImage, gaussianImage, Size(5, 5), 0);
    Mat gradientX( input.size(), CV_32FC1 );
    Mat gradientY( input.size(), CV_32FC1 );
    Scharr(gaussianImage, gradientX, -1, 1, 0);
    Scharr(gaussianImage, gradientY, -1, 0, 1);
    GaussianBlur(gradientX, gradientX, Size(3, 3), 0);
    GaussianBlur(gradientY, gradientY, Size(3, 3), 0);

    // Calculate SWT and return ray vectors
    std::vector<Ray> rays;
    Mat SWTImage( input.size(), CV_32FC1 );
    for( int row = 0; row < input.rows; row++ ){
        float* ptr = (float*)SWTImage.ptr(row);
        for ( int col = 0; col < input.cols; col++ ){
            *ptr++ = -1;  // 初始SWTImage每个像素值都为-1，作为初始化的标志（论文中设为无限大）
        }
    }
    strokeWidthTransform ( edgeImage, gradientX, gradientY, dark_on_light, SWTImage, rays );
    // 对每个ray上的像素都做中值处理
    SWTMedianFilter ( SWTImage, rays );

    Mat output2( input.size(), CV_32FC1 );
    normalizeImage (SWTImage, output2);
    Mat saveSWT( input.size(), CV_8UC1 );
    output2.convertTo(saveSWT, CV_8UC1, 255);
    imwrite ( "SWT.png", saveSWT);

    // Calculate legally connect components from SWT and gradient image.
    // return type is a vector of vectors, where each outer vector is a component and
    // the inner vector contains the (y,x) of each pixel in that component.
    std::vector<std::vector<SWTPoint2d> > components = findLegallyConnectedComponents(SWTImage, rays);

    // Filter the components
    std::vector<std::vector<SWTPoint2d> > validComponents;
    std::vector<SWTPointPair2d > compBB;
    std::vector<Point2dFloat> compCenters;
    std::vector<float> compMedians;
    std::vector<SWTPoint2d> compDimensions;
    filterComponents(SWTImage, components, validComponents, compCenters, compMedians, compDimensions, compBB );

    Mat output3( input.size(), CV_8UC3 );
    renderComponentsWithBoxes (SWTImage, validComponents, compBB, output3);
    imwrite ( "components.png",output3);
    //

    // Make chains of components
    std::vector<Chain> chains;
    chains = makeChains(input, validComponents, compCenters, compMedians, compDimensions, compBB);

    Mat output4( input.size(), CV_8UC1 );
    renderChains ( SWTImage, validComponents, chains, output4 );
    //imwrite ( "text.png", output4);

//    Mat output5( input.size(), CV_8UC3 );
//    cvtColor (output4, output5, CV_GRAY2RGB);

    /*IplImage * output =
            cvCreateImage ( input.size(), CV_8UC3 );
    renderChainsWithBoxes ( SWTImage, validComponents, chains, compBB, output); */
    return output4;
}
    // strokeWidthTransform 需要的已知条件：边缘图，每个像素点上的梯度，输出 SWT 每个像素所属区域的笔画宽度
void strokeWidthTransform (const Mat& edgeImage,
                           Mat& gradientX,
                           Mat& gradientY,
                           bool dark_on_light,
                           Mat& SWTImage,
                           std::vector<Ray> & rays) {
    // First pass
    float prec = .05;
    for( int row = 0; row < edgeImage.rows; row++ ){
        const uchar* ptr = (const uchar*)edgeImage.ptr(row);
        for ( int col = 0; col < edgeImage.cols; col++ ){
            if (*ptr > 0) {
                Ray r;

                SWTPoint2d p;
                p.x = col;
                p.y = row;
                r.p = p;
                std::vector<SWTPoint2d> points;
                points.push_back(p);

                // 计算改点梯度，沿着梯度爬行，直到找到在边缘上的点（边缘上的点为白色，即值不为0）
                float curX = (float)col + 0.5;
                float curY = (float)row + 0.5;
                int curPixX = col;
                int curPixY = row;
                float G_x = gradientX.at<float>(row, col);
                float G_y = gradientY.at<float>(row, col);
                // normalize gradient
                float mag = sqrt( (G_x * G_x) + (G_y * G_y) );
                if (dark_on_light){
                    G_x = -G_x/mag;
                    G_y = -G_y/mag;
                } else {
                    G_x = G_x/mag;
                    G_y = G_y/mag;

                }
                while (true) {
                    curX += G_x*prec;
                    curY += G_y*prec;
                    if ((int)(floor(curX)) != curPixX || (int)(floor(curY)) != curPixY) {
                        curPixX = (int)(floor(curX));
                        curPixY = (int)(floor(curY));
                        // check if pixel is outside boundary of image
                        if (curPixX < 0 || (curPixX >= SWTImage.cols) || curPixY < 0 || (curPixY >= SWTImage.rows)) {
                            break;
                        }
                        SWTPoint2d pnew;
                        pnew.x = curPixX;
                        pnew.y = curPixY;
                        points.push_back(pnew);

                        // 终于找到边缘上的点，
                        if (edgeImage.at<uchar>(curPixY, curPixX) > 0) {
                            r.q = pnew;
                            // dot product
                            float G_xt = gradientX.at<float>(curPixY,curPixX);
                            float G_yt = gradientY.at<float>(curPixY,curPixX);
                            mag = sqrt( (G_xt * G_xt) + (G_yt * G_yt) );
                            if (dark_on_light) {
                                G_xt = -G_xt / mag;
                                G_yt = -G_yt / mag;
                            } else {
                                G_xt = G_xt / mag;
                                G_yt = G_yt / mag;

                            }

                            // p,q 梯度方向偏差不超过直角，将p、q之间的所有点的SWT都设为p、q之间的距离
                            if (acos(G_x * -G_xt + G_y * -G_yt) < PI/2.0 ) {
                                float length = sqrt( ((float)r.q.x - (float)r.p.x)*((float)r.q.x - (float)r.p.x) + ((float)r.q.y - (float)r.p.y)*((float)r.q.y - (float)r.p.y));
                                for (std::vector<SWTPoint2d>::iterator pit = points.begin(); pit != points.end(); pit++) {
                                    if (SWTImage.at<float>(pit->y, pit->x) < 0) {
                                        SWTImage.at<float>(pit->y, pit->x) = length;
                                    } else {
                                        SWTImage.at<float>(pit->y, pit->x) = std::min(length, SWTImage.at<float>(pit->y, pit->x));
                                    }
                                }
                                r.points = points;
                                rays.push_back(r);
                            }
                            break;
                        }
                    }
                }
            }
            ptr++;
        }
    }

}

void SWTMedianFilter (Mat& SWTImage, std::vector<Ray> & rays) {
    for (auto& rit : rays) {
        for (auto& pit : rit.points) {
            pit.SWT = SWTImage.at<float>(pit.y, pit.x);
        }
        // 对每个 ray 根据SWT排序，求得中值，并用这个中值 min(中值，原来值) 来更新 ray 上的所有像素
        std::sort(rit.points.begin(), rit.points.end(), &Point2dSort);
        float median = (rit.points[rit.points.size()/2]).SWT;
        for (auto& pit : rit.points) {
            SWTImage.at<float>(pit.y, pit.x) = std::min(pit.SWT, median);
        }
    }
}

bool Point2dSort (const SWTPoint2d &lhs, const SWTPoint2d &rhs) {
    return lhs.SWT < rhs.SWT;
}

std::vector< std::vector<SWTPoint2d> > findLegallyConnectedComponents (Mat& SWTImage, std::vector<Ray> & rays) {
        boost::unordered_map<int, int> map;
        boost::unordered_map<int, SWTPoint2d> revmap;

        typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> Graph;
        int num_vertices = 0;
        // Number vertices for graph.  Associate each point with number
        for( int row = 0; row < SWTImage.rows; row++ ){
            float * ptr = (float*)SWTImage.ptr(row);
            for (int col = 0; col < SWTImage.cols; col++ ){
                if (*ptr > 0) {
                    map[row * SWTImage.cols + col] = num_vertices;
                    SWTPoint2d p;
                    p.x = col;
                    p.y = row;
                    revmap[num_vertices] = p;
                    num_vertices++;
                }
                ptr++;
            }
        }

        Graph g(num_vertices);

        for( int row = 0; row < SWTImage.rows; row++ ){
            float * ptr = (float*)SWTImage.ptr(row);
            for (int col = 0; col < SWTImage.cols; col++ ){
                if (*ptr > 0) {
                    // check pixel to the right, right-down, down, left-down
                    int this_pixel = map[row * SWTImage.cols + col];
                    if (col+1 < SWTImage.cols) {
                        float right = SWTImage.at<float>(row, col+1);
                        if (right > 0 && ((*ptr)/right <= 3.0 || right/(*ptr) <= 3.0))
                            boost::add_edge(this_pixel, map.at(row * SWTImage.cols + col + 1), g);
                    }
                    if (row+1 < SWTImage.rows) {
                        if (col+1 < SWTImage.cols) {
                            float right_down = SWTImage.at<float>(row+1, col+1);
                            if (right_down > 0 && ((*ptr)/right_down <= 3.0 || right_down/(*ptr) <= 3.0))
                                boost::add_edge(this_pixel, map.at((row+1) * SWTImage.cols + col + 1), g);
                        }
                        float down = SWTImage.at<float>(row+1, col);
                        if (down > 0 && ((*ptr)/down <= 3.0 || down/(*ptr) <= 3.0))
                            boost::add_edge(this_pixel, map.at((row+1) * SWTImage.cols + col), g);
                        if (col-1 >= 0) {
                            float left_down = SWTImage.at<float>(row+1, col-1);
                            if (left_down > 0 && ((*ptr)/left_down <= 3.0 || left_down/(*ptr) <= 3.0))
                                boost::add_edge(this_pixel, map.at((row+1) * SWTImage.cols + col - 1), g);
                        }
                    }
                }
                ptr++;
            }
        }

        std::vector<int> c(num_vertices);

        int num_comp = connected_components(g, &c[0]);

        std::vector<std::vector<SWTPoint2d> > components;
        components.reserve(num_comp);
        std::cout << "Before filtering, " << num_comp << " components and " << num_vertices << " vertices" << std::endl;
        for (int j = 0; j < num_comp; j++) {
            std::vector<SWTPoint2d> tmp;
            components.push_back( tmp );
        }
        for (int j = 0; j < num_vertices; j++) {
            SWTPoint2d p = revmap[j];
            (components[c[j]]).push_back(p);
        }

        return components;
}

std::vector< std::vector<SWTPoint2d> >
findLegallyConnectedComponentsRAY (Mat& SWTImage,
                                std::vector<Ray> & rays)
{
        boost::unordered_map<int, int> map;
        boost::unordered_map<int, SWTPoint2d> revmap;

        typedef boost::adjacency_list<boost::vecS, boost::vecS, boost::undirectedS> Graph;
        int num_vertices = 0;
        // Number vertices for graph.  Associate each point with number
        for( int row = 0; row < SWTImage.rows; row++ ){
            float * ptr = (float*)SWTImage.ptr(row);
            for (int col = 0; col < SWTImage.cols; col++ ){
                if (*ptr > 0) {
                    map[row * SWTImage.cols + col] = num_vertices;
                    SWTPoint2d p;
                    p.x = col;
                    p.y = row;
                    revmap[num_vertices] = p;
                    num_vertices++;
                }
                ptr++;
            }
        }

        Graph g(num_vertices);

        // Traverse and add edges to graph
        for (std::vector<Ray>::const_iterator it = rays.begin(); it != rays.end(); it++) {
                float lastSW = 0;
                int lastRow = 0;
                int lastCol = 0;
                for (std::vector<SWTPoint2d>::const_iterator it2 = it->points.begin(); it2 != it->points.end(); it2++) {
                        float currentSW = SWTImage.at<float>(it2->y, it2->x);
                        if (lastSW == 0) {}
                        else if (lastSW/currentSW<=3.0 || currentSW/lastSW<=3.0){
                                boost::add_edge(map.at(it2->y * SWTImage.cols + it2->x), map.at(lastRow * SWTImage.cols + lastCol), g);
                        }
                        lastSW = currentSW;
                        lastRow = it2->y;
                        lastCol = it2->x;
                }
                lastSW = 0;
                lastRow = 0;
                lastCol = 0;
        }

        std::vector<int> c(num_vertices);

        int num_comp = connected_components(g, &c[0]);

        std::vector<std::vector<SWTPoint2d> > components;
        components.reserve(num_comp);
        std::cout << "Before filtering, " << num_comp << " components and " << num_vertices << " vertices" << std::endl;
        for (int j = 0; j < num_comp; j++) {
            std::vector<SWTPoint2d> tmp;
            components.push_back( tmp );
        }
        for (int j = 0; j < num_vertices; j++) {
            SWTPoint2d p = revmap[j];
            (components[c[j]]).push_back(p);
        }

        return components;
}

void componentStats(Mat& SWTImage,
                                        const std::vector<SWTPoint2d> & component,
                                        float & mean, float & variance, float & median,
                                        int & minx, int & miny, int & maxx, int & maxy)
{
        std::vector<float> temp;
        temp.reserve(component.size());
        mean = 0;
        variance = 0;
        minx = 1000000;
        miny = 1000000;
        maxx = 0;
        maxy = 0;
        for (std::vector<SWTPoint2d>::const_iterator it = component.begin(); it != component.end(); it++) {
                float t = SWTImage.at<float>(it->y, it->x);
                mean += t;
                temp.push_back(t);
                miny = std::min(miny,it->y);
                minx = std::min(minx,it->x);
                maxy = std::max(maxy,it->y);
                maxx = std::max(maxx,it->x);
        }
        mean = mean / ((float)component.size());
        for (std::vector<float>::const_iterator it = temp.begin(); it != temp.end(); it++) {
            variance += (*it - mean) * (*it - mean);
        }
        variance = variance / ((float)component.size());
        std::sort(temp.begin(),temp.end());
        median = temp[temp.size()/2];
}

void filterComponents(Mat& SWTImage,
                      std::vector<std::vector<SWTPoint2d> > & components,
                      std::vector<std::vector<SWTPoint2d> > & validComponents,
                      std::vector<Point2dFloat> & compCenters,
                      std::vector<float> & compMedians,
                      std::vector<SWTPoint2d> & compDimensions,
                      std::vector<SWTPointPair2d > & compBB )
{
        validComponents.reserve(components.size());
        compCenters.reserve(components.size());
        compMedians.reserve(components.size());
        compDimensions.reserve(components.size());
        // bounding boxes
        compBB.reserve(components.size());
        for (std::vector<std::vector<SWTPoint2d> >::iterator it = components.begin(); it != components.end();it++) {
            // compute the stroke width mean, variance, median
            float mean, variance, median;
            int minx, miny, maxx, maxy;
            componentStats(SWTImage, (*it), mean, variance, median, minx, miny, maxx, maxy);

            // check if variance is less than half the mean
            if (variance > 0.5 * mean) {
                 continue;
            }

            float length = (float)(maxx-minx+1);
            float width = (float)(maxy-miny+1);

            // check font height
            if (width > 300) {
                continue;
            }

            float area = length * width;
            float rminx = (float)minx;
            float rmaxx = (float)maxx;
            float rminy = (float)miny;
            float rmaxy = (float)maxy;
            // compute the rotated bounding box
            float increment = 1./36.;
            for (float theta = increment * PI; theta<PI/2.0; theta += increment * PI) {
                float xmin,xmax,ymin,ymax,xtemp,ytemp,ltemp,wtemp;
                    xmin = 1000000;
                    ymin = 1000000;
                    xmax = 0;
                    ymax = 0;
                for (unsigned int i = 0; i < (*it).size(); i++) {
                    xtemp = (*it)[i].x * cos(theta) + (*it)[i].y * -sin(theta);
                    ytemp = (*it)[i].x * sin(theta) + (*it)[i].y * cos(theta);
                    xmin = std::min(xtemp,xmin);
                    xmax = std::max(xtemp,xmax);
                    ymin = std::min(ytemp,ymin);
                    ymax = std::max(ytemp,ymax);
                }
                ltemp = xmax - xmin + 1;
                wtemp = ymax - ymin + 1;
                if (ltemp*wtemp < area) {
                    area = ltemp*wtemp;
                    length = ltemp;
                    width = wtemp;
                }
            }
            // check if the aspect ratio is between 1/10 and 10
            if (length/width < 1./10. || length/width > 10.) {
                continue;
            }

            // compute the diameter TODO finish
            // compute dense representation of component
            std::vector <std::vector<float> > denseRepr;
            denseRepr.reserve(maxx-minx+1);
            for (int i = 0; i < maxx-minx+1; i++) {
                std::vector<float> tmp;
                tmp.reserve(maxy-miny+1);
                denseRepr.push_back(tmp);
                for (int j = 0; j < maxy-miny+1; j++) {\
                    denseRepr[i].push_back(0);
                }
            }
            for (std::vector<SWTPoint2d>::iterator pit = it->begin(); pit != it->end(); pit++) {
                (denseRepr[pit->x - minx])[pit->y - miny] = 1;
            }
            // create graph representing components
            const int num_nodes = it->size();
            /*
            E edges[] = { E(0,2),
                          E(1,1), E(1,3), E(1,4),
                          E(2,1), E(2,3),
                          E(3,4),
                          E(4,0), E(4,1) };

            Graph G(edges + sizeof(edges) / sizeof(E), weights, num_nodes);
            */
            Point2dFloat center;
            center.x = ((float)(maxx+minx))/2.0;
            center.y = ((float)(maxy+miny))/2.0;

            SWTPoint2d dimensions;
            dimensions.x = maxx - minx + 1;
            dimensions.y = maxy - miny + 1;

            SWTPoint2d bb1;
            bb1.x = minx;
            bb1.y = miny;

            SWTPoint2d bb2;
            bb2.x = maxx;
            bb2.y = maxy;
            SWTPointPair2d pair(bb1,bb2);

            compBB.push_back(pair);
            compDimensions.push_back(dimensions);
            compMedians.push_back(median);
            compCenters.push_back(center);
            validComponents.push_back(*it);
        }
       std::vector<std::vector<SWTPoint2d > > tempComp;
       std::vector<SWTPoint2d > tempDim;
       std::vector<float > tempMed;
       std::vector<Point2dFloat > tempCenters;
       std::vector<SWTPointPair2d > tempBB;
       tempComp.reserve(validComponents.size());
       tempCenters.reserve(validComponents.size());
       tempDim.reserve(validComponents.size());
       tempMed.reserve(validComponents.size());
       tempBB.reserve(validComponents.size());
       for (unsigned int i = 0; i < validComponents.size(); i++) {
            int count = 0;
            for (unsigned int j = 0; j < validComponents.size(); j++) {
                if (i != j) {
                    if (compBB[i].first.x <= compCenters[j].x && compBB[i].second.x >= compCenters[j].x &&
                        compBB[i].first.y <= compCenters[j].y && compBB[i].second.y >= compCenters[j].y) {
                        count++;
                    }
                }
            }
            if (count < 2) {
                tempComp.push_back(validComponents[i]);
                tempCenters.push_back(compCenters[i]);
                tempMed.push_back(compMedians[i]);
                tempDim.push_back(compDimensions[i]);
                tempBB.push_back(compBB[i]);
            }
        }
        validComponents = tempComp;
        compDimensions = tempDim;
        compMedians = tempMed;
        compCenters = tempCenters;
        compBB = tempBB;

        compDimensions.reserve(tempComp.size());
        compMedians.reserve(tempComp.size());
        compCenters.reserve(tempComp.size());
        validComponents.reserve(tempComp.size());
        compBB.reserve(tempComp.size());

        std::cout << "After filtering " << validComponents.size() << " components" << std::endl;
}

bool sharesOneEnd( Chain c0, Chain c1) {
    if (c0.p == c1.p || c0.p == c1.q || c0.q == c1.q || c0.q == c1.p) {
        return true;
    }
    else {
        return false;
    }
}

bool chainSortDist (const Chain &lhs, const Chain &rhs) {
    return lhs.dist < rhs.dist;
}

bool chainSortLength (const Chain &lhs, const Chain &rhs) {
    return lhs.components.size() > rhs.components.size();
}

std::vector<Chain> makeChains( const Mat& colorImage,
                 std::vector<std::vector<SWTPoint2d> > & components,
                 std::vector<Point2dFloat> & compCenters,
                 std::vector<float> & compMedians,
                 std::vector<SWTPoint2d> & compDimensions,
                 std::vector<SWTPointPair2d > & compBB) {
    assert (compCenters.size() == components.size());
    // make vector of color averages
    std::vector<Point3dFloat> colorAverages;
    colorAverages.reserve(components.size());
    for (std::vector<std::vector<SWTPoint2d> >::iterator it = components.begin(); it != components.end();it++) {
        Point3dFloat mean;
        mean.x = 0;
        mean.y = 0;
        mean.z = 0;
        int num_points = 0;
        for (std::vector<SWTPoint2d>::iterator pit = it->begin(); pit != it->end(); pit++) {
            mean.x += (float) colorImage.at<uchar>(pit->y, (pit->x)*3 );
            mean.y += (float) colorImage.at<uchar>(pit->y, (pit->x)*3+1 );
            mean.z += (float) colorImage.at<uchar>(pit->y, (pit->x)*3+2 );
            num_points++;
        }
        mean.x = mean.x / ((float)num_points);
        mean.y = mean.y / ((float)num_points);
        mean.z = mean.z / ((float)num_points);
        colorAverages.push_back(mean);
    }

    // form all eligible pairs and calculate the direction of each
    std::vector<Chain> chains;
    for ( unsigned int i = 0; i < components.size(); i++ ) {
        for ( unsigned int j = i + 1; j < components.size(); j++ ) {
            // TODO add color metric
            if ( (compMedians[i]/compMedians[j] <= 2.0 || compMedians[j]/compMedians[i] <= 2.0) &&
                 (compDimensions[i].y/compDimensions[j].y <= 2.0 || compDimensions[j].y/compDimensions[i].y <= 2.0)) {
                float dist = (compCenters[i].x - compCenters[j].x) * (compCenters[i].x - compCenters[j].x) +
                             (compCenters[i].y - compCenters[j].y) * (compCenters[i].y - compCenters[j].y);
                float colorDist = (colorAverages[i].x - colorAverages[j].x) * (colorAverages[i].x - colorAverages[j].x) +
                                  (colorAverages[i].y - colorAverages[j].y) * (colorAverages[i].y - colorAverages[j].y) +
                                  (colorAverages[i].z - colorAverages[j].z) * (colorAverages[i].z - colorAverages[j].z);
                if (dist < 9*(float)(std::max(std::min(compDimensions[i].x,compDimensions[i].y),std::min(compDimensions[j].x,compDimensions[j].y)))
                    *(float)(std::max(std::min(compDimensions[i].x,compDimensions[i].y),std::min(compDimensions[j].x,compDimensions[j].y)))
                    && colorDist < 1600) {
                    Chain c;
                    c.p = i;
                    c.q = j;
                    std::vector<int> comps;
                    comps.push_back(c.p);
                    comps.push_back(c.q);
                    c.components = comps;
                    c.dist = dist;
                    float d_x = (compCenters[i].x - compCenters[j].x);
                    float d_y = (compCenters[i].y - compCenters[j].y);
                    /*
                    float d_x = (compBB[i].first.x - compBB[j].second.x);
                    float d_y = (compBB[i].second.y - compBB[j].second.y);
                    */
                    float mag = sqrt(d_x*d_x + d_y*d_y);
                    d_x = d_x / mag;
                    d_y = d_y / mag;
                    Point2dFloat dir;
                    dir.x = d_x;
                    dir.y = d_y;
                    c.direction = dir;
                    chains.push_back(c);

                    /*std::cout << c.p << " " << c.q << std::endl;
                    std::cout << c.direction.x << " " << c.direction.y << std::endl;
                    std::cout << compCenters[c.p].x << " " << compCenters[c.p].y << std::endl;
                    std::cout << compCenters[c.q].x << " " << compCenters[c.q].y << std::endl;
                    std::cout << std::endl;
                    std::cout << colorDist << std::endl; */
                }
            }
        }
    }
    std::cout << chains.size() << " eligible pairs" << std::endl;
    std::sort(chains.begin(), chains.end(), &chainSortDist);

    const float strictness = PI/6.0;
    //merge chains
    int merges = 1;
    while (merges > 0) {
        for (unsigned int i = 0; i < chains.size(); i++) {
            chains[i].merged = false;
        }
        merges = 0;
        std::vector<Chain> newchains;
        for (unsigned int i = 0; i < chains.size(); i++) {
            for (unsigned int j = 0; j < chains.size(); j++) {
                if (i != j) {
                    if (!chains[i].merged && !chains[j].merged && sharesOneEnd(chains[i],chains[j])) {
                        if (chains[i].p == chains[j].p) {
                            if (acos(chains[i].direction.x * -chains[j].direction.x + chains[i].direction.y * -chains[j].direction.y) < strictness) {
                                  /*      if (chains[i].p == chains[i].q || chains[j].p == chains[j].q) {
                                            std::cout << "CRAZY ERROR" << std::endl;
                                        } else if (chains[i].p == chains[j].p && chains[i].q == chains[j].q) {
                                            std::cout << "CRAZY ERROR" << std::endl;
                                        } else if (chains[i].p == chains[j].q && chains[i].q == chains[j].p) {
                                            std::cout << "CRAZY ERROR" << std::endl;
                                        }
                                        std::cout << 1 <<std::endl;

                                        std::cout << chains[i].p << " " << chains[i].q << std::endl;
                                        std::cout << chains[j].p << " " << chains[j].q << std::endl;
                                std::cout << compCenters[chains[i].q].x << " " << compCenters[chains[i].q].y << std::endl;
                                std::cout << compCenters[chains[i].p].x << " " << compCenters[chains[i].p].y << std::endl;
                                std::cout << compCenters[chains[j].q].x << " " << compCenters[chains[j].q].y << std::endl;
                                std::cout << std::endl; */

                                chains[i].p = chains[j].q;
                                for (std::vector<int>::iterator it = chains[j].components.begin(); it != chains[j].components.end(); it++) {
                                    chains[i].components.push_back(*it);
                                }
                                float d_x = (compCenters[chains[i].p].x - compCenters[chains[i].q].x);
                                float d_y = (compCenters[chains[i].p].y - compCenters[chains[i].q].y);
                                chains[i].dist = d_x * d_x + d_y * d_y;

                                float mag = sqrt(d_x*d_x + d_y*d_y);
                                d_x = d_x / mag;
                                d_y = d_y / mag;
                                Point2dFloat dir;
                                dir.x = d_x;
                                dir.y = d_y;
                                chains[i].direction = dir;
                                chains[j].merged = true;
                                merges++;
                                /*j=-1;
                                i=0;
                                if (i == chains.size() - 1) i=-1;
                                std::stable_sort(chains.begin(), chains.end(), &chainSortLength);*/
                            }
                        } else if (chains[i].p == chains[j].q) {
                            if (acos(chains[i].direction.x * chains[j].direction.x + chains[i].direction.y * chains[j].direction.y) < strictness) {
/*
                                if (chains[i].p == chains[i].q || chains[j].p == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].p && chains[i].q == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].q && chains[i].q == chains[j].p) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                }
                                std::cout << 2 <<std::endl;

                                std::cout << chains[i].p << " " << chains[i].q << std::endl;
                                std::cout << chains[j].p << " " << chains[j].q << std::endl;
                                std::cout << chains[i].direction.x << " " << chains[i].direction.y << std::endl;
                                std::cout << chains[j].direction.x << " " << chains[j].direction.y << std::endl;
                                std::cout << compCenters[chains[i].q].x << " " << compCenters[chains[i].q].y << std::endl;
                                std::cout << compCenters[chains[i].p].x << " " << compCenters[chains[i].p].y << std::endl;
                                std::cout << compCenters[chains[j].p].x << " " << compCenters[chains[j].p].y << std::endl;
                                std::cout << std::endl; */

                                chains[i].p = chains[j].p;
                                for (std::vector<int>::iterator it = chains[j].components.begin(); it != chains[j].components.end(); it++) {
                                    chains[i].components.push_back(*it);
                                }
                                float d_x = (compCenters[chains[i].p].x - compCenters[chains[i].q].x);
                                float d_y = (compCenters[chains[i].p].y - compCenters[chains[i].q].y);
                                float mag = sqrt(d_x*d_x + d_y*d_y);
                                chains[i].dist = d_x * d_x + d_y * d_y;

                                d_x = d_x / mag;
                                d_y = d_y / mag;

                                Point2dFloat dir;
                                dir.x = d_x;
                                dir.y = d_y;
                                chains[i].direction = dir;
                                chains[j].merged = true;
                                merges++;
                                /*j=-1;
                                i=0;
                                if (i == chains.size() - 1) i=-1;
                                std::stable_sort(chains.begin(), chains.end(), &chainSortLength); */
                            }
                        } else if (chains[i].q == chains[j].p) {
                            if (acos(chains[i].direction.x * chains[j].direction.x + chains[i].direction.y * chains[j].direction.y) < strictness) {
     /*                           if (chains[i].p == chains[i].q || chains[j].p == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].p && chains[i].q == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].q && chains[i].q == chains[j].p) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                }
                                std::cout << 3 <<std::endl;

                                std::cout << chains[i].p << " " << chains[i].q << std::endl;
                                std::cout << chains[j].p << " " << chains[j].q << std::endl;

                                std::cout << compCenters[chains[i].p].x << " " << compCenters[chains[i].p].y << std::endl;
                                std::cout << compCenters[chains[i].q].x << " " << compCenters[chains[i].q].y << std::endl;
                                std::cout << compCenters[chains[j].q].x << " " << compCenters[chains[j].q].y << std::endl;
                                std::cout << std::endl; */
                                chains[i].q = chains[j].q;
                                for (std::vector<int>::iterator it = chains[j].components.begin(); it != chains[j].components.end(); it++) {
                                    chains[i].components.push_back(*it);
                                }
                                float d_x = (compCenters[chains[i].p].x - compCenters[chains[i].q].x);
                                float d_y = (compCenters[chains[i].p].y - compCenters[chains[i].q].y);
                                float mag = sqrt(d_x*d_x + d_y*d_y);
                                chains[i].dist = d_x * d_x + d_y * d_y;

                                d_x = d_x / mag;
                                d_y = d_y / mag;
                                Point2dFloat dir;
                                dir.x = d_x;
                                dir.y = d_y;

                                chains[i].direction = dir;
                                chains[j].merged = true;
                                merges++;
                                /*j=-1;
                                i=0;
                                if (i == chains.size() - 1) i=-1;
                                std::stable_sort(chains.begin(), chains.end(), &chainSortLength); */
                            }
                        } else if (chains[i].q == chains[j].q) {
                            if (acos(chains[i].direction.x * -chains[j].direction.x + chains[i].direction.y * -chains[j].direction.y) < strictness) {
                     /*           if (chains[i].p == chains[i].q || chains[j].p == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].p && chains[i].q == chains[j].q) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                } else if (chains[i].p == chains[j].q && chains[i].q == chains[j].p) {
                                    std::cout << "CRAZY ERROR" << std::endl;
                                }
                                std::cout << 4 <<std::endl;
                                std::cout << chains[i].p << " " << chains[i].q << std::endl;
                                std::cout << chains[j].p << " " << chains[j].q << std::endl;
                                std::cout << compCenters[chains[i].p].x << " " << compCenters[chains[i].p].y << std::endl;
                                std::cout << compCenters[chains[i].q].x << " " << compCenters[chains[i].q].y << std::endl;
                                std::cout << compCenters[chains[j].p].x << " " << compCenters[chains[j].p].y << std::endl;
                                std::cout << std::endl; */
                                chains[i].q = chains[j].p;
                                for (std::vector<int>::iterator it = chains[j].components.begin(); it != chains[j].components.end(); it++) {
                                    chains[i].components.push_back(*it);
                                }
                                float d_x = (compCenters[chains[i].p].x - compCenters[chains[i].q].x);
                                float d_y = (compCenters[chains[i].p].y - compCenters[chains[i].q].y);
                                chains[i].dist = d_x * d_x + d_y * d_y;

                                float mag = sqrt(d_x*d_x + d_y*d_y);
                                d_x = d_x / mag;
                                d_y = d_y / mag;
                                Point2dFloat dir;
                                dir.x = d_x;
                                dir.y = d_y;
                                chains[i].direction = dir;
                                chains[j].merged = true;
                                merges++;
                                /*j=-1;
                                i=0;
                                if (i == chains.size() - 1) i=-1;
                                std::stable_sort(chains.begin(), chains.end(), &chainSortLength);*/
                            }
                        }
                    }
                }
            }
        }
        for (unsigned int i = 0; i < chains.size(); i++) {
            if (!chains[i].merged) {
                newchains.push_back(chains[i]);
            }
        }
        chains = newchains;
        std::stable_sort(chains.begin(), chains.end(), &chainSortLength);
    }

    std::vector<Chain> newchains;
    newchains.reserve(chains.size());
    for (std::vector<Chain>::iterator cit = chains.begin(); cit != chains.end(); cit++) {
        if (cit->components.size() >= 3) {
            newchains.push_back(*cit);
        }
    }
    chains = newchains;
    std::cout << chains.size() << " chains after merging" << std::endl;
    return chains;
}

    std::vector<cv::Rect> detect_in_gray(Mat &gray_src) {
//        Mat src = SWT(input, dark_on_light);
        cv::cvtColor(gray_src, gray_src, cv::COLOR_GRAY2RGB);

        // Extract channels to be processed individually
        std::vector<Mat> channels;

        cv::text::computeNMChannels(gray_src, channels);

        int cn = (int)channels.size();
        // Append negative channels to detect_in_gray ER- (bright regions over dark background)
        for (int c = 0; c < cn-1; c++)
            channels.push_back(255-channels[c]);

        // Create ERFilter objects with the 1st and 2nd stage default classifiers
        Ptr<cv::text::ERFilter> er_filter1 = cv::text::createERFilterNM1(cv::text::loadClassifierNM1("/Users/gexinjie/codes/text_detect/trained_classifierNM1.xml"),16,0.00015f,0.13f,0.2f,true,0.1f);
        Ptr<cv::text::ERFilter> er_filter2 = cv::text::createERFilterNM2(cv::text::loadClassifierNM2("/Users/gexinjie/codes/text_detect/trained_classifierNM2.xml"),0.5);

        std::vector<std::vector<cv::text::ERStat> > regions(channels.size());
        // Apply the default cascade classifier to each independent channel (could be done in parallel)
        std::cout << "Extracting Class Specific Extremal Regions from " << (int)channels.size() << " channels ..." << std::endl;
        std::cout << "    (...) this may take a while (...)" << std::endl;
        for (int c=0; c<(int)channels.size(); c++)
        {
            er_filter1->run(channels[c], regions[c]);
            er_filter2->run(channels[c], regions[c]);
        }

        // Detect character groups
        std::cout << "Grouping extracted ERs ... ";
        std::vector< std::vector<Vec2i> > region_groups;
        std::vector<Rect> groups_boxes;
        erGrouping(gray_src, channels, regions, region_groups, groups_boxes, cv::text::ERGROUPING_ORIENTATION_HORIZ);
        //erGrouping(src, channels, regions, region_groups, groups_boxes, ERGROUPING_ORIENTATION_ANY, "./trained_classifier_erGrouping.xml", 0.5);

        // draw groups
//        groups_draw(src, groups_boxes);
//        imshow("grouping",src);

        std::cout << "Done!" << std::endl;
        // memory keep_white-up
        er_filter1.release();
        er_filter2.release();
        regions.clear();

        std::vector<cv::Rect> unique_boxes;
        for (auto &item : groups_boxes) {
            if (std::find(unique_boxes.cbegin(), unique_boxes.cend(), item) == unique_boxes.cend()) {
                unique_boxes.push_back(item);
            }
        }
//        groups_boxes.erase(std::unique(groups_boxes.begin(), groups_boxes.end()), groups_boxes.end());
        return unique_boxes;
    }

    std::vector<cv::Rect> detect(const Mat &src, bool dark_on_light) {
        auto gray_src = SWT(src, dark_on_light);
        return detect_in_gray(gray_src);
    }

}


#include <stdint.h>
#include <boost/python/numpy.hpp>
#include <boost/python.hpp>
#include <scitbx/array_family/versa.h>
#include <scitbx/array_family/accessors/flex_grid.h>
#include <boost_adaptbx/type_id_eq.h>
#include <boost_adaptbx/floating_point_exceptions.h>

#if defined(SCITBX_HAVE_NUMPY_INCLUDE)
// https://docs.scipy.org/doc/numpy/reference/c-api.array.html
#define PY_ARRAY_UNIQUE_SYMOL FLEX_ARRAY_API
// #define NO_IMPORT_ARRAY
#include <numpy/arrayobject.h>
#else
#define NPY_BOOL 0
#define NPY_INT 0
#define NPY_LONG 0
#define NPY_FLOAT 0
#define NPY_DOUBLE 0
#define NPY_CDOUBLE 0
#define NPY_USHORT 0
#define NPY_UINT 0
#define NPY_ULONG 0
#define NPY_ULONGLONG 0
#define NPY_INT8 0
#define NPY_INT16 0
#define NPY_INT32 0
#define NPY_INT64 0
#define NPY_UINT8 0
#define NPY_UINT16 0
#define NPY_UINT32 0
#define NPY_UINT64 0
#endif

#if PY_MAJOR_VERSION >= 3
#define IS_PY3K
#endif

namespace scitbx { namespace af { namespace boost_python {

// import_array returns NULL in python3, and somehow that makes this function
// need a return type as well.
#ifdef IS_PY3K
  void*
#else
  void
#endif
  import_numpy_api_if_available()
  {
#if defined(SCITBX_HAVE_NUMPY_INCLUDE)
    {
    // Fix segmentation faults
    // http://boostorg.github.io/python/doc/html/numpy/tutorial/simple.html
    using namespace boost_adaptbx::floating_point;
    exception_trapping guard(exception_trapping::dont_trap);
    Py_Initialize();
    boost::python::numpy::initialize();
    import_array();
    }
#endif
#ifdef IS_PY3K
    return NULL;
#endif
  }

  char const*
  numpy_api_not_available() { return "numpy API not available"; }

  template <typename ElementType>
  boost::python::object
  ref_flex_as_numpy_array(
    ref<ElementType, flex_grid<> > const& O,
    bool optional,
    int type_num)
  {
    namespace bp = boost::python;
    bp::object result;
#if !defined(SCITBX_HAVE_NUMPY_INCLUDE)
    if (!optional) {
      throw std::runtime_error(numpy_api_not_available());
    }
#else
    npy_intp dims[flex_grid<>::index_type::capacity_value];
    flex_grid<> const& grid = O.accessor();
    int nd = static_cast<int>(grid.nd());
    for(unsigned i=0;i<nd;i++) {
      dims[i] = static_cast<npy_intp>(grid.all()[i]);
    }
    result = bp::object(bp::handle<>(PyArray_SimpleNew(nd, dims, type_num)));
    ElementType* result_data = reinterpret_cast<ElementType*>(
      PyArray_DATA(reinterpret_cast<PyArrayObject*>(result.ptr())));
    std::copy(O.begin(), O.end(), result_data);
#endif
    return result;
  }

  template <typename SourceType, typename TargetType>
  void
  copy_data_with_cast(
    std::size_t n,
    SourceType const* s,
    TargetType* t)
  {
    for(std::size_t i=0;i<n;i++) {
      t[i] = static_cast<TargetType>(static_cast<SourceType>(s[i]));
    }
  }

  template <typename ElementType>
  versa<ElementType, flex_grid<> >
  versa_flex_from_numpy_array(
    boost::python::numpy::ndarray const& arr_obj)
  {
    namespace bp = boost::python;
#if !defined(SCITBX_HAVE_NUMPY_INCLUDE)
    throw std::runtime_error(numpy_api_not_available());
#else
    PyObject* obj_ptr = arr_obj.ptr();
    PyArrayObject* arr_ptr = reinterpret_cast<PyArrayObject*>(obj_ptr);
    if (!PyArray_Check(arr_ptr)) {
      throw std::invalid_argument(
        "Expected a numpy.ndarray instance");
    }
    if (!PyArray_ISCONTIGUOUS(arr_ptr)) {
      throw std::invalid_argument(
        "numpy.ndarray instance is not contiguous");
    }
    typedef typename flex_grid<>::index_type fgit;
    typedef typename fgit::value_type fgivt;
    fgit all;
    npy_intp ndim = PyArray_NDIM(arr_ptr);
    SCITBX_ASSERT(ndim <= all.capacity());
    npy_intp* dims = PyArray_DIMS(arr_ptr);
    for(unsigned i=0;i<ndim;i++) {
      all.push_back(static_cast<fgivt>(dims[i]));
    }
    flex_grid<> grid(all);
    SCITBX_ASSERT(grid.size_1d() == PyArray_Size(obj_ptr));
    versa<ElementType, flex_grid<> > result(
      grid, init_functor_null<ElementType>());
    void* data = PyArray_DATA(arr_ptr);
    int type_num = PyArray_TYPE(arr_ptr);
#define SCITBX_LOC(tn, t) \
    (type_num == tn) { \
      copy_data_with_cast<t, ElementType>( \
        grid.size_1d(), \
        reinterpret_cast<t*>(data), \
        result.begin()); \
    }
#define SCITBX_LOC_COMPLEX(tn) \
    (type_num == tn) { \
      copy_data_with_cast<ElementType, ElementType>( \
        grid.size_1d(), \
        reinterpret_cast<ElementType*>(data), \
        result.begin()); \
    }
    if      SCITBX_LOC(NPY_BOOL, npy_bool)
    else if SCITBX_LOC(NPY_INT, npy_int)
    else if SCITBX_LOC(NPY_LONG, npy_long)
    else if SCITBX_LOC(NPY_FLOAT, npy_float)
    else if SCITBX_LOC(NPY_DOUBLE, npy_double)
    else if SCITBX_LOC_COMPLEX(NPY_CDOUBLE)
    else if SCITBX_LOC(NPY_USHORT, npy_ushort)
    else if SCITBX_LOC(NPY_UINT, npy_uint)
    else if SCITBX_LOC(NPY_ULONG, npy_ulong)
    else if SCITBX_LOC(NPY_ULONGLONG, npy_ulonglong)
    else if SCITBX_LOC(NPY_INT8, npy_int8)
    else if SCITBX_LOC(NPY_INT16, npy_int16)
    else if SCITBX_LOC(NPY_INT32, npy_int32)
    else if SCITBX_LOC(NPY_INT64, npy_int64)
    else if SCITBX_LOC(NPY_UINT8, npy_uint8)
    else if SCITBX_LOC(NPY_UINT16, npy_uint16)
    else if SCITBX_LOC(NPY_UINT32, npy_uint32)
    else if SCITBX_LOC(NPY_UINT64, npy_uint64)
    else {
      throw std::runtime_error(
        "Unsupported numpy.ndarray element type");
    }
#undef SCITBX_LOC_COMPLEX
#undef SCITBX_LOC
    return result;
#endif
  }

#define SCITBX_LOC(pyname, ElementType, type_num) \
  boost::python::object \
  flex_##pyname##_as_numpy_array( \
    ref<ElementType, flex_grid<> > const& O, \
    bool optional) \
  { \
    return ref_flex_as_numpy_array(O, optional, type_num); \
  } \
 \
  versa<ElementType, flex_grid<> >* \
  flex_##pyname##_from_numpy_array( \
    boost::python::numpy::ndarray const& arr_obj) \
  { \
    return new versa<ElementType, flex_grid<> >( \
      versa_flex_from_numpy_array<ElementType >(arr_obj)); \
  }

  SCITBX_LOC(bool, bool, NPY_BOOL);
  SCITBX_LOC(int, int, NPY_INT);
  SCITBX_LOC(long, long, NPY_LONG);
  SCITBX_LOC(float, float, NPY_FLOAT);
  SCITBX_LOC(double, double, NPY_DOUBLE);
  SCITBX_LOC(complex_double, std::complex<double>, NPY_CDOUBLE);
  SCITBX_LOC(int8, int8_t, NPY_INT8);
  SCITBX_LOC(int16, int16_t, NPY_INT16);
  // SCITBX_LOC(int32, int32_t, NPY_INT32);
  #if defined(_MSC_VER)
  SCITBX_LOC(int64, int64_t, NPY_INT64);
  #endif
  SCITBX_LOC(uint8, uint8_t, NPY_UINT8);
  SCITBX_LOC(uint16, uint16_t, NPY_UINT16);
  SCITBX_LOC(uint32, uint32_t, NPY_UINT32);
  // SCITBX_LOC(uint64, uint64_t, NPY_UINT64);

#if defined(BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_UNSIGNED_SHORT)
  SCITBX_LOC(size_t, std::size_t, NPY_USHORT);
#elif defined(BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_UNSIGNED)
  SCITBX_LOC(size_t, std::size_t, NPY_UINT);
#elif defined(BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_UNSIGNED_LONG)
  SCITBX_LOC(size_t, std::size_t, NPY_ULONG);
#elif defined(BOOST_ADAPTBX_TYPE_ID_SIZE_T_EQ_UNSIGNED_LONG_LONG)
  SCITBX_LOC(size_t, std::size_t, NPY_ULONGLONG);
#else
# error Unknown size_t: review boost_adaptbx/type_id_eq.h
#endif

#undef SCITBX_LOC

}}} // namespace scitbx::af::boost_python


#pragma once

#include <list>
#include <map>
#include <string>
#include <armadillo>

#include "../serialisation/json.hpp"

class Renderable;
class Transform {
public:
    Transform();
    explicit Transform(Transform *parent);
    Transform(Transform *parent, const arma::vec2 &position);
    explicit Transform(json j);
    virtual ~Transform() =default;
    void destroy();

    arma::vec2 get_relative_position() const;
    arma::vec2 get_position() const;

    virtual void set_relative_position(const arma::vec2 &relative_position);
    virtual void set_position(const arma::vec2 &position);

    const Transform *get_root() const;
    void set_parent(Transform *parent);
    Transform *get_parent() const;
    void remove_child(Transform *child);
    void add_child(Transform *child);
    const std::list<Transform *> &get_children() const;
    unsigned long count() const;

    void activate();
    virtual void on_activate() {}
    void deactivate();
    virtual void on_deactivate() {}
    bool is_active() const;

    void set_scale(double scale);
    double get_scale() const;

    void set_z(int z);
    void set_relative_z(int rel_z);
    int get_z() const;
    int get_relative_z() const;

    virtual void get_renderables(std::list<const Renderable *> &out) const;

    void update();
    virtual void on_update() {}

    [[nodiscard]] virtual bool is_physics_object() const { return false; }

    virtual json serialise();

    [[nodiscard]] bool is_a(const std::string &tag) const;
    [[nodiscard]] bool is_a(std::size_t tag_hash) const;
    void add_tag(const std::string &tag);
    void add_tag(const std::size_t);
    void find_in_children(const std::string &tag, std::list<Transform *> &out) const;
    void find_in_children(std::size_t tag_hash, std::list<Transform *> &out) const;

    template<typename T>
    void find_in_children_cast(const std::string &tag, std::list<T> &out) const
    {
        this->template find_in_children_cast<T>(std::hash<std::string>{}(tag), out);
    }

    template<typename T>
    void find_in_children_cast(std::size_t tag_hash, std::list<T> &out) const
    {
        std::list<Transform *> transforms;
        this->find_in_children(tag_hash, transforms);
        for (auto *transform: transforms) {
            out.push_back((T)transform);
        }
    }

private:
    Transform *_parent;
    arma::vec2 _relative_position;
    bool _active;
    double _scale;
    int _relative_z;

    std::list<Transform *> _children;
    std::list<std::string> _tags;
    std::list<std::size_t> _tag_hashes;
};


// Copyright Louis Dionne 2013-2017
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE.md or copy at http://boost.org/LICENSE_1_0.txt)

#include <boost/hana/duplicate.hpp>
#include <boost/hana/extract.hpp>
#include <boost/hana/lazy.hpp>
namespace hana = boost::hana;

int main() {
    constexpr auto very_lazy = hana::duplicate(hana::make_lazy(3));
    static_assert(hana::extract(hana::extract(very_lazy)) == 3, "");
}


/// Div operation model class definition.
///
/// PlnDivOperation calculate division and remainder.
/// e.g.) a / b
///
/// @file	PlnDivOperation.cpp
/// @copyright	2017-2021 YAMAGUCHI Toshinobu

#include <boost/assert.hpp>

#include "../../PlnConstants.h"
#include "PlnDivOperation.h"
#include "../../PlnDataAllocator.h"
#include "../../PlnGenerator.h"
#include "../../PlnMessage.h"
#include "../../PlnException.h"
#include "../PlnType.h"
#include "PlnCalcOperationUtils.h"

// PlnDivOperation
PlnExpression* PlnDivOperation::create(PlnExpression* l, PlnExpression* r)
{
	CREATE_CHECK_FLAG(l);
	CREATE_CHECK_FLAG(r);

	// e.g.) 5/2 => 2
	if (is_l_uint && is_r_uint) {
		l->values[0].inf.uintValue = lval.u / rval.u;
		delete r;
		return l;
	}

	if (is_l_flo && is_r_num_lit) {
		double d =	is_r_int ? rval.i:
					is_r_uint ? rval.u:
					rval.d;
		l->values[0].inf.floValue = lval.d / d;
		delete r;
		return l;
	}

	if (is_l_num_lit && is_r_flo) { // is_l_flo == false
		double d = is_l_int ? lval.i : lval.u;
		r->values[0].inf.floValue = d / rval.d;
	 	delete l;
		return r;
	}

	if (is_l_num_lit && is_r_num_lit) {	// promote to integer.
		delete l; delete r;
		return new PlnExpression(lval.i / rval.i);
	}

	if (l->type == ET_DIV && (is_r_int || is_r_uint)) {
		PlnDivOperation* dv = static_cast<PlnDivOperation*>(l);
		PlnExpression* dvr = dv->r;
		CREATE_CHECK_FLAG(dvr);
		if (dv->div_type == DV_DIV && (is_dvr_int || is_dvr_uint)) {
			if (is_dvr_uint && is_r_uint) {
				// e.g.) 5/2 => 2
				dv->r->values[0].inf.uintValue = dvrval.u * rval.u;
			} else {
				dv->r->values[0].type = VL_LIT_INT8;
				dv->r->values[0].inf.intValue = dvrval.i * rval.i;
			}
		}
		delete r;
		return dv;
	}

	return new PlnDivOperation(l, r, DV_DIV);
}

PlnExpression* PlnDivOperation::create_mod(PlnExpression* l, PlnExpression* r)
{
	CREATE_CHECK_FLAG(l);
	CREATE_CHECK_FLAG(r);

	// e.g.) 5%2 => 1
	if (is_l_uint && is_r_uint) {
		l->values[0].inf.uintValue = lval.u % rval.u;
		delete r;
		return l;
	}

	if (is_l_num_lit && is_r_num_lit) {
		delete l; delete r;
		return new PlnExpression(lval.i % rval.i);
	}

	return new PlnDivOperation(l,r, DV_MOD);
}

PlnDivOperation::PlnDivOperation(PlnExpression* l, PlnExpression* r, PlnDivType dt)
	: PlnExpression(ET_DIV), l(l), r(r), div_type(dt), quotient(NULL), remainder(NULL)
{
	PlnValue v;
	v.type = VL_WORK;
	v.inf.wk_type = binaryOperationType(l, r);

	if (div_type == DV_MOD && v.inf.wk_type->data_type() == DT_FLOAT) {
	 	PlnCompileError err(E_CantUseOperatorHere, PlnMessage::floatNumber());
	 	throw err;
	}

	values.push_back(v);
}

PlnDivOperation::~PlnDivOperation()
{
	delete l;
	delete r;
}

void PlnDivOperation::finish(PlnDataAllocator& da, PlnScopeInfo& si)
{
	PlnDataPlace *ldp, *rdp;
	// l => RAX
	ldp = da.prepareAccumulator(values[0].getVarType()->data_type(), values[0].getVarType()->size());

	if (r->type == ET_VALUE) {
		rdp = r->values[0].getDataPlace(da);
	} else {
		static string cmt="(temp)";
		rdp = da.prepareLocalVar(r->values[0].getVarType()->size(), r->getDataType());
		rdp->comment = &cmt;
	}

	l->data_places.push_back(ldp);
	l->finish(da, si);

	r->data_places.push_back(rdp);
	r->finish(da, si);

	da.popSrc(rdp);
	da.popSrc(ldp);
	da.divided(&quotient, &remainder, ldp, rdp);

	if (data_places.size()) {
		if (div_type == DV_DIV)  {
			da.pushSrc(data_places[0], quotient);
			if (remainder) da.releaseDp(remainder);
		} else {	// DV_MOD
			BOOST_ASSERT(remainder != NULL);
			da.releaseDp(quotient);
			da.pushSrc(data_places[0], remainder);
		}
	} else {
		da.releaseDp(quotient);
		if (remainder) da.releaseDp(remainder);
	}
}

void PlnDivOperation::gen(PlnGenerator& g)
{
	l->gen(g);
	r->gen(g);

	auto ldp = l->data_places[0];
	auto rdp = r->data_places[0];

	g.genLoadDp(rdp);
	g.genLoadDp(ldp);

	auto le = g.getEntity(l->data_places[0]);
	auto re = g.getEntity(r->data_places[0]);

	string cmt=ldp->cmt() + " / " + rdp->cmt();
	g.genDiv(le.get(), re.get(), cmt);

	if (data_places.size() > 0) {
		if (div_type == DV_DIV) {
			g.genSaveSrc(data_places[0]);
			BOOST_ASSERT(data_places.size() == 1);
			// if (data_places.size() > 1)
			//	g.genSaveSrc(data_places[1]);

		} else { // div_type == DT_MOD
			g.genSaveSrc(data_places[0]);
		}
	}
}


// Author(s): Wieger Wesselink
// Copyright: see the accompanying file COPYING or copy at
// https://svn.win.tue.nl/trac/MCRL2/browser/trunk/COPYING
//
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
//
/// \file parity_game_test.cpp
/// \brief Test for parity game solver. N.B. Currently no real
/// testing is done. Instead graph representations are produced
/// in a format that can be read by a python script.

//#define MCRL2_PARITY_GAME_DEBUG
//#define MCRL2_ENUMERATE_QUANTIFIERS_BUILDER_DEBUG
//#define MCRL2_ENUMERATE_QUANTIFIERS_REWRITER_DEBUG
//#define MCRL2_PBES_EXPRESSION_BUILDER_DEBUG

#include <fstream>
#include <string>
#include <iomanip>
#include <boost/test/minimal.hpp>
#include "mcrl2/lps/detail/test_input.h"
#include "mcrl2/modal_formula/detail/test_input.h"
#include "mcrl2/pbes/txt2pbes.h"
#include "mcrl2/pbes/lps2pbes.h"
#include "mcrl2/pbes/detail/pbes2bool.h"
#include "mcrl2/pbes/print.h"
#include "mcrl2/pbes/detail/parity_game_output.h"

#ifdef MCRL2_PGSOLVER_ENABLED
#include <cstdlib>
#endif

using namespace mcrl2;

std::string BES1 =
  "pbes mu X = X;                                           \n"
  "                                                         \n"
  "init X;                                                  \n"
  ;

std::string BES2 =
  "pbes nu X = X;                                           \n"
  "                                                         \n"
  "init X;                                                  \n"
  ;

std::string BES3 =
  "pbes mu X = Y;                                           \n"
  "     nu Y = X;                                           \n"
  "                                                         \n"
  "init X;                                                  \n"
  ;

std::string BES4 =
  "pbes nu Y = X;                                           \n"
  "     mu X = Y;                                           \n"
  "                                                         \n"
  "init X;                                                  \n"
  ;

std::string BES5 =
  "pbes mu X1 = X2;                                         \n"
  "     nu X2 = X1 || X3;                                   \n"
  "     mu X3 = X4 && X5;                                   \n"
  "     nu X4 = X1;                                         \n"
  "     nu X5 = X1 || X3;                                   \n"
  "                                                         \n"
  "init X1;                                                 \n"
  ;

std::string BES6 =
  "pbes nu X1 = X2 && X1;                                   \n"
  "     mu X2 = X1 || X3;                                   \n"
  "     nu X3 = X3;                                         \n"
  "                                                         \n"
  "init X1;                                                 \n"
  ;

std::string BES7 =
  "pbes nu X1 = X2 && X3;                                   \n"
  "     nu X2 = X4 && X5;                                   \n"
  "     nu X3 = true;                                       \n"
  "     nu X4 = false;                                      \n"
  "     nu X5 = X6;                                         \n"
  "     nu X6 = X5;                                         \n"
  "                                                         \n"
  "init X1;                                                 \n"
  ;

std::string BES8 =
  "pbes nu X1 = X2 && X1;                                   \n"
  "     mu X2 = X1;                                         \n"
  "                                                         \n"
  "init X1;                                                 \n"
  ;

std::string PBES1 =
  "pbes mu X(m: Nat) =                          \n"
  "       forall n: Nat. val(!(n < 3)) && X(n); \n"
  "                                             \n"
  "init X(0);                                   \n"
  ;

std::string PBES2 =
  "pbes                                                 \n"
  "mu X(m:Nat) = !(exists n:Nat.(val(n < 3) || !X(n))); \n"
  "                                                     \n"
  "init X(0);                                           \n"
  ;

std::string PBES3 =
  "pbes                                                                                \n"
  "nu X0 = ((!(forall m:Nat.((val(m < 3)) && (!X2(m + 1, 0))))) || (((forall k:Nat.((val(k < 3)) && (val(k < 2)))) || (exists n:Nat.((val(n < 3)) || (val(n < 2))))) || ((val(true)) => (exists k:Nat.((val(k < 3)) || (exists m:Nat.((val(m < 3)) || (forall k:Nat.((val(k < 3)) && (X4(1, m + 1))))))))))) && ((!(exists n:Nat.((val(n < 3)) || (exists k:Nat.((val(k < 3)) || (val(false))))))) || (forall n:Nat.((val(n < 3)) && (exists n:Nat.((val(n < 3)) || (forall m:Nat.((val(m < 3)) && (exists k:Nat.((val(k < 3)) || (X3)))))))))); \n"
  "mu X1(b:Bool) = ((exists m:Nat.((val(m < 3)) || (val(b)))) => ((val(false)) || (X3))) && (forall k:Nat.((val(k < 3)) && ((((val(k > 1)) => (X1(k > 1))) && (val(false))) && (exists k:Nat.((val(k < 3)) || (X4(k + 1, k + 1)))))));                                                                                \n"
  "mu X2(n:Nat, m:Nat) = ((exists k:Nat.((val(k < 3)) || (!(val(n < 2))))) && ((X3) || ((forall k:Nat.((val(k < 3)) && (!((val(m < 3)) || (val(m == k)))))) || (!((X0) => (!X4(n + 1, 1))))))) && (exists n:Nat.((val(n < 3)) || (forall m:Nat.((val(m < 3)) && (exists k:Nat.((val(k < 3)) || (val(k < 3))))))));                                                                                \n"
  "mu X3 = ((val(true)) || (X3)) || (!((forall n:Nat.((val(n < 3)) && (!((val(true)) || ((!(val(n > 1))) && ((X1(n < 3)) || (val(n > 1)))))))) && (forall n:Nat.((val(n < 3)) && (exists m:Nat.((val(m < 3)) || (!X0)))))));                                                                                \n"
  "mu X4(n:Nat, m:Nat) = ((((forall m:Nat.((val(m < 3)) && ((val(m < 2)) || (val(n < 2))))) => (X1(true))) || (forall m:Nat.((val(m < 3)) && (exists m:Nat.((val(m < 3)) || (val(n > 0))))))) || (forall n:Nat.((val(n < 3)) && ((!(forall n:Nat.((val(n < 3)) && (!X4(0, 1))))) || (forall k:Nat.((val(k < 3)) && (val(true)))))))) || (exists m:Nat.((val(m < 3)) || (forall n:Nat.((val(n < 3)) && (!(!X0))))));                                                                                \n"
  "                                                                                \n"
  "init X0;                                                                                \n"
  ;

void test_pbes(const std::string& pbes_spec, const bool expected_result)
{
  pbes_system::pbes p = pbes_system::txt2pbes(pbes_spec);
  pbes_system::detail::parity_game_output pgg(p);
  pgg.run();

// check the result
#ifdef MCRL2_PGSOLVER_ENABLED
  static int index;
  BOOST_CHECK(pbes_system::detail::pbes2bool(p,expected_result));
  std::string name = "parity_game_test" + boost::lexical_cast<std::string>(++index);
  std::string output_file   = name + ".pg";
  std::string solution_file = name + ".solution";
  std::string text = pgg.pgsolver_graph();
  std::ofstream to(output_file.c_str());
  to << text << std::endl;
  std::string command = "pgsolver --recursive --solonly " + output_file + " > " + solution_file;
  std::cout << "executing: " << command << std::endl;
  int execution_result = std::system(command.c_str());
  BOOST_CHECK(execution_result == 0);
  std::string solution = utilities::read_text(solution_file);
  std::istringstream is(solution);

  // skip the first line
  std::string dummy;
  std::getline(is, dummy);

  int player;
  int winner;
  is >> player >> winner;
  std::cout << "player = " << player << std::endl;
  std::cout << "winner = " << winner << std::endl;
  BOOST_CHECK(player == 0);
  BOOST_CHECK(expected_result = (winner == 0));
#endif
}

// mimick the way parity_game_generator is used in parity game solver from Twente
void test_pbespgsolve(const std::string& pbes_spec)
{
  pbes_system::pbes p = pbes_system::txt2pbes(pbes_spec);
  pbes_system::parity_game_generator pgg(p, true, true);
  std::size_t begin = 0;
  std::size_t end = 3;
  for (std::size_t v = begin; v < end; ++v)
  {
    std::set<std::size_t> deps = pgg.get_dependencies(v);
    for (std::size_t w: deps)
    {
      assert(w >= begin);
      if (w >= end)
      {
        end = w + 1;
      }
      // edges.push_back(std::make_pair(v - begin, w - begin));
    }

    int max_prio = 0;
    for (std::size_t v = begin; v < end; ++v)
    {
      max_prio = (std::max)(max_prio, (int)pgg.get_priority(v));
    }

    for (std::size_t v = begin; v < end; ++v)
    {
      // Variable below is not used; So, I removed it to avoid warnings. JFG.
      // bool and_op = pgg.get_operation(v) == mcrl2::pbes_system::parity_game_generator::PGAME_AND;
      pgg.get_priority(v);
    }
  }
}

void test_lps(const std::string& lps_spec, const bool expected_result, const std::string& formula = lps::detail::NO_DEADLOCK())
{
  using namespace pbes_system;
  bool timed = false;
  pbes p = lps2pbes(lps_spec, formula, timed);
  std::string text = pbes_system::pp(p);
  test_pbes(text,expected_result);
}

int test_main(int argc, char** argv)
{
  test_pbes(BES1,false);
  test_pbes(BES2,true);
  test_pbes(BES3,false);
  test_pbes(BES4,true);
  test_pbes(BES5,true);
  test_pbes(BES6,false);
  test_pbes(BES7,false);
  test_pbes(BES8,true);
  test_pbes(PBES1,false);
  test_pbes(PBES2,false);
  test_pbes(PBES3,true);

  test_pbespgsolve(PBES1);
  test_pbespgsolve(PBES2);
  test_pbespgsolve(PBES3);

  test_lps(lps::detail::ABP_SPECIFICATION(),true);

#ifdef MCRL2_EXTENDED_TESTS
  test_lps(lps::detail::DINING3_SPECIFICATION(),false);
  test_lps(lps::detail::ONE_BIT_SLIDING_WINDOW_SPECIFICATION(), true, "nu X. <true>true && [true]X");
#endif

  return 0;
}


//////////////////////////////////////////////////////////////////////////////
// statistics::detail::fusion::at_key::iterator.hpp                         //
//                                                                          //
//  (C) Copyright 2009 Erwann Rogard                                        //
//  Use, modification and distribution are subject to the                   //
//  Boost Software License, Version 1.0. (See accompanying file             //
//  LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)        //
//////////////////////////////////////////////////////////////////////////////
#ifndef  BOOST_STATISTICS_DETAIL_FUSION_AT_KEY_ITERATOR_HPP_ER_2009
#define  BOOST_STATISTICS_DETAIL_FUSION_AT_KEY_ITERATOR_HPP_ER_2009
#include <boost/utility/result_of.hpp>
#include <boost/iterator/transform_iterator.hpp>
#include <boost/iterator/iterator_traits.hpp>
#include <boost/statistics/detail/fusion/at_key/functor.hpp>

namespace boost{
namespace statistics{
namespace detail{
namespace fusion{
namespace at_key{

    template<typename It,typename K>
    struct meta_iterator
    {
        typedef statistics::detail::fusion::at_key::functor<K> f_;
        typedef typename iterator_reference<It>::type ref1_;

        // See boost.user mailing list
        // Subject: [transform_iterator]'s reference
        // 10/05/2009

        typedef typename result_of<f_(ref1_)>::type ref2_;
        typedef boost::transform_iterator<f_,It,ref2_> type;

        static type call(It i){
            // don't use make_transform_iterator because not default.
            return type(i,f_());
        }

    };

    template<typename K,typename It>
    typename meta_iterator<It,K>::type
    make_iterator(It i){
        typedef meta_iterator<It,K> m_;
        return m_::call(i);
    }

}// at_key
}// fusion
}// detail
}// statistics
}// boost

#endif