#include "catch.hpp"

#include <boost/filesystem.hpp>

#include <cpp-utils/filesystem.hpp>

#include <pathfinding-utils/GridMap.hpp>
#include <pathfinding-utils/MovingAIGridMapReader.hpp>
#include <pathfinding-utils/GridMapGraphConverter.hpp>
#include <pathfinding-utils/pathValidators.hpp>

#include "DiscountedCpdState.hpp"
#include "CpdJumpTrailSearch.hpp"
#include "CpdJumpTrailSearchVideoProducerListener.hpp"

using namespace cpp_utils;
using namespace pathfinding;
using namespace pathfinding::validator;
using namespace pathfinding::search;
using namespace pathfinding::search::listeners;
using namespace pathfinding::maps;

double MINDISCOUNT = 0.1;
double MAXDISCOUNT = 1.0;
double RATIO = 0.1;

SCENARIO("test CpdJumpTrailSearch with optimality bound", "[cpd-jump-trail-search-optimal]") {

    GIVEN("a gridmap") {

        // CREATE GRAPH WHERE WE WANT TO OPERATE

        MovingAIGridMapReader reader{
            '.', 100, color_t::WHITE,
            'T', 150, color_t::GREEN,
            '@', cost_t::INFTY, color_t::BLACK
        };
        GridMap gridMap = reader.load(boost::filesystem::path{"square03.map"});
        GridMapGraphConverter converter{GridBranching::EIGHT_CONNECTED};

        auto ptrMap = converter.toGraph(gridMap);

        ptrMap->saveJPEG("mappppppp.map");

        critical("perturbated edge VAFFANCULO is ", ptrMap->getOutEdge(ptrMap->idOfVertex(xyLoc{2, 4}), ptrMap->idOfVertex(xyLoc{3, 4})));

        exit(1);

        AdjacentGraph<std::string, xyLoc, cost_t> graph{converter.toGraph(gridMap)};

        /*
         *  01234
         * 0.XX..
         * 1...@@
         * 2..@@.
         * 3...@@
         * 4..XX.
         *
         * top XX = 150
         * bottom XX=200
         */

        // INCLUDE THE CPD

        critical("perturbated edge TOOMANYVERYVERYBEFORE is ", graph.getOutEdge(graph.idOfVertex(xyLoc{2, 4}), graph.idOfVertex(xyLoc{3, 4})));

        CpdManager<std::string, xyLoc> cpdManager{boost::filesystem::path{"./square03.cpd"}, graph};
        const IImmutableGraph<std::string, xyLoc, cost_t>& g = cpdManager.getReorderedGraph();

        // CREATE THE TIME GRAPH WITH PERTURBATIONS

        boost::filesystem::path p{"./square03-reordered.jpeg"};
        if (!cpp_utils::filesystem::exists(p)){
            g.saveJPEG(p);
        }

        cpp_utils::function_t<cost_t, PerturbatedCost> buildPerturbatedCost = [&](auto& c) { return PerturbatedCost{c, false}; };

        nodeid_t sourceId = g.idOfVertex(xyLoc{2, 4});
        nodeid_t sinkId = g.idOfVertex(xyLoc{3, 4});

        auto_critical(sourceId);
        auto_critical(sinkId);

        critical("perturbated edge VERYVERYBEFORE is ", g.getOutEdge(sourceId, sinkId));

        auto perturbatedGraphTmp = std::unique_ptr<IImmutableGraph<std::string, xyLoc, PerturbatedCost>>{g.mapEdges(buildPerturbatedCost)};

        critical("perturbated edge VERYBEFORE is ", perturbatedGraphTmp->getOutEdge(perturbatedGraphTmp->idOfVertex(xyLoc{2, 4}),
            perturbatedGraphTmp->idOfVertex(xyLoc{3, 4})));

        AdjacentGraph<std::string, xyLoc, PerturbatedCost> perturbatedGraph{*perturbatedGraphTmp};

        critical("perturbated edge BEFORE is ", perturbatedGraph.getOutEdge(perturbatedGraph.idOfVertex(xyLoc{2, 4}),
            perturbatedGraph.idOfVertex(xyLoc{3, 4})));

        perturbatedGraph.changeWeightUndirectedEdge(
            perturbatedGraph.idOfVertex(xyLoc{1, 0}),
            perturbatedGraph.idOfVertex(xyLoc{2, 0}),
            PerturbatedCost{150, true}
        );
        perturbatedGraph.changeWeightUndirectedEdge(
            perturbatedGraph.idOfVertex(xyLoc{2, 4}),
            perturbatedGraph.idOfVertex(xyLoc{3, 4}),
            PerturbatedCost{200, true}
        );

        critical("perturbated edge is ", perturbatedGraph.getOutEdge(perturbatedGraph.idOfVertex(xyLoc{2, 4}),
            perturbatedGraph.idOfVertex(xyLoc{3, 4})));

        // USE THE FACTORY TO PROVIDE CpdSearch

        CpdJumpTrailSearchFactory factory{};
        auto factory_output = factory.get(cpdManager, perturbatedGraph, cost_t{1}, MINDISCOUNT, MAXDISCOUNT, RATIO);

        REQUIRE(g.haveSameVertices(perturbatedGraph));

        WHEN("start == goal") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            start.getPayload();
            auto solution = factory_output->search.search(start, goal, false, false);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0})
            ));
            REQUIRE(solution->getCost() == 0);
        }

        WHEN("start is diagonal goal (no perturbations)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{1,1};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0}), std::make_tuple(xyLoc{1,1})
            ));
            REQUIRE(solution->getCost() == 141);
        }

        WHEN("start is above goal (no perturbations)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,1};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0}), std::make_tuple(xyLoc{0,1})
            ));
            REQUIRE(solution->getCost() == 100);
        }

        WHEN("start to goal via perturbated edge") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{4,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{1,0}),
                    std::make_tuple(xyLoc{2,0}),
                    std::make_tuple(xyLoc{3,0}),
                    std::make_tuple(xyLoc{4,0})
            ));
            REQUIRE(solution->getCost() == 450);
        }

        WHEN("early termination (no perturbation)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,4};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(solution->getCost() == 400);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{0,3}),
                    std::make_tuple(xyLoc{0,4})
            ));
        }

        WHEN("early termination (perturbation near the end)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{4,4};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{1,3}),
                    std::make_tuple(xyLoc{2,4}),
                    std::make_tuple(xyLoc{3,4}),
                    std::make_tuple(xyLoc{4,4})
            ));
            REQUIRE(solution->getCost() == (2*141 + 3*100 + 1*200));
        }

        WHEN("early termination (perturbation near the start)") {
            xyLoc startLoc{4,4};
            xyLoc goalLoc{0,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{4,4}),
                    std::make_tuple(xyLoc{3,4}),
                    std::make_tuple(xyLoc{2,4}),
                    std::make_tuple(xyLoc{1,3}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,0})
            ));
            REQUIRE(solution->getCost() == (2*141 + 3*100 + 1*200));
        }

        delete factory_output;
    }
}

SCENARIO("test CpdJumpTrailSearch with suboptimality bound", "[cpd-jump-trail-search-suboptimal]") {

    GIVEN("a gridmap") {

        // CREATE GRAPH WHERE WE WANT TO OPERATE

        MovingAIGridMapReader reader{
            '.', 100, color_t::WHITE,
            'T', 150, color_t::GREEN,
            '@', cost_t::INFTY, color_t::BLACK
        };
        GridMap gridMap = reader.load(boost::filesystem::path{"square03.map"});
        GridMapGraphConverter converter{GridBranching::EIGHT_CONNECTED};
        AdjacentGraph<std::string, xyLoc, cost_t> graph{*converter.toGraph(gridMap)};

        /*
         *  01234
         * 0.XX..
         * 1...@@
         * 2..@@.
         * 3...@@
         * 4..XX.
         *
         * top XX = 150
         * bottom XX=200
         */

        // INCLUDE THE CPD

        CpdManager<std::string, xyLoc> cpdManager{boost::filesystem::path{"./square03.cpd"}, graph};
        const IImmutableGraph<std::string, xyLoc, cost_t>& g = cpdManager.getReorderedGraph();

        // CREATE THE TIME GRAPH WITH PERTURBATIONS

        auto perturbatedGraphTmp = std::unique_ptr<IImmutableGraph<std::string, xyLoc, PerturbatedCost>>(cpdManager.getReorderedGraph().mapEdges<PerturbatedCost>([&](cost_t c) { return PerturbatedCost{c, false}; }));
        AdjacentGraph<std::string, xyLoc, PerturbatedCost> perturbatedGraph{*perturbatedGraphTmp};

        perturbatedGraph.changeWeightUndirectedEdge(perturbatedGraph.idOfVertex(xyLoc{1, 0}), perturbatedGraph.idOfVertex(xyLoc{2, 0}), PerturbatedCost{150, true});
        perturbatedGraph.changeWeightUndirectedEdge(perturbatedGraph.idOfVertex(xyLoc{2, 4}), perturbatedGraph.idOfVertex(xyLoc{3, 4}), PerturbatedCost{200, true});

        // USE THE FACTORY TO PROVIDE CpdSearch

        CpdJumpTrailSearchFactory factory{};
        //focal bound set to 2 ==> WA*
        auto factory_output = factory.get(cpdManager, perturbatedGraph, fractional_number<cost_t>{2}, MINDISCOUNT, MAXDISCOUNT, RATIO);

        REQUIRE(g.haveSameVertices(perturbatedGraph));

        WHEN("start == goal") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            start.getPayload();
            auto solution = factory_output->search.search(start, goal, false, false);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0})
            ));
            REQUIRE(solution->getCost() == 0);
        }

        WHEN("start is diagonal goal (no perturbations)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{1,1};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0}), std::make_tuple(xyLoc{1,1})
            ));
            REQUIRE(solution->getCost() == 141);
        }

        WHEN("start is above goal (no perturbations)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,1};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(std::make_tuple(xyLoc{0,0}), std::make_tuple(xyLoc{0,1})
            ));
            REQUIRE(solution->getCost() == 100);
        }

        WHEN("start to goal via perturbated edge") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{4,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{1,0}),
                    std::make_tuple(xyLoc{2,0}),
                    std::make_tuple(xyLoc{3,0}),
                    std::make_tuple(xyLoc{4,0})
            ));
            REQUIRE(solution->getCost() == 450);
        }

        WHEN("early termination (no perturbation)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{0,4};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(solution->getCost() == 400);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{0,3}),
                    std::make_tuple(xyLoc{0,4})
            ));
        }

        WHEN("early termination (perturbation near the end)") {
            xyLoc startLoc{0,0};
            xyLoc goalLoc{4,4};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{0,0}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{1,3}),
                    std::make_tuple(xyLoc{2,4}),
                    std::make_tuple(xyLoc{3,4}),
                    std::make_tuple(xyLoc{4,4})
            ));
            REQUIRE(solution->getCost() == (2*141 + 3*100 + 1*200));
        }

        WHEN("early termination (perturbation near the start)") {
            xyLoc startLoc{4,4};
            xyLoc goalLoc{0,0};
            nodeid_t startId = g.idOfVertex(startLoc);
            nodeid_t goalId = g.idOfVertex(goalLoc);
            auto& start = factory_output->stateSupplier.getState(startId);
            auto& goal = factory_output->stateSupplier.getState(goalId);
            auto solution = factory_output->search.search(start, goal, false, true);
            REQUIRE(
                solution->map<std::tuple<xyLoc>>([&](auto& x) {
                    return x.getPayload();
                }) == vectorplus<std::tuple<xyLoc>>::make(
                    std::make_tuple(xyLoc{4,4}),
                    std::make_tuple(xyLoc{3,4}),
                    std::make_tuple(xyLoc{2,4}),
                    std::make_tuple(xyLoc{1,3}),
                    std::make_tuple(xyLoc{0,2}),
                    std::make_tuple(xyLoc{0,1}),
                    std::make_tuple(xyLoc{0,0})
            ));
            REQUIRE(solution->getCost() == (2*141 + 3*100 + 1*200));
        }

        delete factory_output;
    }
}

