#ifndef OSMIUM_BG_FACTORY_HPP
#define OSMIUM_BG_FACTORY_HPP

/*

This file is part of Osmium (https://osmcode.org/libosmium).

Copyright 2013-2021 Jochen Topf <jochen@topf.org> and others (see README).

Boost Software License - Version 1.0 - August 17th, 2003

Permission is hereby granted, free of charge, to any person or organization
obtaining a copy of the software and accompanying documentation covered by
this license (the "Software") to use, reproduce, display, distribute,
execute, and transmit the Software, and to prepare derivative works of the
Software, and to permit third-parties to whom the Software is furnished to
do so, all subject to the following:

The copyright notices in the Software and this entire statement, including
the above license grant, this restriction and the following disclaimer,
must be included in all copies of the Software, in whole or in part, and
all derivative works of the Software, unless such copies or derivative
works are solely in the form of machine-executable object code generated by
a source language processor.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.

*/

#include <osmium/geom/coordinates.hpp>
#include <osmium/memory/collection.hpp>
#include <osmium/memory/item.hpp>
#include <osmium/osm/area.hpp>
#include <osmium/osm/item_type.hpp>
#include <osmium/osm/location.hpp>
#include <osmium/osm/node.hpp>
#include <osmium/osm/node_ref.hpp>
#include <osmium/osm/node_ref_list.hpp>
#include <osmium/osm/types.hpp>
#include <osmium/osm/way.hpp>

#include <cstddef>
#include <stdexcept>
#include <string>
#include <utility>

#include <osmium/geom/factory.hpp>

#include <boost/geometry.hpp>
#include <boost/geometry/geometries/point_xy.hpp>
#include <boost/geometry/geometries/linestring.hpp>
#include <boost/geometry/geometries/polygon.hpp>
#include <boost/geometry/geometries/multi_polygon.hpp>

namespace osmium {
    namespace geom {
        /**
         * Boost Geometry factory.
         */
        class BGFactory {

        public:
            using point_type        = boost::geometry::model::d2::point_xy<double, boost::geometry::cs::geographic<boost::geometry::degree>>;
            using box_type          = boost::geometry::model::box<point_type>;
            using linestring_type   = boost::geometry::model::linestring<point_type>;
            using polygon_type      = boost::geometry::model::polygon<point_type>;
            using multipolygon_type = boost::geometry::model::multi_polygon<polygon_type>;
            using ring_type         = boost::geometry::model::ring<point_type>;

        public:
            BGFactory() { }
            BGFactory(const BGFactory &) = default;
            BGFactory(BGFactory&&) = default;
            ~BGFactory() = default;
            BGFactory& operator=(const BGFactory &) = default;
            BGFactory& operator=(BGFactory&&) = default;

            int epsg() const noexcept { return 4326; }
            std::string proj_string() const { return "+proj=longlat +datum=WGS84 +no_defs"; }

            /* Point */

            point_type create_point(const osmium::Location& location) const {
                return point_type(location.lon(), location.lat());
            }

            point_type create_point(const osmium::Node& node) const {
                try {
                    return create_point(node.location());
                } catch (osmium::geometry_error& e) {
                    e.set_id("node", node.id());
                    throw;
                }
            }

            point_type create_point(const osmium::NodeRef& node_ref) const {
                try {
                    return create_point(node_ref.location());
                } catch (osmium::geometry_error& e) {
                    e.set_id("node", node_ref.ref());
                    throw;
                }
            }

            /* LineString */

            linestring_type create_linestring(const osmium::WayNodeList& wnl, use_nodes un = use_nodes::unique, direction dir = direction::forward) const {
                linestring_type l;

                l.resize(wnl.size());
                if(dir == direction::forward)
                    std::transform(wnl.cbegin(), wnl.cend(), l.begin(), [&](const osmium::NodeRef& n){ return create_point(n); });
                else
                    std::transform(wnl.crbegin(), wnl.crend(), l.begin(), [&](const osmium::NodeRef& n){ return create_point(n); });

                if(un == use_nodes::unique)
                    boost::geometry::unique(l);

                if(l.size() < 2)
                    throw osmium::geometry_error{"need at least two points for linestring"};

                if(! boost::geometry::is_valid(l)) {
                    std::string m;
                    boost::geometry::is_valid(l, m);
                    throw osmium::geometry_error{m};
                }

                return l;
            }

            linestring_type create_linestring(const osmium::Way& way, use_nodes un = use_nodes::unique, direction dir = direction::forward) const {
                try {
                    return create_linestring(way.nodes(), un, dir);
                } catch (osmium::geometry_error& e) {
                    e.set_id("way", way.id());
                    throw;
                }
            }

            /* Polygon */

