/*
// Copyright (c) 2020 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
*/
#include "password.hpp"

#include "xyz/openbmc_project/BIOSConfig/Common/error.hpp"
#include "xyz/openbmc_project/Common/error.hpp"

#include <boost/algorithm/hex.hpp>
#include <boost/asio.hpp>
#include <phosphor-logging/elog-errors.hpp>
#include <sdbusplus/asio/connection.hpp>
#include <sdbusplus/asio/object_server.hpp>

#include <fstream>
#include <iostream>

namespace bios_config_pwd
{
using namespace sdbusplus::xyz::openbmc_project::Common::Error;
using namespace sdbusplus::xyz::openbmc_project::BIOSConfig::Common::Error;

bool Password::isMatch(const std::array<uint8_t, maxHashSize>& expected,
                       const std::array<uint8_t, maxSeedSize>& seed,
                       const std::string rawData, const std::string algo)
{
    phosphor::logging::log<phosphor::logging::level::ERR>("isMatch");

    if (algo == "SHA256")
    {
        std::vector<uint8_t> output(SHA256_DIGEST_LENGTH);
        unsigned int hashLen = SHA256_DIGEST_LENGTH;

        if (!PKCS5_PBKDF2_HMAC(
                reinterpret_cast<const char*>(rawData.c_str()),
                rawData.length() + 1,
                reinterpret_cast<const unsigned char*>(seed.data()),
                seed.size(), iterValue, EVP_sha256(), hashLen, output.data()))
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                "Generate PKCS5_PBKDF2_HMAC_SHA256 Integrity Check Value "
                "failed");
            throw InternalFailure();
        }

        int cmp;
        cmp = std::memcmp(output.data(), expected.data(),
                          output.size() * sizeof(uint8_t));
        if (cmp == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }
    if (algo == "SHA384")
    {
        std::array<uint8_t, SHA384_DIGEST_LENGTH> output;
        unsigned int hashLen = SHA384_DIGEST_LENGTH;

        if (!PKCS5_PBKDF2_HMAC(
                reinterpret_cast<const char*>(rawData.c_str()),
                rawData.length() + 1,
                reinterpret_cast<const unsigned char*>(seed.data()),
                seed.size(), iterValue, EVP_sha384(), hashLen, output.data()))
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(
                "Generate PKCS5_PBKDF2_HMAC_SHA384 Integrity Check Value "
                "failed");
            throw InternalFailure();
        }

        int cmp;
        cmp = std::memcmp(output.data(), expected.data(),
                          output.size() * sizeof(uint8_t));
        if (cmp == 0)
        {
            return true;
        }
        else
        {
            return false;
        }
    }

    return false;
}

void Password::verifyPassword(std::string userName, std::string currentPassword,
                              std::string newPassword)
{
    if (fs::exists(seedFile.c_str()))
    {
        std::array<uint8_t, maxHashSize> orgUsrPwdHash;
        std::array<uint8_t, maxHashSize> orgAdminPwdHash;
        std::array<uint8_t, maxSeedSize> seed;
        std::string hashAlgo = "";
        try
        {
            nlohmann::json json = nullptr;
            std::ifstream ifs(seedFile.c_str());
            if (ifs.is_open())
            {
                try
                {
                    json = nlohmann::json::parse(ifs, nullptr, false);
                }
                catch (const nlohmann::json::parse_error& e)
                {
                    phosphor::logging::log<phosphor::logging::level::ERR>(
                        e.what());
                    throw InternalFailure();
                }

                if (json.is_discarded())
                {
                    return;
                }
                orgUsrPwdHash = json["UserPwdHash"];
                orgAdminPwdHash = json["AdminPwdHash"];
                seed = json["Seed"];
                hashAlgo = json["HashAlgo"];
            }
            else
            {
                return;
            }
        }
        catch (nlohmann::detail::exception& e)
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
            throw InternalFailure();
        }
        if (userName == "AdminPassword")
        {
            if (!isMatch(orgAdminPwdHash, seed, currentPassword, hashAlgo))
            {
                throw InvalidCurrentPassword();
            }
        }
        else
        {
            if (!isMatch(orgUsrPwdHash, seed, currentPassword, hashAlgo))
            {
                throw InvalidCurrentPassword();
            }
        }
        if (hashAlgo == "SHA256")
        {
            unsigned int mdLen = 32;
            mNewPwdHash.fill(0);

            if (!PKCS5_PBKDF2_HMAC(
                    reinterpret_cast<const char*>(newPassword.c_str()),
                    newPassword.length() + 1,
                    reinterpret_cast<const unsigned char*>(seed.data()),
                    seed.size(), iterValue, EVP_sha256(), mdLen,
                    mNewPwdHash.data()))
            {
                phosphor::logging::log<phosphor::logging::level::ERR>(
                    "Verify PKCS5_PBKDF2_HMAC_SHA256 Integrity Check failed");
                throw InternalFailure();
            }
        }
        if (hashAlgo == "SHA384")
        {
            unsigned int mdLen = 48;
            mNewPwdHash.fill(0);

            if (!PKCS5_PBKDF2_HMAC(
                    reinterpret_cast<const char*>(newPassword.c_str()),
                    newPassword.length() + 1,
                    reinterpret_cast<const unsigned char*>(seed.data()),
                    seed.size(), iterValue, EVP_sha384(), mdLen,
                    mNewPwdHash.data()))
            {
                phosphor::logging::log<phosphor::logging::level::ERR>(
                    "Verify PKCS5_PBKDF2_HMAC_SHA384 Integrity Check failed");
                throw InternalFailure();
            }
        }
        return;
    }
    throw InternalFailure();
}
void Password::changePassword(std::string userName, std::string currentPassword,
                              std::string newPassword)
{
    phosphor::logging::log<phosphor::logging::level::DEBUG>(
        "BIOS config changePassword");
    verifyPassword(userName, currentPassword, newPassword);

    std::ifstream fs(seedFile.c_str());
    nlohmann::json json = nullptr;

    if (fs.is_open())
    {
        try
        {
            json = nlohmann::json::parse(fs, nullptr, false);
        }
        catch (const nlohmann::json::parse_error& e)
        {
            phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
            throw InternalFailure();
        }

        if (json.is_discarded())
        {
            throw InternalFailure();
        }
        json["AdminPwdHash"] = mNewPwdHash;
        json["IsAdminPwdChanged"] = true;

        std::ofstream ofs(seedFile.c_str(), std::ios::out);
        const auto& writeData = json.dump();
        ofs << writeData;
        ofs.close();
    }
    else
    {
        phosphor::logging::log<phosphor::logging::level::DEBUG>(
            "Cannot open file stream");
        throw InternalFailure();
    }
}
Password::Password(sdbusplus::asio::object_server& objectServer,
                   std::shared_ptr<sdbusplus::asio::connection>& systemBus) :
    sdbusplus::xyz::openbmc_project::BIOSConfig::server::Password(
        *systemBus, objectPathPwd),
    objServer(objectServer), systemBus(systemBus)
{
    phosphor::logging::log<phosphor::logging::level::DEBUG>(
        "BIOS config password is runing");
    try
    {
        fs::path biosDir(BIOS_PERSIST_PATH);
        fs::create_directories(biosDir);
        seedFile = biosDir / biosSeedFile;
    }
    catch (const fs::filesystem_error& e)
    {
        phosphor::logging::log<phosphor::logging::level::ERR>(e.what());
        throw InternalFailure();
    }
}

} // namespace bios_config_pwd

