/*-----------------------------------------------------------------------------+
Copyright (c) 2007-2011: Joachim Faulhaber
+------------------------------------------------------------------------------+
   Distributed under the Boost Software License, Version 1.0.
      (See accompanying file LICENCE.txt or copy at
           http://www.boost.org/LICENSE_1_0.txt)
+-----------------------------------------------------------------------------*/
#ifndef BOOST_ICL_MAP_HPP_JOFA_070519
#define BOOST_ICL_MAP_HPP_JOFA_070519

#include <boost/icl/impl_config.hpp>

#if defined(ICL_USE_BOOST_MOVE_IMPLEMENTATION)
#   include <boost/container/map.hpp>
#   include <boost/container/set.hpp>
#elif defined(ICL_USE_STD_IMPLEMENTATION)
#   include <map>
#   include <set>
#else // Default for implementing containers
#   include <map>
#   include <set>
#endif

#include <string>
#include <boost/type_traits/ice.hpp>
#include <boost/call_traits.hpp>
#include <boost/icl/detail/notate.hpp>
#include <boost/icl/detail/design_config.hpp>
#include <boost/icl/detail/concept_check.hpp>
#include <boost/icl/detail/on_absorbtion.hpp>
#include <boost/icl/type_traits/is_map.hpp>
#include <boost/icl/type_traits/absorbs_identities.hpp>
#include <boost/icl/type_traits/is_total.hpp>
#include <boost/icl/type_traits/is_element_container.hpp>
#include <boost/icl/type_traits/has_inverse.hpp>
#include <boost/icl/type_traits/to_string.hpp>
#include <boost/icl/functors.hpp>

#include <boost/icl/associative_element_container.hpp>