template <typename G, typename V, typename E>
void performCpdJumpTrailSearchOptimalTest(xyLoc startLoc, xyLoc goalLoc, const GridMap& gridMap, const boost::filesystem::path& basename, const IImmutableGraph<G,V,E>& originalMap, const IImmutableGraph<G,V,PerturbatedCost>& perturbatedGraph, const CpdManager<G,V>& cpdManager, fractional_cost epsilon) {
    static costFunction_t<PerturbatedCost> costFunction = [&](auto pc) { return pc.getCost();};
    CpdJumpTrailSearchFactory factory{};
    //focal bound set to 2 ==> WA*
    auto factory_output = factory.get(cpdManager, perturbatedGraph, epsilon, MINDISCOUNT, MAXDISCOUNT, RATIO);
    CpdJumpTrailSearchVideoProducerListener<std::string, xyLoc, cost_t, PerturbatedCost, DiscountedCpdState<G, V, PerturbatedCost, cpd_search_generated_e>, GridMap, GridMapImage> listener{originalMap, perturbatedGraph, gridMap, costFunction};
    factory_output->search.setListener(listener);

    nodeid_t startId = perturbatedGraph.idOfVertex(startLoc);
    nodeid_t goalId = perturbatedGraph.idOfVertex(goalLoc);

    factory_output->search.setupSearch(nullptr, nullptr);
    auto start = &(factory_output->stateSupplier.getState(startId));
    auto goal = &(factory_output->stateSupplier.getState(goalId));

    auto solution = factory_output->search.search(*start, *goal, false, false);
    critical("solution found!");

    info("drawing image of the perturbated graph together with search info!");
    // //draw the grid and the perturbations on it
    // GridMapImage* image = gridMap.getPPM();
    // *image = static_cast<GridMapImage&>(addPerturbationsOnMap(
    //     *image, gridMap,
    //     originalMap, perturbatedGraph,
    //     color_t::RED, color_t::BLUE, costFunction
    // ));
    // function_t<VertexInfo<xyLoc>, statevisited_e> toState = [&](auto e) { return e.state;};
    // *image = static_cast<GridMapImage&>(addExpandedNodesInImage(
    //     *image, gridMap,
    //     perturbatedGraph, listener.getVisitedStates(), toState,
    //     color_t::BLACK, color_t::YELLOW.scale(0.6), color_t::YELLOW.scale(0.8)
    // ));

    // *image = static_cast<GridMapImage&>(addPathInImage(
    //     *image, gridMap,
    //     perturbatedGraph, listener.getSolution(),
    //     color_t::VIOLET.scale(0.6), color_t::VIOLET.scale(0.3), color_t::VIOLET.scale(0.3)
    // ));

    // image->saveBMP("./query1");
    // delete image;

    cpp_utils::function_t<DiscountedCpdState<std::string, xyLoc, PerturbatedCost, cpd_search_generated_e>, nodeid_t> mapper2 = [&](auto& x) { return x.getPosition(); };

    validator::checkIfPathSuboptimalityBound(
        2.0,
        perturbatedGraph,
        startId, goalId,
        *solution,
        costFunction,
        mapper2
    );
}

SCENARIO("test cpd jump trail optimal search failed queries", "[cpd-jump-trail-search-queries]") {

    //CREATE ORIGINAL GRAPH
    MovingAIGridMapReader reader{
        '.', 1000, color_t::WHITE,
        'T', 1500, color_t::GREEN,
        'S', 2000, color_t::CYAN,
        'W', 2500, color_t::BLUE,
        '@', cost_t::INFTY, color_t::BLACK
    };

    // GIVEN("query 1") {

    //     GridMap gridMap = reader.load(boost::filesystem::path{"./arena2.map"});
    //     GridMapGraphConverter converter{GridBranching::EIGHT_CONNECTED};
    //     AdjacentGraph<std::string, xyLoc, cost_t> graph{*converter.toGraph(gridMap)};
    //     // INCLUDE THE CPD REORDERING
    //     CpdManager<std::string, xyLoc> cpdManager{boost::filesystem::path{"./arena2.cpd"}, graph};
    //     const IImmutableGraph<std::string, xyLoc, cost_t>& g = cpdManager.getReorderedGraph();
    //     // CREATE PERTURBATED GRAPH
    //     boost::filesystem::path perturbatedEdgesPath{"./arena2-900-perturbated.graph"};
    //     cpp_utils::function_t<cost_t, PerturbatedCost> mapper = [&](const cost_t& c) { return PerturbatedCost{c, false};};
    //     std::unique_ptr<IImmutableGraph<std::string,xyLoc,PerturbatedCost>> perturbatedGraph{pathfinding::utils::loadPerturbatedMap(
    //             perturbatedEdgesPath, g, mapper
    //     )};
    //     // USE THE FACTORY TO PROVIDE THE SEARCH
    //     REQUIRE(g.haveSameVertices(*perturbatedGraph));

    //     WHEN("query 1 with epsilon set to 2") {
    //         xyLoc startLoc{16, 105};
    //         xyLoc goalLoc{277, 206};
    //         performCpdJumpTrailSearchOptimalTest(
    //             startLoc, goalLoc,
    //             gridMap, "./query1",
    //             g, *perturbatedGraph,
    //             cpdManager,
    //             fractional_cost{2}
    //         );
    //     }
    // }

    GIVEN("query 1") {

        GridMap gridMap = reader.load(boost::filesystem::path{"./arena2.map"});
        GridMapGraphConverter converter{GridBranching::EIGHT_CONNECTED};
        AdjacentGraph<std::string, xyLoc, cost_t> graph{*converter.toGraph(gridMap)};
        // INCLUDE THE CPD REORDERING
        CpdManager<std::string, xyLoc> cpdManager{boost::filesystem::path{"./arena2.cpd"}, graph};
        const IImmutableGraph<std::string, xyLoc, cost_t>& g = cpdManager.getReorderedGraph();
        // CREATE PERTURBATED GRAPH
        boost::filesystem::path perturbatedEdgesPath{"./arena2-900-perturbated.graph"};
        cpp_utils::function_t<cost_t, PerturbatedCost> mapper = [&](const cost_t& c) { return PerturbatedCost{c, false};};
        std::unique_ptr<IImmutableGraph<std::string,xyLoc,PerturbatedCost>> perturbatedGraph{pathfinding::utils::loadPerturbatedMap(
                perturbatedEdgesPath, g, mapper
        )};
        // USE THE FACTORY TO PROVIDE THE SEARCH
        REQUIRE(g.haveSameVertices(*perturbatedGraph));

        WHEN("query 1 with epsilon set to 1.1") {
            xyLoc startLoc{16, 105};
            xyLoc goalLoc{277, 206};
            performCpdJumpTrailSearchOptimalTest(
                startLoc, goalLoc,
                gridMap, "./query1",
                g, *perturbatedGraph,
                cpdManager,
                fractional_cost{1.0}
            );
        }
    }
}


#include <stdio.h>
#include <string>
#include <map>
#include <fstream>
#include "llvm/Support/raw_ostream.h"
#include <cstdio>
#include <iostream>
#include <memory>
#include <stdexcept>
#include <array>
#include <regex>
#include "helper.h"
#include "klee/ExecutionState.h"
#include "klee/Internal/ADT/RNG.h"
#include <boost/math/distributions/binomial.hpp>
#include "Executor.h"
#include <cmath>
#include "CminHandler.h"

using namespace klee;
using namespace std;
using namespace llvm;
using boost::math::binomial;

namespace klee {
  extern RNG theRNG;
}