            polygon_type create_polygon(const osmium::WayNodeList& wnl, use_nodes un = use_nodes::unique, direction dir = direction::forward) const {
                polygon_type p;

                p.outer().resize(wnl.size());
                if(dir == direction::forward)
                    std::transform(wnl.cbegin(), wnl.cend(), p.outer().begin(), [&](const osmium::NodeRef& n){ return create_point(n); });
                else
                    std::transform(wnl.crbegin(), wnl.crend(), p.outer().begin(), [&](const osmium::NodeRef& n){ return create_point(n); });

                if (un == use_nodes::unique)
                    boost::geometry::unique(p);

                if (p.outer().size() < 4)
                    throw osmium::geometry_error{"need at least four points for polygon"};

                if (! boost::geometry::is_valid(p)) {
                    std::string m;
                    boost::geometry::is_valid(p, m);
                    throw osmium::geometry_error{m};
                }

                return p;
            }

            polygon_type create_polygon(const osmium::Way& way, use_nodes un = use_nodes::unique, direction dir = direction::forward) const {
                try {
                    return create_polygon(way.nodes(), un, dir);
                } catch (osmium::geometry_error& e) {
                    e.set_id("way", way.id());
                    throw;
                }
            }

            /* MultiPolygon */

            multipolygon_type create_multipolygon(const osmium::Area& area) const {
                try {
                    multipolygon_type mp;
                    polygon_type * current_polygon = nullptr;
                    for (const auto& item : area) {
                        if (item.type() == osmium::item_type::outer_ring) {
                            const auto& ring = static_cast<const osmium::OuterRing&>(item);
                            current_polygon = &mp.emplace_back();
                            current_polygon->outer().resize(ring.size());
                            std::transform(ring.crbegin(), ring.crend(), current_polygon->outer().begin(),
                                    [&](const osmium::NodeRef& n){ return create_point(n); });
                        } else if (item.type() == osmium::item_type::inner_ring) {
                            const auto& ring = static_cast<const osmium::InnerRing&>(item);
                            ring_type & current_ring = current_polygon->inners().emplace_back();
                            current_ring.resize(ring.size());
                            std::transform(ring.crbegin(), ring.crend(), current_ring.begin(),
                                    [&](const osmium::NodeRef& n){ return create_point(n); });
                        }
                    }

                    if(mp.empty())
                        throw osmium::geometry_error{"invalid area"};

                    if(!boost::geometry::is_valid(mp)) {
                        std::string m;
                        boost::geometry::is_valid(mp, m);
                        throw osmium::geometry_error{m};
                    }

                    return mp;
                } catch (osmium::geometry_error& e) {
                    e.set_id("area", area.id());
                    throw;
                }
            }

            template<class T>
            box_type envelope(T&& elem) {
                const osmium::Box box = elem.envelope();
                return box_type(create_point(box.bottom_left()), create_point(box.top_right()));
            }
        }; // class BGFactory
    } // namespace geom
} // namespace osmium

#endif // OSMIUM_GEOM_FACTORY_HPP


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

  Aggregate SIMD numerical and type limits for ARM NEON

  @copyright 2012 - 2015 NumScale SAS

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

**/
//==================================================================================================
#ifndef BOOST_SIMD_ARCH_ARM_NEON_LIMITS_HPP_INCLUDED
#define BOOST_SIMD_ARCH_ARM_NEON_LIMITS_HPP_INCLUDED

#include <boost/simd/arch/arm/tags.hpp>
#include <boost/simd/arch/common/limits.hpp>
#include <boost/dispatch/meta/make_integer.hpp>

namespace boost { namespace simd
{
  template<> struct limits<boost::simd::neon_>
  {
    using parent = boost::simd::neon64_;

    struct largest_integer
    {
      template<typename Sign> struct apply : boost::dispatch::make_integer<8,Sign> {};
    };

    struct smallest_integer
    {
      template<typename Sign> struct apply : boost::dispatch::make_integer<1,Sign> {};
    };

#ifdef __aarch64__
    using largest_real   = double;
#else
    using largest_real   = float;
#endif
    using smallest_real  = float;

    enum { bits = 128, bytes = 16 };
  };

  template<> struct limits<boost::simd::neon64_> : limits<boost::simd::neon_>
  {
    using parent = boost::simd::simd_;
  };
} }

#endif


#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <random>
#include <chrono>
#include <functional>
#include <cctype>
#include <locale>
#include <algorithm>
#include <regex>
#include <iterator>
#include <vector>
#include <boost/archive/text_oarchive.hpp>
#include <boost/archive/text_iarchive.hpp>
#include <boost/archive/binary_oarchive.hpp>
#include <boost/archive/binary_iarchive.hpp>
#include "perceptron.h"
#include "segmentor.h"
#include "featurizer.h"
#include "naivebayes.h"
#include "util.h"

using namespace std;