int main()
{
    boost::asio::io_service io;
    auto systemBus = std::make_shared<sdbusplus::asio::connection>(io);

    systemBus->request_name(bios_config_pwd::servicePwd);
    sdbusplus::asio::object_server objectServer(systemBus);
    bios_config_pwd::Password password(objectServer, systemBus);

    io.run();
    return 0;
}


#include <cctbx/boost_python/flex_fwd.h>

#include <boost/python.hpp>
#include <scitbx/array_family/boost_python/flex_wrapper.h>
#include <scitbx/array_family/boost_python/shared_wrapper.h>

#include <mmtbx/tls/optimise_amplitudes.h>

namespace mmtbx { namespace tls { namespace optimise {
  namespace bp = boost::python;
  namespace af = scitbx::af;

namespace {
  void init_module()
  {
    using namespace boost::python;
    using boost::python::arg;

    class_<MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator>(
        "MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator",
        init< symArrNd const&,
              dblArrNd const&,
              dblArrNd const&,
              bp::list const&,
              bp::list const&,
              selArr1d const&,
              symArr1d const& >(
                (arg("target_uijs"),
                 arg("target_weights"),
                 arg("base_amplitudes"),
                 arg("base_uijs"),
                 arg("base_atom_indices"),
                 arg("dataset_hash"),
                 arg("residual_uijs"))
                )
              )
      .def("set_current_amplitudes", &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::setCurrentAmplitudes)
      .def("get_current_amplitudes", &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::getCurrentAmplitudes)
      .def("print_current_amplitudes", &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::printCurrentAmplitudes)
      .add_property("x",
          &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::getCurrentAmplitudes,
          &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::setCurrentAmplitudes
          )
      .def("set_residual_mask",
          &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::setResidualMask,
          ( args("mask") ),
          "Select which datasets are used to optimise the residual levels (if any)"
          )
      .def("compute_functional_and_gradients",
          &MultiGroupMultiDatasetUijAmplitudeFunctionalAndGradientCalculator::computeFunctionalAndGradients
          //return_value_policy<manage_new_object>()
          )
    ;
  }

} // Close unnamed

}}} // close mmtbx::tls::optimise

BOOST_PYTHON_MODULE(mmtbx_tls_optimise_amplitudes_ext)
{
  mmtbx::tls::optimise::init_module();
}


#include "rrgenerator.hpp"
#include <boost/noncopyable.hpp>
#include <sys/types.h>
#include <unistd.h>
#include <cstdlib>
#include <sstream>

namespace dns
{
    /**********************************************************
     * RandomGenarator
     **********************************************************/
    RandomGenerator *RandomGenerator::mInstance = nullptr;

    RandomGenerator::RandomGenerator()
        : mGenerator( static_cast<unsigned long>(time(nullptr)) )
    {
        std::srand( getpid() * time( nullptr ) );
    }

    uint32_t RandomGenerator::rand( uint32_t base )
    {
        if ( base == 0 )
            return 0;

        boost::mutex::scoped_lock lock( mMutex );
        boost::uniform_smallint<> dst( 0, base);
        uint32_t v = dst( mGenerator );
        return v;
    }

    PacketData RandomGenerator::randStream( unsigned int size )
    {
        PacketData stream;
	stream.reserve( size );
        for ( unsigned int i = 0 ; i < size ; i++ )
            stream.push_back( this->rand( 0xff ) );
	return stream;
    }

    PacketData RandomGenerator::randSizeStream( unsigned int max_size )
    {
        unsigned int size = rand( max_size );
        PacketData stream;
	stream.reserve( size );
        for ( unsigned int i = 0 ; i < size ; i++ )
            stream.push_back( this->rand( 0xff ) );
	return stream;
    }

    RandomGenerator *RandomGenerator::getInstance()
    {
	if ( mInstance == nullptr )
	    mInstance = new RandomGenerator();
	return mInstance;
    }

    /**********************************************************
     * DomainnameGenarator
     **********************************************************/
    std::string DomainnameGenerator::generateLabel()
    {
        std::string label;
        if ( getRandom( 37 ) == 0 )
	    return "*";

        unsigned int label_size = 1 + getRandom( 62 );
	label.reserve( label_size );
        for ( unsigned int i = 0 ; i < label_size ; i++ )
            label.push_back( getRandom( 0xff ) );
        return label;
    }

    Domainname DomainnameGenerator::generate()
    {
        unsigned int label_count = 1 + getRandom( 100 );
        unsigned int domainname_size = 0;
        std::deque<std::string> labels;
        for ( unsigned int i = 0 ; i < label_count ; i++ ) {
            auto label = generateLabel();
            if ( domainname_size + label.size() + 1 >= 255 )
                break;
            labels.push_back( label );
            domainname_size += ( label.size() + 1 );
        }
        return Domainname( labels );
    }