CminHandler::CminHandler(Executor &_executor)
: executor(_executor)
{
   printf("new Cmin handler created!\n");
}

CminHandler::CminHandler(CminHandler &_handler)
: executor(_handler.executor)
{

}

CminHandler::~CminHandler()
{

}

CminSketch::CminSketch()
{
}

CminSketch::CminSketch(int _size):
ht1(_size),
ht2(_size),
ht3(_size)
{
   LOG(LOG_MASK_CMIN, "creating an empty cmin, size=%d", _size);
}

CminSketch::CminSketch(const CminSketch &_cmin):
ht1(_cmin.ht1),
ht2(_cmin.ht2),
ht3(_cmin.ht3)
{
   //LOG(LOG_MASK_CMIN, "forking a cmin");
}

CminSketch::~CminSketch()
{
}

int CminHandler::cmin_init(ExecutionState &cur, size_t sz)
{
   LOG(LOG_MASK_CMIN, "allocating cmin, size=%d", (int)sz);
   cur.cmin = new CminSketch(sz);
   return 0;
}

double CminSketch::cmin_add_to_ht(int f1, int f2, int f3)
{
   double p1 = ht1.ht_add(f1);
   double p2 = ht2.ht_add(f2);
   double p3 = ht3.ht_add(f3);

   LOG(LOG_MASK_CMIN, "adding one to cmin, f1=%d, f2=%d, f3=%d,"
       " p1=%g, p2=%g, p3=%g",
       f1, f2, f3, p1, p2, p3);
   return p1 * p2 * p3;
}

void CminSketch::dump()
{
   ht1.dump();
   ht2.dump();
   ht3.dump();
}

int CminHandler::cmin_add(ExecutionState &cur,
                         vector<ExecutionState*> &states,
                         vector<int> &rets)
{
   CminSketch *cmin = cur.cmin;
   int nbranch1 = cmin->ht1.cur_size == 0 ? 1 : 2;
   int nbranch2 = cmin->ht2.cur_size == 0 ? 1 : 2;
   int nbranch3 = cmin->ht3.cur_size == 0 ? 1 : 2;

   // TODO: add support for SSE
   // fork n1*n2*n3 states
   int nstates = nbranch1 * nbranch2 * nbranch3;

   LOG(LOG_MASK_CMIN, "adding 1 to cmin, creating %d x %d x %d = %d paths",
       nbranch1, nbranch2, nbranch3, nstates);

   executor.branchTableAccessNoCond(cur, nstates, states);

   // Update Cmin greybox for each forked state
   int i = 0;
   double prob = 0.0;
   for (int f1 = nbranch1-1; f1 >= 0; f1 --) {
      for (int f2 = nbranch2-1; f2 >= 0; f2 --) {
         for (int f3 = nbranch3-1; f3 >= 0; f3 --) {
            rets.push_back(i);
            prob = states[i]->cmin->cmin_add_to_ht(f1, f2, f3);
            LOG(LOG_MASK_CMIN, "path %d prob is %g", i, prob);
            states[i]->updatePathProb(prob);
            // states[i]->cmin->dump();

            i ++;
         }
      }
   }

   LOG(LOG_MASK_CMIN, "returning %d paths, size of rets=%ld",
       nstates, rets.size());
   return 0;
}

int CminHandler::cmin_larger_than(ExecutionState &cur,
                                  int val,
                                  std::vector<ExecutionState*> &states,
                                  std::vector<int> &rets)
{
   // TODO: add support for SSE

   // to make sure cmin is greater than val, each ht has to be greater than val
   double p1 = cur.cmin->ht1.ht_get_prob_larger_than_after_hit(val);
   double p2 = cur.cmin->ht2.ht_get_prob_larger_than_after_hit(val);
   double p3 = cur.cmin->ht3.ht_get_prob_larger_than_after_hit(val);
   double prob = p1 * p2 * p3;

   LOG(LOG_MASK_CMIN, "state prob=%g, testing if cmin value is larger than %d",
       cur.getPathProb(), val);
   LOG(LOG_MASK_CMIN, "p1=%g, p2=%g, p3=%g, prob=%g", p1, p2, p3, prob);

   // greater than val is impossible, returning one branch
   if (prob == 0.0) {
      states.push_back(&cur);
      rets.push_back(0);
      LOG(LOG_MASK_CMIN, "cannot greater than %d, returning one branch", val);
      return 0;
   }

   // greater than val is possible
   executor.branchTableAccessNoCond(cur, 2, states);

   // branch 1: cmin is greater than val
   rets.push_back(1);
   states[0]->updatePathProb(prob);
   // branch 2: cmin is not greater than val
   rets.push_back(0);
   states[1]->updatePathProb(1 - prob);

   LOG(LOG_MASK_CMIN, "greater than %d: %g, else: %g", val, prob, 1-prob);
   return 0;
}

int CminSketch::getMaxValue()
{
   int max1 = ht1.getMaxValue();
   int max2 = ht2.getMaxValue();
   int max3 = ht3.getMaxValue();

   return MIN(MIN(max1, max2), max3);
}


#include <cstdio>
#include <map>
#include <vector>
#include <string>
#include <cstring>
#include <algorithm>
#include <fstream>
#include <opencv2/core/core.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <boost/regex.hpp>
#include <boost/filesystem.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <chrono>

using namespace std;
using namespace boost::filesystem;
using namespace cv;

typedef struct IrisCode
{
  string name;
  unsigned char **data;
  int stepsize;
} IrisCode;

static const int CODE_WIDTH = 512;
static const int CODE_HEIGHT = 20;

static const int MIN_SHIFT = -16;
static const int MAX_SHIFT = 16;

static const int htlut[256] = {0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,1,2,2,3,2,3,3,4,2,3,3,4,3,4,4,5,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,2,3,3,4,3,4,4,5,3,4,4,5,4,5,5,6,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,3,4,4,5,4,5,5,6,4,5,5,6,5,6,6,7,4,5,5,6,5,6,6,7,5,6,6,7,6,7,7,8};

static const int ALG_MINHD = 0, ALG_3A = 1, ALG_3AL = 2, ALG_3AS = 3;
static const int MODE_MAIN = 1, MODE_HELP = 2;
enum MAIN_MODE { STATIC, DYNAMIC};

void printUsage() {
	printf("+-----------------------------------------------------------------------------+\n");
	printf("| TripleA - calculates Hamming distance of iris-codes                         |\n");
	printf("|                                                                             |\n");
	printf("| MODES                                                                       |\n");
	printf("|                                                                             |\n");
	printf("| (# 1) HD calculation of the input images (cross comparison)                 |\n");
	printf("| (# 2) usage                                                                 |\n");
	printf("|                                                                             |\n");
	printf("| ARGUMENTS                                                                   |\n");
	printf("|                                                                             |\n");
	printf("+------+------------+---+---+-------------------------------------------------+\n");
	printf("| Name | Parameters | # | ? | Description                                     |\n");
	printf("+------+------------+---+---+-------------------------------------------------+\n");
	printf("| -f   | folder     | 1 | N | folder of iris-codes generated using LG or QSW  |\n");
	printf("| -o   | file       | 1 | N | output file for the results                     |\n");
	printf("| -s   | step-size  | 1 | Y | step-size used in static mode (5)               |\n");
	printf("| -c   | constant   | 1 | Y | constant used in dynamic mode (1)               |\n");
	printf("| -a   | algorithm  | 1 | Y | TripleA algorithm (3a) (default)                |\n");
	printf("|      |            |   |   | TripleA-limited algorithm (3al)                 |\n");
	printf("|      |            |   |   | TripleA-single-sided algorithm (3as)            |\n");
	printf("| -h   |            | 2 | N | prints usage                                    |\n");
	printf("+------+------------+---+---+-------------------------------------------------+\n");
	printf("|                                                                             |\n");
	printf("| The default is to run the program in dynamic mode. When a step size (-s) is |\n");
	printf("| set the program automatically switches to static-mode.                      |\n");
	printf("|                                                                             |\n");
	printf("| EXAMPLE USAGE                                                               |\n");
    printf("|                                                                             |\n");
    printf("| -f codes -s 4 -c 0.33 -a 3a -o compare.txt                                  |\n");
    printf("|                                                                             |\n");
	printf("| AUTHOR                                                                      |\n");
	printf("|                                                                             |\n");
	printf("| Christian Rathgeb                                                           |\n");
	printf("|                                                                             |\n");
	printf("| COPYRIGHT                                                                   |\n");
	printf("|                                                                             |\n");
	printf("| (C) 2016 All rights reserved. Do not distribute without written permission. |\n");
	printf("+-----------------------------------------------------------------------------+\n");
}

// estimate the Hamming distance between two given codes using the TripleA algorithm
void TripleA(IrisCode code1, IrisCode code2, int start, int stop, ofstream& log, int stepsize){

	float mindist = CODE_HEIGHT*CODE_WIDTH;
	unsigned int dist;
	int pos_min = 0;

	int w = CODE_WIDTH;
	int h;
	int border;

	if (CODE_HEIGHT%8 == 0) h = CODE_HEIGHT/8;
	else h = CODE_HEIGHT/8 + 1;

	auto start_time = chrono::high_resolution_clock::now();

	// alignmnet with step-size
	border = stop%stepsize;
	if (border != 0){
		int k=start;
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
		start+=border;
	}

	for (int k = start; k <= stop; k+=stepsize){
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}

	if (border != 0){
		int k=stop;
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}

	start = max(pos_min - stepsize + 1, start);
	stop = min(pos_min + stepsize - 1, stop);

	for (int k = start; k <= stop; k++){
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}

	auto end_time = chrono::high_resolution_clock::now();

	log << "hd(" << code1.name << "," << code2.name << "):" <<  mindist/(CODE_HEIGHT*CODE_WIDTH) << "\t time: " << chrono::duration_cast<chrono::microseconds>(end_time - start_time).count() << " micro seconds \t step-size: " << stepsize << endl;
	log.flush();
}