int main() {
    int THRESHOLD = 6;

    NBUnigramBOW* nb = new NBUnigramBOW();

    ifstream is("naivebayes.dat", ios::binary);
    boost::archive::binary_iarchive nb_ar(is);
    nb_ar >> nb;

    AveragedPerceptron* perceptron = new AveragedPerceptron();
    Featurizer* feat = new Featurizer();
    map<string, int> index_table;
    feat->index_table = index_table;

    ifstream feature_is("feature_index.dat", ios::binary);
    boost::archive::binary_iarchive feature_ar(feature_is);
    feature_ar >> feat;

    ifstream perceptron_is("perceptron.dat", ios::binary);
    boost::archive::binary_iarchive perceptron_ar(perceptron_is);
    perceptron_ar >> perceptron;

    Segmentor* seg = new Segmentor(perceptron, feat);

    ifstream test_query ("../data/test1.qry");

    vector<string> test_texts;

    string line;
    if (test_query.is_open()) {
        while (getline(test_query, line)) {
            transform(line.begin(), line.end(), line.begin(), ::tolower);
            line = trim(line);

            if (line.empty()) {
                continue;
            }

            test_texts.push_back(line);
        }
    }

    ofstream prediction_file ("../prediction/nb_ap_ensemble.txt");

    for (auto x: test_texts) {
        vector<string> prediction = nb->predict(x);
        double loglikelihood = nb->fitness(prediction);

        // bigram correction
        vector<string> correction;
        for (auto word: prediction) {
            vector<vector<string>> splits = nb->split_pairs(word);

            bool corrected = false;
            for (auto pair: splits) {
                vector<string> ngram;
                ngram.push_back(pair[0]);
                ngram.push_back(pair[1]);

                // this will not search the entire space
                // will pick bi-gram greedily
                auto it = nb->bigram.find(ngram);
                if (it != nb->bigram.end()) {
                    if (it->second > THRESHOLD) {
                        // this bigram has to show up plenty of times
                        if ((pair[1].size() != 1) && (pair[1].size() != 2)) {
                            // will ignore single/double characters since they are usually a word
                            // segmented because they contain apostrophes
                            correction.push_back(pair[0]);
                            correction.push_back(pair[1]);
                            corrected = true;
                            break;
                        }
                    }
                }
            }
            if (not corrected) {
                correction.push_back(word);
            }
        }

        // Averaged Perceptron to the rescue!
        // It's better off to resort to averaged perceptron when dealing with non-words
        bool is_equal = false;
        if (prediction.size() < correction.size()) {
            is_equal = std::equal ( prediction.begin(), prediction.end(), correction.begin() );
        }
        else {
            is_equal = std::equal ( correction.begin(), correction.end(), prediction.begin() );
        }

        if (is_equal) {
            // reaching here means no correction was made
            if (prediction.size() == 2 && loglikelihood < -10.0) {
                // if the final prediction was a single word
                // it is quite likely that this word a english non-word
                // but it might still be segmented
                // fallback to averaged perceptron
                correction.clear();
                vector<int> ap_prediction = seg->predict(prediction[0]);
                stringstream ss;
                for (int i = 0; i < ap_prediction.size(); ++i) {
                    ss << prediction[0][i];
                    if (ap_prediction[i] == 1) {
                        correction.push_back(ss.str());
                        ss.str("");
                        ss.clear();
                    }
                }
                if (not ss.str().empty()) {
                    correction.push_back(ss.str());
                    ss.str("");
                    ss.clear();
                }
            }

        }

        bool anomaly_detected = false;
        for (auto word: prediction) {
            // handle single character anomalies
            // only single character that appears many times
            // so that it will affect the result is 'a'
            if (word.size() == 1 && word[0] != 'a') {
                anomaly_detected = true;
            }
        }

        if (anomaly_detected) {
            // fallback to averaged perceptron
            correction.clear();

            vector<int> ap_prediction = seg->predict(x);
            stringstream ss;
            for (int i = 0; i < ap_prediction.size(); ++i) {
                ss << x[i];
                if (ap_prediction[i] == 1) {
                    correction.push_back(ss.str());
                    ss.str("");
                    ss.clear();
                }
            }
            if (not ss.str().empty()) {
                correction.push_back(ss.str());
                ss.str("");
                ss.clear();
            }
        }

        // if (prediction.size() > 5 && loglikelihood < -50.0) {
        //     for (auto w: prediction) {
        //         cout << w << " ";
        //     }
        //     cout << loglikelihood << endl;
        //     // correction.clear();
        //     // vector<int> ap_prediction = seg->predict(x);
        //     // stringstream ss;
        //     // for (int i = 0; i < ap_prediction.size(); ++i) {
        //     //     ss << x[i];
        //     //     if (ap_prediction[i] == 1) {
        //     //         correction.push_back(ss.str());
        //     //         ss.str("");
        //     //         ss.clear();
        //     //     }
        //     // }
        //     // if (not ss.str().empty()) {
        //     //     correction.push_back(ss.str());
        //     //     ss.str("");
        //     //     ss.clear();
        //     // }
        // }

        stringstream ss;
        for (auto word: correction) {
            ss << word << " ";
        }
        string result = ss.str();

        prediction_file << trim(result) << endl;
        ss.str("");
        ss.clear();
    }

    return 0;
};


// Copyright (c) 2010 Satoshi Nakamoto
// Copyright (c) 2009-2013 The Bitcoin developers
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include "base58.h"
#include "init.h"
#include "main.h"
#include "net.h"
#include "netbase.h"
#include "rpcserver.h"
#include "txdb.h"
#include "timedata.h"
#include "util.h"
#ifdef ENABLE_WALLET
#include "wallet.h"
#include "walletdb.h"
#endif

#include <stdint.h>

#include <boost/assign/list_of.hpp>
#include "json/json_spirit_utils.h"
#include "json/json_spirit_value.h"

