// Copyright (C) 2016-2018 T. Zachary Laine
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <boost/yap/expression.hpp>

#include <boost/assign/list_of.hpp>

#include <map>
#include <iostream>


template<typename Key, typename Value, typename Allocator>
struct map_list_of_transform
{
    template<typename Fn, typename Key2, typename Value2>
    auto operator()(
        boost::yap::expr_tag<boost::yap::expr_kind::call>,
        Fn const & fn,
        Key2 && key,
        Value2 && value)
    {
        boost::yap::transform(
            boost::yap::as_expr<boost::yap::minimal_expr>(fn), *this);
        map.emplace(
            Key{std::forward<Key2 &&>(key)},
            Value{std::forward<Value2 &&>(value)});
        return 0;
    }

    std::map<Key, Value, Allocator> map;
};


template<boost::yap::expr_kind Kind, typename Tuple>
struct map_list_of_expr
{
    static boost::yap::expr_kind const kind = Kind;

    Tuple elements;

    template<typename Key, typename Value, typename Allocator>
    operator std::map<Key, Value, Allocator>() const
    {
        map_list_of_transform<Key, Value, Allocator> transform;
        boost::yap::transform(*this, transform);
        return transform.map;
    }

    BOOST_YAP_USER_CALL_OPERATOR(::map_list_of_expr)
};

struct map_list_of_tag
{};

auto map_list_of =
    boost::yap::make_terminal<map_list_of_expr>(map_list_of_tag{});


#if __GNUC__ || __clang__
#define NOINLINE __attribute__((noinline))
#else
#define NOINLINE
#endif


NOINLINE std::map<std::string, int> make_map_with_boost_yap()
{
    return map_list_of("<", 1)("<=", 2)(">", 3)(">=", 4)("=", 5)("<>", 6);
}


NOINLINE std::map<std::string, int> make_map_with_boost_assign()
{
    return boost::assign::map_list_of("<", 1)("<=", 2)(">", 3)(">=", 4)("=", 5)(
        "<>", 6);
}


NOINLINE std::map<std::string, int> make_map_manually()
{
    std::map<std::string, int> retval;
    retval.emplace("<", 1);
    retval.emplace("<=", 2);
    retval.emplace(">", 3);
    retval.emplace(">=", 4);
    retval.emplace("=", 5);
    retval.emplace("<>", 6);
    return retval;
}


NOINLINE std::map<std::string, int> make_map_inializer_list()
{
    std::map<std::string, int> retval = {
        {"<", 1}, {"<=", 2}, {">", 3}, {">=", 4}, {"=", 5}, {"<>", 6}};
    return retval;
}


int main()
{
    {
        std::map<std::string, int> op = make_map_with_boost_yap();

        std::cout << "\"<\"  --> " << op["<"] << std::endl;
        std::cout << "\"<=\" --> " << op["<="] << std::endl;
        std::cout << "\">\"  --> " << op[">"] << std::endl;
        std::cout << "\">=\" --> " << op[">="] << std::endl;
        std::cout << "\"=\"  --> " << op["="] << std::endl;
        std::cout << "\"<>\" --> " << op["<>"] << std::endl;
    }

    {
        std::map<std::string, int> op = make_map_with_boost_assign();

        std::cout << "\"<\"  --> " << op["<"] << std::endl;
        std::cout << "\"<=\" --> " << op["<="] << std::endl;
        std::cout << "\">\"  --> " << op[">"] << std::endl;
        std::cout << "\">=\" --> " << op[">="] << std::endl;
        std::cout << "\"=\"  --> " << op["="] << std::endl;
        std::cout << "\"<>\" --> " << op["<>"] << std::endl;
    }

    {
        std::map<std::string, int> op = make_map_manually();

        std::cout << "\"<\"  --> " << op["<"] << std::endl;
        std::cout << "\"<=\" --> " << op["<="] << std::endl;
        std::cout << "\">\"  --> " << op[">"] << std::endl;
        std::cout << "\">=\" --> " << op[">="] << std::endl;
        std::cout << "\"=\"  --> " << op["="] << std::endl;
        std::cout << "\"<>\" --> " << op["<>"] << std::endl;
    }

    {
        std::map<std::string, int> op = make_map_inializer_list();

        std::cout << "\"<\"  --> " << op["<"] << std::endl;
        std::cout << "\"<=\" --> " << op["<="] << std::endl;
        std::cout << "\">\"  --> " << op[">"] << std::endl;
        std::cout << "\">=\" --> " << op[">="] << std::endl;
        std::cout << "\"=\"  --> " << op["="] << std::endl;
        std::cout << "\"<>\" --> " << op["<>"] << std::endl;
    }

    return 0;
}