    Domainname DomainnameGenerator::generate( const Domainname &hint1, const Domainname &hint2 )
    {
        Domainname hint = hint1;
        Domainname result = hint1;
        if ( hint2 != "" && getRandom( 2 ) == 0 ) {
            hint   = hint2;
            result = hint2;
        }

        switch ( getRandom( 3 ) ) {
        case 0:
            return result;
        case 1: // erase labels;
            {
                unsigned int erased_label_count = getRandom( hint.getLabels().size() );
                for ( unsigned int i = 0 ; i < erased_label_count ; i++ ) {
                    result.popSubdomain();
                }

                return result;
            }
        case 2: // append labels as subdomain;
            {
                unsigned int label_count        = hint.getLabels().size();
                unsigned int append_label_count = getRandom( 255 - hint.getLabels().size() );
                unsigned int domainname_size    = hint.size();
                for ( unsigned int i = 0 ; i < append_label_count ; i++ ) {
                    std::string new_label = generateLabel();
                    if ( label_count + 1 >= 128 || domainname_size + new_label.size() + 1 >= 255 )
                        break;
                    result.addSubdomain( new_label );
                    domainname_size += ( new_label.size() + 1 );
                    label_count++;
                }

                return result;
            }
        case 3: // replace labels;
            {
                unsigned int erased_label_count = getRandom( hint.getLabels().size() );
                for ( unsigned int i = 0 ; i < erased_label_count ; i++ ) {
                    result.popSubdomain();
                }

                unsigned int label_count        = result.getLabels().size();
                unsigned int append_label_count = getRandom( 255 - result.getLabels().size() );
                unsigned int domainname_size    = result.size();
                for ( unsigned int i = 0 ; i < append_label_count ; i++ ) {
                    std::string new_label = generateLabel();
                    if ( label_count + 1 >= 128 || domainname_size + new_label.size() + 1 >= 255 )
                        break;
                    result.addSubdomain( new_label );
                    domainname_size += ( new_label.size() + 1 );
                    label_count++;
                }

                return result;
            }
        default:
            throw std::logic_error( "generate domainname error" );
        }
    }

    static Domainname generateDomainname( const Domainname &hint1, const Domainname &hint2 = Domainname() )
    {
        DomainnameGenerator g;
        return g.generate( hint1, hint2 );
    }

    Domainname generateDomainname()
    {
        DomainnameGenerator g;
        return g.generate();
    }

    Domainname getDomainname( const MessageInfo &hint )
    {
        std::vector<Domainname> names;
        for ( auto rr : hint.getQuestionSection() ) {
            names.push_back( rr.mDomainname );
        }
        for ( auto rr : hint.getAnswerSection() ) {
            names.push_back( rr.mDomainname );
        }
        for ( auto rr : hint.getAuthoritySection() ) {
            names.push_back( rr.mDomainname );
        }
        for ( auto rr : hint.getAdditionalSection() ) {
            names.push_back( rr.mDomainname );
        }

        unsigned int index = getRandom( names.size() - 1 );
        return names.at( index );
    }

    Domainname generateAlgorithmName()
    {
	if ( withChance( 0.7 ) ) {
	    const char *algorithms[] = {
					"gss-tsig",
					"HMAC-MD5.SIG-ALG.REG.INT",
					"hmac-sha1",
					"hmac-sha224",
					"hmac-sha256",
					"hmac-sha384",
					"hmac-sha512",
	    };

	    return (Domainname)algorithms[ getRandom( sizeof(algorithms)/sizeof(char *) - 1 ) ];
	}
	else {
	    return generateDomainname();
	}
    }

    /**********************************************************
     * XNAMEGenarator
     **********************************************************/
    template<class T>
    std::shared_ptr<RDATA> XNameGenerator<T>::generate( const MessageInfo &hint, const Domainname &hint2 )
    {
        Domainname hint_name;
        uint32_t qdcount = hint.getQuestionSection().size();
        uint32_t ancount = hint.getAnswerSection().size();
        uint32_t nscount = hint.getAuthoritySection().size();
        uint32_t adcount = hint.getAdditionalSection().size();

        uint32_t index = getRandom( qdcount + ancount + nscount + adcount - 1 );
        if ( index < qdcount ) {
            hint_name = hint.getQuestionSection().at( index ).mDomainname;
        }
        else if ( index < qdcount + ancount ) {
            hint_name = hint.getAnswerSection().at( index - qdcount ).mDomainname;
        }
        else if ( index < qdcount + ancount + nscount ) {
            hint_name = hint.getAuthoritySection().at( index - qdcount - ancount ).mDomainname;
        }
        else if ( index < qdcount + ancount + nscount + adcount ) {
            hint_name = hint.getAdditionalSection().at( index - qdcount - ancount - nscount ).mDomainname;
        }
        else {
            throw std::logic_error( "invalid index of XNameGenerator::generate( hint )" );
        }

        return std::shared_ptr<RDATA>( new T( DomainnameGenerator().generate( hint_name, hint2 ) ) );
    }

    template<class T>
    std::shared_ptr<RDATA> XNameGenerator<T>::generate()
    {
        return std::shared_ptr<RDATA>( new T( DomainnameGenerator().generate() ) );
    }