using namespace std;
using namespace boost;
using namespace boost::assign;
using namespace json_spirit;

Value getinfo(const Array& params, bool fHelp)
{
    if (fHelp || params.size() != 0)
        throw runtime_error(
            "getinfo\n"
            "Returns an object containing various state info.");

    proxyType proxy;
    GetProxy(NET_IPV4, proxy);

    Object obj, diff;
    obj.push_back(Pair("version",       FormatFullVersion()));
    obj.push_back(Pair("protocolversion",(int)PROTOCOL_VERSION));
#ifdef ENABLE_WALLET
    if (pwalletMain) {
        obj.push_back(Pair("walletversion", pwalletMain->GetVersion()));
        obj.push_back(Pair("balance",       ValueFromAmount(pwalletMain->GetBalance())));
        obj.push_back(Pair("newmint",       ValueFromAmount(pwalletMain->GetNewMint())));
        obj.push_back(Pair("stake",         ValueFromAmount(pwalletMain->GetStake())));
    }
#endif
    obj.push_back(Pair("blocks",        (int)nBestHeight));
    obj.push_back(Pair("timeoffset",    (int64_t)GetTimeOffset()));
    obj.push_back(Pair("moneysupply",   ValueFromAmount(pindexBest->nMoneySupply)));
    obj.push_back(Pair("digsupply",     ValueFromAmount(pindexBest->nDigsupply)));
    obj.push_back(Pair("stakesupply",   ValueFromAmount(pindexBest->nStakeSupply)));
    obj.push_back(Pair("activesupply",  ValueFromAmount(pindexBest->nDigsupply + pindexBest->nStakeSupply)));
    obj.push_back(Pair("connections",   (int)vNodes.size()));
    obj.push_back(Pair("proxy",         (proxy.first.IsValid() ? proxy.first.ToStringIPPort() : string())));
    obj.push_back(Pair("ip",            GetLocalAddress(NULL).ToStringIP()));

    diff.push_back(Pair("proof-of-stake", GetDifficulty(GetLastBlockIndex(pindexBest, true))));
    obj.push_back(Pair("difficulty",    diff));

    obj.push_back(Pair("testnet",       TestNet()));
#ifdef ENABLE_WALLET
    if (pwalletMain) {
        obj.push_back(Pair("keypoololdest", (int64_t)pwalletMain->GetOldestKeyPoolTime()));
        obj.push_back(Pair("keypoolsize",   (int)pwalletMain->GetKeyPoolSize()));
    }
    obj.push_back(Pair("paytxfee",      ValueFromAmount(nTransactionFee)));
    obj.push_back(Pair("mininput",      ValueFromAmount(nMinimumInputValue)));
    if (pwalletMain && pwalletMain->IsCrypted())
        obj.push_back(Pair("unlocked_until", (int64_t)nWalletUnlockTime));
#endif
    obj.push_back(Pair("errors",        GetWarnings("statusbar")));
    return obj;
}

#ifdef ENABLE_WALLET
class DescribeAddressVisitor : public boost::static_visitor<Object>
{
public:
    Object operator()(const CNoDestination &dest) const { return Object(); }

    Object operator()(const CKeyID &keyID) const {
        Object obj;
        CPubKey vchPubKey;
        pwalletMain->GetPubKey(keyID, vchPubKey);
        obj.push_back(Pair("isscript", false));
        obj.push_back(Pair("pubkey", HexStr(vchPubKey)));
        obj.push_back(Pair("iscompressed", vchPubKey.IsCompressed()));
        return obj;
    }

    Object operator()(const CScriptID &scriptID) const {
        Object obj;
        obj.push_back(Pair("isscript", true));
        CScript subscript;
        pwalletMain->GetCScript(scriptID, subscript);
        std::vector<CTxDestination> addresses;
        txnouttype whichType;
        int nRequired;
        ExtractDestinations(subscript, whichType, addresses, nRequired);
        obj.push_back(Pair("script", GetTxnOutputType(whichType)));
        obj.push_back(Pair("hex", HexStr(subscript.begin(), subscript.end())));
        Array a;
        BOOST_FOREACH(const CTxDestination& addr, addresses)
            a.push_back(CBitcoinAddress(addr).ToString());
        obj.push_back(Pair("addresses", a));
        if (whichType == TX_MULTISIG)
            obj.push_back(Pair("sigsrequired", nRequired));
        return obj;
    }
};
#endif

Value validateaddress(const Array& params, bool fHelp)
{
    if (fHelp || params.size() != 1)
        throw runtime_error(
            "validateaddress <Clamaddress>\n"
            "Return information about <Clamaddress>.");

    CBitcoinAddress address(params[0].get_str());
    bool isValid = address.IsValid();

    Object ret;
    ret.push_back(Pair("isvalid", isValid));
    if (isValid)
    {
        CTxDestination dest = address.Get();
        string currentAddress = address.ToString();
        ret.push_back(Pair("address", currentAddress));
#ifdef ENABLE_WALLET
        bool fMine = pwalletMain ? IsMine(*pwalletMain, dest) : false;
        ret.push_back(Pair("ismine", fMine));
        if (fMine) {
            Object detail = boost::apply_visitor(DescribeAddressVisitor(), dest);
            ret.insert(ret.end(), detail.begin(), detail.end());
        }
        if (pwalletMain && pwalletMain->mapAddressBook.count(dest))
            ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest]));
