#include <boost/filesystem.hpp>

#include "bvsma/include/functions.hpp"
#include "bvsma/include/ZKProofs.hpp"
#include "bvsma/include/SchnorrSignature.hpp"
#include "bvsma/include/fileFunctions.hpp"
#include "bvsma/include/functionsParallel.hpp"

using namespace boost::filesystem;

using namespace std;
typedef unsigned char byte;

/* Gen exp */

//  ./client 0 192 501 1 /home/obre/bvsma/data/BMDB.csv BMDB 36 16
//  ./client 0 224 501 1 /home/obre/bvsma/data/BMDB.csv BMDB 36 16
//  ./client 0 256 501 1 /home/obre/bvsma/data/BMDB.csv BMDB 36 16

//  ./client 0 192 501 1 /home/obre/bvsma/data/PUT.csv PUT 49 64
//  ./client 0 224 501 1 /home/obre/bvsma/data/PUT.csv PUT 49 64
//  ./client 0 256 501 1 /home/obre/bvsma/data/PUT.csv PUT 49 64

//  ./client 0 192 501 1 /home/obre/bvsma/data/FRGC.csv FRGC 94 64
//  ./client 0 224 501 1 /home/obre/bvsma/data/FRGC.csv FRGC 94 64
//  ./client 0 256 501 1 /home/obre/bvsma/data/FRGC.csv FRGC 94 64

/*
* SEMI-HONEST CLIENT
*/