namespace boost{namespace icl
{

struct partial_absorber
{
    enum { absorbs_identities = true };
    enum { is_total = false };
};

template<>
inline std::string type_to_string<partial_absorber>::apply() { return "@0"; }

struct partial_enricher
{
    enum { absorbs_identities = false };
    enum { is_total = false };
};

template<>
inline std::string type_to_string<partial_enricher>::apply() { return "e0"; }

struct total_absorber
{
    enum { absorbs_identities = true };
    enum { is_total = true };
};

template<>
inline std::string type_to_string<total_absorber>::apply() { return "^0"; }

struct total_enricher
{
    enum { absorbs_identities = false };
    enum { is_total = true };
};

template<>
inline std::string type_to_string<total_enricher>::apply() { return "e^0"; }

/** \brief Addable, subractable and intersectable maps */
template
<
    typename DomainT,
    typename CodomainT,
    class Traits = icl::partial_absorber,
    ICL_COMPARE Compare = ICL_COMPARE_INSTANCE(ICL_COMPARE_DEFAULT, DomainT),
    ICL_COMBINE Combine = ICL_COMBINE_INSTANCE(icl::inplace_plus, CodomainT),
    ICL_SECTION Section = ICL_SECTION_INSTANCE(icl::inter_section, CodomainT),
    ICL_ALLOC   Alloc   = std::allocator
>
class map: private ICL_IMPL_SPACE::map<DomainT, CodomainT, ICL_COMPARE_DOMAIN(Compare,DomainT),
                                       Alloc<std::pair<const DomainT, CodomainT> > >
{
public:
    typedef Alloc<typename std::pair<const DomainT, CodomainT> >  allocator_type;

    typedef typename icl::map<DomainT,CodomainT,Traits, Compare,Combine,Section,Alloc> type;
    typedef typename ICL_IMPL_SPACE::map<DomainT, CodomainT, ICL_COMPARE_DOMAIN(Compare,DomainT),
                                         allocator_type>   base_type;

    typedef Traits traits;

public:
    typedef DomainT                                     domain_type;
    typedef typename boost::call_traits<DomainT>::param_type domain_param;
    typedef DomainT                                     key_type;
    typedef CodomainT                                   codomain_type;
    typedef CodomainT                                   mapped_type;
    typedef CodomainT                                   data_type;
    typedef std::pair<const DomainT, CodomainT>         element_type;
    typedef std::pair<const DomainT, CodomainT>         value_type;
    typedef ICL_COMPARE_DOMAIN(Compare,DomainT)         domain_compare;
    typedef ICL_COMBINE_CODOMAIN(Combine,CodomainT)     codomain_combine;
    typedef domain_compare                              key_compare;
    typedef ICL_COMPARE_DOMAIN(Compare,element_type)    element_compare;
    typedef typename inverse<codomain_combine >::type   inverse_codomain_combine;
    typedef typename mpl::if_
        <has_set_semantics<codomain_type>
        , ICL_SECTION_CODOMAIN(Section,CodomainT)
        , codomain_combine
        >::type                                         codomain_intersect;
    typedef typename inverse<codomain_intersect>::type  inverse_codomain_intersect;
    typedef typename base_type::value_compare           value_compare;

    typedef typename ICL_IMPL_SPACE::set<DomainT, domain_compare, Alloc<DomainT> > set_type;
    typedef set_type                                       key_object_type;

    BOOST_STATIC_CONSTANT(bool, _total   = (Traits::is_total));
    BOOST_STATIC_CONSTANT(bool, _absorbs = (Traits::absorbs_identities));
    BOOST_STATIC_CONSTANT(bool,
        total_invertible = (mpl::and_<is_total<type>, has_inverse<codomain_type> >::value));

    typedef on_absorbtion<type,codomain_combine,Traits::absorbs_identities>
                                                        on_identity_absorbtion;

public:
    typedef typename base_type::pointer                 pointer;
    typedef typename base_type::const_pointer           const_pointer;
    typedef typename base_type::reference               reference;
    typedef typename base_type::const_reference         const_reference;
    typedef typename base_type::iterator                iterator;
    typedef typename base_type::const_iterator          const_iterator;
    typedef typename base_type::size_type               size_type;
    typedef typename base_type::difference_type         difference_type;
    typedef typename base_type::reverse_iterator        reverse_iterator;
    typedef typename base_type::const_reverse_iterator  const_reverse_iterator;

public:
    BOOST_STATIC_CONSTANT(bool,
        is_total_invertible = (   Traits::is_total
                               && has_inverse<codomain_type>::value));

    BOOST_STATIC_CONSTANT(int, fineness = 4);

public:
    //==========================================================================
    //= Construct, copy, destruct
    //==========================================================================
    map()
    {
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((LessThanComparableConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<CodomainT>));
        BOOST_CONCEPT_ASSERT((EqualComparableConcept<CodomainT>));
    }

    map(const key_compare& comp): base_type(comp){}

    template <class InputIterator>
    map(InputIterator first, InputIterator past)
        : base_type(first,past){}

    template <class InputIterator>
    map(InputIterator first, InputIterator past, const key_compare& comp)
        : base_type(first,past,comp)
    {}

    map(const map& src)
        : base_type(src)
    {
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((LessThanComparableConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<CodomainT>));
        BOOST_CONCEPT_ASSERT((EqualComparableConcept<CodomainT>));
    }

    explicit map(const element_type& key_value_pair): base_type::map()
    {
        insert(key_value_pair);
    }

    map& operator = (const map& src)
    {
        base_type::operator=(src);
        return *this;
    }

#   ifndef BOOST_NO_CXX11_RVALUE_REFERENCES
    //==========================================================================
    //= Move semantics
    //==========================================================================

    map(map&& src)
        : base_type(boost::move(src))
    {
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((LessThanComparableConcept<DomainT>));
        BOOST_CONCEPT_ASSERT((DefaultConstructibleConcept<CodomainT>));
        BOOST_CONCEPT_ASSERT((EqualComparableConcept<CodomainT>));
    }

    map& operator = (map&& src)
    {
        base_type::operator=(src);
        return *this;
    }
    //==========================================================================
#   endif // BOOST_NO_CXX11_RVALUE_REFERENCES

    void swap(map& src) { base_type::swap(src); }

    //==========================================================================
    using base_type::empty;
    using base_type::clear;

    using base_type::begin;
    using base_type::end;
    using base_type::rbegin;
    using base_type::rend;

    using base_type::size;
    using base_type::max_size;

    using base_type::key_comp;
    using base_type::value_comp;

    using base_type::erase;
    using base_type::find;
    using base_type::count;

    using base_type::lower_bound;
    using base_type::upper_bound;
    using base_type::equal_range;

    using base_type::operator[];

public:
    //==========================================================================
    //= Containedness
    //==========================================================================

    template<class SubObject>
    bool contains(const SubObject& sub)const
    { return icl::contains(*this, sub); }

    bool within(const map& super)const
    { return icl::contains(super, *this); }

    //==========================================================================
    //= Size
    //==========================================================================
    /** \c iterative_size() yields the number of elements that is visited
        throu complete iteration. For interval sets \c iterative_size() is
        different from \c size(). */
    std::size_t iterative_size()const { return base_type::size(); }

    //==========================================================================
    //= Selection
    //==========================================================================

    /** Total select function. */
    codomain_type operator()(const domain_type& key)const
    {
        const_iterator it = find(key);
        return it==end() ? identity_element<codomain_type>::value()
                         : it->second;
    }

    //==========================================================================
    //= Addition
    //==========================================================================
    /** \c add inserts \c value_pair into the map if it's key does
        not exist in the map.
        If \c value_pairs's key value exists in the map, it's data
        value is added to the data value already found in the map. */
    map& add(const value_type& value_pair)
    {
        return _add<codomain_combine>(value_pair);
    }

    /** \c add add \c value_pair into the map using \c prior as a hint to
        insert \c value_pair after the position \c prior is pointing to. */
    iterator add(iterator prior, const value_type& value_pair)
    {
        return _add<codomain_combine>(prior, value_pair);
    }

    //==========================================================================
    //= Subtraction
    //==========================================================================
    /** If the \c value_pair's key value is in the map, it's data value is
        subtraced from the data value stored in the map. */
    map& subtract(const element_type& value_pair)
    {
        on_invertible<type, is_total_invertible>
            ::subtract(*this, value_pair);
        return *this;
    }

    map& subtract(const domain_type& key)
    {
        icl::erase(*this, key);
        return *this;
    }

    //==========================================================================
    //= Insertion, erasure
    //==========================================================================
    std::pair<iterator,bool> insert(const value_type& value_pair)
    {
        if(on_identity_absorbtion::is_absorbable(value_pair.second))
            return std::pair<iterator,bool>(end(),true);
        else
            return base_type::insert(value_pair);
    }

    iterator insert(iterator prior, const value_type& value_pair)
    {
        if(on_identity_absorbtion::is_absorbable(value_pair.second))
            return end();
        else
            return base_type::insert(prior, value_pair);
    }

    template<class Iterator>
    iterator insert(Iterator first, Iterator last)
    {
        iterator prior = end(), it = first;
        while(it != last)
            prior = this->insert(prior, *it++);
    }

    /** With <tt>key_value_pair = (k,v)</tt> set value \c v for key \c k */
    map& set(const element_type& key_value_pair)
    {
        return icl::set_at(*this, key_value_pair);
    }

    /** erase \c key_value_pair from the map.
        Erase only if, the exact value content \c val is stored for the given key. */
    size_type erase(const element_type& key_value_pair)
    {
        return icl::erase(*this, key_value_pair);
    }

    //==========================================================================
    //= Intersection
    //==========================================================================
    /** The intersection of \c key_value_pair and \c *this map is added to \c section. */
    void add_intersection(map& section, const element_type& key_value_pair)const
    {
        on_definedness<type, Traits::is_total>
            ::add_intersection(section, *this, key_value_pair);
    }

    //==========================================================================
    //= Symmetric difference
    //==========================================================================

    map& flip(const element_type& operand)
    {
        on_total_absorbable<type,_total,_absorbs>::flip(*this, operand);
        return *this;
    }

private:
    template<class Combiner>
    map& _add(const element_type& value_pair);

    template<class Combiner>
    iterator _add(iterator prior, const element_type& value_pair);

    template<class Combiner>
    map& _subtract(const element_type& value_pair);

    template<class FragmentT>
    void total_add_intersection(type& section, const FragmentT& fragment)const
    {
        section += *this;
        section.add(fragment);
    }

    void partial_add_intersection(type& section, const element_type& operand)const
    {
        const_iterator it_ = find(operand.first);
        if(it_ != end())
        {
            section.template _add<codomain_combine  >(*it_);
            section.template _add<codomain_intersect>(operand);
        }
    }

private:
    //--------------------------------------------------------------------------
    template<class Type, bool is_total_invertible>
    struct on_invertible;

    template<class Type>
    struct on_invertible<Type, true>
    {
        typedef typename Type::element_type element_type;
        typedef typename Type::inverse_codomain_combine inverse_codomain_combine;

        static void subtract(Type& object, const element_type& operand)
        { object.template _add<inverse_codomain_combine>(operand); }
    };

    template<class Type>
    struct on_invertible<Type, false>
    {
        typedef typename Type::element_type element_type;
        typedef typename Type::inverse_codomain_combine inverse_codomain_combine;

        static void subtract(Type& object, const element_type& operand)
        { object.template _subtract<inverse_codomain_combine>(operand); }
    };

    friend struct on_invertible<type, true>;
    friend struct on_invertible<type, false>;
    //--------------------------------------------------------------------------

    //--------------------------------------------------------------------------
    template<class Type, bool is_total>
    struct on_definedness;

    template<class Type>
    struct on_definedness<Type, true>
    {
        static void add_intersection(Type& section, const Type& object,
                                     const element_type& operand)
        { object.total_add_intersection(section, operand); }
    };

    template<class Type>
    struct on_definedness<Type, false>
    {
        static void add_intersection(Type& section, const Type& object,
                                     const element_type& operand)
        { object.partial_add_intersection(section, operand); }
    };

    friend struct on_definedness<type, true>;
    friend struct on_definedness<type, false>;
    //--------------------------------------------------------------------------

    //--------------------------------------------------------------------------
    template<class Type, bool has_set_semantics, bool absorbs_identities>
    struct on_codomain_model;

    template<class Type>
    struct on_codomain_model<Type, false, false>
    {                // !codomain_is_set, !absorbs_identities
        static void subtract(Type&, typename Type::iterator it_,
                              const typename Type::codomain_type& )
        { (*it_).second = identity_element<typename Type::codomain_type>::value(); }
    };

    template<class Type>
    struct on_codomain_model<Type, false, true>
    {                // !codomain_is_set, absorbs_identities
        static void subtract(Type& object, typename Type::iterator       it_,
                                     const typename Type::codomain_type&     )
        { object.erase(it_); }
    };

    template<class Type>
    struct on_codomain_model<Type, true, false>
    {               // !codomain_is_set, !absorbs_identities
        typedef typename Type::inverse_codomain_intersect inverse_codomain_intersect;
        static void subtract(Type&, typename Type::iterator       it_,
                              const typename Type::codomain_type& co_value)
        {
            inverse_codomain_intersect()((*it_).second, co_value);
        }
    };

    template<class Type>
    struct on_codomain_model<Type, true, true>
    {               // !codomain_is_set, absorbs_identities
        typedef typename Type::inverse_codomain_intersect inverse_codomain_intersect;
        static void subtract(Type& object, typename Type::iterator       it_,
                                     const typename Type::codomain_type& co_value)
        {
            inverse_codomain_intersect()((*it_).second, co_value);
            if((*it_).second == identity_element<codomain_type>::value())
                object.erase(it_);
        }
    };
    //--------------------------------------------------------------------------

    //--------------------------------------------------------------------------
    template<class Type, bool is_total, bool absorbs_identities>
    struct on_total_absorbable;

    template<class Type>
    struct on_total_absorbable<Type, true, true>
    {
        typedef typename Type::element_type  element_type;
        static void flip(Type& object, const typename Type::element_type&)
        { icl::clear(object); }
    };

    template<class Type>
    struct on_total_absorbable<Type, true, false>
    {
        typedef typename Type::element_type  element_type;
        typedef typename Type::codomain_type codomain_type;

        static void flip(Type& object, const element_type& operand)
        {
            object.add(operand);
            ICL_FORALL(typename Type, it_, object)
                (*it_).second = identity_element<codomain_type>::value();
        }
    };

    template<class Type>
    struct on_total_absorbable<Type, false, true>
    {                         // !is_total, absorbs_identities
        typedef typename Type::element_type   element_type;
        typedef typename Type::codomain_type  codomain_type;
        typedef typename Type::iterator       iterator;
        typedef typename Type::inverse_codomain_intersect inverse_codomain_intersect;

        static void flip(Type& object, const element_type& operand)
        {
            std::pair<iterator,bool> insertion = object.insert(operand);
            if(!insertion.second)
                on_codomain_model<Type, has_set_semantics<codomain_type>::value, true>
                ::subtract(object, insertion.first, operand.second);
        }
    };

    template<class Type>
    struct on_total_absorbable<Type, false, false>
    {                         // !is_total  !absorbs_identities
        typedef typename Type::element_type   element_type;
        typedef typename Type::codomain_type  codomain_type;
        typedef typename Type::iterator       iterator;
        typedef typename Type::inverse_codomain_intersect inverse_codomain_intersect;

        static void flip(Type& object, const element_type& operand)
        {
            std::pair<iterator,bool> insertion = object.insert(operand);
            if(!insertion.second)
                on_codomain_model<Type, has_set_semantics<codomain_type>::value, false>
                ::subtract(object, insertion.first, operand.second);
        }
    };

    friend struct on_total_absorbable<type, true,  true >;
    friend struct on_total_absorbable<type, false, true >;
    friend struct on_total_absorbable<type, true,  false>;
    friend struct on_total_absorbable<type, false, false>;
    //--------------------------------------------------------------------------
};

//==============================================================================
//= Addition<ElementMap>
//==============================================================================
template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
    template <class Combiner>
map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>&
    map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>
    ::_add(const element_type& addend)
{
    typedef typename on_absorbtion
        <type,Combiner,absorbs_identities<type>::value>::type on_absorbtion_;

    const codomain_type& co_val    = addend.second;
    if(on_absorbtion_::is_absorbable(co_val))
        return *this;

    std::pair<iterator,bool> insertion
        = base_type::insert(value_type(addend.first, version<Combiner>()(co_val)));

    if(!insertion.second)
    {
        iterator it = insertion.first;
        Combiner()((*it).second, co_val);

        if(on_absorbtion_::is_absorbable((*it).second))
            erase(it);
    }
    return *this;
}

template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
    template <class Combiner>
typename map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>::iterator
    map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>
    ::_add(iterator prior_, const value_type& addend)
{
    typedef typename on_absorbtion
        <type,Combiner,absorbs_identities<type>::value>::type on_absorbtion_;

    const codomain_type& co_val    = addend.second;
    if(on_absorbtion_::is_absorbable(co_val))
        return end();

    iterator inserted_
        = base_type::insert(prior_,
                            value_type(addend.first, Combiner::identity_element()));
    Combiner()((*inserted_).second, addend.second);

    if(on_absorbtion_::is_absorbable((*inserted_).second))
    {
        erase(inserted_);
        return end();
    }
    else
        return inserted_;
}

//==============================================================================
//= Subtraction<ElementMap>
//==============================================================================
template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
    template <class Combiner>
map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>&
    map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc>::_subtract(const value_type& minuend)
{
    typedef typename on_absorbtion
        <type,Combiner,absorbs_identities<type>::value>::type on_absorbtion_;

    iterator it_ = find(minuend.first);
    if(it_ != end())
    {
        Combiner()((*it_).second, minuend.second);
        if(on_absorbtion_::is_absorbable((*it_).second))
            erase(it_);
    }
    return *this;
}

//-----------------------------------------------------------------------------
// type traits
//-----------------------------------------------------------------------------
template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
struct is_map<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> >
{
    typedef is_map<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> > type;
    BOOST_STATIC_CONSTANT(bool, value = true);
};

template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
struct has_inverse<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> >
{
    typedef has_inverse<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> > type;
    BOOST_STATIC_CONSTANT(bool, value = (has_inverse<CodomainT>::value));
};

template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
struct absorbs_identities<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> >
{
    typedef absorbs_identities type;
    BOOST_STATIC_CONSTANT(int, value = Traits::absorbs_identities);
};

template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
struct is_total<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> >
{
    typedef is_total type;
    BOOST_STATIC_CONSTANT(int, value = Traits::is_total);
};

template <class DomainT, class CodomainT, class Traits, ICL_COMPARE Compare, ICL_COMBINE Combine, ICL_SECTION Section, ICL_ALLOC Alloc>
struct type_to_string<icl::map<DomainT,CodomainT,Traits,Compare,Combine,Section,Alloc> >
{
    static std::string apply()
    {
        return "map<"+ type_to_string<DomainT>::apply()  + ","
                     + type_to_string<CodomainT>::apply() + ","
                     + type_to_string<Traits>::apply() +">";
    }
};

}} // namespace icl boost