// estimate the Hamming distance between two given codes using the TripleA-limited algorithm
void TripleA_limited(IrisCode code1, IrisCode code2, int start, int stop, ofstream& log, int stepsize){

	float mindist = CODE_HEIGHT*CODE_WIDTH;
	unsigned int dist;
	int pos_min = 0;

	int w = CODE_WIDTH;
	int h;
	int border;

	if (CODE_HEIGHT%8 == 0) h = CODE_HEIGHT/8;
	else h = CODE_HEIGHT/8 + 1;

	auto start_time = chrono::high_resolution_clock::now();

	// limited alignment with step-size
	border = stop%stepsize;
	if (border != 0){
		int k=start;
		dist = 0;
		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
		start+=border;
	}

	for (int k = start; k <= stop; k+=stepsize){
		dist = 0;
		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}

	if (border != 0){
		int k=stop;
		dist = 0;
		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}
	mindist = CODE_HEIGHT*CODE_WIDTH;

	start = max(pos_min - stepsize + 1, start); // note that we already tested the boundaries
	stop = min(pos_min + stepsize - 1, stop);

	for (int k = start; k <= stop; k++){
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}

	auto end_time = chrono::high_resolution_clock::now();

	log << "hd(" << code1.name << "," << code2.name << "):" <<  mindist/(CODE_HEIGHT*CODE_WIDTH) << "\t time: " << chrono::duration_cast<chrono::microseconds>(end_time - start_time).count() << " micro seconds \t step-size: " << stepsize << endl;
	log.flush();
}

// estimate the Hamming distance between two given codes using the TripleA-single-sided algorithm
void TripleA_singlesided(IrisCode code1, IrisCode code2, int start, int stop, ofstream& log, int stepsize){

	float mindist = CODE_HEIGHT*CODE_WIDTH;
	unsigned int dist;
	int pos_min = 0;
	int idx_min = 0;

	int w = CODE_WIDTH;
	int h;
	int border;
	int off = 0;

	if (CODE_HEIGHT%8 == 0) h = CODE_HEIGHT/8;
	else h = CODE_HEIGHT/8 + 1;

	border = stop%stepsize;
	if (border > 0) off=2;

	int cnt=0;
	int scores[(abs(start)+stop)/stepsize + 1 + off];

	auto start_time = chrono::high_resolution_clock::now();

	if (border != 0){
		int k=start;
		dist = 0;
		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		scores[cnt] = dist;
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
			idx_min = cnt;
		}
		cnt++;
		start+=border;
	}

	for (int k = start; k <= stop; k+=stepsize){
		dist = 0;

		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		scores[cnt] = dist;
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
			idx_min = cnt;
		}
		cnt++;
	}

	if (border != 0){
		int k=stop;
		dist = 0;
		for (int j=0; j < w; j++){
			dist+= htlut[code1.data[j][0] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][0]];
		}
		scores[cnt] = dist;
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
			idx_min = cnt;
		}
	}

	mindist = CODE_HEIGHT*CODE_WIDTH;
	if (idx_min == 0){
		start = pos_min;
		stop = pos_min + stepsize - 1;
	}
	else if (idx_min == (abs(start)+stop)/stepsize + off){
		start = pos_min - stepsize + 1;
		stop = pos_min;
	}
	else if (scores[idx_min +1] > scores[idx_min -1]){
		start = pos_min - stepsize + 1;
		stop = pos_min;
	}
	else{
		start = pos_min;
		stop = pos_min + stepsize - 1;
	}

	for (int k = start; k <= stop; k++){
		dist = 0;
		for (int i=0; i < h; i++){
			for (int j=0; j < w; j++){
				dist+= htlut[code1.data[j][i] ^ code2.data[(j+k+CODE_WIDTH)%CODE_WIDTH][i]];
			}
		}
		if (dist < mindist){
			mindist = dist;
			pos_min = k;
		}
	}
	auto end_time = chrono::high_resolution_clock::now();
	log << "hd(" << code1.name << "," << code2.name << "):" <<  mindist/(CODE_HEIGHT*CODE_WIDTH) << "\t time: " << chrono::duration_cast<chrono::microseconds>(end_time - start_time).count() << " micro seconds \t step-size: " << stepsize << endl;
	log.flush();
}

// read an iris-code form a given file name, extract the (dynamic) step size
// and map it to a more efficient representation
IrisCode getCode(string filename, float k)
{
	IrisCode iriscode;
	Mat img = imread(filename, CV_LOAD_IMAGE_UNCHANGED);
	CV_Assert(img.data != 0);
	CV_Assert(img.type() == CV_8UC1);

	int w = CODE_WIDTH;
	int h;

	iriscode.name=filename;

	if (CODE_HEIGHT%8 == 0) h = CODE_HEIGHT/8;
	else h = CODE_HEIGHT/8 + 1;

	unsigned char c;
	unsigned char bincode[CODE_WIDTH][CODE_HEIGHT];

	// initialize with 0s
	for (int i=0; i < CODE_HEIGHT; i++){
		for (int j=0; j < CODE_WIDTH; j++){
			bincode[j][i] = 0;
		}
	}

	// now map the given codes to 0/1 representation
	for (int i=0; i < CODE_HEIGHT; i++){
		for (int j=0; j < CODE_WIDTH; j++){
			c=pow(2,7-(j%8));
			bincode[j][i]=htlut[img.data[j/8+i*CODE_WIDTH/8]&c];
		}
	}

	float mu = 0;
	int length;
	int count=0;
	for (int i=0; i < CODE_HEIGHT; i++){
		length = 1;
		for (int j=1; j < CODE_WIDTH; j++){
			if (bincode[j][i] == bincode[j-1][i]) length++;
			else {
				mu+=length;
				length=1;
				count++;
			}
		}
		mu+=length;
		length=1;
		count++;
	}
	mu/=count;

	// assign the average dynamic step-size according to the given constant k
	iriscode.stepsize = max((int)(mu*k),1);

	unsigned char **code;
	code = (unsigned char **)malloc(w * sizeof(unsigned char *));
	for (int i = 0; i < w; i++) code[i] = (unsigned char *)malloc(h * sizeof(unsigned char));

	// initialize with 0s
	for (int i=0; i < h; i++){
		for (int j=0; j < w; j++){
			code[j][i] = 0;
		}
	}

	// now map the given codes to a more efficient representation
	// create bytes out of 8 adjacent row-bits
	for (int i=0; i < h; i++){
		for (int j=0; j < w; j++){
			c=pow(2,7-(j%8));
			for (int k = 0; k < 8; k++){
				if (i*8+k >=CODE_HEIGHT) break;
				code[j][i]+=htlut[img.data[j/8+i*w+k*w/8]&c] * pow(2,7-k);
			}
		}
	}

	iriscode.data=code;
	return iriscode;
}

/** ------------------------------- commandline functions ------------------------------- **/

/**
 * Parses a command line
 * This routine should be called for parsing command lines for executables.
 * Note, that all options require '-' as prefix and may contain an arbitrary
 * number of optional arguments.
 *
 * cmd: commandline representation
 * argc: number of parameters
 * argv: string array of argument values
 */
void cmdRead(map<string ,vector<string> >& cmd, int argc, char *argv[]){
	for (int i=1; i< argc; i++){
		char * argument = argv[i];
		if (strlen(argument) > 1 && argument[0] == '-' && (argument[1] < '0' || argument[1] > '9')){
			cmd[argument]; // insert
			char * argument2;
			while (i + 1 < argc && (strlen(argument2 = argv[i+1]) <= 1 || argument2[0] != '-'  || (argument2[1] >= '0' && argument2[1] <= '9'))){
				cmd[argument].push_back(argument2);
				i++;
			}
		}
		else {
			CV_Error(CV_StsBadArg,"Invalid command line format");
		}
	}
}

/**
 * Checks, if each command line option is valid, i.e. exists in the options array
 *
 * cmd: commandline representation
 * validOptions: list of valid options separated by pipe (i.e. |) character
 */
void cmdCheckOpts(map<string ,vector<string> >& cmd, const string validOptions){
	vector<string> tokens;
	const string delimiters = "|";
	string::size_type lastPos = validOptions.find_first_not_of(delimiters,0); // skip delimiters at beginning
	string::size_type pos = validOptions.find_first_of(delimiters, lastPos); // find first non-delimiter
	while (string::npos != pos || string::npos != lastPos){
		tokens.push_back(validOptions.substr(lastPos,pos - lastPos)); // add found token to vector
		lastPos = validOptions.find_first_not_of(delimiters,pos); // skip delimiters
		pos = validOptions.find_first_of(delimiters,lastPos); // find next non-delimiter
	}
	sort(tokens.begin(), tokens.end());
	for (map<string, vector<string> >::iterator it = cmd.begin(); it != cmd.end(); it++){
		if (!binary_search(tokens.begin(),tokens.end(),it->first)){
			CV_Error(CV_StsBadArg,"Command line parameter '" + it->first + "' not allowed.");
			tokens.clear();
			return;
		}
	}
	tokens.clear();
}

/*
 * Checks, if a specific required option exists in the command line
 *
 * cmd: commandline representation
 * option: option name
 */
void cmdCheckOptExists(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
    if (it == cmd.end()) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' is required, but does not exist.");
}

/*
 * Checks, if a specific option has the appropriate number of parameters
 *
 * cmd: commandline representation
 * option: option name
 * size: appropriate number of parameters for the option
 */
void cmdCheckOptSize(map<string ,vector<string> >& cmd, const string option, const unsigned int size = 1){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it->second.size() != size) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' has unexpected size.");
}