int main(int argc, char* argv[]){
    cout << "Run Semi-Honest Client ..." << endl;

    boost::asio::io_service io_service;

    auto clientIp = IpAddress::from_string("127.0.0.1");
	auto serverIp = IpAddress::from_string("127.0.0.1");

    SocketPartyData clientParty(clientIp, 1222);
    SocketPartyData serverParty(serverIp, 1223);

    shared_ptr<CommParty> client = make_shared<CommPartyTCPSynced>(io_service, clientParty, serverParty);

    boost::thread t(boost::bind(&boost::asio::io_service::run, &io_service));

    string dirClient = "../../../data/client/"+ string(argv[6]) +"/P"+ string(argv[2]);
    string enrLogTimer = dirClient +"/SHenrClt"+"P"+ string(argv[2])+ string(argv[6]) +".csv";
    string verLogTimer = dirClient +"/SHverClt"+"P"+ string(argv[2])+ string(argv[6]) +".csv";
    string resultMatch = dirClient +"/SHresultMatch"+"P"+ string(argv[2])+ string(argv[6]) +".csv";
    string keyStorage;
    string dirUser;

    string qBinsFile = "../../../data/lookupTables/"+ string(argv[6]) +"/"+ string(argv[6]) +"_qbins.csv";
    string sensorFile = string(argv[5]);
    string dirHELR = "../../../data/lookupTables/"+ string(argv[6]) +"/";

    vector<int> maxNFQ(stoi(argv[7]), stoi(argv[8]));

    string configFile = "../../../../libscapi/include/configFiles/NISTEC.txt";
    string curve = "P-"+string(argv[2]);

	auto dlog = make_shared<OpenSSLDlogECFp>(configFile, curve);
    auto g = dlog->getGenerator();
    auto q = dlog->getOrder();

    auto random = get_seeded_prg();

    shared_ptr<ElGamalOnGroupElementEnc> elgamalTH = make_shared<ElGamalOnGroupElementEnc>(dlog);

    unsigned int seedClt = 149423;

    int resultInt;
    bool resultBool;

    string str;

    std::chrono::time_point<std::chrono::system_clock> globalStart;
    std::chrono::time_point<std::chrono::system_clock> localStart;

    std::ofstream out_file;

    try
    {

        if (stoi(argv[1]) == 0)
        {
            int count = stoi(argv[3]);
            int step = stoi(argv[4]);

            client->join(500, 5000);

            for (size_t i = 1; i < count; i=i+step)
            {
                out_file.open(enrLogTimer, ios::app);
                if (!out_file)
                {
                    throw "Error creating file"s;
                }
                cout << "======= Enrollment =======" << endl;
                localStart = std::chrono::system_clock::now();
                globalStart = localStart;

                auto pairSample = readSampleWithIDInLineFPD(sensorFile,  i  );
                auto userId = biginteger(pairSample.first);
                auto sample = pairSample.second;
                dirUser = dirClient +"/SH/USERS/user"+userId.str();
                path pathUser(dirUser);
                create_directory(pathUser);
                dirUser = dirUser+"/";
                keyStorage = dirUser+"keystorage.txt";

                sendBigint(client, userId);
                cout << "-> User " << userId << " Enrollment Request ..." << endl;

                auto pairTH = elgamalTH->generateKey();
                auto s1 = dynamic_cast<ElGamalPrivateKey*>(pairTH.second.get())->getX();
                writeBigintinFile(keyStorage, "client", "PrivTH", s1);

                auto clientPKeyTH = pairTH.first;
                writePubKeyinFile(keyStorage, "client", "PubTH", clientPKeyTH);
                cout << "-> Client PrivTH and PubTH Generated and Stored ..." << endl;

                sendElGamalPubKey(client, clientPKeyTH);
                cout << "-> Client PubTH Sent ..." << endl;

                auto serverPKeyTH = receiveElGamalPubKey(client, dlog, elgamalTH);
                writePubKeyinFile(keyStorage, "server", "PubTH", serverPKeyTH);
                cout << "-> Server PubTH Received and Stored ..." << endl;

                auto serPKeyTH = dynamic_cast<ElGamalPublicKey*>(serverPKeyTH.get())->getH();
                auto cltPKeyTH = dynamic_cast<ElGamalPublicKey*>(clientPKeyTH.get())->getH();
                auto jointH = dlog->multiplyGroupElements(cltPKeyTH.get(),serPKeyTH.get());
                ElGamalPublicKey jointPK(jointH);
                shared_ptr<PublicKey> jointPubKey = make_shared<ElGamalPublicKey>(jointPK);
                elgamalTH->setKey(jointPubKey, pairTH.second);
                writePubKeyinFile(keyStorage, "joint", "jointTH", jointPubKey);
                writeCSVNew(out_file, localStart, false);
                cout << "-> Joint jointTH Received and stored ..." << endl;
                cout << "----> Keys Establishment DONE ..." << endl;

                cout << "-------------------------------------------" << endl;

                localStart = std::chrono::system_clock::now();

                auto quantizedSample = quantizefeaturesFPD(qBinsFile, sample);
                auto userTemplate = encryptRowsFromHELR(dirHELR, dlog, elgamalTH, quantizedSample);

                sendMapElGamalCipher(client, userTemplate);
                writeCSVNew(out_file, localStart, false);
                writeCSVNew(out_file, globalStart, true);
                cout << "-> Template Generated and Sent ..." << endl;
                cout << "======= Enrollment END =======" << endl;

            }

        }
        else if(stoi(argv[1]) == 1){

            int count = stoi(argv[3]);
            int step = stoi(argv[4]);

            client->join(500, 5000);

            for (size_t i = 2; i < count; i=i+step)
            {
                out_file.open(verLogTimer, ios::app);
                if (!out_file)
                {
                    throw "Error creating file"s;
                }
                cout << "======= Verification =======" << endl;
                localStart = std::chrono::system_clock::now();
                globalStart = localStart;

                cout << "--> Step 1: User's existence verification ..." << endl;

                auto pairSample = readSampleWithIDInLineFPD(sensorFile, i);
                auto userId = biginteger(pairSample.first);
                userId -= 1;
                sendBigint(client, userId);
                str = receiveStr(client);
                if( "Unregistered" == str){
                    throw "Unregistered User"s;
                }
                cout << "User " << userId << " is " << str << endl;
                writeCSVNew(out_file, localStart, false);

                // load
                localStart = std::chrono::system_clock::now();

                dirUser = dirClient +"/SH/USERS/user"+userId.str()+"/";
                keyStorage = dirUser+"keystorage.txt";

                auto s1 = readBigintFromfile(keyStorage, "client", "PrivTH");
                ElGamalPrivateKey cltPrivKeyTH(s1);
                shared_ptr<PrivateKey> clientPrivKeyTH = make_shared<ElGamalPrivateKey>(cltPrivKeyTH);
                auto serverPKeyTH = readPubKeyFromfile(keyStorage, dlog, "server", "PubTH");
                auto jointPubKey = readPubKeyFromfile(keyStorage, dlog, "joint", "jointTH");
                elgamalTH->setKey(jointPubKey, clientPrivKeyTH);

                writeCSVNew(out_file, localStart, false);
                cout << "----> Keys loaded ..." << endl;
                cout << "--> Step 1: DONE ..." << endl;
                cout << endl;

                cout << "-------------------------------------------" << endl;
                cout << "--> Step 2: Select, Mult, and Randomize the Scores ..." << endl;
                localStart = std::chrono::system_clock::now();

                auto userTemplateReceived = receiveMapElGamalCipher(client, dlog, elgamalTH);
                cout << "----> Template Received ##############" << endl;

                auto sample = pairSample.second;
                vector<int> rawProbe = quantizefeaturesFPD(qBinsFile, sample);
                auto scoreEncFromTemp = pickSpecificCiphers(rawProbe, userTemplateReceived);
                auto sumScores = multiplyCiphersOfVect(elgamalTH, scoreEncFromTemp);
                auto r = getRandomInRange(1, q - 1, random.get());
                auto identityEnc = elgamalTH->encrypt(make_shared<GroupElementPlaintext>(dlog->getIdentity()), r);
                auto finalScore = elgamalTH->multiply(sumScores.get(), identityEnc.get());

                sendElGamalCipher(client, finalScore);

                writeCSVNew(out_file, localStart, false);
                cout << "--> Step 2: DONE ..." << endl;
                cout << endl;

                cout << "-------------------------------------------" << endl;
                cout << "--> Step 3: Receive Partial Comparison ..." << endl;
                localStart = std::chrono::system_clock::now();

                auto partialDecBlindedCompVect = receiveVectElGamalCipher(client, dlog, elgamalTH);

                writeCSVNew(out_file, localStart, false);
                cout << "--> Step 3: DONE ..." << endl;
                cout << endl;

                cout << "-------------------------------------------" << endl;
                cout << "--> Step 4: Comparison Result ..." << endl;
                localStart = std::chrono::system_clock::now();

                resultBool = finalDecryptionANDMatchSH(elgamalTH, partialDecBlindedCompVect);

                if( resultBool == false){
                    client->writeWithSize("No Match");
                    writeResult(resultMatch, 0);
                    cout << "No Match" << endl;
                }else{
                    client->writeWithSize("Match");
                    writeResult(resultMatch, 1);
                    cout << "Match" << endl;
                }

                writeCSVNew(out_file, localStart, false);
                writeCSVNew(out_file, globalStart, true);
                cout << "--> Step 4: DONE ..." << endl;
                cout << endl;
                cout << "======= Verification END =======" << endl;

            }

        }

    }
    catch (const logic_error& e) {
        out_file.close();
        io_service.stop();
        t.join();
		cerr << e.what();
	}
    catch (string &e){
        out_file.close();
        io_service.stop();
        t.join();
        cerr << e << endl;
    }

    io_service.stop();
    t.join();

    cout << "END Semi-Honest Client ..." << endl;
    return 0;

}


// Copyright John Maddock 2015.

// 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)

#ifdef _MSC_VER
#define _SCL_SECURE_NO_WARNINGS
#endif

#include <nil/crypto3/multiprecision/cpp_int.hpp>

#include <boost/algorithm/string/case_conv.hpp>
#include <boost/random/mersenne_twister.hpp>
#include <boost/random/uniform_int.hpp>
#include "test.hpp"
#include <iostream>
#include <iomanip>

#ifdef BOOST_MSVC
#pragma warning(disable : 4127)
#endif
template<class T>
struct unchecked_type {
    typedef T type;
};

template<unsigned MinBits, unsigned MaxBits, nil::crypto3::multiprecision::cpp_integer_type SignType,
         nil::crypto3::multiprecision::cpp_int_check_type Checked, class Allocator,
         nil::crypto3::multiprecision::expression_template_option ExpressionTemplates>