#endif // BOOST_ICL_MAP_HPP_JOFA_070519


// maya
// Copyright (c) 2012-2016, Joshua Scoggins
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:
//     * Redistributions of source code must retain the above copyright
//       notice, this list of conditions and the following disclaimer.
//     * Redistributions in binary form must reproduce the above copyright
//       notice, this list of conditions and the following disclaimer in the
//       documentation and/or other materials provided with the distribution.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
// DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
// ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
// ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
extern "C" {
#include "clips.h"
}
#include "mayasetup.h"
#include "boost.h"
#include <string>
#include <boost/algorithm/string/predicate.hpp>
#include <boost/algorithm/string/trim.hpp>
#include <boost/uuid/uuid.hpp>
#include <boost/uuid/random_generator.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/uuid/uuid_io.hpp>
#include <boost/math/common_factor.hpp>
#include <boost/filesystem.hpp>
#include <boost/system/error_code.hpp>
#include <boost/algorithm/clamp.hpp>

#if BOOST_EXTENSIONS
void HasPrefix(Environment*, UDFContext*, UDFValue*);
void HasSuffix(Environment*, UDFContext*, UDFValue*);
void TrimString(Environment*, UDFContext*, UDFValue*);
void TrimStringFront(Environment*, UDFContext*, UDFValue*);
void TrimStringBack(Environment*, UDFContext*, UDFValue*);
void NewUUID(Environment*, UDFContext*, UDFValue*);
void gcdFunction(Environment*, UDFContext*, UDFValue*);
void lcmFunction(Environment*, UDFContext*, UDFValue*);
void FileExists(Environment*, UDFContext*, UDFValue*);
void IsDirectory(Environment*, UDFContext*, UDFValue*);
void IsRegularFile(Environment*, UDFContext*, UDFValue*);
void ClampValue(Environment*, UDFContext*, UDFValue*);
#endif

extern "C" void InstallBoostExtensions(Environment* theEnv) {
#if BOOST_EXTENSIONS
	AddUDF(theEnv, "has-prefix", "b", 2, 2, "sy;sy;sy", HasPrefix, "HasPrefix",  NULL);
	AddUDF(theEnv, "has-suffix", "b", 2, 2, "sy;sy;sy", HasSuffix, "HasSuffix",  NULL);
	AddUDF(theEnv, "string-trim", "y", 1, 1, "s", TrimString, "TrimString", NULL);
	AddUDF(theEnv, "string-trim-front", "y", 1, 1, "s", TrimStringFront, "TrimStringFront", NULL);
	AddUDF(theEnv, "string-trim-back", "y",  1, 1, "s", TrimStringBack, "TrimStringBack", NULL);
	AddUDF(theEnv, "new-uuid", "s", 0, 0, "", NewUUID, "NewUUID", NULL);
	AddUDF(theEnv, "gcd", "l",  2, 2, "l;l;l", gcdFunction, "gcdFunction", NULL);
	AddUDF(theEnv, "lcm", "l",  2, 2, "l;l;l", lcmFunction, "lcmFunction", NULL);
	AddUDF(theEnv, "path-exists",   "b", 1, 1, "sy", FileExists, "FileExists", NULL);
	AddUDF(theEnv, "directoryp",    "b", 1, 1, "sy", IsDirectory, "IsDirectory", NULL);
	AddUDF(theEnv, "regular-filep", "b", 1, 1, "sy", IsRegularFile, "IsRegularFile", NULL);
	AddUDF(theEnv, "clamp", "l",  3, 3, "l;l;l;l", ClampValue, "ClampValue", NULL);
#endif
}

#if BOOST_EXTENSIONS
void ClampValue(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue v, lo, hi;
	if (!UDFFirstArgument(context, INTEGER_BIT,  &v)) {
		ret->lexemeValue = FalseSymbol(env);
	} else if (!UDFNextArgument(context, INTEGER_BIT, &lo)) {
		ret->lexemeValue = FalseSymbol(env);
	} else if (!UDFNextArgument(context, INTEGER_BIT, &hi)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		ret->integerValue = CreateInteger(env, boost::algorithm::clamp(CVCoerceToInteger(&v), CVCoerceToInteger(&lo), CVCoerceToInteger(&hi)));
	}
}

void FileExists(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue path;
	if (!UDFFirstArgument(context, LEXEME_BITS, &path)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string p(path.lexemeValue->contents);
		ret->lexemeValue = boost::filesystem::exists(p) ? TrueSymbol(env) : FalseSymbol(env);
	}
}

void IsDirectory(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue path;
	if (!UDFFirstArgument(context, LEXEME_BITS, &path)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string p(path.lexemeValue->contents);
		ret->lexemeValue = boost::filesystem::is_directory(p) ? TrueSymbol(env) : FalseSymbol(env);
	}
}

void IsRegularFile(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue path;
	if (!UDFFirstArgument(context, LEXEME_BITS, &path)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string p(path.lexemeValue->contents);
		ret->lexemeValue = boost::filesystem::is_regular_file(p) ? TrueSymbol(env) : FalseSymbol(env);
	}
}

void gcdFunction(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue first, second;
	if (!UDFFirstArgument(context, INTEGER_BIT, &first)) {
		ret->lexemeValue = FalseSymbol(env);
	} else if (!UDFNextArgument(context, INTEGER_BIT, &second)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		ret->integerValue = CreateInteger(env, boost::math::gcd(CVCoerceToInteger(&first), CVCoerceToInteger(&second)));
	}
}
void lcmFunction(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue first, second;
	if (!UDFFirstArgument(context, INTEGER_BIT, &first)) {
		ret->lexemeValue = FalseSymbol(env);
	} else if (!UDFNextArgument(context, INTEGER_BIT, &second)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		ret->integerValue = CreateInteger(env, boost::math::lcm(CVCoerceToInteger(&first), CVCoerceToInteger(&second)));
	}
}
void NewUUID(Environment* env, UDFContext* context, UDFValue* ret) {
	boost::uuids::random_generator rgen;
	boost::uuids::uuid theUUID(rgen());
	const std::string tmp = boost::lexical_cast<std::string>(theUUID);
	ret->value = CreateSymbol(env, tmp.c_str());
}
void HasPrefix(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue data, prefix;
	if (!UDFFirstArgument(context, LEXEME_BITS, &data)) {
		ret->lexemeValue = FalseSymbol(env);
		return;
	} else if (!UDFNextArgument(context, LEXEME_BITS, &prefix)) {
		ret->lexemeValue = FalseSymbol(env);
		return;
	}
	std::string dataStr(data.lexemeValue->contents);
	std::string prefixStr(prefix.lexemeValue->contents);
	ret->lexemeValue = boost::starts_with(dataStr, prefixStr) ? TrueSymbol(env) : FalseSymbol(env);
}

void HasSuffix(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue data, suffix;
	if (!UDFFirstArgument(context, LEXEME_BITS, &data)) {
		ret->lexemeValue = FalseSymbol(env);
		return;
	} else if (!UDFNextArgument(context, LEXEME_BITS, &suffix)) {
		ret->lexemeValue = FalseSymbol(env);
		return;
	}
	std::string dataStr(data.lexemeValue->contents);
	std::string suffixStr(suffix.lexemeValue->contents);
	ret->lexemeValue = boost::ends_with(dataStr, suffixStr) ? TrueSymbol(env) : FalseSymbol(env);
}
void TrimString(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue str;
	if (!UDFFirstArgument(context, STRING_BIT, &str)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string tmp(str.lexemeValue->contents);
		boost::algorithm::trim(tmp);
		ret->value = CreateString(env, tmp.c_str());
	}
}
void TrimStringFront(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue str;
	if (!UDFFirstArgument(context, STRING_BIT, &str)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string tmp(str.lexemeValue->contents);
		boost::algorithm::trim_left(tmp);
		ret->value = CreateString(env, tmp.c_str());
	}
}
void TrimStringBack(Environment* env, UDFContext* context, UDFValue* ret) {
	UDFValue str;
	if (!UDFFirstArgument(context, STRING_BIT, &str)) {
		ret->lexemeValue = FalseSymbol(env);
	} else {
		std::string tmp(str.lexemeValue->contents);
		boost::algorithm::trim_right(tmp);
		ret->value = CreateString(env, tmp.c_str());
	}
}
#endif


// BSD 3-Clause License

// Copyright (c) 2020, Chenyu
// All rights reserved.

// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are met:

// 1. Redistributions of source code must retain the above copyright notice,
// this
//    list of conditions and the following disclaimer.

// 2. Redistributions in binary form must reproduce the above copyright notice,
//    this list of conditions and the following disclaimer in the documentation
//    and/or other materials provided with the distribution.

// 3. Neither the name of the copyright holder nor the names of its
//    contributors may be used to endorse or promote products derived from
//    this software without specific prior written permission.

// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.

#include "base/track_selection.h"

#include <Eigen/Core>
#include <algorithm>
#include <unordered_set>
#include <utility>

#include "base/camera.h"
#include "base/image.h"
#include "base/projection.h"
#include "util/hash.h"

using namespace std;

