//---------------------------------------------------------------------------//
// Copyright (c) 2018-2021 Mikhail Komarov <nemo@nil.foundation>
// Copyright (c) 2020-2021 Ilias Khairullin <ilias@nil.foundation>
//
// MIT License
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// 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 AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//---------------------------------------------------------------------------//

#ifndef CRYPTO3_PUBKEY_BLS_HPP
#define CRYPTO3_PUBKEY_BLS_HPP

#include <map>
#include <vector>
#include <iterator>
#include <type_traits>
#include <utility>
#include <functional>

#include <boost/assert.hpp>
#include <boost/concept_check.hpp>

#include <boost/range/concepts.hpp>

#include <boost/mpl/vector.hpp>

#include <nil/crypto3/detail/stream_endian.hpp>

#include <nil/crypto3/algebra/curves/bls12.hpp>
#include <nil/crypto3/algebra/pairing/bls12.hpp>

#include <nil/crypto3/hash/sha2.hpp>

#include <nil/crypto3/pubkey/detail/bls/bls_basic_policy.hpp>
#include <nil/crypto3/pubkey/detail/bls/bls_basic_functions.hpp>
#include <nil/crypto3/pubkey/keys/private_key.hpp>
#include <nil/crypto3/pubkey/operations/aggregate_op.hpp>
#include <nil/crypto3/pubkey/operations/aggregate_verify_op.hpp>
#include <nil/crypto3/pubkey/operations/aggregate_verify_single_msg_op.hpp>

namespace nil {
    namespace crypto3 {
        namespace pubkey {
            /*!
             * @brief Basic BLS Scheme
             * @tparam SignatureVersion
             * @tparam BlsParams
             * @see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.1
             */
            template<typename SignatureVersion>
            struct bls_basic_scheme {
                typedef SignatureVersion signature_version;
                typedef typename signature_version::basic_functions basic_functions;

                typedef typename basic_functions::private_key_type private_key_type;
                typedef typename basic_functions::public_key_type public_key_type;
                typedef typename basic_functions::signature_type signature_type;

                typedef typename basic_functions::internal_accumulator_type internal_accumulator_type;
                typedef typename basic_functions::internal_aggregation_accumulator_type
                    internal_aggregation_accumulator_type;

                static inline public_key_type generate_public_key(const private_key_type &privkey) {
                    return basic_functions::privkey_to_pubkey(privkey);
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const private_key_type &privkey) {
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const public_key_type &pubkey) {
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    basic_functions::update(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    basic_functions::update(acc, first, last);
                }

                static inline signature_type sign(internal_accumulator_type &acc, const private_key_type &privkey) {
                    return basic_functions::sign(acc, privkey);
                }

                static inline bool verify(internal_accumulator_type &acc, const public_key_type &pubkey,
                                          const signature_type &sig) {
                    return basic_functions::verify(acc, pubkey, sig);
                }

                template<typename SignatureRange>
                static inline void update_aggregate(signature_type &acc, const SignatureRange &signatures) {
                    basic_functions::aggregate(acc, signatures);
                }

                template<typename SignatureIterator>
                static inline void update_aggregate(signature_type &acc, SignatureIterator sig_first,
                                                    SignatureIterator sig_last) {
                    basic_functions::aggregate(acc, sig_first, sig_last);
                }

                static inline bool aggregate_verify(internal_aggregation_accumulator_type &acc,
                                                    const signature_type &signature) {
                    // TODO: add check - If any two input messages are equal, return INVALID.
                    return basic_functions::aggregate_verify(acc, signature);
                }
            };

            /*!
             * @brief
             * @tparam SignatureVersion
             * @tparam BlsParams
             * @see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.2
             */
            template<typename SignatureVersion>
            struct bls_aug_scheme {
                typedef SignatureVersion signature_version;
                typedef typename signature_version::basic_functions basic_functions;

                typedef typename basic_functions::private_key_type private_key_type;
                typedef typename basic_functions::public_key_type public_key_type;
                typedef typename basic_functions::signature_type signature_type;

                typedef typename basic_functions::internal_accumulator_type internal_accumulator_type;
                typedef typename basic_functions::internal_aggregation_accumulator_type
                    internal_aggregation_accumulator_type;

                static inline public_key_type generate_public_key(const private_key_type &privkey) {
                    return basic_functions::privkey_to_pubkey(privkey);
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const private_key_type &privkey) {
                    init_accumulator(acc, generate_public_key(privkey));
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const public_key_type &pubkey) {
                    basic_functions::update(acc, basic_functions::point_to_pubkey(pubkey));
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    basic_functions::update(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    basic_functions::update(acc, first, last);
                }

                static inline signature_type sign(internal_accumulator_type &acc, const private_key_type &privkey) {
                    return basic_functions::sign(acc, privkey);
                }

                static inline bool verify(internal_accumulator_type &acc, const public_key_type &pubkey,
                                          const signature_type &sig) {
                    return basic_functions::verify(acc, pubkey, sig);
                }

                template<typename SignatureRange>
                static inline void update_aggregate(signature_type &acc, const SignatureRange &signatures) {
                    basic_functions::aggregate(acc, signatures);
                }

                template<typename SignatureIterator>
                static inline void update_aggregate(signature_type &acc, SignatureIterator sig_first,
                                                    SignatureIterator sig_last) {
                    basic_functions::aggregate(acc, sig_first, sig_last);
                }

                static inline bool aggregate_verify(internal_aggregation_accumulator_type &acc,
                                                    const signature_type &signature) {
                    return basic_functions::aggregate_verify(acc, signature);
                }
            };

            /*!
             * @brief Proof of possession BLS Scheme
             * @tparam SignatureVersion
             * @tparam BlsParams
             * @see https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-04#section-3.3
             */
            template<typename SignatureVersion>
            struct bls_pop_scheme {
                typedef SignatureVersion signature_version;
                typedef typename signature_version::basic_functions basic_functions;

                typedef typename basic_functions::private_key_type private_key_type;
                typedef typename basic_functions::public_key_type public_key_type;
                typedef typename basic_functions::signature_type signature_type;

                typedef typename basic_functions::internal_accumulator_type internal_accumulator_type;
                typedef typename basic_functions::internal_aggregation_accumulator_type
                    internal_aggregation_accumulator_type;
                typedef typename basic_functions::internal_fast_aggregation_accumulator_type
                    internal_fast_aggregation_accumulator_type;

                static inline public_key_type generate_public_key(const private_key_type &privkey) {
                    return basic_functions::privkey_to_pubkey(privkey);
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const private_key_type &privkey) {
                }

                static inline void init_accumulator(internal_accumulator_type &acc, const public_key_type &pubkey) {
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    basic_functions::update(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    basic_functions::update(acc, first, last);
                }

                static inline signature_type sign(internal_accumulator_type &acc, const private_key_type &privkey) {
                    return basic_functions::sign(acc, privkey);
                }

                static inline bool verify(internal_accumulator_type &acc, const public_key_type &pubkey,
                                          const signature_type &sig) {
                    return basic_functions::verify(acc, pubkey, sig);
                }

                template<typename SignatureRange>
                static inline void update_aggregate(signature_type &acc, const SignatureRange &signatures) {
                    basic_functions::aggregate(acc, signatures);
                }

                template<typename SignatureIterator>
                static inline void update_aggregate(signature_type &acc, SignatureIterator sig_first,
                                                    SignatureIterator sig_last) {
                    basic_functions::aggregate(acc, sig_first, sig_last);
                }

                static inline bool aggregate_verify(internal_aggregation_accumulator_type &acc,
                                                    const signature_type &signature) {
                    return basic_functions::aggregate_verify(acc, signature);
                }

                static inline bool aggregate_verify(internal_fast_aggregation_accumulator_type &acc,
                                                    const signature_type &signature) {
                    return basic_functions::aggregate_verify(acc, signature);
                }

                static inline signature_type pop_prove(const private_key_type &privkey) {
                    return basic_functions::pop_prove(privkey);
                }

                static inline bool pop_verify(const public_key_type &pubkey, const signature_type &proof) {
                    return basic_functions::pop_verify(pubkey, proof);
                }
            };

            //
            // Minimal-signature-size
            // Random oracle version of hash-to-point
            //
            template<typename PublicParams, typename CurveType = algebra::curves::bls12_381>
            struct bls_mss_ro_version {
                typedef detail::bls_mss_ro_policy<PublicParams, CurveType> policy_type;
                typedef detail::bls_basic_functions<policy_type> basic_functions;
            };

            //
            // Minimal-pubkey-size
            // Random oracle version of hash-to-point
            //
            template<typename PublicParams, typename CurveType = algebra::curves::bls12_381>
            struct bls_mps_ro_version {
                typedef detail::bls_mps_ro_policy<PublicParams, CurveType> policy_type;
                typedef detail::bls_basic_functions<policy_type> basic_functions;
            };

            template<hashes::UniformityCount _uniformity_count = hashes::UniformityCount::uniform_count,
                     hashes::ExpandMsgVariant _expand_msg_variant = hashes::ExpandMsgVariant::rfc_xmd>
            struct bls_default_public_params {
                constexpr static hashes::UniformityCount uniformity_count = _uniformity_count;
                constexpr static hashes::ExpandMsgVariant expand_msg_variant = _expand_msg_variant;

                // "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_NUL_"
                typedef std::array<std::uint8_t, 43> dst_type;
                static constexpr dst_type dst = {0x42, 0x4c, 0x53, 0x5f, 0x53, 0x49, 0x47, 0x5f, 0x42, 0x4c, 0x53,
                                                 0x31, 0x32, 0x33, 0x38, 0x31, 0x47, 0x32, 0x5f, 0x58, 0x4d, 0x44,
                                                 0x3a, 0x53, 0x48, 0x41, 0x2d, 0x32, 0x35, 0x36, 0x5f, 0x53, 0x53,
                                                 0x57, 0x55, 0x5f, 0x52, 0x4f, 0x5f, 0x4e, 0x55, 0x4c, 0x5f};
            };

            template<hashes::UniformityCount _uniformity_count = hashes::UniformityCount::uniform_count,
                     hashes::ExpandMsgVariant _expand_msg_variant = hashes::ExpandMsgVariant::rfc_xmd>
            struct bls_pop_prove_default_public_params {
                constexpr static hashes::UniformityCount uniformity_count = _uniformity_count;
                constexpr static hashes::ExpandMsgVariant expand_msg_variant = _expand_msg_variant;

                typedef std::vector<std::uint8_t> dst_type;
                static inline dst_type dst = []() {
                    const std::string _dst_str = "BLS_POP_BLS12381G1_XMD:SHA-256_SSWU_RO_POP_";
                    const std::vector<std::uint8_t> _dst(_dst_str.begin(), _dst_str.end());
                    return _dst;
                }();
            };

            template<hashes::UniformityCount _uniformity_count = hashes::UniformityCount::uniform_count,
                     hashes::ExpandMsgVariant _expand_msg_variant = hashes::ExpandMsgVariant::rfc_xmd>
            struct bls_pop_sign_default_public_params {
                constexpr static hashes::UniformityCount uniformity_count = _uniformity_count;
                constexpr static hashes::ExpandMsgVariant expand_msg_variant = _expand_msg_variant;

                typedef std::vector<std::uint8_t> dst_type;
                static inline dst_type dst = []() {
                    const std::string _dst_str = "BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_";
                    const std::vector<std::uint8_t> _dst(_dst_str.begin(), _dst_str.end());
                    return _dst;
                }();
            };

            template<typename PublicParams = bls_default_public_params<>,
                     template<typename, typename> class BlsVersion = bls_mss_ro_version,
                     template<typename> class BlsScheme = bls_basic_scheme,
                     typename CurveType = algebra::curves::bls12_381>
            struct bls {
                typedef bls<PublicParams, BlsVersion, BlsScheme, CurveType> self_type;
                typedef BlsVersion<PublicParams, CurveType> bls_version_type;
                typedef BlsScheme<bls_version_type> bls_scheme_type;

                typedef public_key<self_type> public_key_type;
                typedef private_key<self_type> private_key_type;
                typedef aggregate_op<self_type> aggregate_op_policy;
                typedef aggregate_verify_op<self_type> aggregate_verify_op_policy;
            };

