#include <qpdf/assert_test.h>

#include <cstdlib>
#include <cstring>
#include <functional>
#include <iostream>
#include <map>
#include <memory>
#include <regex>
#include <type_traits>
#include <vector>

// Functional programming

// Function that returns a callable in the form of a lambda
std::function<int(int)>
make_adder(int x)
{
    return ([=](int a) -> int { return x + a; });
}

void
do_functional()
{
    // Lambda with no capture
    auto simple_lambda = [](int a) { return a + 3; };
    assert(simple_lambda(5) == 8);

    // Capture by value
    int x = 5;
    auto by_value = [x](int a) { return a + x; };
    assert(by_value(1) == 6);
    x = 7; // change not seen by lambda
    assert(by_value(1) == 6);
    // Also >> at end of template
    assert((std::is_convertible<decltype(by_value), std::function<int(int)>>::value));

    // Capture by reference
    auto by_reference = [&x](int a) { return a + x; };
    assert(by_reference(1) == 8);
    x = 8; // change seen my lambda
    assert(by_reference(1) == 9);

    // Get a callable from a function
    auto add3 = make_adder(3);
    assert(add3(5) == 8);

    auto make_addr_lambda = [](int a) { return [a](int b) { return a + b; }; };

    assert(make_addr_lambda(6)(8) == 14);

    // nullptr and {} are empty functions
    std::function<void()> f1 = {};
    assert(!f1);
    std::function<void()> f2 = nullptr;
    assert(!f2);
}

// Integer types, type traits

template <typename T>
void
check_size(size_t size, bool is_signed)
{
    assert(sizeof(T) == size);
    assert(std::is_signed<T>::value == is_signed);
}

void
do_inttypes()
{
    // static_assert is a compile-time check
    static_assert(1 == sizeof(int8_t), "int8_t check");
    check_size<int8_t>(1, true);
    check_size<uint8_t>(1, false);
    check_size<int16_t>(2, true);
    check_size<uint16_t>(2, false);
    check_size<int32_t>(4, true);
    check_size<uint32_t>(4, false);
    check_size<int64_t>(8, true);
    check_size<uint64_t>(8, false);

    // auto, decltype
    auto x = 5LL;
    check_size<decltype(x)>(8, true);
    assert((std::is_same<long long, decltype(x)>::value));
}

class A
{
  public:
    static constexpr auto def_value = 5;
    A(int x) :
        x(x)
    {
    }
    // Constructor delegation
    A() :
        A(def_value)
    {
    }
    int
    getX() const
    {
        return x;
    }

  private:
    int x;
};

void
do_iteration()
{
    // Initializers, foreach syntax, auto for iterators
    std::vector<int> v = {1, 2, 3, 4};
    assert(v.size() == 4);
    int sum = 0;
    for (auto& i: v) {
        sum += i;
    }
    assert(10 == sum);
    for (auto i = v.begin(); i != v.end(); ++i) {
        sum += *i;
    }
    assert(20 == sum);

    std::vector<A> v2 = {A(), A(3)};
    assert(5 == v2.at(0).getX());
    assert(3 == v2.at(1).getX());
}

// Variadic template

template <class A1>
void
variadic1(A1 const& a1)
{
    assert(a1 == 12);
}

template <class A1, class A2>
void
variadic1(A1 const& a1, A2 const& a2)
{
    assert(a1 == a2);
}

template <class... Args>
void
variadic(Args... args)
{
    variadic1(args...);
}

template <class A>
bool
pairwise_equal(A const& a, A const& b)
{
    return (a == b);
}

template <class T, class... Rest>
bool
pairwise_equal(T const& a, T const& b, Rest... rest)
{
    return pairwise_equal(a, b) && pairwise_equal(rest...);
}

void
do_variadic()
{
    variadic(15, 15);
    variadic(12);
    assert(pairwise_equal(5, 5, 2.0, 2.0, std::string("a"), std::string("a")));
    assert(!pairwise_equal(5, 5, 2.0, 3.0));
}

// deleted, default

class B
{
  public:
    B(int x) :
        x(x)
    {
    }
    B() :
        B(5)
    {
    }
    int
    getX() const
    {
        return x;
    }

    virtual ~B() = default;
    B(B const&) = delete;
    B& operator=(B const&) = delete;

  private:
    int x;
};

void
do_default_deleted()
{
    B b1;
    assert(5 == b1.getX());
    assert(std::is_copy_constructible<A>::value);
    assert(!std::is_copy_constructible<B>::value);
}

// smart pointers

class C
{
  public:
    C(int id = 0) :
        id(id)
    {
        incr(id);
    }
    ~C()
    {
        decr(id);
    }
    C(C const& rhs) :
        C(rhs.id)
    {
    }
    C&
    operator=(C const& rhs)
    {
        if (&rhs != this) {
            decr(id);
            id = rhs.id;
            incr(id);
        }
        return *this;
    }
    static void
    check(size_t size, int v, int count)
    {
        assert(m.size() == size);
        auto p = m.find(v);
        if (p != m.end()) {
            assert(p->second == count);
        }
    }