namespace {

// Track statistics are the track length and mean reprojection error.
typedef pair<int, double> TrackStatistics;
typedef pair<point3D_t, TrackStatistics> GridCellElement;
typedef unordered_map<Eigen::Vector2i, vector<GridCellElement>> ImageGrid;

// Sorts the grid cell elements by the track statistics, which will sort first
// by the (truncated) track length, then by the mean reprojection error.
bool CompareGridCellElements(const pair<point3D_t, TrackStatistics>& element1,
                             const pair<point3D_t, TrackStatistics>& element2) {
  return element1.second < element2.second;
}

// Compute the reprojrection error and truncated track length for the
// specific track.
TrackStatistics ComputeStatisticsForTrack(
    const Reconstruction& reconstruction, const point3D_t track_id,
    const int long_track_length_threshold) {
  // Any track that reach this function are guaranteed to exist and
  // be estimated, so no need to check for that here.
  const Point3D& point3d = reconstruction.Point3D(track_id);
  const std::vector<TrackElement> track_elements = point3d.Track().Elements();
  std::vector<image_t> images_observing_track;
  for (const auto track_element : track_elements) {
    images_observing_track.push_back(track_element.image_id);
  }

  double sq_reprojection_error_sum = 0.0;
  int num_valid_reprojections = 0;
  // Compute the squared reprojection error for each view that observes this
  // track and add it to the accumulating sum.
  for (const TrackElement element : track_elements) {
    if (!reconstruction.IsImageRegistered(element.image_id)) {
      continue;
    }

    const Image& image = reconstruction.Image(element.image_id);
    const Camera& camera = reconstruction.Camera(image.CameraId());
    const Point2D& point2d = image.Point2D(element.point2D_idx);

    sq_reprojection_error_sum += colmap::CalculateSquaredReprojectionError(
        point2d.XY(), point3d.XYZ(), image.Qvec(), image.Tvec(), camera);
    num_valid_reprojections++;
  }

  // Compute and return the track statistics.
  const int truncated_track_length =
      std::min(num_valid_reprojections, long_track_length_threshold);
  const double mean_sq_reprojection_error =
      sq_reprojection_error_sum / static_cast<double>(num_valid_reprojections);

  return TrackStatistics(truncated_track_length, mean_sq_reprojection_error);
}

// Compute the mean reprojection error and the truncated track length of each
// track. We truncate the track length based on the observation that while
// larger track lengths provide better constraints for bundle adjustment, larger
// tracks are also more likely to contain outliers in our experience. Truncating
// the track lengths enforces that the long tracks with the lowest reprojection
// error are chosen.
void ComputeTrackStatistics(
    const Reconstruction& reconstruction,
    const std::unordered_set<image_t> image_ids,
    const int long_track_length_threshold,
    std::unordered_map<point3D_t, TrackStatistics>* track_statistics) {
  // Iterate over all registered views and compute the track statistics for each
  // track we encounter.
  for (const image_t image_id : image_ids) {
    const Image image = reconstruction.Image(image_id);
    const std::vector<Point2D> points2d = image.Points2D();
    // Compute statistics for each point that has point3d and compute the
    // track statistics for each track we encounter.
    for (const Point2D& point2d : points2d) {
      if (!point2d.HasPoint3D()) {
        continue;
      }
      const point3D_t track_id = point2d.Point3DId();
      if (track_statistics->count(track_id) != 0) {
        continue;
      }

      // Compute the track statistics and add it to the output map.
      const TrackStatistics& statistics_for_this_track =
          ComputeStatisticsForTrack(reconstruction, track_id,
                                    long_track_length_threshold);
      track_statistics->emplace(track_id, statistics_for_this_track);
    }
  }
}

// Select tracks from the image to ensure good spatial coverage of the image. To
// do this, we first bin the tracks into grid cells in an image grid. Then
// within each cell we find the best ranked track and add it to the list of
// tracks to optimize.
void SelectBestTracksFromEachImageGridCell(
    const Reconstruction& reconstruction, const Image& image,
    const int grid_cell_size,
    const std::unordered_map<point3D_t, TrackStatistics>& track_statistics,
    std::unordered_set<point3D_t>* tracks_to_optimize) {
  static const double inv_grid_cell_size = 1.0 / grid_cell_size;

  // Hash each feature into a grid cell.
  ImageGrid image_grid;
  const std::vector<Point2D> points2d = image.Points2D();
  for (const Point2D& point2d : points2d) {
    if (!point2d.HasPoint3D()) {
      continue;
    }

    const Eigen::Vector2d feature = point2d.XY();
    const point3D_t track_id = point2d.Point3DId();

    if (track_statistics.count(track_id) == 0) {
      continue;
    }

    const TrackStatistics& current_track_statistics =
        track_statistics.at(track_id);
    const Eigen::Vector2i grid_cell =
        (feature * inv_grid_cell_size).cast<int>();

    image_grid[grid_cell].emplace_back(track_id, current_track_statistics);
  }

  // Select the best feature from each grid cell and add it to the tracks to
  // optimize.
  for (auto& grid_cell : image_grid) {
    // Order the features in each cell by track length first, then mean
    // reprojection error.
    const GridCellElement& grid_cell_element =
        *std::min_element(grid_cell.second.begin(), grid_cell.second.end(),
                          CompareGridCellElements);
    // Insert the track id in to the tracks to optimize.
    tracks_to_optimize->emplace(grid_cell_element.first);
  }
}

// Selects the top ranked tracks that have not already been chosen until the
// view observes the minimum number of optimized tracks.
void SelectTopRankedTracksInView(
    const Reconstruction& reconstruction,
    const std::unordered_map<point3D_t, TrackStatistics>& track_statistics,
    const Image& image, const int min_num_optimized_tracks_per_view,
    std::unordered_set<point3D_t>* tracks_to_optimize) {
  int num_optimized_tracks = 0;
  int num_estimated_tracks = 0;
  std::vector<GridCellElement> ranked_candidate_tracks;

  const std::vector<Point2D> points2d = image.Points2D();
  for (const Point2D& point2d : points2d) {
    if (!point2d.HasPoint3D()) {
      continue;
    }

    // We only reach this point if the track is estimated.
    num_estimated_tracks++;

    const point3D_t track_id = point2d.Point3DId();
    // If the track is already slated for optimization, increase the count of
    // optimized features.
    if (tracks_to_optimize->count(track_id) != 0) {
      num_optimized_tracks++;
      // If the number of optimized tracks is greater than the minimum then
      // we can return early since we know that no more features need to added
      // for this image.
      if (num_optimized_tracks >= min_num_optimized_tracks_per_view) {
        return;
      }
    } else {
      // If the track is not already set to be optimized then add it to the list
      // of candidate tracks.
      if (track_statistics.count(track_id) != 0) {
        ranked_candidate_tracks.emplace_back(track_id,
                                             track_statistics.at(track_id));
      }
    }
  }

  // We only reach this point if the number of optimized tracks is less than the
  // minimum. If that is the case then we add the top candidate features until
  // the minimum number of features observed is met.
  if (num_optimized_tracks != num_estimated_tracks) {
    // Select how many tracks to add. If we need more tracks than are estimated
    // then we simply add all remaining features.
    const int num_optimized_tracks_needed =
        std::min(min_num_optimized_tracks_per_view, num_estimated_tracks) -
        num_optimized_tracks;
    std::partial_sort(
        ranked_candidate_tracks.begin(),
        ranked_candidate_tracks.begin() + num_optimized_tracks_needed,
        ranked_candidate_tracks.end());
    // Add the candidate tracks to the list of tracks to be optimized.
    for (int i = 0; i < num_optimized_tracks_needed; i++) {
      tracks_to_optimize->emplace(ranked_candidate_tracks[i].first);
    }
  }
}

}  // namespace

namespace DAGSfM {

// The efficiency of large scale bundle adjustment can be dramatically increased
// by choosing only a subset of 3d points to optimize, as the 3d points tend to
// have increasing scene redundancy. If the points are chosen in a way that
// Properly constraints the nonlinear optimization, similar results in quality
// may be observed compared to optimized all tracks.
//
// The 3d points are chosen such that they fit the following criteria:
//    a) High confidence (i.e. low reprojection error).
//    b) Longer tracks are preferred.
//    c) The tracks used for optimization provide a good spatial coverage in
//    each image.
bool SelectGoodTracksForBundleAdjustment(
    const Reconstruction& reconstruction, const int long_track_length_threshold,
    const int image_grid_cell_size_pixels,
    const int min_num_optimized_tracks_per_view,
    std::unordered_set<point3D_t>* tracks_to_optimize) {
  // Get the registered image ids.
  std::vector<image_t> vec_reg_image_ids = reconstruction.RegImageIds();
  std::unordered_set<image_t> reg_image_ids(vec_reg_image_ids.begin(),
                                            vec_reg_image_ids.end());

  return SelectGoodTracksForBundleAdjustment(
      reconstruction, reg_image_ids, long_track_length_threshold,
      image_grid_cell_size_pixels, min_num_optimized_tracks_per_view,
      tracks_to_optimize);
}

bool SelectGoodTracksForBundleAdjustment(
    const Reconstruction& reconstruction,
    const std::unordered_set<image_t>& image_ids,
    const int long_track_length_threshold,
    const int image_grid_cell_size_pixels,
    const int min_num_optimized_tracks_per_view,
    std::unordered_set<point3D_t>* tracks_to_optimize) {
  // Compute the track mean reprojection errors.
  std::unordered_map<point3D_t, TrackStatistics> track_statistics;
  ComputeTrackStatistics(reconstruction, image_ids, long_track_length_threshold,
                         &track_statistics);

  // For each image, divide the image into a grid and choose the highest quality
  // tracks from each grid cell. This encourages good spatial coverage of tracks
  // within each image.
  for (const image_t image_id : image_ids) {
    const Image& image = reconstruction.Image(image_id);

    // Select the best tracks from each grid cell in the image and add them to
    // the container of tracks to be optimized.
    SelectBestTracksFromEachImageGridCell(reconstruction, image,
                                          image_grid_cell_size_pixels,
                                          track_statistics, tracks_to_optimize);
  }

  // To this point, we have only added features that have as full spatial
  // coverage as possible within each image but we have not ensured that
  // each image is constrained by at least K features. So, we cycle through all
  // images again and add the top M tracks that have not already been added.
  for (const image_t image_id : image_ids) {
    const Image& image = reconstruction.Image(image_id);

    // If this view is not constrained by enough optimized tracks, add the top
    // ranked features until there are enough tracks constraining the view.
    SelectTopRankedTracksInView(reconstruction, track_statistics, image,
                                min_num_optimized_tracks_per_view,
                                tracks_to_optimize);
  }

  return true;
}

}  // namespace DAGSfM


// (C) Copyright 2007 Andrew Sutton
//
// Use, modification and distribution are subject to the
// Boost Software License, Version 1.0 (See accompanying file
// LICENSE_1_0.txt or http://www.boost.org/LICENSE_1_0.txt)

#ifndef BOOST_GRAPH_DETAIL_GEODESIC_HPP
#define BOOST_GRAPH_DETAIL_GEODESIC_HPP

#include <functional>
#include <boost/config.hpp>
#include <boost/graph/graph_concepts.hpp>
#include <boost/graph/numeric_values.hpp>
#include <boost/concept/assert.hpp>

// TODO: Should this really be in detail?