            template<typename PublicParams, template<typename, typename> class BlsVersion,
                     template<typename> class BlsScheme, typename CurveType>
            struct public_key<bls<PublicParams, BlsVersion, BlsScheme, CurveType>> {
                typedef bls<PublicParams, BlsVersion, BlsScheme, CurveType> scheme_type;
                typedef typename scheme_type::bls_scheme_type bls_scheme_type;

                typedef typename bls_scheme_type::private_key_type private_key_type;
                typedef typename bls_scheme_type::public_key_type public_key_type;
                typedef typename bls_scheme_type::signature_type signature_type;

                typedef typename public_key_type::group_type public_key_group_type;
                typedef typename signature_type::group_type signature_group_type;

                typedef typename bls_scheme_type::internal_accumulator_type internal_accumulator_type;

                typedef public_key_type key_type;

                public_key() = delete;
                public_key(const key_type &pubkey) : pubkey(pubkey) {
                }

                inline void init_accumulator(internal_accumulator_type &acc) const {
                    bls_scheme_type::init_accumulator(acc, pubkey);
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    bls_scheme_type::update(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    bls_scheme_type::update(acc, first, last);
                }

                inline bool verify(internal_accumulator_type &acc, const signature_type &sig) const {
                    return bls_scheme_type::verify(acc, pubkey, sig);
                }

                inline public_key_type public_key_data() const {
                    return pubkey;
                }

                // TODO: refactor pop
                template<typename FakeAccumulator>
                inline bool pop_verify(FakeAccumulator, const signature_type &proof) const {
                    return bls_scheme_type::pop_verify(pubkey, proof);
                }

                // FIXME: copy pubkey between equivalent public keys is a bottleneck
                // TODO: support using of the same pubkey even if scheme policy differs in public params and scheme type
                template<typename ToPublicParams, template<typename> class ToBlsScheme>
                operator public_key<bls<ToPublicParams, BlsVersion, ToBlsScheme, CurveType>>() const {
                    return public_key<bls<ToPublicParams, BlsVersion, BlsScheme, CurveType>>(pubkey);
                }

            protected:
                public_key_type pubkey;
            };

            template<typename PublicParams, template<typename, typename> class BlsVersion,
                     template<typename> class BlsScheme, typename CurveType>
            struct private_key<bls<PublicParams, BlsVersion, BlsScheme, CurveType>>
                : public public_key<bls<PublicParams, BlsVersion, BlsScheme, CurveType>> {
                typedef bls<PublicParams, BlsVersion, BlsScheme, CurveType> scheme_type;
                typedef typename scheme_type::bls_scheme_type bls_scheme_type;
                typedef public_key<scheme_type> base_type;

                typedef typename base_type::private_key_type private_key_type;
                typedef typename base_type::public_key_type public_key_type;
                typedef typename base_type::signature_type signature_type;

                typedef typename bls_scheme_type::internal_accumulator_type internal_accumulator_type;

                typedef private_key_type key_type;

                private_key() = delete;
                private_key(const key_type &privkey) :
                    privkey(privkey), base_type(bls_scheme_type::generate_public_key(privkey)) {
                }

                inline void init_accumulator(internal_accumulator_type &acc) const {
                    bls_scheme_type::init_accumulator(acc, privkey);
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    bls_scheme_type::update(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    bls_scheme_type::update(acc, first, last);
                }

                inline signature_type sign(internal_accumulator_type &acc) const {
                    return bls_scheme_type::sign(acc, privkey);
                }

                inline signature_type pop_prove() const {
                    return bls_scheme_type::pop_prove(privkey);
                }

                // FIXME: copy privkey between equivalent private keys is a bottleneck
                // TODO: support using of the same privkey even if scheme policy differs in public params and scheme
                //  type
                template<typename ToPublicParams, template<typename> class ToBlsScheme>
                operator private_key<bls<ToPublicParams, BlsVersion, ToBlsScheme, CurveType>>() const {
                    return private_key<bls<ToPublicParams, BlsVersion, BlsScheme, CurveType>>(privkey);
                }

            protected:
                private_key_type privkey;
            };

            template<typename PublicParams, template<typename, typename> class BlsVersion,
                     template<typename> class BlsScheme, typename CurveType>
            struct aggregate_op<bls<PublicParams, BlsVersion, BlsScheme, CurveType>> {
                typedef bls<PublicParams, BlsVersion, BlsScheme, CurveType> scheme_type;
                typedef typename scheme_type::bls_scheme_type bls_scheme_type;

                typedef typename bls_scheme_type::private_key_type private_key_type;
                typedef typename bls_scheme_type::public_key_type public_key_type;
                typedef typename bls_scheme_type::signature_type signature_type;

                typedef signature_type internal_accumulator_type;
                typedef signature_type result_type;

                static inline void init_accumulator(internal_accumulator_type &acc) {
                    acc = signature_type::zero();
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const InputRange &range) {
                    bls_scheme_type::update_aggregate(acc, range);
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    bls_scheme_type::update_aggregate(acc, first, last);
                }

                static inline result_type process(internal_accumulator_type &acc) {
                    return acc;
                }
            };

            template<typename PublicParams, template<typename, typename> class BlsVersion,
                     template<typename> class BlsScheme, typename CurveType>
            struct aggregate_verify_op<bls<PublicParams, BlsVersion, BlsScheme, CurveType>> {
                typedef bls<PublicParams, BlsVersion, BlsScheme, CurveType> scheme_type;
                typedef typename scheme_type::bls_scheme_type bls_scheme_type;
                typedef public_key<scheme_type> scheme_public_key_type;

                typedef typename bls_scheme_type::private_key_type private_key_type;
                typedef typename bls_scheme_type::public_key_type public_key_type;
                typedef typename bls_scheme_type::signature_type signature_type;

                typedef typename bls_scheme_type::internal_accumulator_type _internal_accumulator_type;
                typedef typename bls_scheme_type::internal_aggregation_accumulator_type
                    _internal_aggregation_accumulator_type;
                typedef _internal_aggregation_accumulator_type internal_accumulator_type;
                typedef bool result_type;

                static inline void init_accumulator(internal_accumulator_type &acc) {
                }

                template<typename InputIterator>
                static inline void update(internal_accumulator_type &acc, const scheme_public_key_type &scheme_pubkey,
                                          InputIterator first, InputIterator last) {
                    auto index = get_public_key_index(acc, scheme_pubkey);
                    bls_scheme_type::update(acc.second[index], first, last);
                }

                template<typename InputRange>
                static inline void update(internal_accumulator_type &acc, const scheme_public_key_type &scheme_pubkey,
                                          const InputRange &range) {
                    auto index = get_public_key_index(acc, scheme_pubkey);
                    bls_scheme_type::update(acc.second[index], range);
                }

                static inline result_type process(internal_accumulator_type &acc, const signature_type &sig) {
                    return bls_scheme_type::aggregate_verify(acc, sig);
                }

            private:
                static inline std::size_t get_public_key_index(internal_accumulator_type &acc,
                                                               const scheme_public_key_type &scheme_pubkey) {
                    assert(std::distance(std::cbegin(acc.first), std::cend(acc.first)) ==
                           std::distance(std::cbegin(acc.second), std::cend(acc.second)));

                    auto found_pos_it =
                        std::find(std::cbegin(acc.first), std::cend(acc.first), scheme_pubkey.public_key_data());

                    if (std::cend(acc.first) == found_pos_it) {
                        acc.first.push_back(scheme_pubkey.public_key_data());
                        acc.second.push_back(_internal_accumulator_type());
                        bls_scheme_type::init_accumulator(acc.second.back(), acc.first.back());
                        return std::size(acc.first) - 1;
                    }

                    return std::distance(std::cbegin(acc.first), found_pos_it);
                }
            };

            template<typename PublicParams, template<typename, typename> class BlsVersion, typename CurveType>
            struct aggregate_verify_single_msg_op<bls<PublicParams, BlsVersion, bls_pop_scheme, CurveType>> {
                typedef bls<PublicParams, BlsVersion, bls_pop_scheme, CurveType> scheme_type;
                typedef typename scheme_type::bls_scheme_type bls_scheme_type;
                typedef public_key<scheme_type> scheme_public_key_type;

                typedef typename bls_scheme_type::private_key_type private_key_type;
                typedef typename bls_scheme_type::public_key_type public_key_type;
                typedef typename bls_scheme_type::signature_type signature_type;

                typedef typename bls_scheme_type::internal_accumulator_type _internal_accumulator_type;
                typedef typename bls_scheme_type::internal_fast_aggregation_accumulator_type
                    _internal_fast_aggregation_accumulator_type;
                typedef _internal_fast_aggregation_accumulator_type internal_accumulator_type;
                typedef bool result_type;

                static inline void init_accumulator(internal_accumulator_type &acc) {
                }

                template<typename InputIterator>
                static inline typename std::enable_if<!std::is_convertible<
                    typename std::iterator_traits<InputIterator>::value_type, scheme_public_key_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    bls_scheme_type::update(acc.second, first, last);
                }

                template<typename InputRange>
                static inline typename std::enable_if<
                    !std::is_convertible<typename std::iterator_traits<typename InputRange::iterator>::value_type,
                                         scheme_public_key_type>::value>::type
                    update(internal_accumulator_type &acc, const InputRange &range) {
                    bls_scheme_type::update(acc.second, range);
                }

                template<typename InputIterator>
                static inline typename std::enable_if<std::is_convertible<
                    typename std::iterator_traits<InputIterator>::value_type, scheme_public_key_type>::value>::type
                    update(internal_accumulator_type &acc, InputIterator first, InputIterator last) {
                    for (auto iter = first; iter != last; ++iter) {
                        update(acc, *iter);
                    }
                }

                template<typename InputRange>
                static inline typename std::enable_if<
                    std::is_convertible<typename std::iterator_traits<typename InputRange::iterator>::value_type,
                                        scheme_public_key_type>::value>::type
                    update(internal_accumulator_type &acc, const InputRange &range) {
                    for (const auto &scheme_pubkey : range) {
                        update(acc, scheme_pubkey);
                    }
                }

                static inline void update(internal_accumulator_type &acc, const scheme_public_key_type &scheme_pubkey) {
                    auto found_pos_it =
                        std::find(std::cbegin(acc.first), std::cend(acc.first), scheme_pubkey.public_key_data());
                    if (std::cend(acc.first) == found_pos_it) {
                        acc.first.push_back(scheme_pubkey.public_key_data());
                    }
                }

                static inline result_type process(internal_accumulator_type &acc, const signature_type &sig) {
                    return bls_scheme_type::aggregate_verify(acc, sig);
                }
            };
        }    // namespace pubkey
    }        // namespace crypto3
}    // namespace nil

#endif    // CRYPTO3_PUBKEY_BLS_HPP


#include <Eigen/Core>
#include <functional>
#include "ear/common/helpers/make_unique.hpp"
#include "ear/dsp/ptr_adapter.hpp"
#include "ear/dsp/variable_block_size.hpp"
#include "ear/helpers/assert.hpp"

namespace ear {
  namespace dsp {
    /// Adapt something that processes fixed-size blocks of samples into one
    /// that processes variable sized blocks by adding some delay.
    ///
    /// This can be used with e.g. BlockConvolver to process arbitrary block
    /// lengths. This isn't built into the BlockConvolver, because it's not
    /// always necessary, and because this introduces some delay; if we adapt
    /// multiple components with this then we can save some delay compared to
    /// having it built into each component.
    class VariableBlockSizeAdapterImpl {
     public:
      using Samples = Eigen::MatrixXf;
      using ProcessFunc = VariableBlockSizeAdapter::ProcessFunc;

      /// \param block_size number of samples accepted by \p process_func
      /// \param num_channels_in number of input channels
      /// \param num_channels_out number of output channels
      /// \param process_func function to call to process block_size samples
      VariableBlockSizeAdapterImpl(size_t block_size, size_t num_channels_in,
                                   size_t num_channels_out,
                                   std::function<ProcessFunc> process_func)
          : process_func(process_func),
            block_size(block_size),
            input_buffer(Samples::Zero(block_size, num_channels_in)),
            output_buffer(Samples::Zero(block_size, num_channels_out)),
            samples_in_input(0),
            input_buffer_ptr(num_channels_in),
            output_buffer_ptr(num_channels_out) {
        input_buffer_ptr.set_eigen(input_buffer);
        output_buffer_ptr.set_eigen(output_buffer);
      }