/*
 * Checks, if a specific option has the appropriate number of parameters
 *
 * cmd: commandline representation
 * option: option name
 * min: minimum appropriate number of parameters for the option
 * max: maximum appropriate number of parameters for the option
 */
void cmdCheckOptRange(map<string ,vector<string> >& cmd, string option, unsigned int min = 0, unsigned int max = 1){
	map<string, vector<string> >::iterator it = cmd.find(option);
	unsigned int size = it->second.size();
	if (size < min || size > max) CV_Error(CV_StsBadArg,"Command line parameter '" + option + "' is out of range.");
}

/*
 * Returns the list of parameters for a given option
 *
 * cmd: commandline representation
 * option: name of the option
 */
vector<string> * cmdGetOpt(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
	return (it != cmd.end()) ? &(it->second) : 0;
}

/*
 * Returns number of parameters in an option
 *
 * cmd: commandline representation
 * option: name of the option
 */
unsigned int cmdSizePars(map<string ,vector<string> >& cmd, const string option){
	map<string, vector<string> >::iterator it = cmd.find(option);
	return (it != cmd.end()) ? it->second.size() : 0;
}

/*
 * Returns a specific parameter type (int) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
int cmdGetParInt(map<string ,vector<string> >& cmd, string option, unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return atoi(it->second[param].c_str());
		}
	}
	return 0;
}

/*
 * Returns a specific parameter type (float) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
float cmdGetParFloat(map<string ,vector<string> >& cmd, const string option, const unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return atof(it->second[param].c_str());
		}
	}
	return 0;
}

/*
 * Returns a specific parameter type (string) given an option and parameter index
 *
 * cmd: commandline representation
 * option: name of option
 * param: name of parameter
 */
string cmdGetPar(map<string ,vector<string> >& cmd, const string option, const unsigned int param = 0){
	map<string, vector<string> >::iterator it = cmd.find(option);
	if (it != cmd.end()) {
		if (param < it->second.size()) {
			return it->second[param];
		}
	}
	return 0;
}

/** ------------------------------- timing functions ------------------------------- **/

/**
 * Class for handling timing progress information
 */
class Timing{
public:
	/** integer indicating progress with respect tot total **/
	int progress;
	/** total count for progress **/
	int total;

	/*
	 * Default constructor for timing initializing time.
	 * Automatically calls init()
	 *
	 * seconds: update interval in seconds
	 * eraseMode: if true, outputs sends erase characters at each print command
	 */
	Timing(long seconds, bool eraseMode){
		updateInterval = seconds;
		progress = 1;
		total = 100;
		eraseCount=0;
		erase = eraseMode;
		init();
	}

	/*
	 * Destructor
	 */
	~Timing(){}

	/*
	 * Initializes timing variables
	 */
	void init(void){
		start = boost::posix_time::microsec_clock::universal_time();
		lastPrint = start - boost::posix_time::seconds(updateInterval);
	}

	/*
	 * Clears printing (for erase option only)
	 */
	void clear(void){
		string erase(eraseCount,'\r');
		erase.append(eraseCount,' ');
		erase.append(eraseCount,'\r');
		printf("%s",erase.c_str());
		eraseCount = 0;
	}

	/*
	 * Updates current time and returns true, if output should be printed
	 */
	bool update(void){
		current = boost::posix_time::microsec_clock::universal_time();
		return ((current - lastPrint > boost::posix_time::seconds(updateInterval)) || (progress == total));
	}

	/*
	 * Prints timing object to STDOUT
	 */
	void print(void){
		lastPrint = current;
		float percent = 100.f * progress / total;
		boost::posix_time::time_duration passed = (current - start);
		boost::posix_time::time_duration togo = passed * (total - progress) / max(1,progress);
		if (erase) {
			string erase(eraseCount,'\r');
			printf("%s",erase.c_str());
			int newEraseCount = (progress != total) ? printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03i Remaining ca. %i:%02i:%02i.%03i)",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000),togo.hours(),togo.minutes(),togo.seconds(),(int)(togo.total_milliseconds() % 1000)) : printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03d)",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000));
			if (newEraseCount < eraseCount) {
				string erase(newEraseCount-eraseCount,' ');
				erase.append(newEraseCount-eraseCount,'\r');
				printf("%s",erase.c_str());
			}
			eraseCount = newEraseCount;
		}
		else {
			eraseCount = (progress != total) ? printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03i Remaining ca. %i:%02i:%02i.%03i)\n",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000),togo.hours(),togo.minutes(),togo.seconds(),(int)(togo.total_milliseconds() % 1000)) : printf("Progress ... %3.2f%% (%i/%i Total %i:%02i:%02i.%03d)\n",percent,progress,total,passed.hours(),passed.minutes(),passed.seconds(),(int)(passed.total_milliseconds()%1000));
		}
	}
private:
	long updateInterval;
	boost::posix_time::ptime start;
	boost::posix_time::ptime current;
	boost::posix_time::ptime lastPrint;
	int eraseCount;
	bool erase;
};

/** ------------------------------- file pattern matching functions ------------------------------- **/

/*
 * Formats a given string, such that it can be used as a regular expression
 * I.e. escapes special characters and uses * and ? as wildcards
 *
 * pattern: regular expression path pattern
 * pos: substring starting index
 * n: substring size
 *
 * returning: escaped substring
 */
string patternSubstrRegex(string& pattern, size_t pos, size_t n){
	string result;
	for (size_t i=pos, e=pos+n; i < e; i++ ) {
		char c = pattern[i];
		if ( c == '\\' || c == '.' || c == '+' || c == '[' || c == '{' || c == '|' || c == '(' || c == ')' || c == '^' || c == '$' || c == '}' || c == ']') {
			result.append(1,'\\');
			result.append(1,c);
		}
		else if (c == '*'){
			result.append("([^/\\\\]*)");
		}
		else if (c == '?'){
			result.append("([^/\\\\])");
		}
		else {
			result.append(1,c);
		}
	}
	return result;
}

/*
 * Converts a regular expression path pattern into a list of files matching with this pattern by replacing wildcards
 * starting in position pos assuming that all prior wildcards have been resolved yielding intermediate directory path.
 * I.e. this function appends the files in the specified path according to yet unresolved pattern by recursive calling.
 *
 * pattern: regular expression path pattern
 * files: the list to which new files can be applied
 * pos: an index such that positions 0...pos-1 of pattern are already considered/matched yielding path
 * path: the current directory (or empty)
 */
void patternToFiles(string& pattern, vector<string>& files, const size_t& pos, const string& path){
	size_t first_unknown = pattern.find_first_of("*?",pos); // find unknown * in pattern
	if (first_unknown != string::npos){
		size_t last_dirpath = pattern.find_last_of("/\\",first_unknown);
		size_t next_dirpath = pattern.find_first_of("/\\",first_unknown);
		if (next_dirpath != string::npos){
			boost::regex expr((last_dirpath != string::npos && last_dirpath > pos) ? patternSubstrRegex(pattern,last_dirpath+1,next_dirpath-last_dirpath-1) : patternSubstrRegex(pattern,pos,next_dirpath-pos));
			boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
			try {
				for ( boost::filesystem::directory_iterator itr( ((path.length() > 0) ? path + pattern[pos-1] : (last_dirpath != string::npos && last_dirpath > pos) ? "" : "./") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) : "")); itr != end_itr; ++itr )
				{
					if (boost::filesystem::is_directory(itr->path())){
						boost::filesystem::path p = itr->path().filename();
						string s =  p.string();
						if (boost::regex_match(s.c_str(), expr)){
							patternToFiles(pattern,files,(int)(next_dirpath+1),((path.length() > 0) ? path + pattern[pos-1] : "") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) + pattern[last_dirpath] : "") + s);
						}
					}
				}
			}
			catch (boost::filesystem::filesystem_error &e){}
		}
		else {
			boost::regex expr((last_dirpath != string::npos && last_dirpath > pos) ? patternSubstrRegex(pattern,last_dirpath+1,pattern.length()-last_dirpath-1) : patternSubstrRegex(pattern,pos,pattern.length()-pos));
			boost::filesystem::directory_iterator end_itr; // default construction yields past-the-end
			try {
				for ( boost::filesystem::directory_iterator itr(((path.length() > 0) ? path +  pattern[pos-1] : (last_dirpath != string::npos && last_dirpath > pos) ? "" : "./") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) : "")); itr != end_itr; ++itr )
				{
					boost::filesystem::path p = itr->path().filename();
					string s =  p.string();
					if (boost::regex_match(s.c_str(), expr)){
						files.push_back(((path.length() > 0) ? path + pattern[pos-1] : "") + ((last_dirpath != string::npos && last_dirpath > pos) ? pattern.substr(pos,last_dirpath-pos) + pattern[last_dirpath] : "") + s);
					}
				}
			}
			catch (boost::filesystem::filesystem_error &e){}
		}
	}
	else { // no unknown symbols
		boost::filesystem::path file(((path.length() > 0) ? path + "/" : "") + pattern.substr(pos,pattern.length()-pos));
		if (boost::filesystem::exists(file)){
			files.push_back(file.string());
		}
	}
}

/**
 * Converts a regular expression path pattern into a list of files matching with this pattern
 *
 * pattern: regular expression path pattern
 * files: the list to which new files can be applied
 */
void patternToFiles(string& pattern, vector<string>& files){
	patternToFiles(pattern,files,0,"");
}

/*
 * Renames a given filename corresponding to the actual file pattern using a renaming pattern.
 * Wildcards can be referred to as ?1, ?2, ... in the order they appeared in the file pattern.
 *
 * pattern: regular expression path pattern
 * renamePattern: renaming pattern using ?1, ?2, ... as placeholders for wildcards
 * infile: path of the file (matching with pattern) to be renamed
 * outfile: path of the renamed file
 * par: used parameter (default: '?')
 */