#endif
    }
    return ret;
}

static void validateoutputs_check_unconfirmed_spend(COutPoint& outpoint, CTransaction& tx, Object& entry)
{
    // check whether unconfirmed output is already spent
    LOCK(mempool.cs); // protect mempool.mapNextTx
    if (mempool.mapNextTx.count(outpoint)) {
        // pull details from mempool
        CInPoint in = mempool.mapNextTx[outpoint];
        Object details;
        entry.push_back(Pair("status", "spent"));
        details.push_back(Pair("txid", in.ptx->GetHash().GetHex()));
        details.push_back(Pair("vin", int(in.n)));
        details.push_back(Pair("confirmations", 0));
        entry.push_back(Pair("spent", details));
    } else {
        entry.push_back(Pair("status", "unspent"));

        const CScript& pk = tx.vout[outpoint.n].scriptPubKey;
        entry.push_back(Pair("scriptPubKey", HexStr(pk.begin(), pk.end())));
    }
}

Value validateoutputs(const Array& params, bool fHelp)
{
    if (fHelp || params.size() != 1)
        throw runtime_error(
            "validateoutputs [{\"txid\":txid,\"vout\":n},...]\n"
            "Return information about outputs (whether they exist, and whether they have been spent).");

    Array inputs = params[0].get_array();

    CTxDB txdb("r");
    CTxIndex txindex;
    Array ret;

    BOOST_FOREACH(Value& input, inputs)
    {
        const Object& o = input.get_obj();

        const Value& txid_v = find_value(o, "txid");
        if (txid_v.type() != str_type)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing txid key");
        string txid = txid_v.get_str();
        if (!IsHex(txid))
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, expected hex txid");

        const Value& vout_v = find_value(o, "vout");
        if (vout_v.type() != int_type)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, missing vout key");
        int nOutput = vout_v.get_int();
        if (nOutput < 0)
            throw JSONRPCError(RPC_INVALID_PARAMETER, "Invalid parameter, vout must be positive");

        Object entry;
        entry.push_back(Pair("txid", txid));
        entry.push_back(Pair("vout", nOutput));

        CTransaction tx;
        uint256 hashBlock = 0;
        CTxIndex txindex;
        COutPoint outpoint(uint256(txid), nOutput);

        // find the output
        if (!GetTransaction(uint256(txid), tx, hashBlock, txindex)) {
            entry.push_back(Pair("status", "txid not found"));
            ret.push_back(entry);
            continue;
        }

        // check that the output number is in range
        if ((unsigned)nOutput >= tx.vout.size()) {
            entry.push_back(Pair("status", "vout too high"));
            entry.push_back(Pair("outputs", (int)tx.vout.size()));
            ret.push_back(entry);
            continue;
        }

        entry.push_back(Pair("amount", ValueFromAmount(tx.vout[nOutput].nValue)));

        // get the address and account
        CTxDestination address;
        if (ExtractDestination(tx.vout[nOutput].scriptPubKey, address))
        {
            entry.push_back(Pair("address", CBitcoinAddress(address).ToString()));
            if (pwalletMain->mapAddressBook.count(address))
                entry.push_back(Pair("account", pwalletMain->mapAddressBook[address]));
        }

        // is the output confirmed?
        if (hashBlock == 0) {
            entry.push_back(Pair("confirmations", 0));
            validateoutputs_check_unconfirmed_spend(outpoint, tx, entry);
            ret.push_back(entry);
            continue;
        }

        // find the block containing the output
        map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
        if (mi != mapBlockIndex.end() && (*mi).second)
        {
            CBlockIndex* pindex = (*mi).second;
            if (pindex->IsInMainChain()) {
                entry.push_back(Pair("height", pindex->nHeight));
                entry.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight));
            } else {
                LogPrintf("can't find block with hash %s\n", hashBlock.GetHex().c_str());
                entry.push_back(Pair("confirmations", 0));
            }
        }

        // check whether any confirmed transaction spends this output
        if (txindex.vSpent[nOutput].IsNull()) {
            // if not, check for an unconfirmed spend
            validateoutputs_check_unconfirmed_spend(outpoint, tx, entry);
            ret.push_back(entry);
            continue;
        }

        entry.push_back(Pair("status", "spent"));

        Object details;
        CTransaction spending_tx;

        // load the transaction that spends this output
        spending_tx.ReadFromDisk(txindex.vSpent[nOutput]);

        details.push_back(Pair("txid", spending_tx.GetHash().GetHex()));

        // find this output's input number in the spending transaction
        int n = 0;
        BOOST_FOREACH(CTxIn input, spending_tx.vin) {
            if (input.prevout == outpoint) {
                details.push_back(Pair("vin", n));
                break;
            }
            n++;
        }

        // get the spending transaction
        if (GetTransaction(uint256(spending_tx.GetHash()), tx, hashBlock, txindex) && hashBlock != 0) {
            map<uint256, CBlockIndex*>::iterator mi = mapBlockIndex.find(hashBlock);
            if (mi != mapBlockIndex.end() && (*mi).second)
            {
                CBlockIndex* pindex = (*mi).second;
                if (pindex->IsInMainChain()) {
                    details.push_back(Pair("height", pindex->nHeight));
                    details.push_back(Pair("confirmations", 1 + nBestHeight - pindex->nHeight));
                } else {
                    LogPrintf("can't find block with hash %s\n", hashBlock.GetHex().c_str());
                    details.push_back(Pair("confirmations", 0));
                }
            }
        }

        entry.push_back(Pair("spent", details));
        ret.push_back(entry);
    }

    return ret;
}