  private:
    void
    incr(int i)
    {
        ++m[i];
    }
    void
    decr(int i)
    {
        if (--m[i] == 0) {
            m.erase(i);
        }
    }

    static std::map<int, int> m;
    int id;
};

std::map<int, int> C::m;

std::shared_ptr<C>
make_c(int id)
{
    return std::make_shared<C>(id);
}

std::shared_ptr<C>
make_c_array(std::vector<int> const& is)
{
    auto p = std::shared_ptr<C>(new C[is.size()], std::default_delete<C[]>());
    C* pp = p.get();
    for (size_t i = 0; i < is.size(); ++i) {
        pp[i] = C(is.at(i));
    }
    return p;
}

void
do_smart_pointers()
{
    auto p1 = make_c(1);
    C::check(1, 1, 1);
    auto p2 = make_c_array({2, 3, 4, 5});
    for (auto i: {1, 2, 3, 4, 5}) {
        C::check(5, i, 1);
    }
    {
        C::check(5, 1, 1);
        C c3(*p1);
        C::check(5, 1, 2);
    }
    C::check(5, 1, 1);
    p1 = nullptr;
    C::check(4, 1, 0);
    p2 = nullptr;
    C::check(0, 0, 0);
    {
        std::unique_ptr<C> p3(new C(6));
        C::check(1, 6, 1);
    }
    C::check(0, 0, 0);
}

// Regular expressions

void
do_regex()
{
    // Basic match/search. Match matches whole string; search searches
    // within string. Use std::smatch for matching std::string and
    // std::cmatch for matching char const*.

    std::regex expr1("([0-9]+)");
    std::regex expr2("([0-9]+)\\s*([a-z]+)[[:space:]]*([0-9]+)");
    std::string str1("one 234 fifth 678 nine");
    std::string str2("234 five 678 nine");
    std::string str3("one 234 five 678");
    std::string str4("234");
    std::string str5("234 five 678");
    std::smatch match1;
    assert(!std::regex_match(str1, match1, expr1));
    assert(!std::regex_match(str2, match1, expr1));
    assert(!std::regex_match(str3, match1, expr1));
    assert(std::regex_match(str4, match1, expr1));
    assert(match1[0].first == match1[1].first);
    assert(match1[0].second == match1[1].second);
    std::string s;
    s.assign(match1[1].first, match1[1].second);
    assert("234" == s);
    assert(s == match1[1].str());
    assert(std::regex_match(str5, match1, expr2));
    assert("234 five 678" == match1[0].str());
    assert("234" == match1[1].str());
    assert("five" == match1[2].str());
    assert("678" == match1[3].str());
    assert(std::regex_search(str1, match1, expr2));
    assert("234 fifth 678" == match1[0].str());
    assert("234" == match1[1].str());
    assert("fifth" == match1[2].str());
    assert("678" == match1[3].str());

    // Iterator
    std::regex expr3("[[:digit:]]+");
    std::string str6 = "asdf234erasdf9453.kgdl423asdf";
    std::sregex_iterator m1(str6.begin(), str6.end(), expr3);
    std::sregex_iterator m2;
    s.clear();
    for (std::sregex_iterator iter = m1; iter != m2; ++iter) {
        std::smatch const& match2 = *iter;
        s += match2[0].str() + "|";
    }
    assert("234|9453|423|" == s);

    // Submatches
    std::regex expr4("(?:(asdf)|(qwer))");
    char const* str7 = "0asdf1qwer2";
    std::cregex_iterator m3(str7, str7 + std::strlen(str7), expr4);
    assert("asdf" == (*m3)[0].str());
    assert((*m3)[1].matched);
    assert(!(*m3)[2].matched);
    ++m3;
    assert("qwer" == (*m3)[0].str());
    assert(!(*m3)[1].matched);
    assert((*m3)[2].matched);
}

static long
operator""_x(char const* v)
{
    return strtol(v, nullptr, 16);
}

static std::string
operator""_y(char const* v, size_t len)
{
    return "y" + std::string(v, len) + "y";
}

void
do_user_defined_literals()
{
    assert(10_x == 16);         // operator""_x("10")
    assert("abc"_y == "yabcy"); // operator""_y("abc", 3)
    // Raw literals. Optional matching label before and after ()
    // allows embedding something that looks like the default end
    // delimiter in the string.
    assert(R"(abc)"_y == "yabcy");

    // This construct does not work in MSVC as of version 2019.
    // assert(R"x(a)"bc)x"_y == "ya)\"bcy");
}

int
main()
{
    do_functional();
    do_inttypes();
    do_iteration();
    do_variadic();
    do_default_deleted();
    do_smart_pointers();
    do_regex();
    do_user_defined_literals();
    std::cout << "assertions passed\n";
    return 0;
}