void patternFileRename(string& pattern, const string& renamePattern, const string& infile, string& outfile, const char par = '?'){
	size_t first_unknown = renamePattern.find_first_of(par,0); // find unknown ? in renamePattern
	if (first_unknown != string::npos){
		string formatOut = "";
		for (size_t i=0, e=renamePattern.length(); i < e; i++ ) {
			char c = renamePattern[i];
			if ( c == par && i+1 < e) {
				c = renamePattern[i+1];
				if (c > '0' && c <= '9'){
					formatOut.append(1,'$');
					formatOut.append(1,c);
				}
				else {
					formatOut.append(1,par);
					formatOut.append(1,c);
				}
				i++;
			}
			else {
				formatOut.append(1,c);
			}
		}
		boost::regex patternOut(patternSubstrRegex(pattern,0,pattern.length()));
		outfile = boost::regex_replace(infile,patternOut,formatOut,boost::match_default | boost::format_perl);
	} else {
		outfile = renamePattern;
	}
}

/** ------------------------------- Program ------------------------------- **/

/*
 * Main program
 */
int main(int argc, char *argv[])
{
	std::vector<IrisCode> iriscodes;

	int mode = MODE_HELP;
	map<string,vector<string> > cmd;
	try {
		cmdRead(cmd,argc,argv);
		if (cmd.size() == 0 || cmdGetOpt(cmd,"-h") != 0) mode = MODE_HELP;
		else mode = MODE_MAIN;
		if (mode == MODE_MAIN){
			// validate command line
			cmdCheckOpts(cmd,"-f|-s|-a|-c|-o|-h");
			string folder;
			if (cmdGetOpt(cmd,"-f") != 0){
				cmdCheckOptSize(cmd,"-f",1);
				folder = cmdGetPar(cmd,"-f");
			}
			int alg = ALG_3A;
			if (cmdGetOpt(cmd,"-a") != 0){
				cmdCheckOptSize(cmd,"-a",1);
				string algo = cmdGetPar(cmd,"-a");
				if (algo == "3al"){
					alg = ALG_3AL;
				}
				if (algo == "3as"){
					alg = ALG_3AS;
				}
			}
            MAIN_MODE main_mode = DYNAMIC;
            string outfilename = "dynamic.txt";
			// parameter for static mode
			int s = 5;
			if (cmdGetOpt(cmd,"-s") != 0){
				cmdCheckOptSize(cmd,"-s",1);
				s = cmdGetParInt(cmd,"-s");
                main_mode =  STATIC;
                outfilename="static.txt";
            }
			float c = 0.33;
			if (cmdGetOpt(cmd,"-c") != 0){
                cmdCheckOptSize(cmd,"-c",1);
                c = cmdGetParFloat(cmd,"-c");
                main_mode = DYNAMIC;
                outfilename="dynamic.txt";
            }

            if (cmdGetOpt(cmd,"-o") != 0){

                cmdCheckOptSize(cmd,"-o",1);
                outfilename = cmdGetPar(cmd,"-o");
            }

			// starting routine
			path p(folder);
			cout << "loading iriscodes..." << endl;
			for (auto i = directory_iterator(p); i != directory_iterator(); i++){
				if (!is_directory(i->path())){
					iriscodes.push_back(getCode(i->path().string(), c));
				}
				else continue;
			}
			cout << "loaded " << (int)iriscodes.size() << " iriscodes" << endl;

			ofstream log;
            log.open(outfilename, std::ios_base::app);
            cout << "output written to " << outfilename << endl;

            if (  main_mode == STATIC){
                // evaluation with static step size
                cout << "evaluation with static step size of " << s << " bit" << endl;
                for (int i=0; i<(int)iriscodes.size()-1; i++){
                    for (int j=i+1; j<(int)iriscodes.size(); j++){
                        if (i!=j) {
                            if (alg == ALG_3A){
                                TripleA(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, s);
                            }
                            else if (alg == ALG_3AL){
                                TripleA_limited(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, s);
                            }
                            else {
                                TripleA_singlesided(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, s);
                            }
                        }
                    }
                }
            } else if ( main_mode == DYNAMIC){
                // evaluation with dynamic step size
                cout << "evaluation with dynamic step size and constant of " << c << " bit" << endl;
                for (int i=0; i<(int)iriscodes.size()-1; i++){
                    for (int j=i+1; j<(int)iriscodes.size(); j++){
                        if (i!=j) {
                            if (alg == ALG_3A){
                                TripleA(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, iriscodes.at(i).stepsize);
                            }
                            else if (alg == ALG_3AL){
                                TripleA_limited(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, iriscodes.at(i).stepsize);
                            }
                            else {
                                TripleA_singlesided(iriscodes.at(i), iriscodes.at(j), MIN_SHIFT, MAX_SHIFT, log, iriscodes.at(i).stepsize);
                            }
                        }
                    }
                }
            }
			log.close();
    	}
    	else if (mode == MODE_HELP){
			// validate command line
			cmdCheckOpts(cmd,"-h");
			if (cmdGetOpt(cmd,"-h") != 0) cmdCheckOptSize(cmd,"-h",0);
			// starting routine
			printUsage();
    	}
    }
	catch (...){
	   	printf("Exit with errors.\n");
	   	exit(EXIT_FAILURE);
	}
    return EXIT_SUCCESS;
}


#define BOOST_TEST_MAIN

#include <boost/test/unit_test.hpp>
#include "../src/App.h"

static volatile bool interrupt = false;

void generateTestCube(std::vector<float>& verts,
                      std::vector<unsigned int>& indices) {
  float vert[24] = { 0.0,5.0,0.0, 0.0,0.0,0.0, 0.0,0.0,3.0, 0.0,5.0,3.0,
                      7.0, 0.0,3.0, 7.0,5.0,3.0, 7.0,5.0,0.0, 7.0,0.0,0.0};
  unsigned int idx[36] = {3, 1, 2, 1, 3, 0, 4, 3, 2, 3, 4, 5, 6, 1, 0, 1, 6, 7,
                          3, 6, 0, 6, 3, 5, 6, 4, 7, 4, 6, 5, 4, 1, 7, 1, 4, 2};

  for(int i = 0; i < 24; i++) verts.push_back(vert[i]);
  for(int i = 0; i < 36; i++) indices.push_back(idx[i]);

}
enum VoxelizationType vox_type = SOLID;
float dx = 0.015;

BOOST_AUTO_TEST_SUITE(AppTest)

BOOST_AUTO_TEST_CASE(App_init) {
  FDTD::App app;

  std::vector<float> vertices;
  std::vector<unsigned int> indices;
  generateTestCube(vertices, indices);

  float global_a = 0.05;

  // The app will throw -1 on error
  try {
    app.initializeDevices();
    app.initializeGeometry(&indices[0], &vertices[0], 36, 24);
    app.m_materials.setGlobalMaterial(app.m_geometry.getNumberOfTriangles(),
                                      absorption2Admitance(global_a));
    float fs = 1.0/dx*344.0*sqrt(3.0);
    app.m_parameters.setVoxelizationType(vox_type);
    app.m_parameters.setSpatialFs(fs);
    app.m_parameters.setNumSteps(300);
    app.m_parameters.setUpdateType(SRL_FORWARD);
    app.m_parameters.readGridIr("./Data/grid_ir.txt");
    app.m_parameters.addSource(Source(12.5f, 2.5f,20.5f, SRC_HARD, GAUSSIAN, 1));
    app.runSimulation();
  }
  catch(...) {

  }

  float V = app.getVolume();
  BOOST_CHECK_EQUAL(V, 5.0*7.0*3.0);

  BOOST_CHECK_EQUAL(app.m_geometry.getNumberOfIndices(), 36);
  BOOST_CHECK_EQUAL(app.m_geometry.getNumberOfTriangles(), 12);
  float SA = app.getTotalAborptionArea(0);
  float A = app.m_geometry.getTotalSurfaceArea();
  BOOST_CHECK_EQUAL(SA, (5.0*7.0+5.0*3.0+7.0*3.0)*2.0*global_a);

  float sab = app.getSabine(0);
  BOOST_CHECK_EQUAL(sab, 0.1611*V/SA);

  float eyr = app.getEyring(0);
  BOOST_CHECK_EQUAL(eyr, 0.1611*V/(-1.f*A*logf(1.f- SA/A)));

  app.close();
}

BOOST_AUTO_TEST_CASE(App_init_material_list) {
  FDTD::App app;

  std::vector<float> vertices;
  std::vector<unsigned int> indices;
  generateTestCube(vertices, indices);

  float global_a = 0.05;
  std::vector<float> materials;

  int number_of_surfaces = 12;
  int number_of_coefficients = 20;

  // add same material to all 12 surfaces
  for(int i = 0; i < 12; i ++){
    if(i < 2) global_a = 0.01;
    else global_a = 0.05;

    for (int j = 0; j < 20; j++) {
      materials.push_back(absorption2Admitance(global_a));
    }
  }
  BOOST_CHECK_EQUAL(materials.size(), 240);
  // The app will throw -1 on error
  try {
    app.initializeDevices();
    app.initializeGeometry(&indices[0], &vertices[0], 36, 24);
    app.m_materials.addMaterials(&materials[0],
                                 number_of_surfaces,
                                 number_of_coefficients);
    float fs = 1.0/dx*344.0*sqrt(3.0);
    app.m_parameters.setSpatialFs(fs);
    app.setVoxelizationType(vox_type);
    app.m_parameters.setNumSteps(300);
    app.m_parameters.setUpdateType(SRL_FORWARD);
    app.m_parameters.readGridIr("./Data/grid_ir.txt");
    app.m_parameters.addSource(Source(12.5f, 2.5f,20.5f, SRC_HARD, GAUSSIAN, 1));
    app.runSimulation();
  }
  catch(...) {

  }

  float V = app.getVolume();
  BOOST_CHECK_EQUAL(V, 5.0*7.0*3.0);

  BOOST_CHECK_EQUAL(app.m_geometry.getNumberOfIndices(), 36);
  BOOST_CHECK_EQUAL(app.m_geometry.getNumberOfTriangles(), 12);
  float SA = app.getTotalAborptionArea(0);
  float A = app.m_geometry.getTotalSurfaceArea();
  BOOST_CHECK_EQUAL(SA, ((5.0*7.0+.0+7.0*3.0)*2.0+5.0*3)*0.05+5.0*3*0.01);

  float sab = app.getSabine(0);
  BOOST_CHECK_EQUAL(sab, 0.1611*V/SA);

  float eyr = app.getEyring(0);
  BOOST_CHECK_EQUAL(eyr, 0.1611*V/(-1.f*A*logf(1.f- SA/A)));

  app.close();
}