struct unchecked_type<nil::crypto3::multiprecision::number<
    nil::crypto3::multiprecision::cpp_int_backend<MinBits, MaxBits, SignType, Checked, Allocator>,
    ExpressionTemplates>> {
    typedef nil::crypto3::multiprecision::number<
        nil::crypto3::multiprecision::cpp_int_backend<MinBits, MaxBits, SignType,
                                                      nil::crypto3::multiprecision::unchecked, Allocator>,
        ExpressionTemplates>
        type;
};

template<class T>
T generate_random() {
    typedef typename unchecked_type<T>::type unchecked_T;

    static const unsigned limbs = std::numeric_limits<T>::is_specialized && std::numeric_limits<T>::is_bounded ?
                                      std::numeric_limits<T>::digits / std::numeric_limits<unsigned>::digits + 3 :
                                      20;

    static boost::random::uniform_int_distribution<unsigned> ui(0, limbs);
    static boost::random::mt19937 gen;
    unchecked_T val = gen();
    unsigned lim = ui(gen);
    for (unsigned i = 0; i < lim; ++i) {
        val *= (gen.max)();
        val += gen();
    }
    return val;
}

template<class T>
void test_round_trip_neg(T val, const boost::mpl::true_&) {
    // Try some negative values:
    std::vector<unsigned char> cv;
    T newval;
    val = -val;
    export_bits(val, std::back_inserter(cv), 8, false);
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(-val, newval);
}

template<class T>
void test_round_trip_neg(const T&, const boost::mpl::false_&) {
}

template<class T>
void test_round_trip(T val) {
    std::vector<unsigned char> cv;
    export_bits(val, std::back_inserter(cv), 8);
    T newval;
    import_bits(newval, cv.begin(), cv.end());
    BOOST_CHECK_EQUAL(val, newval);
    // Should get the same value if we reverse the bytes:
    std::reverse(cv.begin(), cv.end());
    newval = 0;
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);
    // Also try importing via pointers as these may memcpy:
    newval = 0;
    import_bits(newval, &cv[0], &cv[0] + cv.size(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 8, false);
    import_bits(newval, cv.begin(), cv.end(), 8, false);
    BOOST_CHECK_EQUAL(val, newval);
    std::reverse(cv.begin(), cv.end());
    newval = 0;
    import_bits(newval, cv.begin(), cv.end(), 8, true);
    BOOST_CHECK_EQUAL(val, newval);

    std::vector<boost::uintmax_t> bv;
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits);
    import_bits(newval, bv.begin(), bv.end());
    BOOST_CHECK_EQUAL(val, newval);
    // Should get the same value if we reverse the values:
    std::reverse(bv.begin(), bv.end());
    newval = 0;
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);
    // Also try importing via pointers as these may memcpy:
    newval = 0;
    import_bits(newval, &bv[0], &bv[0] + bv.size(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);

    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits, false);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits, false);
    BOOST_CHECK_EQUAL(val, newval);
    //
    // Try with an unconventional number of bits, to model some machine with guard bits:
    //
    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits - 3);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits - 3);
    BOOST_CHECK_EQUAL(val, newval);

    bv.clear();
    export_bits(val, std::back_inserter(bv), std::numeric_limits<boost::uintmax_t>::digits - 3, false);
    import_bits(newval, bv.begin(), bv.end(), std::numeric_limits<boost::uintmax_t>::digits - 3, false);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 6);
    import_bits(newval, cv.begin(), cv.end(), 6);
    BOOST_CHECK_EQUAL(val, newval);

    cv.clear();
    export_bits(val, std::back_inserter(cv), 6, false);
    import_bits(newval, cv.begin(), cv.end(), 6, false);
    BOOST_CHECK_EQUAL(val, newval);

    test_round_trip_neg(val, boost::mpl::bool_<std::numeric_limits<T>::is_signed>());
}

template<class T>
void test_round_trip() {
    std::cout << std::hex;
    std::cerr << std::hex;
    for (unsigned i = 0; i < 1000; ++i) {
        T val = generate_random<T>();
        test_round_trip(val);
    }
    //
    // Bug cases.
    // See https://github.com/boostorg/multiprecision/issues/21
    T bug(1);
    bug << std::numeric_limits<T>::digits - 1;
    --bug;
    test_round_trip(bug);
}

int main() {
    test_round_trip<nil::crypto3::multiprecision::cpp_int>();
    test_round_trip<nil::crypto3::multiprecision::checked_int1024_t>();
    test_round_trip<nil::crypto3::multiprecision::checked_uint512_t>();
    test_round_trip<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_int_backend<
        64, 64, nil::crypto3::multiprecision::unsigned_magnitude, nil::crypto3::multiprecision::checked, void>>>();
    test_round_trip<nil::crypto3::multiprecision::number<nil::crypto3::multiprecision::cpp_int_backend<
        23, 23, nil::crypto3::multiprecision::unsigned_magnitude, nil::crypto3::multiprecision::checked, void>>>();
    return boost::report_errors();
}


#pragma once

#include <random>
#include <boost/functional/hash.hpp>
#include "xtensor/xtensor.hpp"
#include "affogato/util.hxx"

namespace affogato {
namespace segmentation {

    class MWSGridGraph {

    public:
        typedef std::pair<uint64_t, uint64_t> EdgeType;
        typedef std::vector<int> OffsetType;
        typedef std::unordered_map<std::pair<uint64_t, uint64_t>,
                                   float,
                                   boost::hash<std::pair<uint64_t, uint64_t>>
                                  > SeedStateType;

        MWSGridGraph(const std::vector<std::size_t> & shape): _shape(shape.begin(), shape.end()),
                                                              _ndim(shape.size()),
                                                              _n_nodes(std::accumulate(shape.begin(),
                                                                                shape.end(),
                                                                                1, std::multiplies<std::size_t>())),
                                                              _add_attractive_seed_edges(true),
                                                              _attractive_seed_weight(1.2),
                                                              _repulsive_seed_weight(1.1){
            init_strides();
        }

        //
        // mask functionality
        //

        template<class MASK>
        inline void set_mask(const MASK & mask) {
            init_mask(mask);
        }

        inline void clear_mask() {
            _masked_nodes.clear();
        }

        //
        // seed functionality
        //