Value validatepubkey(const Array& params, bool fHelp)
{
    if (fHelp || !params.size() || params.size() > 2)
        throw runtime_error(
            "validatepubkey <Clampubkey>\n"
            "Return information about <Clampubkey>.");

    std::vector<unsigned char> vchPubKey = ParseHex(params[0].get_str());
    CPubKey pubKey(vchPubKey);

    bool isValid = pubKey.IsValid();
    bool isCompressed = pubKey.IsCompressed();
    CKeyID keyID = pubKey.GetID();

    CBitcoinAddress address;
    address.Set(keyID);

    Object ret;
    ret.push_back(Pair("isvalid", isValid));
    if (isValid)
    {
        CTxDestination dest = address.Get();
        string currentAddress = address.ToString();
        ret.push_back(Pair("address", currentAddress));
        ret.push_back(Pair("iscompressed", isCompressed));
#ifdef ENABLE_WALLET
        bool fMine = pwalletMain ? IsMine(*pwalletMain, dest) : false;
        ret.push_back(Pair("ismine", fMine));
        if (fMine) {
            Object detail = boost::apply_visitor(DescribeAddressVisitor(), dest);
            ret.insert(ret.end(), detail.begin(), detail.end());
        }
        if (pwalletMain && pwalletMain->mapAddressBook.count(dest))
            ret.push_back(Pair("account", pwalletMain->mapAddressBook[dest]));
#endif
    }
    return ret;
}

Value verifymessage(const Array& params, bool fHelp)
{
    if (fHelp || params.size() != 3)
        throw runtime_error(
            "verifymessage <Clamaddress> <signature> <message>\n"
            "Verify a signed message");

    string strAddress  = params[0].get_str();
    string strSign     = params[1].get_str();
    string strMessage  = params[2].get_str();

    CBitcoinAddress addr(strAddress);
    if (!addr.IsValid())
        throw JSONRPCError(RPC_TYPE_ERROR, "Invalid address");

    CKeyID keyID;
    if (!addr.GetKeyID(keyID))
        throw JSONRPCError(RPC_TYPE_ERROR, "Address does not refer to key");

    bool fInvalid = false;
    vector<unsigned char> vchSig = DecodeBase64(strSign.c_str(), &fInvalid);

    if (fInvalid)
        throw JSONRPCError(RPC_INVALID_ADDRESS_OR_KEY, "Malformed base64 encoding");

    CHashWriter ss(SER_GETHASH, 0);
    ss << strMessageMagic;
    ss << strMessage;

    CPubKey pubkey;
    if (!pubkey.RecoverCompact(ss.GetHash(), vchSig))
        return false;

    return (pubkey.GetID() == keyID);
}

Value setspeech(const Array& params, bool fHelp)
{
    if (fHelp || params.size() != 1)
        throw runtime_error(
            "setspeech <text>\n"
            "Sets the text to be used as the transaction comment when staking or making other transactions.");

    strDefaultSpeech = params[0].get_str();

    LogPrint("speech", "set default speech to \"%s\"\n", strDefaultSpeech);

    return Value::null;
}


// -*- C++ -*-
//
// Copyright Sylvain Bougerel 2009 - 2012.
// Distributed under the Boost Software License, Version 1.0.
// (See accompanying file COPYING or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#define BOOST_TEST_DYN_LINK
#define SPATIAL_ENABLE_ASSERT // detect interal issues that should not occur

#include <boost/test/unit_test.hpp>
#include <utility> // std::make_pair
#include "../../src/idle_point_multimap.hpp"
#include "spatial_test_types.hpp"

using namespace spatial;

BOOST_AUTO_TEST_CASE( test_idle_point_map_constructors )
{
  idle_point_multimap<2, int2, int> idle_points;
  idle_point_multimap<0, int2, int> runtime_idle_points;
}

BOOST_AUTO_TEST_CASE( test_idle_point_map_copy_assignment )
{
  idle_point_multimap<2, int2, int> idle_points;
  idle_points.insert(std::make_pair(zeros, 0));
  idle_points.insert(std::make_pair(ones, 1));
  idle_points.insert(std::make_pair(twos, 2));
  idle_point_multimap<2, int2, int> copy(idle_points);
  BOOST_CHECK_EQUAL(idle_points.size(), copy.size());
  BOOST_CHECK(*idle_points.begin() == *copy.begin());
  idle_points = copy;
  BOOST_CHECK_EQUAL(idle_points.size(), copy.size());
  BOOST_CHECK(*idle_points.begin() == *copy.begin());
}