namespace boost
{
// This is a very good discussion on centrality measures. While I can't
// say that this has been the motivating factor for the design and
// implementation of ths centrality framework, it does provide a single
// point of reference for defining things like degree and closeness
// centrality. Plus, the bibliography seems fairly complete.
//
//     @article{citeulike:1144245,
//         author = {Borgatti, Stephen  P. and Everett, Martin  G.},
//         citeulike-article-id = {1144245},
//         doi = {10.1016/j.socnet.2005.11.005},
//         journal = {Social Networks},
//         month = {October},
//         number = {4},
//         pages = {466--484},
//         priority = {0},
//         title = {A Graph-theoretic perspective on centrality},
//         url = {https://doi.org/10.1016/j.socnet.2005.11.005},
//             volume = {28},
//             year = {2006}
//         }
//     }

namespace detail {
    // Note that this assumes T == property_traits<DistanceMap>::value_type
    // and that the args and return of combine are also T.
    template <typename Graph,
                typename DistanceMap,
                typename Combinator,
                typename Distance>
    inline Distance
    combine_distances(const Graph& g,
                        DistanceMap dist,
                        Combinator combine,
                        Distance init)
    {
        BOOST_CONCEPT_ASSERT(( VertexListGraphConcept<Graph> ));
        typedef typename graph_traits<Graph>::vertex_descriptor Vertex;
        typedef typename graph_traits<Graph>::vertex_iterator VertexIterator;
        BOOST_CONCEPT_ASSERT(( ReadablePropertyMapConcept<DistanceMap,Vertex> ));
        BOOST_CONCEPT_ASSERT(( NumericValueConcept<Distance> ));
        typedef numeric_values<Distance> DistanceNumbers;
        BOOST_CONCEPT_ASSERT(( AdaptableBinaryFunction<Combinator,Distance,Distance,Distance> ));

        // If there's ever an infinite distance, then we simply return
        // infinity. Note that this /will/ include the a non-zero
        // distance-to-self in the combined values. However, this is usually
        // zero, so it shouldn't be too problematic.
        Distance ret = init;
        VertexIterator i, end;
        for(boost::tie(i, end) = vertices(g); i != end; ++i) {
            Vertex v = *i;
            if(get(dist, v) != DistanceNumbers::infinity()) {
                ret = combine(ret, get(dist, v));
            }
            else {
                ret = DistanceNumbers::infinity();
                break;
            }
        }
        return ret;
    }

    // Similar to std::plus<T>, but maximizes parameters
    // rather than adding them.
    template <typename T>
    struct maximize
    {
        typedef T result_type;
        typedef T first_argument_type;
        typedef T second_argument_type;
        T operator ()(T x, T y) const
        { BOOST_USING_STD_MAX(); return max BOOST_PREVENT_MACRO_SUBSTITUTION (x, y); }
    };

    // Another helper, like maximize() to help abstract functional
    // concepts. This is trivially instantiated for builtin numeric
    // types, but should be specialized for those types that have
    // discrete notions of reciprocals.
    template <typename T>
    struct reciprocal
    {
        typedef T result_type;
        typedef T argument_type;
        T operator ()(T t)
        { return T(1) / t; }
    };
} /* namespace detail */

// This type defines the basic facilities used for computing values
// based on the geodesic distances between vertices. Examples include
// closeness centrality and mean geodesic distance.
template <typename Graph, typename DistanceType, typename ResultType>
struct geodesic_measure
{
    typedef DistanceType distance_type;
    typedef ResultType result_type;
    typedef typename graph_traits<Graph>::vertices_size_type size_type;

    typedef numeric_values<distance_type> distance_values;
    typedef numeric_values<result_type> result_values;

    static inline distance_type infinite_distance()
    { return distance_values::infinity(); }

    static inline result_type infinite_result()
    { return result_values::infinity(); }

    static inline result_type zero_result()
    { return result_values::zero(); }
};

} /* namespace boost */

#endif


// ----------------------------------------------------------------------------
// -                        Open3D: www.open3d.org                            -
// ----------------------------------------------------------------------------
// The MIT License (MIT)
//
// Copyright (c) 2018 www.open3d.org
//
// 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.
// ----------------------------------------------------------------------------

#include "Open3D/Geometry/BoundingVolume.h"
#include "Open3D/Geometry/PointCloud.h"
#include "Open3D/Geometry/Qhull.h"
#include "Open3D/Geometry/TriangleMesh.h"
#include "Open3D/Utility/Console.h"

#include <numeric>

#include <Eigen/Eigenvalues>