    /**********************************************************
     * RAWGenarator
     **********************************************************/
    std::shared_ptr<RDATA> RawGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	return generate();
    }

    std::shared_ptr<RDATA> RawGenerator::generate()
    {
        return std::shared_ptr<RDATA>( new RecordRaw( getRandom( 0x3ff ), getRandomSizeStream( 0xff ) ) );
    }

    /**********************************************************
     * AGenarator
     **********************************************************/
    std::shared_ptr<RDATA> AGenerator::generate( const MessageInfo &hint, const Domainname &hint2 )
    {
        std::vector<std::shared_ptr<RDATA> > record_a_list;
        for ( auto rr : hint.getAnswerSection() ) {
            if ( rr.mType == TYPE_A ) {
                record_a_list.push_back( rr.mRData );
            }
        }
        for ( auto rr : hint.getAuthoritySection() ) {
            if ( rr.mType == TYPE_A ) {
                record_a_list.push_back( rr.mRData );
            }
        }
        for ( auto rr : hint.getAdditionalSection() ) {
            if ( rr.mType == TYPE_A ) {
                record_a_list.push_back( rr.mRData );
            }
        }

        if ( record_a_list.size() == 0 )
            return generate();

        unsigned int index = getRandom( record_a_list.size() - 1 );
	return std::shared_ptr<RDATA>( record_a_list.at( index )->clone() );
    }

    std::shared_ptr<RDATA> AGenerator::generate()
    {
        return std::shared_ptr<RDATA>( new RecordA( getRandom() ) );
    }

    /**********************************************************
     * WKSGenarator
     **********************************************************/
    std::shared_ptr<RDATA> WKSGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	return generate();
    }

    std::shared_ptr<RDATA> WKSGenerator::generate()
    {
        std::vector<Type> bitmap;
        if ( getRandom( 32 ) == 0 )
            bitmap.resize( 0 );
        else if ( getRandom( 32 ) == 0 ) {
            bitmap.resize( 256 * 256 );
            for ( unsigned int i = 0 ; i < bitmap.size() ; i++ )
                bitmap[i] = i;
        }
        else {
            bitmap.resize( getRandom( 0xffff ) );
            for ( unsigned int i = 0 ; i < bitmap.size() ; i++ )
                bitmap[i] = getRandom( 0xffff);
        }

        return std::shared_ptr<RDATA>( new RecordWKS( getRandom(), getRandom( 255 ), bitmap ) );
    }

    /**********************************************************
     * AAAAGenarator
     **********************************************************/
    std::shared_ptr<RDATA> AAAAGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        std::vector<std::shared_ptr<RDATA> > record_a_list;
        for ( auto rr : hint1.getAnswerSection() ) {
            if ( rr.mType == TYPE_AAAA ) {
                record_a_list.push_back( rr.mRData );
            }
        }
        for ( auto rr : hint1.getAuthoritySection() ) {
            if ( rr.mType == TYPE_AAAA ) {
                record_a_list.push_back( rr.mRData );
            }
        }
        for ( auto rr : hint1.getAdditionalSection() ) {
            if ( rr.mType == TYPE_AAAA ) {
                record_a_list.push_back( rr.mRData );
            }
        }

        if ( record_a_list.size() == 0 )
            return generate();

        unsigned int index = getRandom( record_a_list.size() - 1 );
	return std::shared_ptr<RDATA>( record_a_list.at( index )->clone() );
    }

    std::shared_ptr<RDATA> AAAAGenerator::generate()
    {
        PacketData sin_addr = getRandomStream( 16 );
        return std::shared_ptr<RDATA>( new RecordAAAA( &sin_addr[0] ) );
    }

    /**********************************************************
     * SOAGenarator
     **********************************************************/
    std::shared_ptr<RDATA> SOAGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	return std::shared_ptr<RDATA>( new RecordSOA( getDomainname( hint1 ),
						      getDomainname( hint1 ),
						      getRandom(),
						      getRandom(),
						      getRandom(),
						      getRandom(),
						      getRandom() ) );
    }

    std::shared_ptr<RDATA> SOAGenerator::generate()
    {
	return std::shared_ptr<RDATA>( new RecordSOA( generateDomainname(),
						      generateDomainname(),
						      getRandom(),
						      getRandom(),
						      getRandom(),
						      getRandom(),
						      getRandom() ));
    }

    /**********************************************************
     * SRVGenarator
     **********************************************************/
    std::shared_ptr<RDATA> SRVGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	return std::shared_ptr<RDATA>( new RecordSRV( getRandom( 0xffff ),
						      getRandom( 0xffff ),
						      getRandom( 0xffff ),
						      getDomainname( hint1 ) ) );
    }

    std::shared_ptr<RDATA> SRVGenerator::generate()
    {
	return std::shared_ptr<RDATA>( new RecordSRV( getRandom( 0xffff ),
						      getRandom( 0xffff ),
						      getRandom( 0xffff ),
						      generateDomainname() ) );
    }

    /**********************************************************
     * RRSIGGenarator
     **********************************************************/
    std::shared_ptr<RDATA> RRSIGGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        PacketData signature = getRandomSizeStream( 0xff );

	std::shared_ptr<RDATA> p( new RecordRRSIG( getRandom( 0xffff ), // type covered
						   getRandom( 0xff ),   // algorithm
						   getRandom( 0xff ),   // label
						   getRandom(),         // original ttl
						   getRandom(),         // expiration
						   getRandom(),         // inception
						   getRandom( 0xffff ), // key tag
						   generateDomainname( getDomainname( hint1 ), hint2 ),
						   signature ) );
        return p;
    }

    std::shared_ptr<RDATA> RRSIGGenerator::generate()
    {
        PacketData signature = getRandomSizeStream( 0xff );
	return std::shared_ptr<RDATA>( new RecordRRSIG( getRandom( 0xffff ), // type covered
							getRandom( 0xff ),   // algorithm
							getRandom( 0xff ),   // label
							getRandom(),         // original ttl
							getRandom(),         // expiration
							getRandom(),         // inception
							getRandom( 0xffff ), // key tag
							generateDomainname(),
							signature ) );
    }

    /**********************************************************
     * DNSKEYGenarator
     **********************************************************/
    std::shared_ptr<RDATA> DNSKEYGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        PacketData public_key = getRandomStream( 132 );
	return std::shared_ptr<RDATA>( new RecordDNSKEY( getRandom() % 2 ? RecordDNSKEY::KSK : RecordDNSKEY::ZSK,
							 RecordDNSKEY::RSASHA1,
							 public_key ) );
    }

    std::shared_ptr<RDATA> DNSKEYGenerator::generate()
    {
        PacketData public_key = getRandomStream( 132 );
	return std::shared_ptr<RDATA>( new RecordDNSKEY( getRandom() % 2 ? RecordDNSKEY::KSK : RecordDNSKEY::ZSK,
							 RecordDNSKEY::RSASHA1,
							 public_key ) );
    }

    /**********************************************************
     * DSGenarator
     **********************************************************/
    std::shared_ptr<RDATA> DSGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        if ( getRandom( 2 ) ) {
            PacketData hash = getRandomStream( 20 );
            return std::shared_ptr<RDATA>( new RecordDS( getRandom( 0xffff ),
							 5,
							 1,
							 hash ) );
        }
        else {
            PacketData hash = getRandomStream( 32 );
            return std::shared_ptr<RDATA>( new RecordDS( getRandom( 0xffff ),
							 5,
							 2,
							 hash ) );
        }
    }

    std::shared_ptr<RDATA> DSGenerator::generate()
    {
        if ( getRandom( 2 ) ) {
            PacketData hash = getRandomStream( 40 );
            return std::shared_ptr<RDATA>( new RecordDS( getRandom( 0xffff ),
							 5,
							 1,
							 hash ) );
        }
        else {
            PacketData hash = getRandomStream( 64 );
            return std::shared_ptr<RDATA>( new RecordDS( getRandom( 0xffff ),
							 5,
							 2,
							 hash ) );
        }
    }

    /**********************************************************
     * NSECGenarator
     **********************************************************/
    std::shared_ptr<RDATA> NSECGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        std::vector<Type> types;
        unsigned int type_count = getRandom( 4 );
	types.reserve( type_count );
        for ( unsigned int i = 0 ; i < type_count ; i++ ) {
            types.push_back( getRandom( 0xffff ) );
        }

        return std::shared_ptr<RDATA>( new RecordNSEC( generateDomainname( getDomainname( hint1 ), hint2 ),
						       types ) );
    }

    std::shared_ptr<RDATA> NSECGenerator::generate()
    {
        std::vector<Type> types;
        unsigned int type_count = getRandom( 0xffff );
	types.reserve( type_count );
        for ( unsigned int i = 0 ; i < type_count ; i++ ) {
            types.push_back( getRandom( 0xffff ) );
        }

        return std::shared_ptr<RDATA>( new RecordNSEC( generateDomainname(), types ) );
    }

    /**********************************************************
     * NSEC3Genarator
     **********************************************************/
    std::shared_ptr<RDATA> NSEC3Generator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        return generate();
    }

    std::shared_ptr<RDATA> NSEC3Generator::generate()
    {
        uint8_t optout = 0x07;
        if ( getRandom( 8 ) ) {
            optout = 0;
        }
        std::vector<Type> types;
        unsigned int type_count = getRandom( 4 );
        for ( unsigned int i = 0 ; i < type_count ; i++ ) {
            types.push_back( getRandom( 0xffff ) );
        }

        return std::shared_ptr<RDATA>( new RecordNSEC3( 0x01,
                                                        optout,
                                                        getRandom( 0x00ff ),
                                                        getRandomSizeStream( 0xff ),
                                                        getRandomStream( 20 ),
                                                        types ) );
    }

    /**********************************************************
     * NSEC3PARAMGenarator
     **********************************************************/
    std::shared_ptr<RDATA> NSEC3PARAMGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        return generate();
    }

    std::shared_ptr<RDATA> NSEC3PARAMGenerator::generate()
    {
        uint8_t optout = 0x07;
        if ( getRandom( 8 ) ) {
            optout = 0;
        }

        return std::shared_ptr<RDATA>( new RecordNSEC3PARAM( 0x01,
                                                             optout,
                                                             getRandom( 0x00ff ),
                                                             getRandomSizeStream( 0xff ) ) );
    }

    /**********************************************************
     * TLSAGenarator
     **********************************************************/
    std::shared_ptr<RDATA> TLSAGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        return generate();
    }

    std::shared_ptr<RDATA> TLSAGenerator::generate()
    {
        return std::shared_ptr<RDATA>( new RecordTLSA( getRandom( 0xff ),
                                                       getRandom( 0xff ),
                                                       getRandom( 0xff ),
                                                       getRandomSizeStream( 0x01ff ) ) );
    }

    /**********************************************************
     * SIGGenarator
     **********************************************************/
    std::shared_ptr<RDATA> SIGGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        PacketData signature = getRandomStream( 256 );

	std::shared_ptr<RDATA> p( new RecordSIG( getRandom( 0xffff ), // type covered
						 getRandom( 0xff ),   // algorithm
						 getRandom( 0xff ),   // label
						 getRandom(),         // original ttl
						 getRandom(),         // expiration
						 getRandom(),         // inception
						 getRandom( 0xffff ), // key tag
						 generateDomainname( getDomainname( hint1 ), hint2 ),
						 signature ) );
        return p;
    }

    std::shared_ptr<RDATA> SIGGenerator::generate()
    {
        PacketData signature = getRandomSizeStream( 256 );
	return std::shared_ptr<RDATA>( new RecordSIG( getRandom( 0xffff ), // type covered
						      getRandom( 0xff ),   // algorithm
						      getRandom( 0xff ),   // label
						      getRandom(),         // original ttl
						      getRandom(),         // expiration
						      getRandom(),         // inception
						      getRandom( 0xffff ), // key tag
						      generateDomainname(),
						      signature ) );
    }

    /**********************************************************
     * KEYGenarator
     **********************************************************/
    std::shared_ptr<RDATA> KEYGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        PacketData public_key = getRandomStream( 132 );
	return std::shared_ptr<RDATA>( new RecordKEY( 0xffff,
						      RecordDNSKEY::RSASHA1,
						      public_key ) );
    }

    std::shared_ptr<RDATA> KEYGenerator::generate()
    {
        PacketData public_key = getRandomStream( 132 );
	return std::shared_ptr<RDATA>( new RecordKEY( getRandom( 0xffff ),
						      RecordDNSKEY::RSASHA1,
						      public_key ) );
    }

    /**********************************************************
     * NXTGenarator
     **********************************************************/
    std::shared_ptr<RDATA> NXTGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        std::vector<Type> types;
        unsigned int type_count = getRandom( 0xffff );
	types.reserve( type_count );
        for ( unsigned int i = 0 ; i < type_count ; i++ ) {
            types.push_back( getRandom( 0xffff ) );
        }

        return std::shared_ptr<RDATA>( new RecordNXT( generateDomainname( getDomainname( hint1 ), hint2 ),
						      types ) );
    }

    std::shared_ptr<RDATA> NXTGenerator::generate()
    {
        std::vector<Type> types;
        unsigned int type_count = getRandom( 0xffff );
	types.reserve( type_count );
        for ( unsigned int i = 0 ; i < type_count ; i++ ) {
            types.push_back( getRandom( 0xffff ) );
        }

        return std::shared_ptr<RDATA>( new RecordNXT( generateDomainname(), types ) );
    }

    /**********************************************************
     * TKEYGenarator
     **********************************************************/
    std::shared_ptr<RDATA> TKEYGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	PacketData signature = getRandomSizeStream( 0xff );
	PacketData other     = getRandomSizeStream( 0xff );

	std::shared_ptr<RDATA> p( new RecordTKEY( generateDomainname( getDomainname( hint1 ), hint2 ), // domain
						  generateAlgorithmName(),  // algorithm
						  getRandom(),         // inception
						  getRandom(),         // expiration
						  getRandom( 0xff ),
						  getRandom( 0xff ),
						  signature,
                                                  other) );
        return p;
    }

    std::shared_ptr<RDATA> TKEYGenerator::generate()
    {
	PacketData signature = getRandomSizeStream( 0xff );
	PacketData other     = getRandomSizeStream( 0xff );

	std::shared_ptr<RDATA> p( new RecordTKEY( generateDomainname(), // domain
						  generateAlgorithmName(),  // algorithm
						  getRandom(),          // inception
						  getRandom(),          // expiration
						  getRandom(),
						  getRandom(),
						  signature ) );
        return p;
    }

    /**********************************************************
     * TSIGGenarator
     **********************************************************/
    std::shared_ptr<RDATA> TSIGGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
	PacketData signature = getRandomStream( 16 );
	uint64_t signed_time = (uint64_t)getRandom() + (((uint64_t)getRandom() ) << 32 );

	PacketData other = getRandomSizeStream( 0xff );

	return std::shared_ptr<RDATA>( new RecordTSIGData( generateDomainname( getDomainname( hint1 ), hint2 ), // domain
                                                           generateAlgorithmName(),  // algorithm
                                                           signed_time,              // signed time
                                                           getRandom( 0xffff ),      // fudge
                                                           signature,                // mac
                                                           getRandom( 0xffff ),      // original id
                                                           getRandom( 0xffff ),      // error
                                                           other ) );
    }

    std::shared_ptr<RDATA> TSIGGenerator::generate()
    {
	PacketData signature = getRandomStream( 16 );
	uint64_t signed_time = (uint64_t)getRandom() + (((uint64_t)getRandom() ) << 32 );

	PacketData other = getRandomSizeStream( 0xff );

	return std::shared_ptr<RDATA>( new RecordTSIGData( generateDomainname(),     // domain
                                                           generateAlgorithmName(),  // algorithm
                                                           signed_time,              // signed time
                                                           getRandom( 0xffff ),      // fudge
                                                           signature,                // mac
                                                           getRandom( 0xffff ),      // original id
                                                           getRandom( 0xffff ),      // error
                                                           other ) );
    }

    /**********************************************************
     * ResourceRecordGenarator
     **********************************************************/
    ResourceRecordGenerator::ResourceRecordGenerator()
    {
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new RawGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new NSGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new CNAMEGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new DNAMEGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new AGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new AAAAGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new WKSGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new SOAGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new SRVGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new RRSIGGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new DNSKEYGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new DSGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new NSECGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new NSEC3Generator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new NSEC3PARAMGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new TLSAGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new SIGGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new KEYGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new NXTGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new TKEYGenerator ) );
        mGenerators.push_back( std::shared_ptr<RDATAGeneratable>( new TSIGGenerator ) );
    }

    RRSet ResourceRecordGenerator::generate( const MessageInfo &hint1, const Domainname &hint2 )
    {
        Class class_table[] = { CLASS_IN, CLASS_CH, CLASS_HS, CLASS_NONE, CLASS_ANY };

        std::shared_ptr<RDATA> resource_data = mGenerators.at( getRandom( mGenerators.size() - 1 ) )->generate( hint1, hint2 );

        Domainname owner;
        if ( resource_data->type() == TYPE_NSEC3 ) {
            std::string hash;
            encodeToBase32Hex( getRandomStream( 20 ), hash );
            owner = getDomainname( hint1  );
            owner.addSubdomain( hash );
        }
        else {
            owner = generateDomainname( getDomainname( hint1 ), hint2 );
        }

        unsigned int index = getRandom( sizeof(class_table)/sizeof(Class) - 1 );
        if ( index >= sizeof(class_table)/sizeof(Class) ) {
            throw std::logic_error( "invalid class index" );
        }
        RRSet rrset( owner,
                     class_table[index],
                     resource_data->type(),
                     getRandom( 0xffffffff ) );
        rrset.add( resource_data );

        return rrset;
    }

    std::shared_ptr<OptPseudoRROption> RawOptionGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> RawOptionGenerator::generate()
    {
 	return std::shared_ptr<OptPseudoRROption>( new RAWOption( getRandom( 0x0f ), getRandomSizeStream( 0xff ) ) );
    }

    std::shared_ptr<OptPseudoRROption> NSIDGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> NSIDGenerator::generate()
    {
	ssize_t length = getRandom( 0xff );
	std::string data;
	data.reserve( length );
	for ( ssize_t i = 0 ; i < length ; i++ )
	    data.push_back( getRandom( 0xff ) );
	return std::shared_ptr<OptPseudoRROption>( new NSIDOption( data ) );
    }

    std::shared_ptr<OptPseudoRROption> ClientSubnetGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> ClientSubnetGenerator::generate()
    {
	if ( getRandom( 2 ) ) {
	    std::ostringstream os;
	    os << getRandom( 0xff ) << "." << getRandom( 0xff ) << "." << getRandom( 0xff ) << getRandom( 0xff );
	    return std::shared_ptr<OptPseudoRROption>( new ClientSubnetOption( ClientSubnetOption::IPv4,
									       getRandom( 32 ),
									       getRandom( 32 ),
									       os.str() ) );
	}
	else {
	    std::ostringstream os;
	    os << std::hex << getRandom( 0xff );
	    for ( int i = 0 ; i < 15 ; i++ )
		os << ":" << getRandom( 0xff );
	    return std::shared_ptr<OptPseudoRROption>( new ClientSubnetOption( ClientSubnetOption::IPv6,
									       getRandom( 128 ),
									       getRandom( 128 ),
									       os.str() ) );
	}
    }

    std::shared_ptr<OptPseudoRROption> CookieGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> CookieGenerator::generate()
    {
        PacketData client, server;
        unsigned int client_length = getRandom( 64 );
        unsigned int server_length = getRandom( 64 );

	client.reserve( client_length );
	server.reserve( server_length );
        for ( unsigned int i = 0 ; i < client_length ; i++ )
            client.push_back( getRandom( 0xff ) );
        for ( unsigned int i = 0 ; i < server_length ; i++ )
            server.push_back( getRandom( 0xff ) );

        return std::shared_ptr<OptPseudoRROption>( new CookieOption( client, server ) );
    }

    std::shared_ptr<OptPseudoRROption> TCPKeepaliveGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> TCPKeepaliveGenerator::generate()
    {
        uint16_t timeout = 0;
        if ( getRandom( 4 ) ) {
            timeout = getRandom( 0xffff );
        }
        return std::shared_ptr<OptPseudoRROption>( new TCPKeepaliveOption( timeout ) );
    }

    std::shared_ptr<OptPseudoRROption> KeyTagGenerator::generate( const MessageInfo &hint )
    {
	return generate();
    }

    std::shared_ptr<OptPseudoRROption> KeyTagGenerator::generate()
    {
        uint16_t count = getRandom( 0x0fff );
        std::vector<uint16_t> tags;
	tags.reserve( count );
        for ( uint16_t i = 0 ; i < count ; i++ )
            tags.push_back( getRandom( 0xffff ) );
        return std::shared_ptr<OptPseudoRROption>( new KeyTagOption( tags ) );
    }

    /**********************************************************
     * OptionGenarator
     **********************************************************/
    OptionGenerator::OptionGenerator()
    {
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new RawOptionGenerator ) );
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new NSIDGenerator ) );
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new ClientSubnetGenerator ) );
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new CookieGenerator ) );
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new TCPKeepaliveGenerator ) );
        mGenerators.push_back( std::shared_ptr<OptGeneratable>( new KeyTagGenerator ) );
    }

    void OptionGenerator::generate( MessageInfo &packet )
    {
	if ( ! packet.isEDNS0() )
	    return;

        std::shared_ptr<OptPseudoRROption> option = mGenerators.at( getRandom( mGenerators.size() - 1 ) )->generate( packet );
	packet.addOption( option );
    }
}