        // TODO allow for ROI
        template<class SEEDS>
        void update_seeds(const SEEDS & seeds) {
            util::for_each_coordinate(_shape, [&](const xt::xindex & coord){
                const uint64_t seed_id = seeds[coord];
                // 0 means un-seeded
                if(seed_id == 0) {
                    return;
                }
                const uint64_t this_node = get_node(coord);
                _seed_ids.insert(seed_id);
                _seeded_nodes[this_node] = seed_id;
                _seed_representatives[seed_id] = this_node;
            });
        }

        inline void clear_seeds() {
            _seeded_nodes.clear();
        }

        inline void set_add_attactive_seed_edges(const bool val) {
            _add_attractive_seed_edges = val;
        }

        inline bool get_add_attactive_seed_edges() const {
            return _add_attractive_seed_edges;
        }

        //
        //
        //

        // STATE = std::unordered_map<std::pair<uint64_t, uint64_t>, std::pair<float, bool>>
        // + boost hash function for pair
        template<class AFFS, class SEG, class STATE>
        inline void compute_state_for_segmentation(const AFFS & affs, const SEG & seg,
                                                   const std::vector<OffsetType> & offsets,
                                                   const unsigned n_attactive_channels,
                                                   const bool ignore_label,
                                                   STATE & state) const {

            typedef typename SEG::value_type SegType;

            const auto & aff_shape = affs.shape();
            xt::xindex coord(_ndim), ngb_coord(_ndim);
            // Iterate over the affinities and extract the segmentation state
            util::for_each_coordinate(aff_shape, [&](const xt::xindex & aff_coord) {

                // get the spatial coords
                std::copy(aff_coord.begin() + 1, aff_coord.end(), coord.begin());
                ngb_coord = coord;

                // update the ngb-coord and check that we are in range
                const auto & offset = offsets[aff_coord[0]];
                for(unsigned d = 0; d < _ndim; ++d) {
                    ngb_coord[d] += offset[d];
                    if(ngb_coord[d] < 0 || ngb_coord[d] >= _shape[d]) {
                        return;
                    }
                }

                // get the seg ids
                SegType u = seg[coord];
                SegType v = seg[coord];

                // skip if we have an ignore label
                if((u == 0 || v == 0) && ignore_label) {
                    return;
                }

                // TODO should we also skip masked nodes here ?
                // if(_masked_nodes.size()) {
                //  if(_maksed_nodes[u] || _masked_nodes[v]) {
                //    return;
                //   }
                // }

                if(u > v) {
                    std::swap(u, v);
                }

                const auto uv = std::make_pair(u, v);
                const auto edge_it = state.find(uv);

                const bool is_attractive = aff_coord[0] < n_attactive_channels;
                const float weight = affs[aff_coord];

                // check if we had this edge already
                if(edge_it != state.end()) { // yes -> update the edge state
                    // TODO support other update than max
                    auto & edge_state = edge_it->second;
                    if(weight > edge_state.first) {
                        edge_state.first = weight;
                        edge_state.second = is_attractive;
                    }
                }
                else { // no -> insert new edge state
                    state.emplace(uv, std::make_pair(weight, is_attractive));
                }
            });
        }

        template<class AFFS>
        inline void compute_nh_and_weights(const AFFS & affs, const std::vector<OffsetType> & offsets,
                                           const std::vector<std::size_t> & strides, const bool randomize_strides,
                                           std::vector<std::pair<uint64_t, uint64_t>> & uv_ids, std::vector<float> & weights) const {
            // check if we have strides
            const std::size_t stride_product = std::accumulate(strides.begin(), strides.end(), 1,
                                                               std::multiplies<std::size_t>());
            const bool have_strides = stride_product > 1;

            // call appropriate initializer
            if(have_strides && randomize_strides) {
                // compute fraction of lr edges that we keep
                const double fraction = 1. / stride_product;
                compute_nh_and_weights_impl(affs, offsets, fraction,
                                            uv_ids, weights);
            }
            else if(have_strides) {
                compute_nh_and_weights_impl(affs, offsets, strides,
                                            uv_ids, weights);
            }
            else {
                compute_nh_and_weights_impl(affs, offsets,
                                            uv_ids, weights);
            }

        }

        inline uint64_t get_node(const xt::xindex & coord) const {
            uint64_t node = 0;
            for(unsigned d = 0; d < _ndim; ++d) {
                node += coord[d] * _strides[d];
            }
            return node;
        }

        inline uint64_t get_node(const std::vector<int64_t> & coord) const {
            uint64_t node = 0;
            for(unsigned d = 0; d < _ndim; ++d) {
                node += coord[d] * _strides[d];
            }
            return node;
        }

        inline std::vector<int64_t> get_coordinate(const uint64_t node) const {
            std::vector<int64_t> coord(_ndim);
            uint64_t index = node;
            for(auto d = 0; d < _ndim; ++d) {
                coord[d] = index / _strides[d];
                index -= coord[d] * _strides[d];
            }
            return coord;
        }

        std::size_t n_nodes() const {
            return _n_nodes;
        }

        unsigned ndim() const {
            return _ndim;
        }

        std::size_t n_seeds() const {
            return _seed_ids.size();
        }

        template<class NODE_LABELS>
        inline void relabel_to_seeds(NODE_LABELS & node_labels) const {
            uint64_t next_id = *std::max_element(node_labels.begin(), node_labels.end()) + 1;

            std::unordered_map<uint64_t, uint64_t> seed_labels;
            for(uint64_t seed_id : _seed_ids) {
                const uint64_t representative = _seed_representatives.at(seed_id);
                seed_labels[node_labels(representative)] = seed_id;
            }

            std::unordered_map<uint64_t, uint64_t> new_labels;
            for(std::size_t node_id = 0; node_id < node_labels.size(); ++node_id) {
                uint64_t label = node_labels[node_id];

                // check if this label should be mapped to a seed
                auto label_it = seed_labels.find(label);
                if(label_it != seed_labels.end()) {
                    label = label_it->second;
                }

                // otherwise check if this label needs to be remapped
                else {
                    // check if this label overlaps with a seed
                    auto seed_it = _seed_ids.find(label);

                    if(seed_it != _seed_ids.end()) {
                        // if it does overlap, check if we have assigned it
                        // to a new label already
                        auto new_it = new_labels.find(label);
                        if(new_it == new_labels.end()) {
                            // no we haven't -> get the next id
                            new_labels[label] = next_id;
                            label = next_id;
                            ++next_id;
                        } else {
                            // yes we have
                            label = new_it->second;
                        }
                    }
                }

                node_labels[node_id] = label;
            }
        }

