#include <iostream>
#include <vector>

#include <mapbox/variant.hpp>
#include <mapbox/variant_visitor.hpp>

#if __cplusplus >= 201402L
#define HAS_CPP14_SUPPORT
#endif

using namespace mapbox::util;

template <typename Left, typename Right>
using Either = mapbox::util::variant<Left, Right>;

struct Response
{
};

struct Error
{
};

void test_lambda_overloads()
{
    Either<Error, Response> rv;

    rv = Response{};

    auto visitor = make_visitor([](Response) { std::cout << "Response\n"; }, //
                                [](Error) { std::cout << "Error\n"; });      //
    apply_visitor(visitor, rv);
}

void test_lambda_overloads_capture()
{
    Either<Error, Response> rv;

    rv = Error{};

    int ok = 0;
    int err = 0;

    auto visitor = make_visitor([&](Response) { ok += 1; }, //
                                [&](Error) { err += 1; });  //
    apply_visitor(visitor, rv);

    std::cout << "Got " << ok << " ok, " << err << " err" << std::endl;
}

void test_singleton_variant()
{

    variant<int> singleton;
    apply_visitor(make_visitor([](int) {}), singleton);
}

void test_lambda_overloads_sfinae()
#ifdef HAS_CPP14_SUPPORT
{
    variant<int, float, std::vector<int>> var;

    auto visitor = make_visitor([](auto range) -> decltype(std::begin(range), void()) {
                                    for (auto each : range)
                                        std::cout << each << ' '; },
                                [](auto x) -> decltype(std::cout << x, void()) {
                                    std::cout << x << std::endl;
                                });

    var = 1;
    apply_visitor(visitor, var);

    var = 2.f;
    apply_visitor(visitor, var);

    var = std::vector<int>{4, 5, 6};
    apply_visitor(visitor, var);
}
#else
{
}
#endif

void test_match_singleton()
{
    variant<int> singleton = 5;
    singleton.match([](int) {});
    
    auto lambda = [](int) {};
    singleton.match(lambda);
}

void test_match_overloads()
{
    Either<Error, Response> rv;

    rv = Response{};

    rv.match([](Response) { std::cout << "Response\n"; }, //
             [](Error) { std::cout << "Error\n"; });      //
}

void test_match_overloads_capture()
{
    Either<Error, Response> rv;

    rv = Error{};

    int ok = 0;
    int err = 0;

    rv.match([&](Response) { ok += 1; }, //
             [&](Error) { err += 1; });  //

    std::cout << "Got " << ok << " ok, " << err << " err" << std::endl;
}

struct MovableOnly
{
    MovableOnly() = default;
    
    MovableOnly(MovableOnly&&) = default;
    MovableOnly& operator=(MovableOnly&&) = default;
};

struct MovableCopyable
{
    MovableCopyable() = default;
    
    MovableCopyable(MovableCopyable&&) = default;
    MovableCopyable& operator=(MovableCopyable&&) = default;
    
    MovableCopyable(const MovableCopyable&) = default;
    MovableCopyable& operator=(const MovableCopyable&) = default;
};

void test_match_overloads_init_capture()
#ifdef HAS_CPP14_SUPPORT
{
    Either<Error, Response> rv;

    rv = Error{};

    rv.match([p = MovableOnly{}](auto&&) {});
    {
        auto lambda = [p = MovableCopyable{}](auto&&) {};
        rv.match(lambda);
    
        rv.match([p = MovableOnly{}](Response) { std::cout << "Response\n"; },
                 [p = MovableOnly{}](Error) { std::cout << "Error\n"; });
    }    
    {
        auto lambda = [](Error) { std::cout << "Error\n"; };
        rv.match([p = MovableOnly{}](Response) { std::cout << "Response\n"; },
                 lambda);
        rv.match(lambda,
                 [p = MovableOnly{}](Response) { std::cout << "Response\n"; });
    }
}
#else
{
}
#endif

// See #140
void test_match_overloads_otherwise()
#ifdef HAS_CPP14_SUPPORT
{

    struct Center
    {
    };
    struct Indent
    {
    };
    struct Justify
    {
    };
    struct None
    {
    };

    using Properties = mapbox::util::variant<Center, Indent, Justify, None>;

    Properties props = Justify{};

    props.match([&](Center) { std::cout << "Center\n"; },     //
                [&](Indent) { std::cout << "Indent\n"; },     //
                [&](auto&&) { std::cout << "Otherwise\n"; }); //
}
#else
{
}
#endif

int main()
{
    test_lambda_overloads();
    test_singleton_variant();
    test_lambda_overloads_capture();
    test_lambda_overloads_sfinae();

    test_match_singleton();
    test_match_overloads();
    test_match_overloads_capture();
    test_match_overloads_init_capture();
    test_match_overloads_otherwise();
}

#undef HAS_CPP14_SUPPORT