#include <boost/multiprecision/complex_adaptor.hpp>
#include <boost/multiprecision/cpp_bin_float.hpp>
#include <boost/multiprecision/cpp_complex.hpp>
#include <boost/multiprecision/cpp_dec_float.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <boost/multiprecision/debug_adaptor.hpp>
#include <boost/multiprecision/eigen.hpp>
#include <boost/multiprecision/integer.hpp>
#include <boost/multiprecision/logged_adaptor.hpp>
#include <boost/multiprecision/miller_rabin.hpp>
#include <boost/multiprecision/number.hpp>
#include <boost/multiprecision/rational_adaptor.hpp>

int
main ()
{
  return 0;
}


// Boost.Units - A C++ library for zero-overhead dimensional analysis and
// unit/quantity manipulation and conversion
//
// Copyright (C) 2003-2008 Matthias Christian Schabel
// Copyright (C) 2008 Steven Watanabe
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)

#include <boost/units/systems/si/length.hpp>
#include <boost/units/systems/cgs/length.hpp>
#include <boost/units/quantity.hpp>

void foo()
{
    boost::units::quantity<boost::units::si::dimensionless> d(1.0 * boost::units::si::meters / boost::units::cgs::centimeters);
}

#include <boost/test/test_tools.hpp>

int main()
{
  BOOST_CHECK( 1 == 2 );
}