    private:

        void init_strides() {
            _strides.resize(_ndim);
            _strides[_ndim - 1] = 1;
            for(int d = _ndim - 2; d >= 0; --d) {
                _strides[d] = _shape[d + 1] * _strides[d + 1];
            }
        }

        template<class MASK>
        void init_mask(const MASK & mask) {
            _masked_nodes.resize(_n_nodes);
            util::for_each_coordinate(_shape, [&](const xt::xindex & coord){
                _masked_nodes[get_node(coord)] = mask[coord] == 0;
            });
        }

        template<class AFFS>
        inline void insert_edge(const AFFS & affs, const xt::xindex & aff_coord, const OffsetType & offset,
                                std::vector<std::pair<uint64_t, uint64_t>> & uv_ids, std::vector<float> & weights) const{
            // initialize the spatial coordinates
            xt::xindex coord(_ndim);
            std::copy(aff_coord.begin() + 1, aff_coord.end(), coord.begin());
            xt::xindex ngb_coord = coord;

            // update the ngb-coord and check that we are in range
            for(unsigned d = 0; d < _ndim; ++d) {
                ngb_coord[d] += offset[d];
                if(ngb_coord[d] < 0 || ngb_coord[d] >= _shape[d]) {
                    return;
                }
            }

            uint64_t u = get_node(coord);
            uint64_t v = get_node(ngb_coord);

            // if we have a mask,
            // check if any of the nodes is masked
            if(_masked_nodes.size()) {
                if(_masked_nodes[u] || _masked_nodes[v]) {
                    return;
                }
            }

            if(u > v) {
                std::swap(u, v);
            }
            uv_ids.emplace_back(u, v);
            weights.emplace_back(affs[aff_coord]);
        }

        template<class AFFS>
        void compute_nh_and_weights_impl(const AFFS & affs, const std::vector<OffsetType> & offsets,
                                         std::vector<std::pair<uint64_t, uint64_t>> & uv_ids, std::vector<float> & weights) const {
            const auto & aff_shape = affs.shape();
            // first, compute the weights from the affinities with the given offset pattern
            util::for_each_coordinate(aff_shape, [&](const xt::xindex & aff_coord){
                // set the ngb coord from the offsets
                const auto & offset = offsets[aff_coord[0]];
                // insert the edge and weight corresponding to this grid connection
                insert_edge(affs, aff_coord, offset, uv_ids, weights);
            });

            // if we have seeds, insert all edges between the seeds
            if(_seeded_nodes.size()) {
                if(_add_attractive_seed_edges) {
                    update_attractive_edges_from_seeds(uv_ids, weights);
                } else {
                    update_repulsive_edges_from_seeds(uv_ids, weights);
                }
            }
        }

        template<class AFFS>
        void compute_nh_and_weights_impl(const AFFS & affs, const std::vector<OffsetType> & offsets,
                                         const double fraction, std::vector<std::pair<uint64_t, uint64_t>> & uv_ids,
                                         std::vector<float> & weights) const {

            const auto & aff_shape = affs.shape();
            // random number generator
            std::default_random_engine generator;
            std::uniform_real_distribution<float> distribution;
            auto draw = std::bind(distribution, generator);

            // first, compute the weights from the affinities with the given offset pattern
            util::for_each_coordinate(aff_shape, [&](const xt::xindex & aff_coord){
                // keep edge if random number is below fraction
                if(draw() > fraction) {
                    return;
                }
                // set the ngb coord from the offsets
                const auto & offset = offsets[aff_coord[0]];
                // insert the edge and weight corresponding to this grid connection
                insert_edge(affs, aff_coord, offset, uv_ids, weights);
            });

            // if we have seeds, insert all edges between the seeds
            if(_seeded_nodes.size()) {
                if(_add_attractive_seed_edges) {
                    update_attractive_edges_from_seeds(uv_ids, weights);
                } else {
                    update_repulsive_edges_from_seeds(uv_ids, weights);
                }
            }
        }

        template<class AFFS>
        void compute_nh_and_weights_impl(const AFFS & affs, const std::vector<OffsetType> & offsets,
                                         const std::vector<std::size_t> & strides, std::vector<std::pair<uint64_t, uint64_t>> & uv_ids,
                                         std::vector<float> & weights) const {
            const auto & aff_shape = affs.shape();

            util::for_each_coordinate(aff_shape, [&](const xt::xindex & aff_coord){
                // check if this coordinate is in the strides
                for(unsigned d = 1; d < _ndim + 1; ++d) {
                    if(aff_coord[d] % strides[d - 1] != 0) {
                        return;
                    }
                }
                // set the ngb coord from the offsets
                const auto & offset = offsets[aff_coord[0]];
                // insert the edge and weight corresponding to this grid connection
                insert_edge(affs, aff_coord, offset, uv_ids, weights);
            });

            // if we have seeds, insert all edges between the seeds
            if(_seeded_nodes.size()) {
                if(_add_attractive_seed_edges) {
                    update_attractive_edges_from_seeds(uv_ids, weights);
                } else {
                    update_repulsive_edges_from_seeds(uv_ids, weights);
                }
            }
        }

        inline void update_attractive_edges_from_seeds(std::vector<std::pair<uint64_t, uint64_t>> & uv_ids,
                                                       std::vector<float> & weights) const {
            for(const auto & seeded_node: _seeded_nodes ) {
                const uint64_t seed_id = seeded_node.second;
                uint64_t u = seeded_node.first;
                uint64_t v = _seed_representatives.at(seed_id);

                // don't add self-edge
                if(u == v) {
                    continue;
                }
                if(u > v) {
                    std::swap(u, v);
                }
                uv_ids.emplace_back(u, v);
                weights.emplace_back(_attractive_seed_weight);
            }
        }