BOOST_AUTO_TEST_SUITE_END()


// Copyright (c) 2021 Graphcore Ltd. All rights reserved.
#define BOOST_TEST_MODULE Test_SGD2_CompoundScalarHelpers
#include <boost/test/unit_test.hpp>
#include <popart/compoundscalarhelper.hpp>
#include <popart/sessionoptions.hpp>
#include <popart/sgd.hpp>

using namespace popart;

namespace {
void validate(SGD sgd,
              float expected_lr,
              float expected_wd,
              float expected_dp,
              float expected_mm) {
  ScaledLearningRate2Helper lr;
  ScaledWeightDecay1Helper wd;
  DampeningScaleFactor2Helper dp;
  ScaledMomentum2Helper mm;

  BOOST_CHECK_EQUAL(lr.val("", sgd), expected_lr);
  BOOST_CHECK_EQUAL(wd.val("", sgd), expected_wd);
  BOOST_CHECK_EQUAL(dp.val("", sgd), expected_dp);
  BOOST_CHECK_EQUAL(mm.val("", sgd), expected_mm);
}
} // namespace

BOOST_AUTO_TEST_CASE(TestSGD2_Base) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, 1.0f - 0.8f, 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_lossScaling) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
              {"lossScaling", {4.0f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f) / 4.0f, 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_velocityScaling) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
              {"defaultVelocityScaling", {2.0f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  sgd.setFactorsFromOptions(opts);
  validate(sgd,
           0.1f / 2.0f,
           (1.0f - 0.8f) * 0.01f * 2.0f,
           (1.0f - 0.8f) * 2.0f,
           0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_ReplicaSum) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableReplicatedGraphs = true;
  opts.replicatedGraphCount   = 2;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f), 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_ReplicaMeanPostAndLoss) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableReplicatedGraphs                  = true;
  opts.replicatedGraphCount                    = 2;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::PostAndLoss;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f), 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_ReplicaMeanPost) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableReplicatedGraphs                  = true;
  opts.replicatedGraphCount                    = 2;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::Post;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f) / 2.0f, 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_ReplicaMeanRunning) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableReplicatedGraphs                  = true;
  opts.replicatedGraphCount                    = 2;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::Running;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f), 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_AccumSum) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableGradientAccumulation = true;
  opts.accumulationFactor         = 4;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f), 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_AccumMeanPostAndLoss) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableGradientAccumulation              = true;
  opts.accumulationFactor                      = 4;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::PostAndLoss;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f) / 4.0f, 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_AccumMeanPost) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableGradientAccumulation              = true;
  opts.accumulationFactor                      = 4;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::Post;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f) / 4.0f, 0.9f);
}

BOOST_AUTO_TEST_CASE(TestSGD2_AccumMeanRunning) {
  SGD sgd{{
              {"defaultLearningRate", {0.1f, true}},
              {"defaultWeightDecay", {0.01f, true}},
              {"defaultDampening", {0.8f, true}},
              {"defaultMomentum", {0.9f, true}},
          },
          {},
          SGDAccumulatorAndMomentum::Separate};
  SessionOptions opts;
  opts.enableGradientAccumulation              = true;
  opts.accumulationFactor                      = 4;
  opts.accumulationAndReplicationReductionType = ReductionType::Mean;
  opts.meanAccumulationAndReplicationReductionStrategy =
      MeanReductionStrategy::Running;
  sgd.setFactorsFromOptions(opts);
  validate(sgd, 0.1f, (1.0f - 0.8f) * 0.01f, (1.0f - 0.8f), 0.9f);
}


#include <iostream>
#include <math.h>
#include <string.h>

#include <pcl/io/pcd_io.h>
#include <pcl/io/png_io.h>
#include <pcl/io/io.h>
#include <pcl/point_types.h>
#include <pcl/ModelCoefficients.h>
#include <pcl/common/transforms.h>

#include <pcl/segmentation/sac_segmentation.h>
#include <pcl/sample_consensus/method_types.h>
#include <pcl/sample_consensus/model_types.h>

#include <pcl/filters/project_inliers.h>
#include <pcl/filters/extract_indices.h>
#include <pcl/filters/passthrough.h>
#include <pcl/filters/voxel_grid.h>
#include <pcl/filters/statistical_outlier_removal.h>

#include <pcl/features/organized_edge_detection.h>

#include <pcl/range_image/range_image.h>
#include <pcl/visualization/range_image_visualizer.h>
#include <pcl/visualization/cloud_viewer.h>

#include <opencv2/opencv.hpp>
#include <opencv2/imgcodecs.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/objdetect/objdetect.hpp>
#include <opencv2/highgui/highgui.hpp>

#include <boost/thread/thread.hpp>

int thresh = 50, N = 11;
const char* wndname = "Square Detection Demo";

static double angle( cv::Point pt1, cv::Point pt2, cv::Point pt0 )
{
    double dx1 = pt1.x - pt0.x;
    double dy1 = pt1.y - pt0.y;
    double dx2 = pt2.x - pt0.x;
    double dy2 = pt2.y - pt0.y;
    return (dx1*dx2 + dy1*dy2)/sqrt((dx1*dx1 + dy1*dy1)*(dx2*dx2 + dy2*dy2) + 1e-10);
}

static void findSquares( const cv::Mat& image, std::vector<std::vector<cv::Point> >& squares )
{
    squares.clear();

    cv::Mat pyr, timg, gray0(image.size(), CV_8U), gray;

    // down-scale and upscale the image to filter out the noise
    cv::pyrDown(image, pyr, cv::Size(image.cols/2, image.rows/2));
    cv::pyrUp(pyr, timg, image.size());
    std::vector<std::vector<cv::Point> > contours;

    // find squares in every color plane of the image
    for( int c = 0; c < 3; c++ )
    {
        int ch[] = {c, 0};
        cv::mixChannels(&timg, 1, &gray0, 1, ch, 1);

        // try several threshold levels
        for( int l = 0; l < N; l++ )
        {
            // hack: use Canny instead of zero threshold level.
            // Canny helps to catch squares with gradient shading
            if( l == 0 )
            {
                // apply Canny. Take the upper threshold from slider
                // and set the lower to 0 (which forces edges merging)
                cv::Canny(gray0, gray, 0, thresh, 5);
                // dilate canny output to remove potential
                // holes between edge segments
                cv::dilate(gray, gray, cv::Mat(), cv::Point(-1,-1));
            }
            else
            {
                // apply threshold if l!=0:
                //     tgray(x,y) = gray(x,y) < (l+1)*255/N ? 255 : 0
                gray = gray0 >= (l+1)*255/N;
            }

            // find contours and store them all as a list
            cv::findContours(gray, contours, cv::RETR_LIST, cv::CHAIN_APPROX_SIMPLE);

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

            // test each contour
            for( size_t i = 0; i < contours.size(); i++ )
            {
                // approximate contour with accuracy proportional
                // to the contour perimeter
                cv::approxPolyDP(cv::Mat(contours[i]), approx, cv::arcLength(cv::Mat(contours[i]), true)*0.02, true);

                // square contours should have 4 vertices after approximation
                // relatively large area (to filter out noisy contours)
                // and be convex.
                // Note: absolute value of an area is used because
                // area may be positive or negative - in accordance with the
                // contour orientation
                if( approx.size() == 4 &&
                    fabs(cv::contourArea(cv::Mat(approx))) > 1000 &&
                    cv::isContourConvex(cv::Mat(approx)) )
                {
                    double maxCosine = 0;

                    for( int j = 2; j < 5; j++ )
                    {
                        // find the maximum cosine of the angle between joint edges
                        double cosine = fabs(angle(approx[j%4], approx[j-2], approx[j-1]));
                        maxCosine = MAX(maxCosine, cosine);
                    }

                    // if cosines of all angles are small
                    // (all angles are ~90 degree) then write quandrange
                    // vertices to resultant sequence
                    if( maxCosine < 0.3 )
                        squares.push_back(approx);
                }
            }
        }
    }
}

// the function draws all the squares in the image
static void drawSquares( cv::Mat& image, const std::vector< std::vector<cv::Point> >& squares )
{
    for(size_t i = 0; i < squares.size(); i++ )
    {
        const cv::Point* p = &squares[i][0];
        int n = (int)squares[i].size();
        cv::polylines(image, &p, &n, 1, true, cv::Scalar(0,255,0), 3, cv::LINE_AA);
    }

    imshow(wndname, image);
}