namespace open3d {
namespace geometry {

OrientedBoundingBox& OrientedBoundingBox::Clear() {
    center_.setZero();
    x_axis_.setZero();
    y_axis_.setZero();
    z_axis_.setZero();
    return *this;
}

bool OrientedBoundingBox::IsEmpty() const { return Volume() == 0; }

Eigen::Vector3d OrientedBoundingBox::GetMinBound() const {
    auto points = GetBoxPoints();
    return ComputeMinBound(points);
}

Eigen::Vector3d OrientedBoundingBox::GetMaxBound() const {
    auto points = GetBoxPoints();
    return ComputeMaxBound(points);
}

Eigen::Vector3d OrientedBoundingBox::GetCenter() const { return center_; }

AxisAlignedBoundingBox OrientedBoundingBox::GetAxisAlignedBoundingBox() const {
    return AxisAlignedBoundingBox::CreateFromPoints(GetBoxPoints());
}

OrientedBoundingBox OrientedBoundingBox::GetOrientedBoundingBox() const {
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Transform(
        const Eigen::Matrix4d& transformation) {
    Eigen::Vector4d c;
    c << center_, 1;
    Eigen::Vector4d x;
    x << center_ + x_axis_, 1;
    Eigen::Vector4d y;
    y << center_ + y_axis_, 1;
    Eigen::Vector4d z;
    z << center_ + z_axis_, 1;
    c = transformation * c;
    x = transformation * x;
    y = transformation * y;
    z = transformation * z;
    center_ = c.head<3>() / c(3);
    x_axis_ = x.head<3>() / x(3) - center_;
    y_axis_ = y.head<3>() / y(3) - center_;
    z_axis_ = z.head<3>() / z(3) - center_;
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Translate(
        const Eigen::Vector3d& translation, bool relative) {
    if (relative) {
        center_ += translation;
    } else {
        center_ = translation;
    }
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Scale(const double scale,
                                                bool center) {
    if (center) {
        x_axis_ *= scale;
        y_axis_ *= scale;
        z_axis_ *= scale;
    } else {
        Eigen::Vector3d x = scale * (center_ + x_axis_);
        Eigen::Vector3d y = scale * (center_ + y_axis_);
        Eigen::Vector3d z = scale * (center_ + z_axis_);
        center_ *= scale;
        x_axis_ = x - center_;
        y_axis_ = y - center_;
        z_axis_ = z - center_;
    }
    return *this;
}

OrientedBoundingBox& OrientedBoundingBox::Rotate(
        const Eigen::Vector3d& rotation, bool center, RotationType type) {
    const Eigen::Matrix3d R = GetRotationMatrix(rotation, type);
    if (center) {
        x_axis_ = R * x_axis_;
        y_axis_ = R * y_axis_;
        z_axis_ = R * z_axis_;
    } else {
        Eigen::Vector3d x = R * (center_ + x_axis_);
        Eigen::Vector3d y = R * (center_ + y_axis_);
        Eigen::Vector3d z = R * (center_ + z_axis_);
        center_ = R * center_;
        x_axis_ = x - center_;
        y_axis_ = y - center_;
        z_axis_ = z - center_;
    }
    return *this;
}

double OrientedBoundingBox::Volume() const {
    return (2 * x_axis_.norm()) * (2 * y_axis_.norm()) * (2 * z_axis_.norm());
}

std::vector<Eigen::Vector3d> OrientedBoundingBox::GetBoxPoints() const {
    std::vector<Eigen::Vector3d> points(8);
    points[0] = center_ - x_axis_ - y_axis_ - z_axis_;
    points[1] = center_ + x_axis_ - y_axis_ - z_axis_;
    points[2] = center_ - x_axis_ + y_axis_ - z_axis_;
    points[3] = center_ - x_axis_ - y_axis_ + z_axis_;
    points[4] = center_ + x_axis_ + y_axis_ + z_axis_;
    points[5] = center_ - x_axis_ + y_axis_ + z_axis_;
    points[6] = center_ + x_axis_ - y_axis_ + z_axis_;
    points[7] = center_ + x_axis_ + y_axis_ - z_axis_;
    return points;
}

OrientedBoundingBox OrientedBoundingBox::CreateFromAxisAlignedBoundingBox(
        const AxisAlignedBoundingBox& aabox) {
    Eigen::Vector3d half_extend = aabox.GetHalfExtend();
    OrientedBoundingBox obox;
    obox.center_ = aabox.GetCenter();
    obox.x_axis_ << half_extend(0), 0, 0;
    obox.y_axis_ << 0, half_extend(1), 0;
    obox.z_axis_ << 0, 0, half_extend(2);
    return obox;
}

OrientedBoundingBox OrientedBoundingBox::CreateFromPoints(
        const std::vector<Eigen::Vector3d>& points) {
    PointCloud hull_pcd;
    hull_pcd.points_ = Qhull::ComputeConvexHull(points)->vertices_;

    Eigen::Vector3d mean;
    Eigen::Matrix3d cov;
    std::tie(mean, cov) = hull_pcd.ComputeMeanAndCovariance();

    Eigen::SelfAdjointEigenSolver<Eigen::Matrix3d> es(cov);
    Eigen::Vector3d evals = es.eigenvalues();
    Eigen::Matrix3d R = es.eigenvectors();
    R.col(0) /= R.col(0).norm();
    R.col(1) /= R.col(1).norm();
    R.col(2) /= R.col(2).norm();

    if (evals(1) > evals(0)) {
        std::swap(evals(1), evals(0));
        Eigen::Vector3d tmp = R.col(1);
        R.col(1) = R.col(0);
        R.col(0) = tmp;
    }
    if (evals(2) > evals(0)) {
        std::swap(evals(2), evals(0));
        Eigen::Vector3d tmp = R.col(2);
        R.col(2) = R.col(0);
        R.col(0) = tmp;
    }
    if (evals(2) > evals(1)) {
        std::swap(evals(2), evals(1));
        Eigen::Vector3d tmp = R.col(2);
        R.col(2) = R.col(1);
        R.col(1) = tmp;
    }

    for (auto& pt : hull_pcd.points_) {
        pt = R.transpose() * (pt - mean);
    }
    const auto aabox = hull_pcd.GetAxisAlignedBoundingBox();
    const Eigen::Vector3d half_extend = aabox.GetHalfExtend();

    OrientedBoundingBox obox;
    obox.center_ = R * aabox.GetCenter() + mean;
    obox.x_axis_ = R * Eigen::Vector3d(half_extend(0), 0, 0);
    obox.y_axis_ = R * Eigen::Vector3d(0, half_extend(1), 0);
    obox.z_axis_ = R * Eigen::Vector3d(0, 0, half_extend(2));

    return obox;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Clear() {
    min_bound_.setZero();
    max_bound_.setZero();
    return *this;
}

bool AxisAlignedBoundingBox::IsEmpty() const { return Volume() == 0; }
Eigen::Vector3d AxisAlignedBoundingBox::GetMinBound() const {
    return min_bound_;
}

Eigen::Vector3d AxisAlignedBoundingBox::GetMaxBound() const {
    return max_bound_;
}

Eigen::Vector3d AxisAlignedBoundingBox::GetCenter() const {
    return (min_bound_ + max_bound_) * 0.5;
}

AxisAlignedBoundingBox AxisAlignedBoundingBox::GetAxisAlignedBoundingBox()
        const {
    return *this;
}

OrientedBoundingBox AxisAlignedBoundingBox::GetOrientedBoundingBox() const {
    return OrientedBoundingBox::CreateFromAxisAlignedBoundingBox(*this);
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Transform(
        const Eigen::Matrix4d& transformation) {
    utility::LogWarning(
            "A general transform of a AxisAlignedBoundingBox would not be axis "
            "aligned anymore, convert it to a OrientedBoundingBox first\n");
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Translate(
        const Eigen::Vector3d& translation, bool relative) {
    if (relative) {
        min_bound_ += translation;
        max_bound_ += translation;
    } else {
        const Eigen::Vector3d half_extend = GetHalfExtend();
        min_bound_ = translation - half_extend;
        max_bound_ = translation + half_extend;
    }
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Scale(const double scale,
                                                      bool center) {
    if (center) {
        Eigen::Vector3d center = GetCenter();
        min_bound_ = center + scale * (min_bound_ - center);
        max_bound_ = center + scale * (max_bound_ - center);
    } else {
        min_bound_ *= scale;
        max_bound_ *= scale;
    }
    return *this;
}

AxisAlignedBoundingBox& AxisAlignedBoundingBox::Rotate(
        const Eigen::Vector3d& rotation, bool center, RotationType type) {
    utility::LogWarning(
            "A rotation of a AxisAlignedBoundingBox would not be axis aligned "
            "anymore, convert it to a OrientedBoundingBox first\n");
    return *this;
}

std::string AxisAlignedBoundingBox::GetLogInfo() const {
    return fmt::format("[({:.4f}, {:.4f}, {:.4f}) - ({:.4f}, {:.4f}, {:.4f})]",
                       min_bound_(0), min_bound_(1), min_bound_(2),
                       max_bound_(0), max_bound_(1), max_bound_(2));
}

AxisAlignedBoundingBox AxisAlignedBoundingBox::CreateFromPoints(
        const std::vector<Eigen::Vector3d>& points) {
    AxisAlignedBoundingBox box;
    if (points.empty()) {
        box.min_bound_ = Eigen::Vector3d(0.0, 0.0, 0.0);
        box.max_bound_ = Eigen::Vector3d(0.0, 0.0, 0.0);
    } else {
        box.min_bound_ = std::accumulate(
                points.begin(), points.end(), points[0],
                [](const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
                    return a.array().min(b.array()).matrix();
                });
        box.max_bound_ = std::accumulate(
                points.begin(), points.end(), points[0],
                [](const Eigen::Vector3d& a, const Eigen::Vector3d& b) {
                    return a.array().max(b.array()).matrix();
                });
    }
    return box;
}

double AxisAlignedBoundingBox::Volume() const { return GetExtend().prod(); }

std::vector<Eigen::Vector3d> AxisAlignedBoundingBox::GetBoxPoints() const {
    std::vector<Eigen::Vector3d> points(8);
    Eigen::Vector3d extend = GetExtend();
    points[0] = min_bound_;
    points[1] = min_bound_ + Eigen::Vector3d(extend(0), 0, 0);
    points[2] = min_bound_ + Eigen::Vector3d(0, extend(1), 0);
    points[3] = min_bound_ + Eigen::Vector3d(0, 0, extend(2));
    points[4] = max_bound_;
    points[5] = max_bound_ - Eigen::Vector3d(extend(0), 0, 0);
    points[6] = max_bound_ - Eigen::Vector3d(0, extend(1), 0);
    points[7] = max_bound_ - Eigen::Vector3d(0, 0, extend(2));
    return points;
}

}  // namespace geometry
}  // namespace open3d


#define lsafemathlib_cpp

#include "uvm/lprefix.h"

#include <stdlib.h>
#include <math.h>

#include <uvm/lua.h>

#include <uvm/lauxlib.h>
#include <uvm/lualib.h>
#include <uvm/lobject.h>
#include <boost/algorithm/hex.hpp>
#include <boost/multiprecision/cpp_int.hpp>
#include <uvm/uvm_lutil.h>
#include <safenumber/safenumber.h>
//#include <boost/multiprecision/cpp_dec_float.hpp>
//#include <boost/multiprecision/mpfi.hpp>
//#include <boost/multiprecision/mpfr.hpp>

// bigint stores as { hex: hex string, type: 'bigint' }
// bignumber stores as { value: base10 string, type: 'bignumber' }
// safenumber stores as {value: base10 string, type: 'safenumber' }

typedef boost::multiprecision::int512_t sm_bigint;
//typedef boost::multiprecision::mpf_float sm_bigdecimal;

static void push_bigint(lua_State *L, sm_bigint value) {
	auto hex_str = uvm::util::hex(value.str());
	lua_newtable(L);
	lua_pushstring(L, hex_str.c_str());
	lua_setfield(L, -2, "hex");
	lua_pushstring(L, "bigint");
	lua_setfield(L, -2, "type");
}

//static void push_bignumber(lua_State *L, sm_bigdecimal value) {
//	auto value_str = value.str();
//	lua_newtable(L);
//	lua_pushstring(L, value_str.c_str());
//	lua_setfield(L, -2, "value");
//	lua_pushstring(L, "bignumber");
//	lua_setfield(L, -2, "type");
//}

static int safemath_bigint(lua_State *L) {
	if (lua_gettop(L) < 1) {
		luaL_argcheck(L, false, 1, "argument is empty");
		return 0;
	}
	if (lua_isinteger(L, 1)) {
		lua_Integer n = lua_tointeger(L, 1);
		std::string value_hex;
		try {
			value_hex = uvm::util::hex(std::to_string(n));
		}
		catch (...) {
			lua_pushnil(L);
			return 1;
		}
		lua_newtable(L);
		lua_pushstring(L, value_hex.c_str());
		lua_setfield(L, -2, "hex");
		lua_pushstring(L, "bigint");
		lua_setfield(L, -2, "type");
		return 1;
	}
	else if (lua_isstring(L, 1)) {
		std::string int_str = luaL_checkstring(L, 1);
		std::string value_hex;
		try {
			value_hex = uvm::util::hex(int_str);
		}
		catch (const std::exception& e) {
			lua_pushnil(L);
			return 1;
		}
		lua_newtable(L);
		lua_pushstring(L, value_hex.c_str());
		lua_setfield(L, -2, "hex");
		lua_pushstring(L, "bigint");
		lua_setfield(L, -2, "type");
		return 1;
	}
	else {
		luaL_argcheck(L, false, 1, "first argument must be integer or hex string");
		return 0;
	}
	return 1;
}

//static int safemath_bignumber(lua_State *L) {
//	if (lua_gettop(L) < 1) {
//		luaL_argcheck(L, false, 1, "argument is empty");
//		return 0;
//	}
//	if (lua_isinteger(L, 1)) {
//		lua_Integer n = lua_tointeger(L, 1);
//		sm_bigdecimal sm_value(n);
//		push_bignumber(L, sm_value);
//		return 1;
//	}
//	else if (lua_type(L, 1) == LUA_TNUMBER || lua_type(L, 1) == LUA_TNUMFLT) {
//		lua_Number n = lua_tonumber(L, 1);
//		sm_bigdecimal sm_value(n);
//		push_bignumber(L, sm_value);
//		return 1;
//	}
//	else if (lua_isstring(L, 1)) {
//		std::string value_str = luaL_checkstring(L, 1);
//		try {
//			sm_bigdecimal sm_value(value_str);
//			push_bignumber(L, sm_value);
//			return 1;
//		}
//		catch (const std::exception& e) {
//			luaL_error(L, "invalid number string");
//			return 0;
//		}
//	}
//	else {
//		luaL_argcheck(L, false, 1, "first argument must be number or hex string");
//		return 0;
//	}
//	return 1;
//}

static bool is_valid_bigint_obj(lua_State *L, int index, std::string& out)
{
	if ((index > 0 && lua_gettop(L) < index) || (index < 0 && lua_gettop(L) < -index) || index == 0) {
		return false;
	}
	if (!lua_istable(L, index)) {
		return false;
	}
	lua_getfield(L, index, "type");
	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strcmp("bigint", luaL_checkstring(L, -1)) != 0) {
		lua_pop(L, 1);
		return false;
	}
	lua_pop(L, 1);
	lua_getfield(L, index, "hex");
	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strlen(luaL_checkstring(L, -1)) < 1) {
		lua_pop(L, 1);
		return false;
	}
	std::string hex_str = luaL_checkstring(L, -1);
	lua_pop(L, 1);
	try {
		boost::algorithm::hex(hex_str);
		out = hex_str;
		return true;
	}
	catch (...) {
		return false;
	}
}

//static bool is_valid_bignumber_obj(lua_State *L, int index, std::string& out)
//{
//	if ((index > 0 && lua_gettop(L) < index) || (index < 0 && lua_gettop(L) < -index) || index == 0) {
//		return false;
//	}
//	if (!lua_istable(L, index)) {
//		return false;
//	}
//	lua_getfield(L, index, "type");
//	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strcmp("bignumber", luaL_checkstring(L, -1)) != 0) {
//		lua_pop(L, 1);
//		return false;
//	}
//	lua_pop(L, 1);
//	lua_getfield(L, index, "value");
//	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strlen(luaL_checkstring(L, -1)) < 1) {
//		lua_pop(L, 1);
//		return false;
//	}
//	std::string value_str = luaL_checkstring(L, -1);
//	lua_pop(L, 1);
//	try {
//		sm_bigdecimal(hex_str);
//		out = value_str;
//		return true;
//	}
//	catch (const std::exception& e) {
//		return false;
//	}
//}

static bool is_same_direction_safe_int(sm_bigint a, sm_bigint b)
{
	return (a > 0 && b > 0) || (a < 0 && b < 0);
}

static bool is_same_direction_int1024(boost::multiprecision::int1024_t a, boost::multiprecision::int1024_t b)
{
	return (a > 0 && b > 0) || (a < 0 && b < 0);
}

static int safemath_add(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "add need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	auto result_int = first_int + second_int;
	// overflow check
	if (is_same_direction_safe_int(first_int, second_int)) {
		if ((first_int > 0 && result_int <= 0) || (first_int<0 && result_int >= 0)) {
			luaL_error(L, "int512 overflow");
		}
	}
	push_bigint(L, result_int);
	return 1;
}

//static int safemath_add(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "add need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	auto result_value = first_value + second_value;
//	push_bignumber(L, result_value);
//	return 1;
//}

static int safemath_mul(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "mul need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	auto result_int = boost::multiprecision::int512_t(first_int) * boost::multiprecision::int512_t(second_int);
	// overflow check
	sm_bigint int512_max("13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095");
	sm_bigint int512_min("-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048");
	if (is_same_direction_safe_int(first_int, second_int) && result_int > int512_max) {
		luaL_error(L, "int512 overflow");
	}
	else if (is_same_direction_safe_int(first_int, second_int) && result_int < int512_min) {
		luaL_error(L, "int512 overflow");
	}
	push_bigint(L, result_int.convert_to<sm_bigint>());
	return 1;
}

//static int safemath_mul(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "mul need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	auto result_value = first_value * second_value;
//	push_bignumber(L, result_value);
//	return 1;
//}

static sm_bigint int512_pow(lua_State *L, sm_bigint value, sm_bigint n) {
	if (n > 100) {
		luaL_error(L, "too large value in bigint pow");
	}
	boost::multiprecision::int1024_t result(value);
	sm_bigint int512_max("13407807929942597099574024998205846127479365820592393377723561443721764030073546976801874298166903427690031858186486050853753882811946569946433649006084095");
	sm_bigint int512_min("-6703903964971298549787012499102923063739682910296196688861780721860882015036773488400937149083451713845015929093243025426876941405973284973216824503042048");
	for (int i = 1; i < n; i++) {
		auto mid_value = result * value;
		// overflow check
		if (is_same_direction_int1024(result, value) && mid_value > int512_max) {
			luaL_error(L, "int512 overflow");
		}
		else if(!is_same_direction_int1024(result, value) && mid_value < int512_min) {
			luaL_error(L, "int512 overflow");
		}
		result = mid_value;
	}
	return result.convert_to<boost::multiprecision::int512_t>();
}

//static sm_bigdecimal bignumber_pow(lua_State *L, sm_bigdecimal value, sm_bigdecimal n) {
//	if (n > 100) {
//		luaL_error(L, "too large value in bignumber pow");
//	}
//	sm_bigdecimal result(value);
//	for (int i = 1; i < n; i++) {
//		auto mid_value = result * value;
//		result = mid_value;
//	}
//	return result;
//}

static int safemath_pow(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "pow need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	auto result_int = int512_pow(L, first_int, second_int);
	// overflow check
	if (result_int <= 0) {
		luaL_error(L, "int512 overflow");
	}
	push_bigint(L, result_int);
	return 1;
}

enum compare_type {
	GT,
	GE,
	LT,
	LE,
	EQ,
	NE
};

static int _safemath_compare(lua_State* L, compare_type type) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "pow need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	bool result = false;
	if ( (type==compare_type::GT && first_int > second_int)
		|| (type == compare_type::GE && first_int >= second_int)
		|| (type == compare_type::LT && first_int < second_int)
		|| (type == compare_type::LE && first_int <= second_int)
		|| (type == compare_type::EQ && first_int == second_int)
		|| (type == compare_type::NE && first_int != second_int)
		){
		result = true;
	}
	else {
		result = false;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_gt(lua_State* L) {
	return _safemath_compare(L, compare_type::GT);
}
static int safemath_ge(lua_State* L) {
	return _safemath_compare(L, compare_type::GE);
}
static int safemath_lt(lua_State* L) {
	return _safemath_compare(L, compare_type::LT);
}
static int safemath_le(lua_State* L) {
	return _safemath_compare(L, compare_type::LE);
}
static int safemath_eq(lua_State* L) {
	return _safemath_compare(L, compare_type::EQ);
}
static int safemath_ne(lua_State* L) {
	return _safemath_compare(L, compare_type::NE);
}

//static int safemath_pow(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "pow need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	auto result_value = bignumber_pow(L, first_value, second_value);
//	push_bignumber(L, result_value);
//	return 1;
//}

static int safemath_div(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "div need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	if (second_int.is_zero()) {
		luaL_error(L, "div by 0 error");
	}
	sm_bigint result_int;
	try {
		result_int = first_int / second_int;
	}
	catch (...) {
		luaL_error(L, "bigint divide error");
		return 0;
	}
	// overflow check
	if (is_same_direction_safe_int(first_int, second_int)) {
		if ((first_int > 0 && result_int < 0) || (first_int<0 && result_int > 0)) {
			luaL_error(L, "int512 overflow");
		}
	}
	push_bigint(L, result_int);
	return 1;
}

//static int safemath_div(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "div need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	if (second_value.is_zero()) {
//		luaL_error(L, "div by 0 error");
//	}
//	sm_bigdecimal result_value;
//	try {
//		result_value = first_value / second_value;
//	}
//	catch (const std::exception& e) {
//		luaL_error(L, "bignumber divid by zero error");
//	}
//	push_bignumber(L, result_value);
//	return 1;
//}

static int safemath_rem(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "rem need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	std::string first_int_str;
	std::string second_int_str;
	sm_bigint first_int;
	sm_bigint second_int;
	try {
		if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
			luaL_argcheck(L, false, 1, "invalid bigint obj");
			return 0;
		}
		if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
			luaL_argcheck(L, false, 2, "invalid bigint obj");
			return 0;
		}
		first_int_str = uvm::util::unhex(first_hex_str);
		first_int = sm_bigint(first_int_str);
		auto second_int_str = uvm::util::unhex(second_hex_str);
		second_int = sm_bigint(second_int_str);
		if (second_int == 0) {
			luaL_error(L, "rem by 0 error");
		}
		auto result_int = first_int % second_int;
		// overflow check
		if (is_same_direction_safe_int(first_int, second_int)) {
			if ((first_int > 0 && result_int < 0) || (first_int < 0 && result_int > 0)) {
				luaL_error(L, "int512 overflow");
			}
		}
		push_bigint(L, result_int);
		return 1;
	}
	catch (...) {
		return 0;
	}
}

//static int safemath_rem(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "rem need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	if (second_value == 0) {
//		luaL_error(L, "rem by 0 error");
//	}
//	auto result_int = first_value.convert_to<sm_bigint>() % second_value.convert_to<sm_bigint>();
//	sm_bigdecimal result_value(result_int);
//	push_bignumber(L, result_value);
//	return 1;
//}

static int safemath_sub(lua_State *L) {
	if (lua_gettop(L) < 2) {
		luaL_error(L, "sub need at least 2 argument");
	}
	std::string first_hex_str;
	std::string second_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint obj");
	}
	if (!is_valid_bigint_obj(L, 2, second_hex_str)) {
		luaL_argcheck(L, false, 2, "invalid bigint obj");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint first_int(first_int_str);
	auto second_int_str = uvm::util::unhex(second_hex_str);
	sm_bigint second_int(second_int_str);
	auto result_int = first_int - second_int;
	// overflow check
	if (!is_same_direction_safe_int(first_int, second_int)) {
		if ((first_int > 0 && result_int <= 0) || (first_int<0 && result_int >= 0)) {
			luaL_error(L, "int512 overflow");
		}
	}
	push_bigint(L, result_int);
	return 1;
}

//static int safemath_sub(lua_State *L) {
//	if (lua_gettop(L) < 2) {
//		luaL_error(L, "sub need at least 2 argument");
//	}
//	std::string first_value_str;
//	std::string second_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber obj");
//	}
//	if (!is_valid_bignumber_obj(L, 2, second_value_str)) {
//		luaL_argcheck(L, false, 2, "invalid bignumber obj");
//	}
//	sm_bigdecimal first_value(first_value_str);
//	sm_bigdecimal second_value(second_value_str);
//	auto result_value = first_value - second_value;
//	push_bignumber(L, result_value);
//	return 1;
//}

static int safemath_toint(lua_State* L) {
	std::string hex_str;
	if (!is_valid_bigint_obj(L, 1, hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint object");
		return 0;
	}
	try {
		auto value_str = uvm::util::unhex(hex_str);
		sm_bigint bigint_value(value_str);
		auto value = bigint_value.convert_to<lua_Integer>();
		lua_pushinteger(L, value);
		return 1;
	}
	catch (...) {
		luaL_argcheck(L, false, 1, "invalid bigint object");
		return 0;
	}
}

//static int safemath_toint(lua_State* L) {
//	std::string value_str;
//	if (!is_valid_bignumber_obj(L, 1, value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber object");
//	}
//	sm_bigdecimal big_value(value_str);
//	auto value = big_value.convert_to<lua_Integer>();
//	lua_pushinteger(L, value);
//	return 1;
//}

static int safemath_tohex(lua_State* L) {
	std::string hex_str;
	if (!is_valid_bigint_obj(L, 1, hex_str)) {
		luaL_argcheck(L, false, 1, "invalid bigint object");
	}
	lua_pushstring(L, hex_str.c_str());
	return 1;
}

//static int safemath_tohex(lua_State* L) {
//	std::string value_str;
//	if (!is_valid_bignumber_obj(L, 1, value_str)) {
//		luaL_argcheck(L, false, 1, "invalid bignumber object");
//	}
//	lua_pushstring(L, value_str.c_str());
//	return 1;
//}

static int safemath_tostring(lua_State* L) {
	std::string hex_str;
	if (is_valid_bigint_obj(L, 1, hex_str)) {
		auto value_str = uvm::util::unhex(hex_str);
		sm_bigint bigint_value(value_str);
		auto bigint_value_str = bigint_value.str();
		lua_pushstring(L, bigint_value_str.c_str());
		return 1;
	}
	/*else if (is_valid_bignumber_obj(L, 1, hex_str)) {
		auto value_str = hex_str;
		lua_pushstring(L, value_str.c_str());
		return 1;
	}*/
	else {
		luaL_error(L, "invalid bignumber value");
		return 0;
	}
}

static int safemath_min(lua_State *L) {
	int n = lua_gettop(L);  /* number of arguments */
	int imin = 1;  /* index of current minimum value */
	std::string first_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "bigint value expected");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint min_value(first_int_str);
	luaL_argcheck(L, n >= 1, 1, "value expected");
	for (int i = 2; i <= n; i++) {
		std::string hex_value;
		if (!is_valid_bigint_obj(L, i, hex_value)) {
			luaL_argcheck(L, false, i, "bigint value expected");
		}
		auto int_str = uvm::util::unhex(hex_value);
		sm_bigint int_value(int_str);
		if (int_value < min_value) {
			imin = i;
			min_value = int_value;
		}
	}
	lua_pushvalue(L, imin);
	return 1;
}

//static int safemath_min(lua_State *L) {
//	int n = lua_gettop(L);  /* number of arguments */
//	int imin = 1;  /* index of current minimum value */
//	std::string first_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "bignumber value expected");
//	}
//	sm_bigdecimal min_value(first_value_str);
//	luaL_argcheck(L, n >= 1, 1, "value expected");
//	for (int i = 2; i <= n; i++) {
//		std::string value_str;
//		if (!is_valid_bignumber_obj(L, i, value_str)) {
//			luaL_argcheck(L, false, i, "bignumber value expected");
//		}
//		sm_bigdecimal big_value(value_str);
//		if (big_value < min_value) {
//			imin = i;
//			min_value = big_value;
//		}
//	}
//	lua_pushvalue(L, imin);
//	return 1;
//}

static int safemath_max(lua_State *L) {
	int n = lua_gettop(L);  /* number of arguments */
	int imax = 1;  /* index of current max value */
	std::string first_hex_str;
	if (!is_valid_bigint_obj(L, 1, first_hex_str)) {
		luaL_argcheck(L, false, 1, "bigint value expected");
	}
	auto first_int_str = uvm::util::unhex(first_hex_str);
	sm_bigint max_value(first_int_str);
	luaL_argcheck(L, n >= 1, 1, "value expected");
	for (int i = 2; i <= n; i++) {
		std::string hex_value;
		if (!is_valid_bigint_obj(L, i, hex_value)) {
			luaL_argcheck(L, false, i, "bigint value expected");
		}
		auto int_str = uvm::util::unhex(hex_value);
		sm_bigint int_value(int_str);
		if (int_value > max_value) {
			imax = i;
			max_value = int_value;
		}
	}
	lua_pushvalue(L, imax);
	return 1;
}

//static int safemath_max(lua_State *L) {
//	int n = lua_gettop(L);  /* number of arguments */
//	int imax = 1;  /* index of current max value */
//	std::string first_value_str;
//	if (!is_valid_bignumber_obj(L, 1, first_value_str)) {
//		luaL_argcheck(L, false, 1, "bignumber value expected");
//	}
//	sm_bigdecimal max_value(first_value_str);
//	luaL_argcheck(L, n >= 1, 1, "value expected");
//	for (int i = 2; i <= n; i++) {
//		std::string value_str;
//		if (!is_valid_bignumber_obj(L, i, value_str)) {
//			luaL_argcheck(L, false, i, "bignumber value expected");
//		}
//		sm_bigdecimal big_value(value_str);
//		if (big_value > max_value) {
//			imax = i;
//			max_value = big_value;
//		}
//	}
//	lua_pushvalue(L, imax);
//	return 1;
//}

static void push_safenumber(lua_State *L, const SafeNumber& value) {
	const auto& value_str = std::to_string(value);
	lua_newtable(L);
	lua_pushstring(L, value_str.c_str());
	lua_setfield(L, -2, "value");
	lua_pushstring(L, "safenumber");
	lua_setfield(L, -2, "type");
}

static int safemath_safe_number_create(lua_State *L) {
	if (lua_gettop(L) < 1) {
		luaL_argcheck(L, false, 1, "argument is empty");
		return 0;
	}
	std::string value_str;
	if (lua_isinteger(L, 1)) {
		lua_Integer n = lua_tointeger(L, 1);
		std::string value_str;
		value_str = std::to_string(n);
	}
	else if (lua_isstring(L, 1)) {
		std::string int_str = luaL_checkstring(L, 1);
		value_str = int_str;
	}
	else {
		luaL_argcheck(L, false, 1, "first argument must be integer or base10 string");
		return 0;
	}
	SafeNumber sn_value;
	try {
		sn_value = safe_number_create(value_str);
	}
	catch (const std::exception& e) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, sn_value);
	return 1;
}

static bool is_valid_safe_number(lua_State *L, int index, SafeNumber& sn_value) {
	if ((index > 0 && lua_gettop(L) < index) || (index < 0 && lua_gettop(L) < -index) || index == 0) {
		return false;
	}
	if (!lua_istable(L, index)) {
		return false;
	}
	lua_getfield(L, index, "type");
	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strcmp("safenumber", luaL_checkstring(L, -1)) != 0) {
		lua_pop(L, 1);
		return false;
	}
	lua_pop(L, 1);
	lua_getfield(L, index, "value");
	if (lua_isnil(L, -1) || !lua_isstring(L, -1) || strlen(luaL_checkstring(L, -1)) < 1) {
		lua_pop(L, 1);
		return false;
	}
	std::string value_str = luaL_checkstring(L, -1);
	lua_pop(L, 1);
	try {
		sn_value = safe_number_create(value_str);
		return true;
	}
	catch (...) {
		return false;
	}
}

static int safemath_safe_number_gt(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_gt(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_gte(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_gte(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_lt(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_lt(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_lte(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_lte(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_eq(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_eq(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_ne(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (!is_valid_safe_number(L, 2, b)) {
		luaL_argcheck(L, false, 2, "second argument must be SafeNumber value");
		return 0;
	}
	bool result;
	try {
		result = safe_number_ne(a, b);
	}
	catch (...) {
		lua_pushboolean(L, false);
		return 1;
	}
	lua_pushboolean(L, result);
	return 1;
}

static int safemath_safe_number_add(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_add(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_minus(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_minus(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_neg(lua_State *L) {
	SafeNumber a{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	SafeNumber result{};
	try {
		result = safe_number_neg(a);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_multiply(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_multiply(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_mod(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_mod(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_abs(lua_State *L) {
	SafeNumber a{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	SafeNumber result{};
	try {
		result = safe_number_abs(a);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_div(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_div(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_idiv(lua_State *L) {
	SafeNumber a{};
	SafeNumber b{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	if (lua_isinteger(L, 2)) {
		try {
			b = safe_number_create(luaL_checkinteger(L, 2));
		}
		catch (...) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	else {
		if (!is_valid_safe_number(L, 2, b)) {
			luaL_argcheck(L, false, 2, "second argument must be SafeNumber/integer value");
			return 0;
		}
	}
	SafeNumber result{};
	try {
		result = safe_number_idiv(a, b);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	push_safenumber(L, result);
	return 1;
}

static int safemath_safe_number_to_string(lua_State *L) {
	SafeNumber a{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	std::string result;
	try {
		result = std::to_string(a);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	lua_pushstring(L, result.c_str());
	return 1;
}

static int safemath_safe_number_to_int64(lua_State *L) {
	SafeNumber a{};
	if (!is_valid_safe_number(L, 1, a)) {
		luaL_argcheck(L, false, 1, "first argument must be SafeNumber value");
		return 0;
	}
	lua_Integer result;
	try {
		result = safe_number_to_int64(a);
	}
	catch (...) {
		lua_pushnil(L);
		return 1;
	}
	lua_pushinteger(L, result);
	return 1;
}

static const luaL_Reg safemathlib[] = {
	{ "bigint", safemath_bigint },
	/*{ "bignumber", safemath_bignumber },
	{ "iadd", safemath_iadd },
	{ "isub", safemath_isub },
	{ "imul", safemath_imul },
	{ "imin", safemath_imin },
	{ "imax", safemath_imax },
	{ "idiv", safemath_idiv },
	{ "irem", safemath_irem },
	{ "ipow", safemath_ipow },
	{ "itoint", safemath_itoint },
	{ "itohex", safemath_itohex },*/
	{ "add", safemath_add },
	{ "sub", safemath_sub },
	{ "mul", safemath_mul },
	{ "min", safemath_min },
	{ "max", safemath_max },
	{ "div", safemath_div },
	{ "rem", safemath_rem },
	{ "pow", safemath_pow },
	{ "gt", safemath_gt },
	{ "ge", safemath_ge },
	{ "lt", safemath_lt },
	{ "le", safemath_le },
	{ "eq", safemath_eq },
	{ "ne", safemath_ne },
	{ "toint", safemath_toint },
	{ "tohex", safemath_tohex },
	{ "tostring", safemath_tostring },

	{ "safenumber", safemath_safe_number_create },
	{ "number_add", safemath_safe_number_add },
	{ "number_minus", safemath_safe_number_minus },
	{ "number_multiply", safemath_safe_number_multiply },
	{ "number_div", safemath_safe_number_div },
	{ "number_idiv", safemath_safe_number_idiv },
	{ "number_abs", safemath_safe_number_abs },
	{ "number_neg", safemath_safe_number_neg },
	{ "number_gt", safemath_safe_number_gt },
	{ "number_gte", safemath_safe_number_gte },
	{ "number_lt", safemath_safe_number_lt },
	{ "number_lte", safemath_safe_number_lte },
	{ "number_eq", safemath_safe_number_eq },
	{ "number_ne", safemath_safe_number_ne },
	{ "number_tostring", safemath_safe_number_to_string },
	{ "number_toint", safemath_safe_number_to_int64 },
	{ "number_mod", safemath_safe_number_mod },

	{ nullptr, nullptr }
};

/*
** Open math library
*/
LUAMOD_API int luaopen_safemath(lua_State *L) {
	luaL_newlib(L, safemathlib);
	return 1;
}