        inline void update_repulsive_edges_from_seeds(std::vector<std::pair<uint64_t, uint64_t>> & uv_ids,
                                                      std::vector<float> & weights) const {
            for(const auto & seed_a: _seed_representatives) {
                for(const auto & seed_b: _seed_representatives) {
                    const uint64_t id_a = seed_a.first;
                    const uint64_t id_b = seed_b.first;
                    if(id_a >= id_b) {
                        continue;
                    }

                    uint64_t u = seed_a.second;
                    uint64_t v = seed_b.second;

                    if(u > v) {
                        std::swap(u, v);
                    }
                    uv_ids.emplace_back(u, v);
                    weights.emplace_back(_repulsive_seed_weight);

                }
            }
        }

    private:
        xt::xindex _shape;
        unsigned _ndim;
        std::size_t _n_nodes;
        xt::xindex _strides;

        // data structures for masks
        std::vector<bool> _masked_nodes;

        // data structures for seeds
        std::unordered_map<uint64_t, uint64_t> _seeded_nodes;
        std::set<uint64_t> _seed_ids;
        std::unordered_map<uint64_t, uint64_t> _seed_representatives;

        bool _add_attractive_seed_edges;
        float _attractive_seed_weight;
        float _repulsive_seed_weight;
    };

}
}


#include <SceneParser.hpp>
#include "Tokenizer.hpp"
#include <sstream>
#include <set>
#include <Eigen/Geometry>
#include <math.h>

CSceneParser::CSceneParser(ISceneGenerator& generator) :
    m_generator(generator)
{
    //Create unit transform
    m_transformStack.push(Matrix4d::Identity());
}

bool CSceneParser::ParseScene(std::string scene) {
     std::istringstream s(scene);
     return ParseScene(s);
}

bool CSceneParser::ParseScene(std::istream& scene) {
    CTokenizer tokenizer(scene);
    while(tokenizer.hasMoreTokens()) {
        SToken::TokenType type = tokenizer.peekNextToken();

        if(type == SToken::KEYWORD) {
            CPropertyMap properties;
            auto keyw = getKeywordToken(tokenizer).str;
            if(keyw == "camera") {
                readBlock(tokenizer, properties);
                if(!parseCamera(tokenizer, properties)) return false;
            } else if(keyw == "material") {
                readBlock(tokenizer, properties);
                if(!parseMaterial(tokenizer, properties)) return false;
            }  else if(keyw == "light") {
                readBlock(tokenizer, properties);
                if(!parseLight(tokenizer, properties)) return false;
            }  else if(keyw == "geometry") {
                readBlock(tokenizer, properties);
                if(!parseGeometry(tokenizer, properties)) return false;
            }  else if(keyw == "object") {
                readBlock(tokenizer, properties);
                if(!parseObject(tokenizer, properties)) return false;
            }   else if(keyw == "misc") {
                readBlock(tokenizer, properties);
                if(!parseMisc(tokenizer, properties)) return false;
            }
        } else if(type == SToken::TRANSF) {
            handleTransf(tokenizer);
        } else {
            std::unique_ptr<SToken> tok;
            tokenizer.getNextToken(&tok);
            std::ostringstream oss;
            oss << "Unexpected token of type " << tok->toStr() << " at pos " << tokenizer.getCurrentPos();
            throw ParserException(oss.str().c_str());
        }
   }
    return true;
}

bool CSceneParser::handleTransf(CTokenizer& tokenizer) {
    STransfToken::TransfTypeID id = getTransfToken(tokenizer).id;
    switch(id) {
        case STransfToken::PUSH:
            m_transformStack.push(Matrix4d::Identity());
        break;
        case STransfToken::POP:
            m_transformStack.push(Matrix4d::Identity());
        break;
        case STransfToken::TRANSLATE:
        {
            std::vector<double> vals;
            readConstVect<3>(tokenizer, vals);
            m_transformStack.top() *=  Eigen::Affine3d(Eigen::Translation3d(vals[0], vals[1], vals[2])).matrix();
        }
        break;
        case STransfToken::SCALE:
        {
            std::vector<double> vals;
            readConstVect<3>(tokenizer, vals);
            m_transformStack.top() *=  Eigen::Affine3d(Eigen::Scaling(vals[0], vals[1], vals[2])).matrix();
        }
        break;
        case STransfToken::ROTATE:
        {
            std::vector<double> vals;
            readConstVect<4>(tokenizer, vals);
            m_transformStack.top() *=  Eigen::Affine3d(Eigen::AngleAxis<double>( vals[3] * M_PI / 180.0,
                    Eigen::Vector3d(vals[0], vals[1], vals[2]))).matrix();
        }
        break;
        case STransfToken::TRANSFORM:
        {
            std::vector<double> vals;
            readConstVect<12>(tokenizer, vals);
            std::vector<double> tmp = {0.0, 0.0, 0.0, 1.0};
            vals.insert(std::end(vals), std::begin(tmp), std::end(tmp));
            m_transformStack.top() *=  Eigen::Matrix4d(vals.data());
        }
        break;
        case STransfToken::LOAD:
           m_transformStack.top() =  Eigen::Matrix4d::Identity();
        break;

        default:
            throw ParserException("Unknown transform %d", id);
    }
    return true;
}

bool CSceneParser::readBlock(CTokenizer& tokenizer, CPropertyMap& map) {
        getSymToken(tokenizer);  // First block separator
        SToken::TokenType type;

        while((type = tokenizer.peekNextToken()) != SToken::NONE)  {
            switch(type) {
                case SToken::SYM:
                    if((getSymToken(tokenizer).str != "}")) {
                        //CPropertyMap pm;
                        //readBlock(tokenizer, pm);
                        return false;
                    }

                    return true;     // Block closed hopefully
                    break;

                // The Object keyword contains geometry and material keywords as
                // properties, need to read them here
                case SToken::KEYWORD:
                {
                    auto name = getKeywordToken(tokenizer).str;
                    SPropertyValue prop;
                    readPropertyValue(tokenizer, prop);
                    map[name] =  prop;
                }
                 break;
                case SToken::ID:
                {
                    auto name = getIdToken(tokenizer).str;
                    SPropertyValue prop;
                    readPropertyValue(tokenizer, prop);
                    // TRI is a special case as it might occure several times in a
                    // given block
                    if(name == "tri") {
                        auto tmp = prop.toDoubleList();
                        map[name] <<  tmp[0] << tmp[1] << tmp[2];
                    } else
                        map[name] =  prop;
                }
                 break;

                default:
                    return false; // malformed code block
            }
        }

        return false; // Block never closed
}