void projectTo2D (pcl::ModelCoefficients coefficients, pcl::PointCloud<pcl::PointXYZRGB>::Ptr inputCloud, pcl::PointCloud<pcl::PointXYZRGB>::Ptr planeCloud)
{
    double discrete_ = 0.002;
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr  rotatedPlaneCloud (new pcl::PointCloud<pcl::PointXYZRGB>);
    pcl::ModelCoefficients::Ptr             modelCoefficientsPtr (new pcl::ModelCoefficients (coefficients));
    pcl::ProjectInliers<pcl::PointXYZRGB>   proj;

    Eigen::Matrix<float, 1, 3> floorPlaneNormalVec, zAxis, rotationVec;
    zAxis << 0.0,0.0,1.0;

    for (int i = 0; i < 3; i++) {
        floorPlaneNormalVec[i] = coefficients.values[i];    // Already normalized
    }

    // transform (rotate) plane to view, where Z == const.
    Eigen::Affine3f affineTransform = Eigen::Affine3f::Identity();
    affineTransform.translation() << 0,0,0;

    rotationVec = zAxis.cross (floorPlaneNormalVec);
    float theta = -atan2 (rotationVec.norm(), zAxis.dot (floorPlaneNormalVec));
    affineTransform.rotate (Eigen::AngleAxisf (theta, rotationVec));

    pcl::transformPointCloud (*planeCloud, *rotatedPlaneCloud,  affineTransform);
    // planeCloud all points projected on the corresponded ideal planes
    // rotatedPlaneCloud - rotated, to make  Z == const

    // looking for bounding rectangle to crop only informative part of plane
    pcl::PointXYZRGB minPoint;
    pcl::PointXYZRGB maxPoint;

    pcl::getMinMax3D (*rotatedPlaneCloud, minPoint, maxPoint);
    // fill 2D Mat with values of corresponding cloud points (ignore Z)
    int w = ceil ((maxPoint.x - minPoint.x)/discrete_);
    int h = ceil ((maxPoint.y - minPoint.y)/discrete_);
    cv::Mat discretedPlane = cv::Mat::zeros (w+1, h+1, CV_64FC3);
    cv::Mat channels[3];
    for (int i = 0; i < 3; i++) {
        channels[i] = cv::Mat::zeros (w+1, h+1, CV_64FC1);
    }

    for (int i = 0; i < rotatedPlaneCloud->size(); i++) {
        channels[0].at<double>(ceil ((rotatedPlaneCloud->points[i].x - minPoint.x)/discrete_),
                               ceil ((rotatedPlaneCloud->points[i].y - minPoint.y)/discrete_)) = rotatedPlaneCloud->points[i].getRGBVector3i()[0];
        channels[1].at<double>(ceil ((rotatedPlaneCloud->points[i].x - minPoint.x)/discrete_),
                               ceil ((rotatedPlaneCloud->points[i].y - minPoint.y)/discrete_)) = rotatedPlaneCloud->points[i].getRGBVector3i()[1];
        channels[2].at<double>(ceil ((rotatedPlaneCloud->points[i].x - minPoint.x)/discrete_),
                               ceil ((rotatedPlaneCloud->points[i].y - minPoint.y)/discrete_)) = rotatedPlaneCloud->points[i].getRGBVector3i()[2];
    }
    cv::merge (channels, 3, discretedPlane);

    cv::imwrite ("image_from_point_cloud.png", discretedPlane);
    std::vector<std::vector<cv::Point> > squares;
    findSquares(discretedPlane, squares);
}

int main(int argc, char** argv)
{
    std::string box_filename = argv[1];
    std::cout << "Reading " << box_filename << std::endl;
    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud (new pcl::PointCloud<pcl::PointXYZRGB>);

    if(pcl::io::loadPCDFile<pcl::PointXYZRGB> (box_filename, *cloud) == -1) // load the file
    {
    PCL_ERROR ("Couldn't read file");
    return -1;
    }
    std::cout << "points: " << cloud->points.size () <<std::endl;

    std::cerr << "PointCloud after filtering: " << cloud->width * cloud->height << " data points." << std::endl;

    pcl::ModelCoefficients::Ptr coefficients (new pcl::ModelCoefficients ());
    pcl::PointIndices::Ptr inliers (new pcl::PointIndices ());
    // Create the segmentation object
    pcl::SACSegmentation<pcl::PointXYZRGB> seg;
    // Optional
    seg.setOptimizeCoefficients (true);
    // Mandatory
    seg.setModelType (pcl::SACMODEL_PLANE);
    seg.setMethodType (pcl::SAC_RANSAC);
    Eigen::Vector3f perpendicular_to(1, 1, 0);
    seg.setAxis(perpendicular_to);
    seg.setMaxIterations (1000);
    seg.setDistanceThreshold (0.01);

    // Create the filtering object
    pcl::ExtractIndices<pcl::PointXYZRGB> extract;

    // Segment the largest planar component from the remaining cloud
    seg.setInputCloud (cloud);
    seg.segment (*inliers, *coefficients);
    if (inliers->indices.size () == 0)
    {
        std::cerr << "Could not estimate a planar model for the given dataset." << std::endl;
    }

    pcl::PointCloud<pcl::PointXYZRGB>::Ptr cloud_p (new pcl::PointCloud<pcl::PointXYZRGB>);
    // Extract the inliers
    extract.setInputCloud (cloud);
    extract.setIndices (inliers);
    extract.setNegative (false);
    extract.filter (*cloud_p);
    std::cerr << "PointCloud representing the planar component: " << cloud_p->width * cloud_p->height << " data points." << std::endl;
    pcl::PCDWriter writer;
    writer.write<pcl::PointXYZRGB> ("extracted_plane.pcd", *cloud_p, false);

    projectTo2D(*coefficients, cloud, cloud_p);

    return 0;
}


#include "merchantrepair.hpp"

#include <components/esm/loadgmst.hpp>

#include <boost/lexical_cast.hpp>

#include "../mwbase/world.hpp"
#include "../mwbase/environment.hpp"
#include "../mwbase/mechanicsmanager.hpp"
#include "../mwbase/windowmanager.hpp"
#include "../mwbase/soundmanager.hpp"

#include "../mwmechanics/creaturestats.hpp"

#include "../mwworld/class.hpp"
#include "../mwworld/containerstore.hpp"
#include "../mwworld/esmstore.hpp"

namespace MWGui
{

MerchantRepair::MerchantRepair()
    : WindowBase("openmw_merchantrepair.layout")
{
    getWidget(mList, "RepairView");
    getWidget(mOkButton, "OkButton");
    getWidget(mGoldLabel, "PlayerGold");

    mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onOkButtonClick);
}

void MerchantRepair::startRepair(const MWWorld::Ptr &actor)
{
    mActor = actor;

    while (mList->getChildCount())
        MyGUI::Gui::getInstance().destroyWidget(mList->getChildAt(0));

    int currentY = 0;

    MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();
    int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId);

    MWWorld::ContainerStore& store = player.getClass().getContainerStore(player);
    int categories = MWWorld::ContainerStore::Type_Weapon | MWWorld::ContainerStore::Type_Armor;
    for (MWWorld::ContainerStoreIterator iter (store.begin(categories));
         iter!=store.end(); ++iter)
    {
        if (iter->getClass().hasItemHealth(*iter))
        {
            int maxDurability = iter->getClass().getItemMaxHealth(*iter);
            int durability = iter->getClass().getItemHealth(*iter);
            if (maxDurability == durability)
                continue;

            int basePrice = iter->getClass().getValue(*iter);
            float fRepairMult = MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                    .find("fRepairMult")->getFloat();

            float p = std::max(1, basePrice);
            float r = std::max(1, static_cast<int>(maxDurability / p));

            int x = ((maxDurability - durability) / r);
            x = (fRepairMult * x);

            int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mActor, x, true);

            std::string name = iter->getClass().getName(*iter)
                    + " - " + boost::lexical_cast<std::string>(price)
                    + MWBase::Environment::get().getWorld()->getStore().get<ESM::GameSetting>()
                    .find("sgp")->getString();

            MyGUI::Button* button =
                mList->createWidget<MyGUI::Button>(price <= playerGold ? "SandTextButton" : "SandTextButtonDisabled", // can't use setEnabled since that removes tooltip
                    0,
                    currentY,
                    0,
                    18,
                    MyGUI::Align::Default
                );

            currentY += 18;

            button->setUserString("Price", boost::lexical_cast<std::string>(price));
            button->setUserData(*iter);
            button->setCaptionWithReplacing(name);
            button->setSize(button->getTextSize().width,18);
            button->eventMouseWheel += MyGUI::newDelegate(this, &MerchantRepair::onMouseWheel);
            button->setUserString("ToolTipType", "ItemPtr");
            button->eventMouseButtonClick += MyGUI::newDelegate(this, &MerchantRepair::onRepairButtonClick);
        }
    }
    // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden
    mList->setVisibleVScroll(false);
    mList->setCanvasSize (MyGUI::IntSize(mList->getWidth(), std::max(mList->getHeight(), currentY)));
    mList->setVisibleVScroll(true);

    mGoldLabel->setCaptionWithReplacing("#{sGold}: "
        + boost::lexical_cast<std::string>(playerGold));
}

void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel)
{
    if (mList->getViewOffset().top + _rel*0.3 > 0)
        mList->setViewOffset(MyGUI::IntPoint(0, 0));
    else
        mList->setViewOffset(MyGUI::IntPoint(0, mList->getViewOffset().top + _rel*0.3));
}

void MerchantRepair::open()
{
    center();
}

void MerchantRepair::exit()
{
    MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_MerchantRepair);
}

void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender)
{
    MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr();

    int price = boost::lexical_cast<int>(sender->getUserString("Price"));
    if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId))
        return;

    // repair
    MWWorld::Ptr item = *sender->getUserData<MWWorld::Ptr>();
    item.getCellRef().setCharge(item.getClass().getItemMaxHealth(item));

    player.getClass().getContainerStore(player).restack(item);

    MWBase::Environment::get().getSoundManager()->playSound("Repair",1,1);

    player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player);

    // add gold to NPC trading gold pool
    MWMechanics::CreatureStats& actorStats = mActor.getClass().getCreatureStats(mActor);
    actorStats.setGoldPool(actorStats.getGoldPool() + price);

    startRepair(mActor);
}

void MerchantRepair::onOkButtonClick(MyGUI::Widget *sender)
{
    exit();
}

}