      /// Process n samples.
      /// \param in input samples, n rows and num_channels_in cols
      /// \param out output samples, n rows and num_channels_out cols
      void process(size_t nsamples, const float* const* in, float* const* out) {
        Eigen::Index sample = 0;
        while (sample < nsamples) {
          // move in -> input_buffer and out -> output_buffer until out of
          // samples (or input_buffer is full and output_buffer is empty)
          Eigen::Index to_transfer = std::min((Eigen::Index)nsamples - sample,
                                              block_size - samples_in_input);

          for (size_t in_channel = 0; in_channel < input_buffer.cols();
               in_channel++)
            for (size_t i = 0; i < to_transfer; i++)
              input_buffer(samples_in_input + i, in_channel) =
                  in[in_channel][sample + i];

          for (size_t out_channel = 0; out_channel < output_buffer.cols();
               out_channel++)
            for (size_t i = 0; i < to_transfer; i++)
              out[out_channel][sample + i] =
                  output_buffer(samples_in_input + i, out_channel);

          sample += to_transfer;
          samples_in_input += to_transfer;

          // run process from input_buffer to output_buffer
          bool run_process = samples_in_input == block_size;
          if (run_process) {
            process_func(input_buffer_ptr.ptrs(), output_buffer_ptr.ptrs());
            samples_in_input = 0;
          }

          // check that we made progress
          ear_assert(run_process || to_transfer > 0, "no progress made");
        }

        ear_assert(sample == nsamples, "processed more samples than expected");
      }

      /// The delay introduced by the variable block size processing, not
      /// accounting for any delay introduced by the inner process.
      Eigen::Index get_delay() const { return block_size; }

     private:
      std::function<ProcessFunc> process_func;
      Eigen::Index block_size;
      // Buffers for input and output samples, both block_size long.
      // input_buffer contains samples_in_input samples, starting at the start.
      // output_buffer contains (block_size - samples_in_input) samples,
      // starting at samples_in_input (so, aligned at the end of the buffer).
      Samples input_buffer;
      Samples output_buffer;
      Eigen::Index samples_in_input;

      PtrAdapter input_buffer_ptr;
      PtrAdapter output_buffer_ptr;
    };

    VariableBlockSizeAdapter::VariableBlockSizeAdapter(
        size_t block_size, size_t num_channels_in, size_t num_channels_out,
        std::function<ProcessFunc> process_func)
        : impl(std::make_unique<VariableBlockSizeAdapterImpl>(
              block_size, num_channels_in, num_channels_out, process_func)) {}

    void VariableBlockSizeAdapter::process(size_t nsamples,
                                           const float* const* in,
                                           float* const* out) {
      impl->process(nsamples, in, out);
    }

    /// The delay introduced by the variable block size processing, not
    /// accounting for any delay introduced by the inner process.
    int VariableBlockSizeAdapter::get_delay() const {
      return impl->get_delay();
    }

    VariableBlockSizeAdapter::~VariableBlockSizeAdapter() = default;
  }  // namespace dsp
}  // namespace ear


#include <cctbx/boost_python/flex_fwd.h>

#include <cctbx/miller/asu.h>
#include <boost/python/class.hpp>
#include <boost/python/def.hpp>
#include <boost/python/args.hpp>

namespace cctbx { namespace miller { namespace boost_python {

namespace {

  struct index_table_layout_adaptor_wrappers
  {
    typedef index_table_layout_adaptor w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t,bases<sym_equiv_index> >("index_table_layout_adaptor",no_init)
        .def("h", &w_t::h)
        .def("i_column", &w_t::i_column)
      ;
    }
  };

  struct asym_index_wrappers
  {
    typedef asym_index w_t;

    static void
    wrap()
    {
      using namespace boost::python;
      class_<w_t, bases<sym_equiv_index> >("asym_index", no_init)
        .def(init<sgtbx::space_group const&,
                  sgtbx::reciprocal_space::asu const&,
                  index<> const&>())
        .def(init<sgtbx::space_group const&,
                  index<> const&>())
        .def(init<sym_equiv_indices const&>())
        .def("one_column", &w_t::one_column)
        .def("two_column", &w_t::two_column)
      ;
    }
  };

  template <typename ValueType>
  struct map_to_asu_no_bool
  {
    static
    void
    wrapper(
      sgtbx::space_group_type const& sg_type,
      bool anomalous_flag,
      af::ref<index<> > const& miller_indices,
      af::ref<ValueType> const& data)
    {
      map_to_asu(sg_type, anomalous_flag, miller_indices, data);
    }
  };

  template <typename ValueType>
  struct map_to_asu_with_bool
  {
    static
    void
    wrapper(
      sgtbx::space_group_type const& sg_type,
      bool anomalous_flag,
      af::ref<index<> > const& miller_indices,
      af::ref<ValueType> const& data,
      bool deg)
    {
      map_to_asu(sg_type, anomalous_flag, miller_indices, data, deg);
    }
  };

} // namespace <anoymous>