BOOST_AUTO_TEST_CASE( test_zero_idle_point_map_copy_assignment )
{
  idle_point_multimap<0, int2, int> idle_points;
  idle_points.insert(std::make_pair(zeros, 0));
  idle_points.insert(std::make_pair(ones, 1));
  idle_points.insert(std::make_pair(twos, 2));
  idle_point_multimap<0, int2, int> copy(idle_points);
  BOOST_CHECK_EQUAL(idle_points.size(), copy.size());
  BOOST_CHECK(*idle_points.begin() == *copy.begin());
  idle_points = copy;
  BOOST_CHECK_EQUAL(idle_points.size(), copy.size());
  BOOST_CHECK(*idle_points.begin() == *copy.begin());
}


#ifndef DQN_HPP_
#define DQN_HPP_

#include <memory>
#include <random>
#include <tuple>
#include <unordered_map>
#include <vector>
#include <HFO.hpp>
#include <caffe/caffe.hpp>
#include <boost/functional/hash.hpp>
#include <boost/optional.hpp>
#include <mutex>
#include "hfo_game.hpp"

namespace dqn {

constexpr auto kStateInputCount = 1;
constexpr auto kMinibatchSize = 32;
constexpr auto kActionSize = 4;
constexpr auto kActionParamSize = 6;

constexpr auto kActionInputDataSize = kMinibatchSize * kActionSize;
constexpr auto kActionParamsInputDataSize = kMinibatchSize * kActionParamSize;
constexpr auto kTargetInputDataSize = kMinibatchSize * kActionSize;
constexpr auto kFilterInputDataSize = kMinibatchSize * kActionSize;

using ActorOutput = std::array<float, kActionSize + kActionParamSize>;
using StateData   = std::vector<float>;
using StateDataSp = std::shared_ptr<StateData>;
using InputStates = std::array<StateDataSp, kStateInputCount>;
using Transition  = std::tuple<InputStates, ActorOutput, float,
                               float, boost::optional<StateDataSp>>;
using SolverSp    = std::shared_ptr<caffe::Solver<float>>;
using NetSp       = boost::shared_ptr<caffe::Net<float>>;

// Layer Names
constexpr auto state_input_layer_name         = "state_input_layer";
constexpr auto action_input_layer_name        = "action_input_layer";
constexpr auto action_params_input_layer_name = "action_params_input_layer";
constexpr auto target_input_layer_name        = "target_input_layer";
constexpr auto filter_input_layer_name        = "filter_input_layer";
constexpr auto q_values_layer_name            = "q_values_layer";
// Blob names
constexpr auto states_blob_name        = "states";
constexpr auto actions_blob_name       = "actions";
constexpr auto action_params_blob_name = "action_params";
constexpr auto targets_blob_name       = "target";
constexpr auto filter_blob_name        = "filter";
constexpr auto q_values_blob_name      = "q_values";
constexpr auto loss_blob_name          = "loss";

/**
 * Deep Q-Network
 */
class DQN {
public:
  DQN(caffe::SolverParameter& actor_solver_param,
      caffe::SolverParameter& critic_solver_param,
      std::string save_path, int state_size, int tid);
  ~DQN();

  // Benchmark the speed of updates
  void Benchmark(int iterations=1000);

  // Loading methods
  void RestoreActorSolver(const std::string& actor_solver);
  void RestoreCriticSolver(const std::string& critic_solver);
  void LoadActorWeights(const std::string& actor_model_file);
  void LoadCriticWeights(const std::string& critic_weights);
  void LoadReplayMemory(const std::string& filename);

  // Snapshot the model/solver/replay memory. Produces files:
  // snapshot_prefix_iter_N.[caffemodel|solverstate|replaymem]. Optionally
  // removes snapshots with same prefix but lower iteration.
  void Snapshot();
  void Snapshot(const std::string& snapshot_prefix, bool remove_old=false,
                bool snapshot_memory=true);

  ActorOutput GetRandomActorOutput();

  // Select an action using epsilon-greedy action selection.
  ActorOutput SelectAction(const InputStates& input_states, double epsilon);

  // Select a batch of actions using epsilon-greedy action selection.
  std::vector<ActorOutput> SelectActions(const std::vector<InputStates>& states_batch,
                                         double epsilon);

  // Converts an ActorOutput into an action by samping over discrete actions
  Action SampleAction(const ActorOutput& actor_output);

  // Evaluate a state-action, returning the q-value.
  float EvaluateAction(const InputStates& input_states, const ActorOutput& action);

  // Add a transition to replay memory
  void AddTransition(const Transition& transition);
  void AddTransitions(const std::vector<Transition>& transitions);

  // Computes a tabular Q-Value for each transition
  void LabelTransitions(std::vector<Transition>& transitions);

  // Update the model(s)
  void Update();

  // Clear the replay memory
  void ClearReplayMemory() { replay_memory_->clear(); }

  // Save the replay memory to a gzipped compressed file
  void SnapshotReplayMemory(const std::string& filename);

