/*=============================================================================
    Copyright (c) 2001-2015 Joel de Guzman
    Copyright (c) 2001-2011 Hartmut Kaiser

    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/spirit/home/x3.hpp>
#include <boost/fusion/include/std_pair.hpp>

#include <iostream>
#include <map>
#include <unordered_map>
#include <boost/unordered_map.hpp>
#include <vector>
#include <list>
#include <deque>
#include <set>
#include <unordered_set>
#include <boost/unordered_set.hpp>
#include <string>
#include "test.hpp"

namespace x3 = boost::spirit::x3;


// check if we did not break user defined specializations
namespace check_substitute {
template <typename T> struct foo {};
template <typename T> struct bar { using type = T; };
template <typename T> struct is_bar : std::false_type {};
template <typename T> struct is_bar<bar<T>> : std::true_type {};
}

namespace boost { namespace spirit { namespace x3 { namespace traits {
using namespace check_substitute;

template <typename T, typename U>
struct is_substitute<foo<T>, foo<U>> : is_substitute<T, U> {};

template <typename T, typename U>
struct is_substitute<T, U, std::enable_if_t<is_bar<T>::value && is_bar<U>::value>>
  : is_substitute<typename T::type, typename U::type> {};
}}}}

namespace check_substitute {
using x3::traits::is_substitute;
static_assert(is_substitute<foo<int>, foo<int>>::value, "is_substitute problem");
static_assert(!is_substitute<foo<int>, foo<long>>::value, "is_substitute problem");
static_assert(is_substitute<bar<int>, bar<int>>::value, "is_substitute problem");
static_assert(!is_substitute<bar<int>, bar<long>>::value, "is_substitute problem");
}


x3::rule<class pair_rule, std::pair<std::string,std::string>> const pair_rule("pair");
x3::rule<class string_rule, std::string> const string_rule("string");

auto const pair_rule_def = string_rule > x3::lit('=') > string_rule;
auto const string_rule_def = x3::lexeme[*x3::alnum];

BOOST_SPIRIT_DEFINE(pair_rule, string_rule)

template <typename Container>
void test_map_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {{"k1", "v1"}, {"k2", "v2"}};
    auto const rule = pair_rule % x3::lit(',');

    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", rule, container));
    BOOST_TEST(container.size() == 2);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = pair_rule >> ',' >> pair_rule >> ',' >> pair_rule;
    container.clear();
    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = pair_rule >> +(',' >> pair_rule);
    container.clear();
    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", cic_rule, container));
}

template <typename Container>
void test_multimap_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {{"k1", "v1"}, {"k2", "v2"}, {"k2", "v3"}};
    auto const rule = pair_rule % x3::lit(',');

    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", rule, container));
    BOOST_TEST(container.size() == 3);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = pair_rule >> ',' >> pair_rule >> ',' >> pair_rule;
    container.clear();
    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = pair_rule >> +(',' >> pair_rule);
    container.clear();
    BOOST_TEST(test_attr("k1=v1,k2=v2,k2=v3", cic_rule, container));
}

template <typename Container>
void test_sequence_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {"e1", "e2", "e2"};
    auto const rule = string_rule % x3::lit(',');

    BOOST_TEST(test_attr("e1,e2,e2", rule, container));
    BOOST_TEST(container.size() == 3);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = string_rule >> ',' >> string_rule >> ',' >> string_rule;
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = string_rule >> +(',' >> string_rule);
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", cic_rule, container));
}

template <typename Container>
void test_set_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {"e1", "e2"};
    auto const rule = string_rule % x3::lit(',');

    BOOST_TEST(test_attr("e1,e2,e2", rule, container));
    BOOST_TEST(container.size() == 2);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = string_rule >> ',' >> string_rule >> ',' >> string_rule;
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = string_rule >> +(',' >> string_rule);
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", cic_rule, container));
}

template <typename Container>
void test_multiset_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {"e1", "e2", "e2"};
    auto const rule = string_rule % x3::lit(',');

    BOOST_TEST(test_attr("e1,e2,e2", rule, container));
    BOOST_TEST(container.size() == 3);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = string_rule >> ',' >> string_rule >> ',' >> string_rule;
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = string_rule >> +(',' >> string_rule);
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", cic_rule, container));
}

template <typename Container>
void test_string_support()
{
    using spirit_test::test_attr;

    Container container;
    Container const compare {"e1e2e2"};
    auto const rule = string_rule % x3::lit(',');

    BOOST_TEST(test_attr("e1,e2,e2", rule, container));
    BOOST_TEST(container.size() == 6);
    BOOST_TEST(container == compare);

    // test sequences parsing into containers
    auto const seq_rule = string_rule >> ',' >> string_rule >> ',' >> string_rule;
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", seq_rule, container));

    // test parsing container into container
    auto const cic_rule = string_rule >> +(',' >> string_rule);
    container.clear();
    BOOST_TEST(test_attr("e1,e2,e2", cic_rule, container));
}

int
main()
{
    using x3::traits::is_associative;

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

    static_assert(is_associative<std::set<int>>::value, "is_associative problem");
    static_assert(is_associative<std::unordered_set<int>>::value, "is_associative problem");
    static_assert(is_associative<boost::unordered_set<int>>::value, "is_associative problem");
    static_assert(is_associative<std::multiset<int>>::value, "is_associative problem");
    static_assert(is_associative<std::unordered_multiset<int>>::value, "is_associative problem");
    static_assert(is_associative<boost::unordered_multiset<int>>::value, "is_associative problem");
    static_assert(is_associative<std::map<int,int>>::value, "is_associative problem");
    static_assert(is_associative<std::unordered_map<int,int>>::value, "is_associative problem");
    static_assert(is_associative<boost::unordered_map<int,int>>::value, "is_associative problem");
    static_assert(is_associative<std::multimap<int,int>>::value, "is_associative problem");
    static_assert(is_associative<std::unordered_multimap<int,int>>::value, "is_associative problem");
    static_assert(is_associative<boost::unordered_multimap<int,int>>::value, "is_associative problem");

    static_assert(!is_associative<std::vector<int>>::value, "is_associative problem");
    static_assert(!is_associative<std::string>::value, "is_associative problem");
    static_assert(!is_associative<std::deque<int>>::value, "is_associative problem");
    static_assert(!is_associative<std::list<int>>::value, "is_associative problem");

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

    test_string_support<std::string>();

    test_sequence_support<std::vector<std::string>>();
    test_sequence_support<std::list<std::string>>();
    test_sequence_support<std::deque<std::string>>();

    test_set_support<std::set<std::string>>();
    test_set_support<std::unordered_set<std::string>>();
    test_set_support<boost::unordered_set<std::string>>();

    test_multiset_support<std::multiset<std::string>>();
    test_multiset_support<std::unordered_multiset<std::string>>();
    test_multiset_support<boost::unordered_multiset<std::string>>();

    test_map_support<std::map<std::string,std::string>>();
    test_map_support<std::unordered_map<std::string,std::string>>();
    test_map_support<boost::unordered_map<std::string,std::string>>();

    test_multimap_support<std::multimap<std::string,std::string>>();
    test_multimap_support<std::unordered_multimap<std::string,std::string>>();
    test_multimap_support<boost::unordered_multimap<std::string,std::string>>();

    return boost::report_errors();
}