bool CSceneParser::readPropertyValue(CTokenizer& tokenizer, SPropertyValue& val) {
    auto nextTok = tokenizer.peekNextToken() ;
    if(nextTok == SToken::ID) {
        val = getIdToken(tokenizer).str;
    } else if (nextTok == SToken::CONST) {
        // Glob all consts
        while( tokenizer.peekNextToken() == SToken::CONST)
            val << getConstToken(tokenizer).val;
    } else if (nextTok == SToken::SYM) {
         while( tokenizer.peekNextToken() == SToken::SYM) {
             CPropertyMap pm;;
             readBlock(tokenizer, pm);
             val <<  pm;
         }
    }
    return true;
}

bool CSceneParser::parseMisc(CTokenizer& tokenizer, CPropertyMap& properties) {
    SMisc misc;
    return m_generator.Misc(misc, m_transformStack.top());
}

bool CSceneParser::parseObject(CTokenizer& tokenizer, CPropertyMap& properties) {
    if(!properties.hasProperty("geometry")) throw ParserException("Object: Missing required field GEOMETRY");
    if(!properties.hasProperty("material")) throw ParserException("Object: Missing required field MATERIAL");
    SObject obj(properties["geometry"].toStr(), properties["material"].toStr());
    if(properties.hasProperty("name")) obj.name = properties["name"].toStr();

    return m_generator.Object(obj, m_transformStack.top());
}

bool CSceneParser::parseLight(CTokenizer& tokenizer, CPropertyMap& properties) {
    SLight* light;

    std::set<std::string> validKeywords = {"name", "type", "ambient", "diffuse", "specular", "position", "attenuation_coefs", "direction"};
    //if(!checkKeywords(properties, validKeywords)) throw ParserException("Light: unknown keyword");
    for(auto keyw : properties) {
        if(validKeywords.find(keyw.first) == validKeywords.end()) {
            std::ostringstream oss;
            oss << "Light, unknown property " << keyw.first;
            throw ParserException(oss.str().c_str());
        }
    }

    if(properties.first() != "type") throw ParserException("Light: TYPE field missing or not first in block");
    if(!properties.hasProperty("name")) throw ParserException("Light: Missing required field NAME");

    SLight::LightType type = SLight::NONE;
    if(properties["type"].toStr() == "point") type = SLight::POINT;
    else if(properties["type"].toStr() == "directional") type =SLight::DIRECTIONAL;

    if(type == SLight::POINT) light = new SPointLight(properties["name"].toStr());
    else if(type == SLight::DIRECTIONAL) light = new SDirectionalLight(properties["name"].toStr());
    else throw ParserException("Light: unknown light type");

    if(properties.hasProperty("ambient")) light->ambient = toVector3d(properties["ambient"].toDoubleList());
    if(properties.hasProperty("diffuse")) light->diffuse = toVector3d(properties["diffuse"].toDoubleList());
    if(properties.hasProperty("specular")) light->specular = toVector3d(properties["specular"].toDoubleList());

    bool ret = false;
    if(type == SLight::POINT) {
        SPointLight* pl = static_cast<SPointLight*>(light);
        if(properties.hasProperty("position")) pl->position = toVector3d(properties["position"].toDoubleList());
        if(properties.hasProperty("attenuation_coefs")) pl->attenuationCoefs = toVector3d(properties["attenuation_coefs"].toDoubleList());
        ret = m_generator.Light(*pl, m_transformStack.top());
    } else if (type == SLight::DIRECTIONAL) {
        SDirectionalLight* dl = static_cast<SDirectionalLight*>(light) ;
        if(properties.hasProperty("direction")) dl->direction = toVector3d(properties["direction"].toDoubleList());
        ret = m_generator.Light(*dl, m_transformStack.top());
    }

    delete light;
    return ret;
}

bool CSceneParser::parseMaterial(CTokenizer& tokenizer, CPropertyMap& properties) {
    std::set<std::string> validKeywords = {"name", "type", "emission", "ambient", "diffuse", "specular", "specular_power", "texture"};
    if(!checkKeywords(properties, validKeywords)) throw ParserException("Material: unknown keyword");

    if(properties.first() != "type") throw ParserException("Camera: TYPE field missing or not first in block");
    if(!properties.hasProperty("name")) throw ParserException("Camera: Missing required field NAME");

    SMaterial mat(SMaterial::BASIC,  properties["name"].toStr());
    if(properties.hasProperty("emission")) mat.emission = toVector3d(properties["emission"].toDoubleList());
    if(properties.hasProperty("ambient")) mat.ambient = toVector3d(properties["ambient"].toDoubleList());
    if(properties.hasProperty("diffuse")) mat.diffuse = toVector3d(properties["diffuse"].toDoubleList());
    if(properties.hasProperty("specular")) mat.specular = toVector3d(properties["specular"].toDoubleList());
    if(properties.hasProperty("specular_power")) mat.specularPower = properties["specular_power"].toDouble();
    if(properties.hasProperty("texture")) mat.texture = properties["texture"].toStr();

    return m_generator.Material(mat, m_transformStack.top());
}