  // Get the current size of the replay memory
  int memory_size() const { return replay_memory_->size(); }

  // Share the parameters in a layer. Owner keeps the params, slave loses them
  void ShareLayer(caffe::Layer<float>& param_owner,
                  caffe::Layer<float>& param_slave);

  // Share parameters between DQNs
  void ShareParameters(DQN& other,
                       int num_actor_layers_to_share,
                       int num_critic_layers_to_share);

  // Free's the replay memory of other, which now points to our own replay mem
  void ShareReplayMemory(DQN& other);

  // Return the current iteration of the solvers
  int min_iter() const { return std::min(actor_iter(), critic_iter()); }
  int max_iter() const { return std::max(actor_iter(), critic_iter()); }
  int critic_iter() const { return critic_solver_->iter(); }
  int actor_iter() const { return actor_solver_->iter(); }
  int state_size() const { return state_size_; }
  const std::string& save_path() const { return save_path_; }
  int unum() const { return unum_; }
  void set_unum(int unum) { unum_ = unum; }

protected:
  // Initialize DQN. Called by the constructor
  void Initialize();

  // Update both the actor and critic.
  std::pair<float, float> UpdateActorCritic();

  // Randomly sample the replay memory n-times, returning transition indexes
  std::vector<int> SampleTransitionsFromMemory(int n);
  // Randomly sample the replay memory n-times returning input_states
  std::vector<InputStates> SampleStatesFromMemory(int n);

  // Clone the network and store the result in clone_net_
  void CloneNet(NetSp& net_from, NetSp& net_to);
  // Update the parameters of net_to towards net_from.
  // net_to = tau * net_from + (1 - tau) * net_to
  void SoftUpdateNet(NetSp& net_from, NetSp& net_to, float tau);

  // Given input states, use the actor network to select an action.
  ActorOutput SelectActionGreedily(caffe::Net<float>& actor,
                                   const InputStates& last_states);

  // Given a batch of input states, return a batch of selected actions.
  std::vector<ActorOutput> SelectActionGreedily(
      caffe::Net<float>& actor,
      const std::vector<InputStates>& states_batch);

  // Runs forward on critic to produce q-values. Actions inferred by actor.
  std::vector<float> CriticForwardThroughActor(
      caffe::Net<float>& critic, caffe::Net<float>& actor,
      const std::vector<InputStates>& states_batch);

  // Runs forward on critic to produce q-values.
  std::vector<float> CriticForward(caffe::Net<float>& critic,
                                   const std::vector<InputStates>& states_batch,
                                   const std::vector<ActorOutput>& action_batch);

  // Input data into the State/Target/Filter layers of the given
  // net. This must be done before forward is called.
  void InputDataIntoLayers(caffe::Net<float>& net,
                           float* states_input,
                           float* actions_input,
                           float* action_params_input,
                           float* target_input,
                           float* filter_input);

protected:
  caffe::SolverParameter actor_solver_param_;
  caffe::SolverParameter critic_solver_param_;
  const int replay_memory_capacity_;
  const double gamma_;
  std::shared_ptr<std::deque<Transition> > replay_memory_;
  SolverSp actor_solver_;
  NetSp actor_net_; // The actor network used for continuous action evaluation.
  SolverSp critic_solver_;
  NetSp critic_net_;  // The critic network used for giving q-value of a continuous action;
  NetSp critic_target_net_; // Clone of critic net. Used to generate targets.
  NetSp actor_target_net_; // Clone of the actor net. Used to generate targets.
  std::mt19937 random_engine;
  float smoothed_critic_loss_, smoothed_actor_loss_;
  int last_snapshot_iter_;
  std::string save_path_;
  const int state_size_; // Number of state features
  const int state_input_data_size_;
  int tid_;
  int unum_;
};

caffe::NetParameter CreateActorNet(int state_size);
caffe::NetParameter CreateCriticNet(int state_size);

/**
 * Converts an ActorOutput into an action by maxing over discrete actions
 */
Action GetAction(const ActorOutput& actor_output);

/**
 * Returns a vector of filenames matching a given regular expression.
 */
std::vector<std::string> FilesMatchingRegexp(const std::string& regexp);

// Removes all files matching a given regular expression
void RemoveFilesMatchingRegexp(const std::string& regexp);

/**
 * Removes snapshots matching regexp that have an iteration less than
 * min_iter.
 */
void RemoveSnapshots(const std::string& regexp, int min_iter);

/**
 * Look for the latest snapshot to resume from. Returns a string
 * containing the path to the .solverstate. Returns empty string if
 * none is found. Will only return if the snapshot contains all of:
 * .solverstate,.caffemodel,.replaymemory
 */
void FindLatestSnapshot(const std::string& snapshot_prefix,
                        std::string& actor_snapshot,
                        std::string& critic_snapshot,
                        std::string& memory_snapshot);

/**
 * Look for the best HiScore matching the given snapshot prefix
 */
int FindHiScore(const std::string& snapshot_prefix);

std::string PrintActorOutput(const ActorOutput& actor_output);

} // namespace dqn

#endif /* DQN_HPP_ */