  void wrap_asu()
  {
    using namespace boost::python;
    index_table_layout_adaptor_wrappers::wrap();
    asym_index_wrappers::wrap();
    def("map_to_asu",
      (void(*)(sgtbx::space_group_type const&,
               bool,
               af::ref<index<> > const&))
      map_to_asu);
    def("map_to_asu_isym",
      (void(*)(sgtbx::space_group_type const&,
               bool,
               af::ref<index<> > const&,
               af::ref<int> const &))
      map_to_asu_isym);
    def("map_to_asu", map_to_asu_no_bool<double>::wrapper);
    def("map_to_asu", map_to_asu_with_bool<double>::wrapper);
    def("map_to_asu", map_to_asu_no_bool<std::complex<double> >::wrapper);
    def("map_to_asu", map_to_asu_no_bool<hendrickson_lattman<> >::wrapper);
    def("is_unique_set_under_symmetry", is_unique_set_under_symmetry, (
      arg("space_group_type"),
      arg("anomalous_flag"),
      arg("miller_indices")));
    def("unique_under_symmetry_selection", unique_under_symmetry_selection, (
      arg("space_group_type"),
      arg("anomalous_flag"),
      arg("miller_indices")));
  }

}}} // namespace cctbx::miller::boost_python


/* ----------------------------------------------------------------------- *//**
 *
 * @file GLM_impl.hpp
 *
 *//* ----------------------------------------------------------------------- */

#ifndef MADLIB_MODULES_GLM_GLM_IMPL_HPP
#define MADLIB_MODULES_GLM_GLM_IMPL_HPP

#include <dbconnector/dbconnector.hpp>
#include <boost/math/distributions.hpp>
#include <limits>

namespace madlib {

namespace modules {

namespace glm {

// Use Eigen
using namespace dbal::eigen_integration;

template <class Container, class Family, class Link>
inline
GLMAccumulator<Container,Family,Link>::GLMAccumulator(
        Init_type& inInitialization)
: Base(inInitialization) {

    this->initialize();
}

/**
 * @brief Reset the accumulator before accumulating the first tuple
 */
template <class Container, class Family, class Link>
inline
void
GLMAccumulator<Container,Family,Link>::reset() {
    num_rows = 0;
    terminated = false;
    loglik = 0.;
    dispersion_accum = 0.;
    grad.setZero();
    hessian.setZero();
}

/**
 * @brief Bind all elements of the state to the data in the stream
 *
 * The bind() is special in that even after running operator>>() on an element,
 * there is no guarantee yet that the element can indeed be accessed. It is
 * cruicial to first check this.
 *
 * Provided that this methods correctly lists all member variables, all other
 * methods can, however, rely on that fact that all variables are correctly
 * initialized and accessible.
 */
template <class Container, class Family, class Link>
inline
void
GLMAccumulator<Container,Family,Link>::bind(
        ByteStream_type& inStream) {
    inStream
        >> num_rows
        >> terminated
        >> loglik
        >> dispersion
        >> dispersion_accum
        >> num_coef;

        uint16_t M = num_coef.isNull()
            ? static_cast<uint16_t>(0)
            : static_cast<uint16_t>(num_coef);
        inStream
            >> beta.rebind(M)
            >> grad.rebind(M)
            >> hessian.rebind(M, M);

    // bind vcov and optimizer.hessian onto the same memory as we don't need them
    // at the same time
    vcov.rebind(hessian.memoryHandle(),
                hessian.rows(),
                hessian.cols());
}

/**
 * @brief Update the accumulation state by feeding a tuple
 */
template <class Container, class Family, class Link>
inline
GLMAccumulator<Container,Family,Link>&
GLMAccumulator<Container,Family,Link>::operator<<(
        const tuple_type& inTuple) {
    const MappedColumnVector& x = std::get<0>(inTuple);
    const double& y = std::get<1>(inTuple);
    // The following checks were introduced with MADLIB-138. It still seems
    // useful to have clear error messages in case of infinite input values.
    if (!std::isfinite(y)) {
        warning("Dependent variables are not finite.");
    //domain check for y
    } else if (!Family::in_range(y)) {
        std::stringstream err_msg;
        err_msg << "Dependent variables are out of range: "
            << Family::out_of_range_err_msg();
        throw std::runtime_error(err_msg.str());
    } else if (!dbal::eigen_integration::isfinite(x)) {
        warning("Design matrix is not finite.");
    } else if (x.size() > std::numeric_limits<uint16_t>::max()) {
        warning("Number of independent variables cannot be "
            "larger than 65535.");
    } else if (num_coef != static_cast<uint16_t>(x.size())) {
        warning("Inconsistent numbers of independent variables.");
    } else {
        // normal case
        if (beta.norm() == 0.) {
            double mu = Link::init(y);
            double ita = Link::link_func(mu);
            double G_prime = Link::mean_derivative(ita);
            double V = Family::variance(mu);
            double w = G_prime * G_prime / V;
            // dispersion_accum += (y - mu) * (y - mu) / V;
            loglik += Family::loglik(y, mu, dispersion);
            hessian += x * trans(x) * w; // X_trans_W_X
            grad -= x * w * ita; // X_trans_W_Y
        } else {
            double ita = trans(x) * beta;
            double mu = Link::mean_func(ita);
            double G_prime = Link::mean_derivative(ita);
            double V = Family::variance(mu);
            double w = G_prime * G_prime / V;
            dispersion_accum += (y - mu) * (y - mu) / V;
            loglik += Family::loglik(y, mu, dispersion);

            if (!std::isfinite(static_cast<double>(loglik))) {
                terminated = true;
                warning("Log-likelihood becomes negative infinite. Maybe the model is not proper for this data set.");
                return *this;
            }

            hessian += x * trans(x) * w; // X_trans_W_X
            grad -= x * (y - mu) * G_prime / V; // X_trans_W_Y
        }
        num_rows ++;
        return *this;
    }

    // error case
    terminated = true;
    return *this;
}

/**
 * @brief Merge with another accumulation state
 */
template <class Container, class Family, class Link>
template <class C, class F, class L>
inline
GLMAccumulator<Container,Family,Link>&
GLMAccumulator<Container,Family,Link>::operator<<(
        const GLMAccumulator<C,F,L>& inOther) {
    if (this->empty()) {
        *this = inOther;
    } else if (inOther.empty()) {
    } else if (num_coef != inOther.num_coef) {
        warning("Inconsistent numbers of independent variables.");
        terminated = true;
    } else {
        num_rows += inOther.num_rows;
        loglik += inOther.loglik;
        grad += inOther.grad;
        hessian += inOther.hessian;
        dispersion_accum += inOther.dispersion_accum;
    }

    return *this;
}

/**
 * @brief Copy from a previous state
 */
template <class Container, class Family, class Link>
template <class C, class F, class L>
inline
GLMAccumulator<Container,Family,Link>&
GLMAccumulator<Container,Family,Link>::operator=(
        const GLMAccumulator<C,F,L>& inOther) {
    this->copy(inOther);
    return *this;
}

/**
 * @brief Apply the accumulated intra-state values to inter-state member
 */
template <class Container, class Family, class Link>
inline
void
GLMAccumulator<Container,Family,Link>::apply() {
    if (!dbal::eigen_integration::isfinite(hessian) ||
            !dbal::eigen_integration::isfinite(grad)) {
        warning("Hessian or gradient is not finite.");
        terminated = true;
    } else {
        SymmetricPositiveDefiniteEigenDecomposition<Matrix> decomposition(
                hessian, EigenvaluesOnly, ComputePseudoInverse);

        if (beta.norm() == 0.) {
            dispersion = 1.;
        } else {
            dispersion = dispersion_accum / static_cast<double>(num_rows);
        }
        hessian = decomposition.pseudoInverse(); // become inverse after apply
        beta -= hessian * grad;
    }
}

// -----------------------------------------------------------------------

template <class Container>
GLMResult::GLMResult(const GLMAccumulator<Container>& state) {
    compute(state);
}

/**
 * @brief Transform an accumulation state into a result
 */
template <class Container>
inline
GLMResult&
GLMResult::compute(const GLMAccumulator<Container>& state) {
    // Vector of coefficients: For efficiency reasons, we want to return this
    // by reference, so we need to bind to db memory
    Allocator& allocator = defaultAllocator();
    uint64_t n = state.num_rows;
    uint16_t p = state.num_coef;
    coef.rebind(allocator.allocateArray<double>(p));
    std_err.rebind(allocator.allocateArray<double>(p));
    z_stats.rebind(allocator.allocateArray<double>(p));
    p_values.rebind(allocator.allocateArray<double>(p));

    loglik = state.loglik;
    coef = state.beta;
    dispersion = state.dispersion *
            static_cast<double>(n) / static_cast<double>(n - p);
    std_err = state.vcov.diagonal().cwiseSqrt();
    z_stats = coef.cwiseQuotient(std_err);
    for (Index i = 0; i < p; i ++) {
        p_values(i) = 2. * prob::cdf(prob::normal(), -std::abs(z_stats(i)));
    }
    num_rows_processed = n;

    return *this;
}

} // namespace glm

} // namespace modules

} // namespace madlib

#endif // defined(MADLIB_MODULES_GLM_GLM_IMPL_HPP)


#include <algorithm>
#include <cstdint>
#include <iostream>
#include <stdexcept>
#include <string>

#ifndef CPPDEBUG /* Ubuntu's Boost does not provide binaries compatible with libstdc++'s debug mode so we just reduce functionality here */
#include <boost/program_options.hpp>
#endif

#include "libiop/algebra/fields/gf64.hpp"
#include "libiop/algebra/fields/gf128.hpp"
#include "libiop/algebra/fields/gf192.hpp"
#include "libiop/algebra/fields/gf256.hpp"
#include "libiop/algebra/fields/utils.hpp"

#include "boost_profile.cpp"
#include "libiop/snark/aurora_snark.hpp"
#include "libiop/bcs/bcs_common.hpp"
#include "libiop/protocols/aurora_iop.hpp"
#include "libiop/protocols/ldt/fri/argument_size_optimizer.hpp"
#include "libiop/relations/examples/r1cs_examples.hpp"
#include <libff/algebra/curves/edwards/edwards_pp.hpp>
#include <libff/algebra/curves/alt_bn128/alt_bn128_pp.hpp>

#ifndef CPPDEBUG
bool process_prover_command_line(const int argc, const char** argv,
                                 options &options, bool heuristic_fri_soundness, bool optimize_localization)
{
    namespace po = boost::program_options;

    try
    {

        po::options_description desc = gen_options(options);
        desc.add_options()
             ("optimize_localization", po::value<bool>(&optimize_localization)->default_value(false))
             ("heuristic_fri_soundness", po::value<bool>(&heuristic_fri_soundness)->default_value(true));

        po::variables_map vm;
        po::store(po::parse_command_line(argc, argv, desc), vm);

        if (vm.count("help"))
        {
            std::cout << desc << "\n";
            return false;
        }

        po::notify(vm);
        options.hash_enum = static_cast<libiop::bcs_hash_type>(options.hash_enum_val);
    }
    catch(std::exception& e)
    {
        std::cerr << "Error: " << e.what() << "\n";
        return false;
    }

    return true;
}
#endif

using namespace libiop;

template<typename FieldT, typename hash_type>
void print_argument_size(
    aurora_snark_parameters<FieldT, hash_type> params,
    const r1cs_constraint_system<FieldT> &constraint_system,
    aurora_snark_argument<FieldT, hash_type> argument)
{
    /* We go through registration on the verifier to know what the domains look like */
    bcs_verifier<FieldT, hash_type> verifier(params.bcs_params_, argument);
    aurora_iop<FieldT> full_protocol(verifier, constraint_system, params.iop_params_);
    full_protocol.register_interactions();
    verifier.seal_interaction_registrations();
    full_protocol.register_queries();
    verifier.seal_query_registrations();
    const bool holographic = false;
    print_detailed_transcript_data<FieldT, hash_type>(
        holographic,
        argument,
        params.bcs_params_,
        verifier);
}

template<typename FieldT, typename hash_type>
void instrument_aurora_snark(options &options,
                            LDT_reducer_soundness_type ldt_reducer_soundness_type,
                            FRI_soundness_type fri_soundness_type,
                            bool &optimize_localization)

{
    // TODO: Unhard code this
    const size_t RS_extra_dimensions = 3 + (options.make_zk ? 0 : 2);
    const size_t fri_localization_parameter = 2;
    field_subset_type domain_type = affine_subspace_type;
    if (options.is_multiplicative) {
        domain_type = multiplicative_coset_type;
    }

    for (std::size_t log_n = options.log_n_min; log_n <= options.log_n_max; ++log_n)
    {
        print_separator();

        const std::size_t n = 1ul << log_n;
        /* k+1 needs to be a power of 2 (proof system artifact) so we just fix it to 15 here */
        const std::size_t k = 15;
        const std::size_t m = n - 1;
        r1cs_example<FieldT> example = generate_r1cs_example<FieldT>(n, k, m);

        aurora_snark_parameters<FieldT, hash_type> parameters(
            options.security_level,
            ldt_reducer_soundness_type,
            fri_soundness_type,
            options.hash_enum,
            fri_localization_parameter,
            RS_extra_dimensions,
            options.make_zk,
            domain_type,
            example.constraint_system_.num_constraints(),
            example.constraint_system_.num_variables());

        std::vector<std::size_t> localization_parameter_array;
        if (optimize_localization)
        {
            const size_t codeword_domain_dim = parameters.iop_params_.codeword_domain_dim();
            size_t num_query_sets = parameters.iop_params_.FRI_params_.query_repetitions();
            size_t interactive_repetitions =
                parameters.iop_params_.FRI_params_.interactive_repetitions() *
                parameters.iop_params_.LDT_reducer_params_.num_output_LDT_instances();
            const size_t max_tested_degree_bound =
                parameters.iop_params_.encoded_aurora_params_.max_tested_degree_bound();
            const size_t hash_size = (parameters.bcs_params_.security_parameter + 3) / 4;
            std::vector<size_t> oracle_locality_vector = parameters.iop_params_.locality_vector();
            // if (parameters.iop_params_.make_zk())
            // {
            //     /* Handle the zk leaves for the SNARK. TODO: Where should this go? */
            //     oracle_locality_vector[0] += 1;
            // }
            localization_parameter_array =
                compute_argument_size_optimal_localization_parameters<FieldT>(
                    oracle_locality_vector, codeword_domain_dim,
                    num_query_sets, interactive_repetitions,
                    max_tested_degree_bound, hash_size);

            parameters.reset_fri_localization_parameters(localization_parameter_array);
        }

        enter_block("Check satisfiability of R1CS example");
        const bool is_satisfied = example.constraint_system_.is_satisfied(
            example.primary_input_, example.auxiliary_input_);
        assert(is_satisfied);
        leave_block("Check satisfiability of R1CS example");
        printf("\n");
        print_indent(); printf("* R1CS number of constraints: %zu\n", example.constraint_system_.num_constraints());
        print_indent(); printf("* R1CS number of variables: %zu\n", example.constraint_system_.num_variables());
        print_indent(); printf("* R1CS number of variables for primary input: %zu\n", example.primary_input_.size());
        print_indent(); printf("* R1CS number of variables for auxiliary input: %zu\n", example.auxiliary_input_.size());
        print_indent(); printf("* R1CS size of constraint system (bytes): %zu\n", example.constraint_system_.size_in_bytes());
        print_indent(); printf("* R1CS size of primary input (bytes): %zu\n", example.primary_input_.size() * sizeof(FieldT));
        print_indent(); printf("* R1CS size of auxiliary input (bytes): %zu\n", example.auxiliary_input_.size() * sizeof(FieldT));
        printf("\n");
        const aurora_snark_argument<FieldT, hash_type> proof = aurora_snark_prover<FieldT, hash_type>(
            example.constraint_system_,
            example.primary_input_,
            example.auxiliary_input_,
            parameters);

        print_argument_size(parameters, example.constraint_system_, proof);

        const bool bit = aurora_snark_verifier<FieldT, hash_type>(
            example.constraint_system_,
            example.primary_input_,
            proof,
            parameters);

        printf("\n\n");

        print_indent(); printf("* Verifier satisfied: %s\n", bit ? "true" : "false");
    }
}

int main(int argc, const char * argv[])
{
    /* Set up R1CS */

    options default_vals;

    bool optimize_localization = false;
    bool heuristic_fri_soundness = true;

#ifdef CPPDEBUG
    /* set reasonable defaults */
    if (argc > 1)
    {
        printf("There is no argument parsing in CPPDEBUG mode.");
        exit(1);
    }
    libiop::UNUSED(argv);

#else
    if (!process_prover_command_line(argc, argv, default_vals, heuristic_fri_soundness, optimize_localization))
    {
        return 1;
    }
#endif
    /** TODO: eventually get a string from program options, and then have a from string methods in protocols */
    LDT_reducer_soundness_type ldt_reducer_soundness_type = LDT_reducer_soundness_type::proven;
    if (default_vals.heuristic_ldt_reducer_soundness)
    {
        ldt_reducer_soundness_type = LDT_reducer_soundness_type::optimistic_heuristic;
    }
    FRI_soundness_type fri_soundness_type = FRI_soundness_type::proven;
    if (heuristic_fri_soundness) {
        fri_soundness_type = FRI_soundness_type::heuristic;
    }
    start_profiling();

    printf("Selected parameters:\n");
    printf("- log_n_min = %zu\n", default_vals.log_n_min);
    printf("- log_n_max = %zu\n", default_vals.log_n_max);
    printf("- security_level = %zu\n", default_vals.security_level);
    printf("- LDT_reducer_soundness_type = %s\n", LDT_reducer_soundness_type_to_string(ldt_reducer_soundness_type));
    printf("- FRI_soundness_type = %s\n", FRI_soundness_type_to_string(fri_soundness_type));
    printf("- is_multiplicative = %s\n", default_vals.is_multiplicative ? "true" : "false");
    printf("- field_size = %zu\n", default_vals.field_size);
    printf("- make_zk = %s\n", default_vals.make_zk ? "true" : "false");
    printf("- hash_enum = %s\n", bcs_hash_type_names[default_vals.hash_enum]);

    if (default_vals.is_multiplicative) {
        switch (default_vals.field_size) {
            case 181:
                edwards_pp::init_public_params();
                instrument_aurora_snark<edwards_Fr, binary_hash_digest>(
                    default_vals, ldt_reducer_soundness_type,
                    fri_soundness_type, optimize_localization);
                break;
            case 256:
                libff::alt_bn128_pp::init_public_params();

                if (default_vals.hash_enum == libiop::blake2b_type)
                {
                    instrument_aurora_snark<libff::alt_bn128_Fr, binary_hash_digest>(
                        default_vals, ldt_reducer_soundness_type,
                        fri_soundness_type, optimize_localization);
                }
                else
                {
                    instrument_aurora_snark<libff::alt_bn128_Fr, libff::alt_bn128_Fr>(
                        default_vals, ldt_reducer_soundness_type,
                        fri_soundness_type, optimize_localization);
                }
                break;
            default:
                throw std::invalid_argument("Field size not supported.");
        }

    } else {
        switch (default_vals.field_size)
        {
            case 64:
                instrument_aurora_snark<gf64, binary_hash_digest>(
                    default_vals, ldt_reducer_soundness_type, fri_soundness_type, optimize_localization);
                break;
            case 128:
                instrument_aurora_snark<gf128, binary_hash_digest>(
                    default_vals, ldt_reducer_soundness_type, fri_soundness_type, optimize_localization);
                break;
            case 192:
                instrument_aurora_snark<gf192, binary_hash_digest>(
                    default_vals, ldt_reducer_soundness_type, fri_soundness_type, optimize_localization);
                break;
            case 256:
                instrument_aurora_snark<gf256, binary_hash_digest>(
                    default_vals, ldt_reducer_soundness_type, fri_soundness_type, optimize_localization);
                break;
            default:
                throw std::invalid_argument("Field size not supported.");
        }
    }
}


#include "astroutils.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <errno.h>
#include <unistd.h>
#include <math.h>
#include "libwcs/wcs.h"
#include "libwcs/fitsfile.h"
#include "libwcs/wcscat.h"
#include "libwcs/lwcs.h"

#include <iostream>
#include <vector>
#include <string>

#include <boost/algorithm/string/split.hpp>
#include <boost/algorithm/string/classification.hpp>
#include <boost/algorithm/string.hpp>
using namespace boost::algorithm;

extern void setsys();

AstroUtils::AstroUtils()
{
}

void AstroUtils::GetCenterCoords(std::string file, double *coords)
{
    WorldCoor *wc = AstroUtils().GetWCSFITS((char*) file.c_str(), 1);
    AstroUtils().xy2sky(file, wc->nxpix/2.0, wc->nypix/2.0, coords, WCS_GALACTIC);
    wcsfree(wc);
}

/// values = [dl, db]
void AstroUtils::GetRectSize(std::string file, double *values)
{
    WorldCoor *wc = AstroUtils().GetWCSFITS((char*) file.c_str(), 1);
    double sky_coords[2], delta[2];
    AstroUtils().xy2sky(file, 0, 0, sky_coords, WCS_GALACTIC);

    /* dl */
    AstroUtils().xy2sky(file, wc->nxpix, 0, delta, WCS_GALACTIC);
    values[0] = abs(delta[0] - sky_coords[0]);

    /* db */
    AstroUtils().xy2sky(file, 0, wc->nypix, delta, WCS_GALACTIC);
    values[1] = abs(delta[1] - sky_coords[1]);

    wcsfree(wc);
}

double AstroUtils::GetRadiusSize(std::string file)
{
    double rect[2];
    AstroUtils().GetRectSize(file, rect);
    double radius = fmax(rect[0], rect[1]) / 2.0;
    return radius;
}

void AstroUtils::GetBounds(std::string file, double *top, double *bottom, double *right, double *left)
{
    double tl[2], br[2];
    WorldCoor *wc = AstroUtils().GetWCSFITS((char*) file.c_str(), 1);
    AstroUtils().xy2sky(file, 0, wc->nypix, tl, WCS_GALACTIC);
    AstroUtils().xy2sky(file, wc->nxpix, 0, br, WCS_GALACTIC);
    *top = tl[1];
    *left = tl[0];
    *bottom = br[1];
    *right = br[0];
    wcsfree(wc);
}

bool AstroUtils::CheckOverlap(std::string f1, std::string f2, bool full)
{
    if (full) {
        // Full overlap
        return CheckFullOverlap(f1, f2) || CheckFullOverlap(f2, f1);
    } else {
        // Check partial overlap
        double T1, B1, R1, L1;
        AstroUtils().GetBounds(f1, &T1, &B1, &R1, &L1);

        double T2, B2, R2, L2;
        AstroUtils().GetBounds(f2, &T2, &B2, &R2, &L2);

        return L1 > R2 && R1 < L2 && T1 > B2 && B1 < T2;
    }
}

bool AstroUtils::CheckFullOverlap(std::string f1, std::string f2)
{
    double T1, B1, R1, L1;
    AstroUtils().GetBounds(f1, &T1, &B1, &R1, &L1);

    double T2, B2, R2, L2;
    AstroUtils().GetBounds(f2, &T2, &B2, &R2, &L2);

    // returns true if f2 is completely inside f1
    return R2 > R1 && L2 < L1 && T2 < T1 && B2 > B1;
}

double AstroUtils::arcsecPixel(std::string file)
{
    char *fn = new char[file.length() + 1];
    strcpy(fn, file.c_str());

    struct WorldCoor *wcs;
    char *header;
    double cra, cdec, dra, ddec, secpix;
    int wp, hp;
    int sysout = 0;
    double eqout = 0.0;

    header = GetFITShead(fn, 0);
    wcs = GetFITSWCS(fn, header, 0, &cra, &cdec, &dra, &ddec, &secpix, &wp, &hp, &sysout, &eqout);
    wcsfree(wcs);

    return secpix;
}

void AstroUtils::xy2sky(std::string map, float x, float y, double* coord, int wcs_type)
{
    struct WorldCoor *wcs;
    char *fn = new char[map.length() + 1];
    static char coorsys[16];
    char wcstring[64];
    char lstr = 64;
    *coorsys = 0;

    strcpy(fn, map.c_str());
    wcs = GetWCSFITS(fn, 0);
    wcs->sysout = wcs_type;
    // force the set of wcs in degree
    setwcsdeg(wcs,1);

    if (wcs_type == WCS_GALACTIC)
    {
        wcs->eqout = 2000.0;
    }

    if (pix2wcst(wcs, x, y, wcstring, lstr))
    {
        std::string str(wcstring);
        std::vector<std::string> tokens;
        trim(str);
        split(tokens, str, is_any_of(" "), boost::token_compress_on);
        coord[0] = atof(tokens[0].c_str());
        coord[1] = atof(tokens[1].c_str());
    }

    delete [] fn;
    wcsfree(wcs);
}

int AstroUtils::getSysOut(std::string file)
{
    char *fn = new char[file.length() + 1];
    strcpy(fn, file.c_str());

    struct WorldCoor *wcs;
    char *header;
    double cra, cdec, dra, ddec, secpix;
    int wp, hp;
    int sysout = 0;
    double eqout = 0.0;

    header = GetFITShead(fn, 0);
    wcs = GetFITSWCS(fn, header, 1, &cra, &cdec, &dra, &ddec, &secpix, &wp, &hp, &sysout, &eqout);
    wcsfree(wcs);

    delete [] fn;

    return sysout;
}

void AstroUtils::getRotationAngle(std::string file)
{
    char *fn = new char[file.length() + 1];
    strcpy(fn, file.c_str());

    struct WorldCoor *wcs;
    char *header;
    double cra, cdec, dra, ddec, secpix;
    int wp, hp;
    int sysout = 0;
    double eqout = 0.0;

    header = GetFITShead(fn, 0);
    wcs = GetFITSWCS(fn, header, 1, &cra, &cdec, &dra, &ddec, &secpix, &wp, &hp, &sysout, &eqout);
    wcsoutinit(wcs, (char*) "GALACTIC");

    int off;
    double xc, xn, xe, yc, yn, ye;

    /* If image is one-dimensional, leave rotation angle alone */
    if (wcs->nxpix < 1.5 || wcs->nypix < 1.5) {
        wcs->imrot = wcs->rot;
        wcs->pa_north = wcs->rot + 90.0;
        wcs->pa_east = wcs->rot + 180.0;
        return;
    }

    /* Do not try anything if image is LINEAR (not Cartesian projection) */
    if (wcs->syswcs == WCS_LINEAR)
        return;

    wcs->xinc = fabs (wcs->xinc);
    wcs->yinc = fabs (wcs->yinc);

    /* Compute position angles of North and East in image */
    xc = wcs->xrefpix;
    yc = wcs->yrefpix;
    pix2wcs (wcs, xc, yc, &cra, &cdec);
    if (wcs->coorflip) {
        wcs2pix (wcs, cra+wcs->yinc, cdec, &xe, &ye, &off);
        wcs2pix (wcs, cra, cdec+wcs->xinc, &xn, &yn, &off);
    }
    else {
        wcs2pix (wcs, cra+wcs->xinc, cdec, &xe, &ye, &off);
        wcs2pix (wcs, cra, cdec+wcs->yinc, &xn, &yn, &off);
    }
    wcs->pa_north = raddeg (atan2 (yn-yc, xn-xc));
    if (wcs->pa_north < -90.0)
        wcs->pa_north = wcs->pa_north + 360.0;
    wcs->pa_east = raddeg (atan2 (ye-yc, xe-xc));
    if (wcs->pa_east < -90.0)
        wcs->pa_east = wcs->pa_east + 360.0;

    /* Compute image rotation angle from North */
    if (wcs->pa_north < -90.0)
        wcs->imrot = 270.0 + wcs->pa_north;
    else
        wcs->imrot = wcs->pa_north - 90.0;

    /* Compute CROTA */
    if (wcs->coorflip) {
        wcs->rot = wcs->imrot + 90.0;
        if (wcs->rot < 0.0)
            wcs->rot = wcs->rot + 360.0;
    }
    else
        wcs->rot = wcs->imrot;
    if (wcs->rot < 0.0)
        wcs->rot = wcs->rot + 360.0;
    if (wcs->rot >= 360.0)
        wcs->rot = wcs->rot - 360.0;

    /* Set image mirror flag based on axis orientation */
    wcs->imflip = 0;
    if (wcs->pa_east - wcs->pa_north < -80.0 &&
            wcs->pa_east - wcs->pa_north > -100.0)
        wcs->imflip = 1;
    if (wcs->pa_east - wcs->pa_north < 280.0 &&
            wcs->pa_east - wcs->pa_north > 260.0)
        wcs->imflip = 1;
    if (wcs->pa_north - wcs->pa_east > 80.0 &&
            wcs->pa_north - wcs->pa_east < 100.0)
        wcs->imflip = 1;
    if (wcs->coorflip) {
        if (wcs->imflip)
            wcs->yinc = -wcs->yinc;
    }
    else {
        if (!wcs->imflip)
            wcs->xinc = -wcs->xinc;
    }

    delete [] fn;
    wcsfree(wcs);
}

bool AstroUtils::sky2xy(std::string map, double ra, double dec, double* coord)
{
    char *fn = new char[map.length() + 1];
    strcpy(fn, map.c_str());

    struct WorldCoor *wcs;
    char *header;
    double cra, cdec, dra, ddec, secpix;
    int wp, hp;
    int sysout = 0;
    double eqout = 0.0;
    double x, y;
    int sysin;
    char csys[16];
    double eqin = 0.0;
    int offscale;

    header = GetFITShead(fn, 0);
    wcs = GetFITSWCS(fn, header, 0, &cra, &cdec, &dra, &ddec, &secpix, &wp, &hp, &sysout, &eqout);

    if (wcs->prjcode < 0)
        strcpy (csys, "PIXEL");
    else if (wcs->prjcode < 2)
        strcpy (csys, "LINEAR");
    else
        strcpy (csys, wcs->radecsys);

    sysin = wcscsys (csys);
    eqin = wcsceq (csys);

    if (wcs->syswcs > 0 && wcs->syswcs != 6 && wcs->syswcs != 10)
        wcscon (sysin, wcs->syswcs, eqin, eqout, &ra, &dec, wcs->epoch);

    wcsc2pix (wcs, ra, dec, csys, &x, &y, &offscale);

    coord[0] = x;
    coord[1] = y;
    coord[2] = secpix;

    delete [] fn;
    delete [] header;
    wcsfree(wcs);

    return true;
}

static double secpix0 = PSCALE;		/* Set image scale--override header */
static int usecdelt = 0;		    /* Use CDELT if 1, else CD matrix */
static int hp0 = 0;			        /* Initial height of image */
static int wp0 = 0;			        /* Initial width of image */
static double ra0 = -99.0;		    /* Initial center RA in degrees */
static double dec0 = -99.0;		    /* Initial center Dec in degrees */
static int comsys = WCS_J2000;		/* Command line center coordinte system */
static int ptype0 = -1;			    /* Projection type to fit */
static int  nctype = 28;		    /* Number of possible projections */
static char ctypes[32][4];		    /* 3-letter codes for projections */
static double xref0 = -99999.0;		/* Reference pixel X coordinate */
static double yref0 = -99999.0;		/* Reference pixel Y coordinate */
static double secpix2 = PSCALE;		/* Set image scale 2--override header */
static double *cd0 = NULL;		    /* Set CD matrix--override header */
static double rot0 = 361.0;		    /* Initial image rotation */
static char *dateobs0 = NULL;		/* Initial DATE-OBS value in FITS date format */

WorldCoor* AstroUtils::GetFITSWCS(char *filename, char	*header, int verbose, double *cra, double *cdec,double	*dra, double *ddec, double *secpix, int *wp,int	*hp,int	*sysout ,double	*eqout)
{
    int naxes;
    double eq1, x, y;
    double ra1, dec1, dx, dy;
    double xmin, xmax, ymin, ymax, ra2, dec2, ra3, dec3, ra4, dec4;
    double dra0, dra1, dra2, dra3, dra4;
    struct WorldCoor *wcs;
    char rstr[64], dstr[64], cstr[16];

    /* Initialize WCS structure from possibly revised FITS header */
    wcs = ChangeFITSWCS (filename, header, verbose);
    if (wcs == NULL) {
        return (NULL);
    }
    *hp = (int) wcs->nypix;
    *wp = (int) wcs->nxpix;

    /* If incomplete WCS in header, drop out */
    if (nowcs (wcs)) {
        setwcsfile (filename);
        /* wcserr(); */
        if (verbose)
            fprintf (stderr,"Insufficient information for initial WCS\n");
        return (NULL);
    }

    /* If in linear coordinates, do not print as sexigesimal */
    if (wcs->sysout < 1 || wcs->sysout == 6 || wcs->sysout == 10)
        wcs->degout = 1;

    /* Set flag to get appropriate equinox for catalog search */
    if (!*sysout)
        *sysout = wcs->syswcs;
    if (*eqout == 0.0)
        *eqout = wcs->equinox;
    eq1 = wcs->equinox;
    if (wcs->coorflip) {
        ra1 = wcs->crval[1];
        dec1 = wcs->crval[0];
    }
    else {
        ra1 = wcs->crval[0];
        dec1 = wcs->crval[1];
    }

    /* Print reference pixel position and value */
    if (verbose && (eq1 != *eqout || wcs->syswcs != *sysout)) {
        if (wcs->degout) {
            deg2str (rstr, 32, ra1, 6);
            deg2str (dstr, 32, dec1, 6);
        }
        else {
            ra2str (rstr, 32, ra1, 3);
            dec2str (dstr, 32, dec1, 2);
        }
        wcscstr (cstr, wcs->syswcs, wcs->equinox, wcs->epoch);
        fprintf (stderr,"Reference pixel (%.2f,%.2f) %s %s %s\n",
                 wcs->xrefpix, wcs->yrefpix, rstr, dstr, cstr);
    }

    /* Get coordinates of corners for size for catalog searching */
    dx = wcs->nxpix;
    dy = wcs->nypix;
    xmin = 0.5;
    ymin = 0.5;
    xmax = 0.5 + dx;
    ymax = 0.5 + dy;
    pix2wcs (wcs, xmin, ymin, &ra1, &dec1);
    pix2wcs (wcs, xmin, ymax, &ra2, &dec2);
    pix2wcs (wcs, xmax, ymin, &ra3, &dec3);
    pix2wcs (wcs, xmax, ymax, &ra4, &dec4);

    /* Convert search corners to output coordinate system and equinox */
    if (wcs->syswcs > 0 && wcs->syswcs != 6 && wcs->syswcs != 10) {
        wcscon (wcs->syswcs,*sysout,wcs->equinox,*eqout,&ra1,&dec1,wcs->epoch);
        wcscon (wcs->syswcs,*sysout,wcs->equinox,*eqout,&ra2,&dec2,wcs->epoch);
        wcscon (wcs->syswcs,*sysout,wcs->equinox,*eqout,&ra3,&dec3,wcs->epoch);
        wcscon (wcs->syswcs,*sysout,wcs->equinox,*eqout,&ra4,&dec4,wcs->epoch);
    }

    /* Find center and convert to output coordinate system and equinox */
    x = 0.5 + (dx * 0.5);
    y = 0.5 + (dy * 0.5);
    pix2wcs (wcs, x, y, cra, cdec);
    if (wcs->syswcs > 0 && wcs->syswcs != 6 && wcs->syswcs != 10)
        wcscon (wcs->syswcs,*sysout,wcs->equinox,*eqout,cra,cdec,wcs->epoch);

    /* Find maximum half-width in declination */
    *ddec = fabs (dec1 - *cdec);
    if (fabs (dec2 - *cdec) > *ddec)
        *ddec = fabs (dec2 - *cdec);
    if (fabs (dec3 - *cdec) > *ddec)
        *ddec = fabs (dec3 - *cdec);
    if (fabs (dec4 - *cdec) > *ddec)
        *ddec = fabs (dec4 - *cdec);

    /* Find maximum half-width in right ascension */
    dra0 = (dx / dy) * (*ddec / cos (*cdec));
    dra1 = ra1 - *cra;
    dra2 = ra2 - *cra;
    if (*cra < 0 && *cra + dra0 > 0.0) {
        dra1 = -(dra1 - 360.0);
        dra2 = -(dra2 - 360.0);
    }
    if (dra1 > 180.0)
        dra1 = dra1 - 360.0;
    else if (dra1 < -180.0)
        dra1 = dra1 + 360.0;
    else if (dra1 < 0.0)
        dra1 = -dra1;
    if (dra2 > 180.0)
        dra2 = dra2 - 360.0;
    else if (dra2 < -180.0)
        dra2 = dra2 + 360.0;
    else if (dra2 < 0.0)
        dra2 = -dra2;
    dra3 = *cra - ra3;
    dra4 = *cra - ra4;
    if (*cra > 0 && *cra - dra0 < 0.0) {
        dra3 = dra3 + 360.0;
        dra4 = dra4 + 360.0;
    }
    if (dra3 > 180.0)
        dra3 = dra3 - 360.0;
    else if (dra3 < -180.0)
        dra3 = dra3 + 360.0;
    else if (dra3 < 0.0)
        dra3 = -dra3;
    if (dra4 > 180.0)
        dra4 = dra4 - 360.0;
    else if (dra4 < -180.0)
        dra4 = dra4 + 360.0;
    else if (dra4 < 0.0)
        dra4 = -dra4;
    *dra = dra1;
    if (dra2 > *dra)
        *dra = dra2;
    if (dra3 > *dra)
        *dra = dra3;
    if (dra4 > *dra)
        *dra = dra4;

    /* wcssize (wcs, cra, cdec, dra, ddec); */

    /* Set reference pixel to center of image if it has not been set */
    if (wcs->xref == -999.0 && wcs->yref == -999.0) {
        wcs->xref = *cra;
        wcs->cel.ref[0] = *cra;
        wcs->crval[0] = *cra;
        wcs->yref = *cdec;
        wcs->cel.ref[1] = *cdec;
        wcs->crval[1] = *cdec;
        ra1 = *cra;
        dec1 = *cdec;
        if (wcs->xrefpix == 0.0 && wcs->yrefpix == 0.0) {
            wcs->xrefpix = 0.5 + (double) wcs->nxpix * 0.5;
            wcs->yrefpix = 0.5 + (double) wcs->nypix * 0.5;
        }
        wcs->xinc = *dra * 2.0 / (double) wcs->nxpix;
        wcs->yinc = *ddec * 2.0 / (double) wcs->nypix;
        /* hchange (header,"PLTRAH","PLT0RAH");
    wcs->plate_fit = 0; */
    }

    /* Convert center to desired coordinate system */
    else if (wcs->syswcs != *sysout && wcs->equinox != *eqout) {
        wcscon (wcs->syswcs, *sysout, wcs->equinox, *eqout, &ra1, &dec1, wcs->epoch);
        if (wcs->coorflip) {
            wcs->yref = ra1;
            wcs->xref = dec1;
        }
        else {
            wcs->xref = ra1;
            wcs->yref = dec1;
        }
    }

    /* Compute plate scale to return if it was not set on the command line */
    if (secpix0 <= 0.0) {
        pix2wcs (wcs, wcs->xrefpix-0.5, wcs->yrefpix, &ra1, &dec1);
        pix2wcs (wcs, wcs->xrefpix+0.5, wcs->yrefpix, &ra2, &dec2);
        *secpix = 3600.0 * wcsdist (ra1, dec1, ra2, dec2);
    }

    wcs->crval[0] = wcs->xref;
    wcs->crval[1] = wcs->yref;
    if (wcs->coorflip) {
        wcs->cel.ref[0] = wcs->crval[1];
        wcs->cel.ref[1] = wcs->crval[0];
    }
    else {
        wcs->cel.ref[0] = wcs->crval[0];
        wcs->cel.ref[1] = wcs->crval[1];
    }

    if (wcs->syswcs > 0 && wcs->syswcs != 6 && wcs->syswcs != 10) {
        wcs->cel.flag = 0;
        wcs->wcsl.flag = 0;
    }
    else {
        wcs->lin.flag = LINSET;
        wcs->wcsl.flag = WCSSET;
    }

    wcs->equinox = *eqout;
    wcs->syswcs = *sysout;
    wcs->sysout = *sysout;
    wcs->eqout = *eqout;
    wcs->sysin = *sysout;
    wcs->eqin = *eqout;
    wcscstr (cstr,*sysout,*eqout,wcs->epoch);
    strcpy (wcs->radecsys, cstr);
    strcpy (wcs->radecout, cstr);
    strcpy (wcs->radecin, cstr);
    wcsininit (wcs, wcs->radecsys);
    wcsoutinit (wcs, wcs->radecsys);

    naxes = wcs->naxis;
    if (naxes < 1 || naxes > 9) {
        naxes = wcs->naxes;
        wcs->naxis = naxes;
    }

    if (usecdelt) {
        hputnr8 (header, "CDELT1", 9, wcs->xinc);
        if (naxes > 1) {
            hputnr8 (header, "CDELT2", 9, wcs->yinc);
            hputnr8 (header, "CROTA2", 9, wcs->rot);
        }
        hdel (header, "CD1_1");
        hdel (header, "CD1_2");
        hdel (header, "CD2_1");
        hdel (header, "CD2_2");
    }
    else {
        hputnr8 (header, "CD1_1", 9, wcs->cd[0]);
        if (naxes > 1) {
            hputnr8 (header, "CD1_2", 9, wcs->cd[1]);
            hputnr8 (header, "CD2_1", 9, wcs->cd[2]);
            hputnr8 (header, "CD2_2", 9, wcs->cd[3]);
        }
    }

    /* Print reference pixel position and value */
    if (verbose) {
        if (wcs->degout) {
            deg2str (rstr, 32, ra1, 6);
            deg2str (dstr, 32, dec1, 6);
        }
        else {
            ra2str (rstr, 32, ra1, 3);
            dec2str (dstr, 32, dec1, 2);
        }
        wcscstr (cstr,*sysout,*eqout,wcs->epoch);
        fprintf (stderr,"Reference pixel (%.2f,%.2f) %s %s %s\n",
                 wcs->xrefpix, wcs->yrefpix, rstr, dstr, cstr);
    }

    /* Image size for catalog search */
    if (verbose) {
        if (wcs->degout) {
            deg2str (rstr, 32, *cra, 6);
            deg2str (dstr, 32, *cdec, 6);
        }
        else {
            ra2str (rstr, 32, *cra, 3);
            dec2str (dstr, 32, *cdec, 2);
        }
        wcscstr (cstr, *sysout, *eqout, wcs->epoch);
        fprintf (stderr,"Search at %s %s %s", rstr, dstr, cstr);
        if (wcs->degout) {
            deg2str (rstr, 32, *dra, 6);
            deg2str (dstr, 32, *ddec, 6);
        }
        else {
            ra2str (rstr, 32, *dra, 3);
            dec2str (dstr, 32, *ddec, 2);
        }
        fprintf (stderr," +- %s %s\n", rstr, dstr);
        fprintf (stderr,"Image width=%d height=%d, %g arcsec/pixel\n",
                 *wp, *hp, *secpix);
    }

    return (wcs);
}

WorldCoor* AstroUtils::ChangeFITSWCS(char *filename, char* header, int verbose)
{
    int nax, i, hp, wp;
    double xref, yref, degpix, secpix;
    struct WorldCoor *wcs;
    char temp[16];
    char *cwcs;

    /* Set the world coordinate system from the image header */
    if (strlen (filename) > 0)
    {
        cwcs = strchr (filename, '%');
        if (cwcs != NULL)
            cwcs++;
    }

    if (!strncmp (header, "END", 3)) {
        cwcs = NULL;
        for (i = 0; i < 2880; i++)
            header[i] = (char) 32;
        hputl (header, "SIMPLE", 1);
        hputi4 (header, "BITPIX", 0);
        hputi4 (header, "NAXIS", 2);
        hputi4 (header, "NAXIS1", 1);
        hputi4 (header, "NAXIS2", 1);
    }

    /* Set image dimensions */
    nax = 0;
    if (hp0 > 0 || wp0 > 0) {
        hp = hp0;
        wp = wp0;
        if (hp > 0 && wp > 0)
            nax = 2;
        else
            nax = 1;
        hputi4 (header, "NAXIS", nax);
        hputi4 (header, "NAXIS1", wp);
        hputi4 (header, "NAXIS2", hp);
    }
    else if (hgeti4 (header,"NAXIS",&nax) < 1 || nax < 1) {
        if (hgeti4 (header, "WCSAXES", &nax) < 1)
            return (NULL);
        else {
            if (hgeti4 (header, "IMAGEW", &wp) < 1)
                return (NULL);
            if (hgeti4 (header, "IMAGEH", &wp) < 1)
                return (NULL);
        }
    }
    else {
        if (hgeti4 (header,"NAXIS1",&wp) < 1)
            return (NULL);
        if (hgeti4 (header,"NAXIS2",&hp) < 1)
            return (NULL);
    }

    /* Set plate center from command line, if it is there */
    if (ra0 > -99.0 && dec0 > -99.0) {
        hputnr8 (header, "CRVAL1" ,8,ra0);
        hputnr8 (header, "CRVAL2" ,8,dec0);
        hputra (header, "RA", ra0);
        hputdec (header, "DEC", dec0);
        if (comsys == WCS_B1950) {
            hputi4 (header, "EPOCH", 1950);
            hputi4 (header, "EQUINOX", 1950);
            hputs (header, "RADECSYS", "FK4");
        }
        else {
            hputi4 (header, "EPOCH", 2000);
            hputi4 (header, "EQUINOX", 2000);
            if (comsys == WCS_GALACTIC)
                hputs (header, "RADECSYS", "GALACTIC");
            else if (comsys == WCS_ECLIPTIC)
                hputs (header, "RADECSYS", "ECLIPTIC");
            else if (comsys == WCS_ICRS)
                hputs (header, "RADECSYS", "ICRS");
            else
                hputs (header, "RADECSYS", "FK5");
        }
        if (hgetr8 (header, "SECPIX", &secpix)) {
            degpix = secpix / 3600.0;
            hputnr8 (header, "CDELT1", 8, -degpix);
            hputnr8 (header, "CDELT2", 8, degpix);
            hdel (header, "CD1_1");
            hdel (header, "CD1_2");
            hdel (header, "CD2_1");
            hdel (header, "CD2_2");
        }
    }
    if (ptype0 > -1 && ptype0 < nctype) {
        strcpy (temp,"RA---");
        strcat (temp, ctypes[ptype0]);
        hputs (header, "CTYPE1", temp);
        strcpy (temp,"DEC--");
        strcat (temp, ctypes[ptype0]);
        hputs (header, "CTYPE2", temp);
    }

    /* Set reference pixel from command line, if it is there */
    if (xref0 > -99999.0 && yref0 > -99999.0) {
        hputr8 (header, "CRPIX1", xref0);
        hputr8 (header, "CRPIX2", yref0);
    }
    else if (hgetr8 (header, "CRPIX1", &xref) < 1) {
        xref = 0.5 + (double) wp / 2.0;
        yref = 0.5 + (double) hp / 2.0;
        hputnr8 (header, "CRPIX1", 3, xref);
        hputnr8 (header, "CRPIX2", 3, yref);
    }

    /* Set plate scale from command line, if it is there */
    if (secpix0 != 0.0 || cd0 != NULL) {
        if (secpix2 != 0.0) {
            secpix = 0.5 * (secpix0 + secpix2);
            hputnr8 (header, "SECPIX1", 5, secpix0);
            hputnr8 (header, "SECPIX2", 5, secpix2);
            degpix = -secpix0 / 3600.0;
            hputnr8 (header, "CDELT1", 8, degpix);
            degpix = secpix2 / 3600.0;
            hputnr8 (header, "CDELT2", 8, degpix);
            hdel (header, "CD1_1");
            hdel (header, "CD1_2");
            hdel (header, "CD2_1");
            hdel (header, "CD2_2");
        }
        else if (secpix0 != 0.0) {
            secpix = secpix0;
            hputnr8 (header, "SECPIX", 5, secpix);
            degpix = secpix / 3600.0;
            hputnr8 (header, "CDELT1", 8, -degpix);
            hputnr8 (header, "CDELT2", 8, degpix);
            hdel (header, "CD1_1");
            hdel (header, "CD1_2");
            hdel (header, "CD2_1");
            hdel (header, "CD2_2");
        }
        else {
            hputr8 (header, "CD1_1", cd0[0]);
            hputr8 (header, "CD1_2", cd0[1]);
            hputr8 (header, "CD2_1", cd0[2]);
            hputr8 (header, "CD2_2", cd0[3]);
            hdel (header, "CDELT1");
            hdel (header, "CDELT2");
            hdel (header, "CROTA1");
            hdel (header, "CROTA2");
        }
        if (!ksearch (header,"CRVAL1")) {
            hgetra (header, "RA", &ra0);
            hgetdec (header, "DEC", &dec0);
            hputnr8 (header, "CRVAL1", 8, ra0);
            hputnr8 (header, "CRVAL2", 8, dec0);
        }
        if (!ksearch (header,"CRPIX1")) {
            xref = (double) wp / 2.0;
            yref = (double) hp / 2.0;
            hputnr8 (header, "CRPIX1", 3, xref);
            hputnr8 (header, "CRPIX2", 3, yref);
        }
        if (!ksearch (header,"CTYPE1")) {
            if (comsys == WCS_GALACTIC) {
                hputs (header, "CTYPE1", "GLON-TAN");
                hputs (header, "CTYPE2", "GLAT-TAN");
            }
            else {
                hputs (header, "CTYPE1", "RA---TAN");
                hputs (header, "CTYPE2", "DEC--TAN");
            }
        }
    }

    /* Set rotation angle from command line, if it is there */
    if (rot0 < 361.0) {
        hputnr8 (header, "CROTA1", 5, rot0);
        hputnr8 (header, "CROTA2", 5, rot0);
    }

    /* Set observation date for epoch, if it is there */
    if (dateobs0 != NULL)
        hputs (header, "DATE-OBS", dateobs0);

    /* Initialize WCS structure from FITS header */
    wcs = wcsinitn (header, cwcs);

    /* If incomplete WCS in header, drop out */
    if (nowcs (wcs)) {
        setwcsfile (filename);
        /* wcserr(); */
        if (verbose)
            fprintf (stderr,"Insufficient information for initial WCS\n");
        return (NULL);
    }
    return (wcs);
}

WorldCoor * AstroUtils::GetWCSFITS(char *filename, int verbose)
{
    char *header;		    /* FITS header */
    struct WorldCoor *wcs;	/* World coordinate system structure */
    char *cwcs;			    /* Multiple wcs string (name or character) */

    /* Read the FITS or IRAF image file header */
    header = GetFITShead (filename, verbose);
    if (header == NULL)
        return (NULL);

    verbose=true;

    /* Set the world coordinate system from the image header */
    cwcs = strchr (filename, '%');
    if (cwcs != NULL)
        cwcs++;
    wcs = wcsinitn (header, cwcs);
    if (wcs == NULL) {
        setwcsfile (filename);
        if (verbose)
            wcserr ();
    }
    free (header);

    return (wcs);
}

char * AstroUtils::GetFITShead(char * filename, int verbose)
{
    char *header;		/* FITS header */
    int lhead;			/* Maximum number of bytes in FITS header */
    char *irafheader;   /* IRAF image header */
    int nbiraf, nbfits;

    /* Open IRAF image if .imh extension is present */
    if (isiraf (filename)) {
        if ((irafheader = irafrhead (filename, &nbiraf)) != NULL) {
            if ((header = iraf2fits (filename, irafheader, nbiraf, &lhead)) == NULL) {
                if (verbose)
                    fprintf (stderr, "Cannot translate IRAF header %s\n",filename);
                free (irafheader);
                irafheader = NULL;
                return (NULL);
            }
            free (irafheader);
            irafheader = NULL;
        }
        else {
            if (verbose)
                fprintf (stderr, "Cannot read IRAF header file %s\n", filename);
            return (NULL);
        }
    }
    else if (istiff (filename) || isgif (filename) || isjpeg (filename)) {
        if ((header = fitsrtail (filename, &lhead, &nbfits)) == NULL) {
            if (verbose)
                fprintf (stderr, "TIFF file %s has no appended header\n", filename);
            return (NULL);
        }
    }

    /* Open FITS file if .imh extension is not present */
    else {
        if ((header = fitsrhead (filename, &lhead, &nbfits)) == NULL) {
            if (verbose)
                /* fprintf (stderr, "Cannot read FITS file %s\n", filename); */
                fitserr ();
            return (NULL);
        }
    }

    return (header);
}


/*
 * test_util のユニットテスト
 */

#include <iostream>
#include <boost/regex.hpp>
#include "Util.h"

int main(int argc, char** argv) {
  // 東京
  double lat0 = 35.65500;
  double lon0 = 139.74472;
  // 筑波
  double lat1 = 36.10056;
  double lon1 = 140.09111;
  // 距離
  double dist = geonlp::Util::latlonDist(lat0, lon0, lat1, lon1);
  // 国土地理院サイトによる正解との誤差
  double delta_gsi = dist - 58.501873;

  // 結果表示
  std::cout << "距離：" << dist << ", 国土地理院サイトとの誤差：" << delta_gsi << std::endl;

  // URL 分解のテスト
  std::string url = "http://www.info-proto.com/foo.cgi?key=20141009&t=abc";
  std::vector<std::string> result;
  if (geonlp::Util::split_url(url, result)) {
    std::cerr << "url:    " << result[0] << std::endl;
    std::cerr << "schema: " << result[1] << std::endl;
    std::cerr << "server: " << result[2] << std::endl;
    std::cerr << "port:   " << result[3] << std::endl;
    std::cerr << "fullpath: " << result[4] << std::endl;
    std::cerr << "path:   " << result[5] << std::endl;
    std::cerr << "param:  " << result[6] << std::endl;
  } else {
    std::cout << "URL が解析できませんでした。" << std::endl;
  }
}


#ifndef CAMCAP_HPP_
#define CAMCAP_HPP_
#define PI 3.14159265453
//dimensões dos quadrados do tabuleiro
#define CALIBRATION_SQUARE_DIMENSION 0.02629f
// número de intersecções do tabuleiro
#define CHESSBOARD_DIMENSION cv::Size(6,9)

#include "Strategy2/Strategy2.hpp"
#include "Strategy2/Attacker.h"
#include "Strategy2/Defender.hpp"
#include "Strategy2/Goalkeeper.hpp"
#include "opencv2/opencv.hpp"
#include <opencv2/core/core.hpp>
#include <opencv2/highgui/highgui.hpp>
#include <opencv2/imgproc/imgproc.hpp>
#include <opencv2/video/video.hpp>
#include <opencv2/video/tracking.hpp>

#include "StrategyGUI.hpp"
#include "RobotGUI.hpp"
#include "vision/vision.hpp"
#include "V4LInterface.hpp"
#include <boost/thread/thread.hpp>
#include <boost/thread/mutex.hpp>
#include <boost/bind.hpp>
#include <vector>
#include <cstdlib>
#include <ctgmath>
#include <gtkmm.h>
#include <cmath>
#include <fstream>
#include <chrono>
#include "LS.h"

#define MAX_THETA_TOLERATION 3
#define MAX_POSITIONING_VEL 0.1

class CamCap : public Gtk::HBox {