#include "RevGeocoder.h"
#include "FeatureReader.h"
#include "ProjUtils.h"
#include "AddressInterpolator.h"

#include <functional>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>

#include <sqlite3pp.h>

namespace carto { namespace geocoding {
    bool RevGeocoder::import(const std::shared_ptr<sqlite3pp::database>& db) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        Database database;
        database.id = "db" + std::to_string(_databases.size());
        database.db = db;
        database.bounds = getBounds(*db);
        database.origin = getOrigin(*db);
        _databases.push_back(database);
        return true;
    }

    std::string RevGeocoder::getLanguage() const {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _language;
    }

    void RevGeocoder::setLanguage(const std::string& language) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _language = language;
        _addressCache.clear();
    }

    unsigned int RevGeocoder::getMaxResults() const {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return _maxResults;
    }

    void RevGeocoder::setMaxResults(unsigned int maxResults) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        _maxResults = maxResults;
    }

    bool RevGeocoder::isFilterEnabled(Address::EntityType type) const {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        return std::find(_enabledFilters.begin(), _enabledFilters.end(), type) != _enabledFilters.end();
    }

    void RevGeocoder::setFilterEnabled(Address::EntityType type, bool enabled) {
        std::lock_guard<std::recursive_mutex> lock(_mutex);
        auto it = std::find(_enabledFilters.begin(), _enabledFilters.end(), type);
        if (enabled && it == _enabledFilters.end()) {
            _enabledFilters.push_back(type);
        }
        else if (!enabled && it != _enabledFilters.end()) {
            _enabledFilters.erase(it);
        }
    }

    std::vector<std::pair<Address, float>> RevGeocoder::findAddresses(double lng, double lat, float radius) const {
        std::lock_guard<std::recursive_mutex> lock(_mutex);

        std::vector<std::pair<Address, float>> addresses;
        for (const Database& database : _databases) {
            if (database.bounds) {
                // TODO: -180/180 wrapping
                cglib::vec2<double> lngLatMeters = wgs84Meters({ lng, lat });
                cglib::vec2<double> point = database.bounds->nearest_point({ lng, lat });
                cglib::vec2<double> diff = point - cglib::vec2<double>(lng, lat);
                double dist = cglib::length(cglib::vec2<double>(diff(0) * lngLatMeters(0), diff(1) * lngLatMeters(1)));
                if (dist > radius) {
                    continue;
                }
            }

            _previousEntityQueryCounter = _entityQueryCounter;
            QuadIndex index(std::bind(&RevGeocoder::findGeometryInfo, this, std::cref(database), std::placeholders::_1, std::placeholders::_2));
            std::vector<QuadIndex::Result> results = index.findGeometries(lng, lat, radius);

            for (const QuadIndex::Result& result : results) {
                float rank = 1.0f - static_cast<float>(result.second) / radius;
                if (rank > 0) {
                    Address address;
                    std::string addrKey = database.id + std::string(1, 0) + std::to_string(result.first);
                    if (!_addressCache.read(addrKey, address)) {
                        address.loadFromDB(*database.db, result.first, _language, [&database](const cglib::vec2<double>& pos) {
                            return database.origin + pos;
                        });
                        _addressCache.put(addrKey, address);
                    }
                    addresses.emplace_back(address, rank);
                }
            }
        }

        std::sort(addresses.begin(), addresses.end(), [](const std::pair<Address, float>& addrRank1, const std::pair<Address, float>& addrRank2) {
            return addrRank1.second > addrRank2.second;
        });

        if (addresses.size() > _maxResults) {
            addresses.erase(addresses.begin() + _maxResults, addresses.end());
        }

        return addresses;
    }

    std::vector<QuadIndex::GeometryInfo> RevGeocoder::findGeometryInfo(const Database& database, const std::vector<std::uint64_t>& quadIndices, const PointConverter& converter) const {
        std::string sql = "SELECT id, features, housenumbers FROM entities WHERE quadindex in (";
        for (std::size_t i = 0; i < quadIndices.size(); i++) {
            sql += (i > 0 ? "," : "") + std::to_string(quadIndices[i]);
        }
        sql += ")";
        if (!_enabledFilters.empty()) {
            std::string values;
            for (const Address::EntityType type : _enabledFilters) {
                values += (values.empty() ? "" : ",") + std::to_string(static_cast<int>(type));
            }
            sql += " AND (type IN (" + values + "))";
        }

        std::vector<QuadIndex::GeometryInfo> geomInfos;
        std::string queryKey = database.id + std::string(1, 0) + sql;
        if (_queryCache.read(queryKey, geomInfos)) {
            return geomInfos;
        }

        sqlite3pp::query query(*database.db, sql.c_str());
        for (auto qit = query.begin(); qit != query.end(); qit++) {
            auto entityId = qit->get<unsigned int>(0);

            EncodingStream featureStream(qit->get<const void*>(1), qit->column_bytes(1));
            FeatureReader featureReader(featureStream, [&database, &converter](const cglib::vec2<double>& pos) {
                return converter(database.origin + pos);
            });

            if (qit->get<const void*>(2)) {
                EncodingStream houseNumberStream(qit->get<const void*>(2), qit->column_bytes(2));
                AddressInterpolator interpolator(houseNumberStream);

                std::vector<std::pair<std::uint64_t, std::vector<Feature>>> idFeatures = interpolator.readAddressesAndFeatures(featureReader);
                for (std::size_t i = 0; i < idFeatures.size(); i++) {
                    std::uint64_t encodedId = (idFeatures[i].first ? static_cast<std::uint64_t>(i + 1) << 32 : 0) | entityId;
                    std::vector<std::shared_ptr<Geometry>> geometries;
                    for (const Feature& feature : idFeatures[i].second) {
                        if (feature.getGeometry()) {
                            geometries.push_back(feature.getGeometry());
                        }
                    }
                    geomInfos.emplace_back(encodedId, std::make_shared<MultiGeometry>(std::move(geometries)));
                }
            }
            else {
                std::vector<std::shared_ptr<Geometry>> geometries;
                for (const Feature& feature : featureReader.readFeatureCollection()) {
                    if (feature.getGeometry()) {
                        geometries.push_back(feature.getGeometry());
                    }
                }
                geomInfos.emplace_back(entityId, std::make_shared<MultiGeometry>(std::move(geometries)));
            }
        }

        _entityQueryCounter++;
        _queryCache.put(queryKey, geomInfos);
        return geomInfos;
    }

    cglib::vec2<double> RevGeocoder::getOrigin(sqlite3pp::database& db) {
        sqlite3pp::query query(db, "SELECT value FROM metadata WHERE name='origin'");
        for (auto qit = query.begin(); qit != query.end(); qit++) {
            std::string value = qit->get<const char*>(0);

            std::vector<std::string> origin;
            boost::split(origin, value, boost::is_any_of(","), boost::token_compress_off);
            return cglib::vec2<double>(std::stod(origin.at(0)), std::stod(origin.at(1)));
        }
        return cglib::vec2<double>(0, 0);
    }

    std::optional<cglib::bbox2<double>> RevGeocoder::getBounds(sqlite3pp::database& db) {
        sqlite3pp::query query(db, "SELECT value FROM metadata WHERE name='bounds'");
        for (auto qit = query.begin(); qit != query.end(); qit++) {
            std::string value = qit->get<const char*>(0);

            std::vector<std::string> bounds;
            boost::split(bounds, value, boost::is_any_of(","), boost::token_compress_off);
            cglib::vec2<double> min(std::stod(bounds.at(0)), std::stod(bounds.at(1)));
            cglib::vec2<double> max(std::stod(bounds.at(2)), std::stod(bounds.at(3)));
            return cglib::bbox2<double>(min, max);
        }
        return std::optional<cglib::bbox2<double>>();
    }
} }