bool CSceneParser::parseCamera(CTokenizer& tokenizer, CPropertyMap& properties) {
    SCamera* cam = nullptr;

    std::set<std::string> validKeywords = {"name", "type", "eye_point", "look_point", "up", "distance_image_plane", "fov", "aspect_ratio", "top", "bottom", "left", "right"};
    if(!checkKeywords(properties, validKeywords)) throw ParserException("Camera: unknown keyword");

    if(properties.first() != "type") throw ParserException("Camera: TYPE field missing or not first in block");
    if(!properties.hasProperty("name")) throw ParserException("Camera: Missing required field NAME");

    auto type = properties["type"].toStr() == "basic" ? SCamera::BASIC : SCamera::ADVANCED;
    if(type == SCamera::BASIC) cam = new SBasicCamera( properties["name"].toStr()) ;
    else if(type == SCamera::ADVANCED) cam = new SAdvancedCamera(properties["name"].toStr());
    else throw ParserException("Camera: unknown camera type");

    if(properties.hasProperty("eye_point")) cam->eyePoint = toVector3d(properties["eye_point"].toDoubleList());
    if(properties.hasProperty("look_point")) cam->lookPoint = toVector3d(properties["look_point"].toDoubleList());
    if(properties.hasProperty("up"))  cam->up = toVector3d(properties["up"].toDoubleList());
    if(properties.hasProperty("distance_image_plane")) cam->distanceImagePlane = properties["distance_image_plane"].toDouble();

    bool ret = false;
    if(type == SCamera::BASIC) {
        SBasicCamera* bc = static_cast<SBasicCamera*>(cam);
        if(properties.hasProperty("fov")) bc->fov = properties["fov"].toDouble();
        if(properties.hasProperty("aspect_ratio")) bc->aspectRatio = properties["aspect_ratio"].toDouble();
        ret = m_generator.Camera(*bc, m_transformStack.top());
    } else if (type == SCamera::ADVANCED) {
        SAdvancedCamera* ac = static_cast<SAdvancedCamera*>(cam);
        if(properties.hasProperty("left")) ac->left = properties["left"].toDouble();
        if(properties.hasProperty("right")) ac->right = properties["right"].toDouble();
        if(properties.hasProperty("top")) ac->top = properties["top"].toDouble();
        if(properties.hasProperty("bottom")) ac->bottom = properties["bottom"].toDouble();
        ret = m_generator.Camera(*ac, m_transformStack.top());
    }

    delete cam;
    return ret;
}

bool CSceneParser::parseGeometry(CTokenizer& tokenizer, CPropertyMap& properties) {
     SGeometry* geom = nullptr;

    std::set<std::string> validKeywords = {"name", "type", "vertices", "tri"};
    if(!checkKeywords(properties, validKeywords)) throw ParserException("Geometry: unknown keyword");

    if(properties.first() != "type") throw ParserException("Geometry: TYPE field missing or not first in block");
    if(!properties.hasProperty("name")) throw ParserException("Geometry: Missing required field NAME");

    auto type = SGeometry::NONE;;
    if(properties["type"].toStr() == "sphere") type = SGeometry::SPHERE;
    else if(properties["type"].toStr() == "mesh") type = SGeometry::MESH;

    if(type == SGeometry::SPHERE) geom = new SSphere( properties["name"].toStr()) ;
    else if(type == SGeometry::MESH) geom = new SMesh(properties["name"].toStr());
    else throw ParserException("Geometry: unknown geometry");

    bool ret = false;
      if(type == SGeometry::MESH) {
        SMesh* mesh = static_cast<SMesh*>(geom);
        if(properties.hasProperty("tri")) {
            auto tmp = properties["tri"].toDoubleList();
            for(unsigned int i = 0; i < tmp.size(); ) {
                int v1 = tmp[i++];
                int v2 = tmp[i++];
                int v3 = tmp[i++];
                mesh->tri.push_back({{v1, v2, v3}});
            }
        }
        if(properties.hasProperty("vertices")) {
            auto verts = properties["vertices"].toMapList();
            for(unsigned int i = 0; i < verts.size(); i++ ) {
               SVertex v;
               if(verts[i].hasProperty("p")) v.p = toVector3d(verts[i]["p"].toDoubleList());
               if(verts[i].hasProperty("n")) v.n = toVector3d(verts[i]["n"].toDoubleList());
               if(verts[i].hasProperty("tc")) v.tc = toVector3d(verts[i]["tc"].toDoubleList());
                mesh->vertices.push_back(v);
            }

        }
        ret = m_generator.Geometry(*mesh, m_transformStack.top());

    } else
        ret = m_generator.Geometry(*geom, m_transformStack.top());
    delete geom;
    return ret;
}

SKeywordToken CSceneParser::getKeywordToken(CTokenizer& tokenizer) {
    std::unique_ptr<SToken> tok;
    tokenizer.getNextToken(&tok);
    return *static_cast<SKeywordToken*>(tok.get());
}

bool CSceneParser::checkKeywords(CPropertyMap& properties, std::set<std::string> validKeywords) {
    for(auto keyw : properties)
        if(validKeywords.find(keyw.first) == validKeywords.end()) return false;
    return true;
}

SSymToken CSceneParser::getSymToken(CTokenizer& tokenizer) {
    std::unique_ptr<SToken> tok;
    tokenizer.getNextToken(&tok);
    return *static_cast<SSymToken*>(tok.get());
}

SConstToken CSceneParser::getConstToken(CTokenizer& tokenizer) {
    std::unique_ptr<SToken> tok;
    tokenizer.getNextToken(&tok);
//    if(tok->type != SToken::CONST) return nullptr;
    return *static_cast<SConstToken*>(tok.get());
}

SIdToken CSceneParser::getIdToken(CTokenizer& tokenizer) {
    std::unique_ptr<SToken> tok;
    tokenizer.getNextToken(&tok);
    return *static_cast<SIdToken*>(tok.get());
}

STransfToken CSceneParser::getTransfToken(CTokenizer& tokenizer) {
    std::unique_ptr<SToken> tok;
    tokenizer.getNextToken(&tok);
    return *static_cast<STransfToken*>(tok.get());
}

Vector3d CSceneParser::toVector3d(std::vector<double> dblList) {
    if(dblList.size() != 3) throw ParserException("Not enough values in list");
    Vector3d tmp = Vector3d::Zero();
    for(unsigned int i = 0; i < dblList.size(); i ++) {
        tmp(i) = dblList[i];
    }
    return tmp;
}


// Boost name_generator_md5.hpp header file  ------------------------//

// Copyright 2017 James E. King III

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

#ifndef BOOST_UUID_NAME_GENERATOR_MD5_HPP
#define BOOST_UUID_NAME_GENERATOR_MD5_HPP

#include <boost/uuid/basic_name_generator.hpp>
#include <boost/uuid/detail/md5.hpp>

namespace boost {
namespace uuids {

//! \brief MD5 hashing is defined in RFC 4122 however it is not commonly
//!        used; the definition is provided for backwards compatibility.
typedef basic_name_generator<detail::md5> name_generator_md5;

} // uuids
} // boost

#endif // BOOST_UUID_NAME_GENERATOR_MD5_HPP