	public:
		Attacker attacker;
		Defender defender;
		Goalkeeper goalkeeper;

		Geometry::Point ball;
		Geometry::Point ball_est;

		std::array<Robot2 *, 3> robots;

		unsigned char *data;

		int width;
		int height;

		int frameCounter;

		boost::thread msg_thread;
		boost::condition_variable data_ready_cond;
		boost::mutex data_ready_mutex;
		bool data_ready_flag = false;
		bool ekf_data_ready = false;

		Strategy2 strategy;

		StrategyGUI strategyGUI;
		RobotGUI robotGUI;
		capture::V4LInterface interface;

		LS ls_x, ls_y;

		int fps_average = 0;
		std::chrono::time_point<std::chrono::high_resolution_clock> timer_start;

		cv::Point2f Ball_Est;

		Gtk::Frame fm;
		Gtk::Frame info_fm;
		Gtk::VBox camera_vbox;
		Gtk::Notebook notebook;

		sigc::connection con;

		void update_positions(const std::map<unsigned int, vision::Vision::RecognizedTag>& tags);
		bool start_signal(bool b);
		bool capture_and_show();
		void send_cmd_thread();
		void notify_data_ready(bool send_ekf_data);
		double distance(cv::Point a, cv::Point b);
		void calculate_ball_est();
		explicit CamCap(bool isLowRes);
		~CamCap() override;
};

#endif /* CAMCAP_HPP_ */


#include <iostream>
#include <fstream>
#include <string>
#include <algorithm>
#include <chrono>
#include <memory>
#include <cmath>
#include <signal.h>

#include <Eigen/Sparse>

#include <SmurffCpp/DataMatrices/Data.h>
#include <SmurffCpp/IO/MatrixIO.h>
#include <SmurffCpp/Utils/Distribution.h>

#include <SmurffCpp/Model.h>

#include <SmurffCpp/VMatrixIterator.hpp>
#include <SmurffCpp/ConstVMatrixIterator.hpp>
#include <SmurffCpp/VMatrixExprIterator.hpp>
#include <SmurffCpp/ConstVMatrixExprIterator.hpp>

#include <SmurffCpp/Utils/Error.h>
#include <SmurffCpp/Utils/StepFile.h>

#include <SmurffCpp/IO/GenericIO.h>

using namespace std;
using namespace smurff;

Model::Model()
   : m_num_latent(-1), m_dims(0)
{
}

//num_latent - size of latent dimention
//dims - dimentions of train data
//init_model_type - samples initialization type
void Model::init(int num_latent, const PVec<>& dims, ModelInitTypes model_init_type, bool save_model, bool aggregate)
{
   m_num_latent = num_latent;
   m_dims = dims;
   m_collect_aggr = aggregate;
   m_save_model = save_model;
   m_num_aggr = std::vector<int>(dims.size(), 0);

   for(size_t i = 0; i < dims.size(); ++i)
   {
      std::shared_ptr<Eigen::MatrixXd> mat(new Eigen::MatrixXd(m_num_latent, dims[i]));

      switch(model_init_type)
      {
      case ModelInitTypes::random:
         bmrandn(*mat);
         break;
      case ModelInitTypes::zero:
         mat->setZero();
         break;
      default:
         {
            THROWERROR("Invalid model init type");
         }
      }

      m_factors.push_back(mat);

      if (aggregate)
      {
         std::shared_ptr<Eigen::MatrixXd> aggr_sum(new Eigen::MatrixXd(m_num_latent, dims[i]));
         aggr_sum->setZero();
         m_aggr_sum.push_back(aggr_sum);
         std::shared_ptr<Eigen::MatrixXd> aggr_dot(new Eigen::MatrixXd(m_num_latent * m_num_latent, dims[i]));
         aggr_dot->setZero();
         m_aggr_dot.push_back(aggr_dot);
      }
   }

   m_link_matrices.resize(nmodes());
   m_mus.resize(nmodes());

   Pcache.init(Eigen::ArrayXd::Ones(m_num_latent));
}

void Model::setLinkMatrix(int mode,
   std::shared_ptr<Eigen::MatrixXd> link_matrix,
   std::shared_ptr<Eigen::VectorXd> mu
   )
{
   m_link_matrices.at(mode) = link_matrix;
   m_mus.at(mode) = mu;
}

double Model::predict(const PVec<> &pos) const
{
   if (nmodes() == 2)
   {
      return col(0, pos[0]).dot(col(1, pos[1]));
   }

   auto &P = Pcache.local();
   P.setOnes();
   for(uint32_t d = 0; d < nmodes(); ++d)
      P *= col(d, pos.at(d)).array();
   return P.sum();
}

const Eigen::MatrixXd &Model::U(uint32_t f) const
{
   return *m_factors.at(f);
}

Eigen::MatrixXd &Model::U(uint32_t f)
{
   return *m_factors[f];
}

VMatrixIterator<Eigen::MatrixXd> Model::Vbegin(std::uint32_t mode)
{
   return VMatrixIterator<Eigen::MatrixXd>(shared_from_this(), mode, 0);
}

VMatrixIterator<Eigen::MatrixXd> Model::Vend()
{
   return VMatrixIterator<Eigen::MatrixXd>(m_factors.size());
}

ConstVMatrixIterator<Eigen::MatrixXd> Model::CVbegin(std::uint32_t mode) const
{
   return ConstVMatrixIterator<Eigen::MatrixXd>(this, mode, 0);
}

ConstVMatrixIterator<Eigen::MatrixXd> Model::CVend() const
{
   return ConstVMatrixIterator<Eigen::MatrixXd>(m_factors.size());
}

Eigen::MatrixXd::ConstColXpr Model::col(int f, int i) const
{
   return U(f).col(i);
}

std::uint64_t Model::nmodes() const
{
   return m_factors.size();
}

int Model::nlatent() const
{
   return m_num_latent;
}

int Model::nsamples() const
{
   return std::accumulate(m_factors.begin(), m_factors.end(), 0,
      [](const int &a, const std::shared_ptr<Eigen::MatrixXd> &b) { return a + b->cols(); });
}

const PVec<>& Model::getDims() const
{
   return m_dims;
}

SubModel Model::full()
{
   return SubModel(*this);
}

void Model::updateAggr(int m, int i)
{
   if (!m_collect_aggr) return;

   const auto &r = col(m, i);
   Matrix cov = (r * r.transpose());
   m_aggr_sum.at(m)->col(i) += r;
   m_aggr_dot.at(m)->col(i) += Eigen::Map<Eigen::VectorXd>(cov.data(), nlatent() * nlatent());
}

void Model::updateAggr(int m)
{
   m_num_aggr.at(m)++;
}

void Model::save(std::shared_ptr<const StepFile> sf, bool saveAggr) const
{
   std::uint64_t i = 0;
   for (auto U : m_factors)
   {
      auto path = sf->makeModelFileName(i++);
      smurff::matrix_io::eigen::write_matrix(path, *U);
   }

   unsigned nmodes = sf->getNModes();
   if (m_collect_aggr && saveAggr)
   {
      for (std::uint64_t m = 0; m < nmodes; ++m)
      {
         double n = m_num_aggr.at(m);

         Eigen::MatrixXd &Usum = *m_aggr_sum.at(m);
         Eigen::MatrixXd &Uprod = *m_aggr_dot.at(m);

         Eigen::MatrixXd mu = Eigen::MatrixXd::Zero(Usum.rows(), Usum.cols());
         // inverse of the covariance
         Eigen::MatrixXd prec = Eigen::MatrixXd::Zero(Uprod.rows(), Uprod.cols());

         // calculate real mu and Lambda
         for (int i = 0; i < U(m).cols(); i++)
         {
            Eigen::VectorXd sum  = Usum.col(i);
            Eigen::MatrixXd prod = Eigen::Map<Eigen::MatrixXd>( Uprod.col(i).data(), nlatent(), nlatent());
            Eigen::MatrixXd prec_i = ((prod - (sum * sum.transpose() / n)) / (n - 1)).inverse();

            prec.col(i) = Eigen::Map<Eigen::VectorXd>(prec_i.data(), nlatent() * nlatent());
            mu.col(i) = sum / n;
         }

         std::string mu_path = sf->makePostMuFileName(m);
         smurff::matrix_io::eigen::write_matrix(mu_path, mu);

         std::string prec_path = sf->makePostLambdaFileName(m);
         smurff::matrix_io::eigen::write_matrix(prec_path, prec);
      }
   }
}

void Model::restore(std::shared_ptr<const StepFile> sf, int skip_mode)
{
   unsigned nmodes = sf->getNModes();
   m_factors.clear();
   m_dims = PVec<>(nmodes);

   for(std::uint64_t i = 0; i<nmodes; ++i)
   {
      auto U = std::make_shared<Eigen::MatrixXd>();
      std::string path = sf->getModelFileName(i);
      if ((int)i != skip_mode) {
          THROWERROR_FILE_NOT_EXIST(path);
          smurff::matrix_io::eigen::read_matrix(path, *U);
          m_dims.at(i) = U->cols();
          m_num_latent = U->rows();
      }
      else
      {
          m_dims.at(i) = -1;
      }
      m_factors.push_back(U);
   }

   m_link_matrices.resize(nmodes);
   m_mus.resize(nmodes);
   Pcache.init(Eigen::ArrayXd::Ones(m_num_latent));
}

std::ostream& Model::info(std::ostream &os, std::string indent) const
{
   os << indent << "Num-latents: " << m_num_latent << std::endl;
   return os;
}

std::ostream& Model::status(std::ostream &os, std::string indent) const
{
   os << indent << "  Umean: " << std::endl;
   for(std::uint64_t d = 0; d < nmodes(); ++d)
      os << indent << "    U(" << d << ").colwise().mean: "
         << U(d).rowwise().mean().transpose()
         << std::endl;

   return os;
}

Eigen::MatrixXd::ConstBlockXpr SubModel::U(int f) const
{
   const Eigen::MatrixXd &u = m_model.U(f); //force const
   return u.block(0, m_off.at(f), m_model.nlatent(), m_dims.at(f));
}

ConstVMatrixExprIterator<Eigen::MatrixXd::ConstBlockXpr> SubModel::CVbegin(std::uint32_t mode) const
{
   return ConstVMatrixExprIterator<Eigen::MatrixXd::ConstBlockXpr>(&m_model, m_off, m_dims, mode, 0);
}

ConstVMatrixExprIterator<Eigen::MatrixXd::ConstBlockXpr> SubModel::CVend() const
{
   return ConstVMatrixExprIterator<Eigen::MatrixXd::ConstBlockXpr>(m_model.nmodes());
}


// Software License for MTL
//
// Copyright (c) 2007 The Trustees of Indiana University.
//               2008 Dresden University of Technology and the Trustees of Indiana University.
//               2010 SimuNova UG (haftungsbeschränkt), www.simunova.com.
// All rights reserved.
// Authors: Peter Gottschling and Andrew Lumsdaine
//
// This file is part of the Matrix Template Library
//
// See also license.mtl.txt in the distribution.

#ifndef MTL_ARPREC_INCLUDE
#define MTL_ARPREC_INCLUDE

#ifdef MTL_HAS_ARPREC

#include <arprec/mp_real.h>
#include <arprec/mp_complex.h>

#include <boost/numeric/mtl/concept/magnitude.hpp>
#include <boost/numeric/mtl/utility/true_copy.hpp>
#include <boost/numeric/linear_algebra/identity.hpp>

namespace mtl {

    /// Specialization for ARPREC complex numbers
    template <>
    struct Magnitude<mp_complex>
    {
	/// The associated type is ARPREC real
	typedef mp_real type;
    };

    namespace traits {

	template <>
	struct true_copy<mp_real_temp>
	{
	    typedef mp_real type;
	};
    }
} // namespace mtl

namespace math {

    template <>
    struct identity_t< add<mp_complex>, mp_complex >
      : public std::binary_function<add<mp_complex>, mp_complex, mp_complex>
    {
	mp_complex operator() (const add<mp_complex>&, const mp_complex& /*ref*/) const
	{
	    return mp_complex("0", "0");
	}
    };

} // math

#endif // MTL_HAS_ARPREC

#endif // MTL_ARPREC_INCLUDE