#include <typeinfo>
#include <array>
#include <algorithm>

#include "gtest.h"
#include "util/optional.hpp"

using namespace nest::mc::util;

TEST(optionalm,ctors) {
    optional<int> a,b(3),c=b,d=4;

    ASSERT_FALSE((bool)a);
    ASSERT_TRUE((bool)b);
    ASSERT_TRUE((bool)c);
    ASSERT_TRUE((bool)d);

    EXPECT_EQ(3,b.get());
    EXPECT_EQ(3,c.get());
    EXPECT_EQ(4,d.get());
}

TEST(optionalm,unset_throw) {
    optional<int> a;
    int check=10;

    try { a.get(); }
    catch(optional_unset_error &e) {
        ++check;
    }
    EXPECT_EQ(11,check);

    check=20;
    a=2;
    try { a.get(); }
    catch(optional_unset_error &e) {
        ++check;
    }
    EXPECT_EQ(20,check);

    check=30;
    a.reset();
    try { a.get(); }
    catch(optional_unset_error &e) {
        ++check;
    }
    EXPECT_EQ(31,check);
}

TEST(optionalm,deref) {
    struct foo {
        int a;
        explicit foo(int a_): a(a_) {}
        double value() { return 3.0*a; }
    };
    
    optional<foo> f=foo(2);
    EXPECT_EQ(6.0,f->value());
    EXPECT_EQ(2,(*f).a);
}

TEST(optionalm,ctor_conv) {
    optional<std::array<int,3>> x{{1,2,3}};
    EXPECT_EQ(3u,x->size());
}

TEST(optionalm,ctor_ref) {
    int v=10;
    optional<int &> a(v);

    EXPECT_EQ(10,a.get());
    v=20;
    EXPECT_EQ(20,a.get());

    optional<int &> b(a),c=b,d=v;
    EXPECT_EQ(&(a.get()),&(b.get()));
    EXPECT_EQ(&(a.get()),&(c.get()));
    EXPECT_EQ(&(a.get()),&(d.get()));
}

TEST(optionalm,assign_returns) {
    optional<int> a=3;

    auto b=(a=4);
    EXPECT_EQ(typeid(optional<int>),typeid(b));

    auto bp=&(a=4);
    EXPECT_EQ(&a,bp);
}

namespace {
    struct nomove {
        int value;

        nomove(): value(0) {}
        nomove(int i): value(i) {}
        nomove(const nomove &n): value(n.value) {}
        nomove(nomove &&n) = delete;

        nomove &operator=(const nomove &n) { value=n.value; return *this; }

        bool operator==(const nomove &them) const { return them.value==value; }
        bool operator!=(const nomove &them) const { return !(*this==them); }
    };
}
 
TEST(optionalm,ctor_nomove) {
    optional<nomove> a(nomove(3));
    EXPECT_EQ(nomove(3),a.get());

    optional<nomove> b;
    b=a;
    EXPECT_EQ(nomove(3),b.get());

    b=optional<nomove>(nomove(4));
    EXPECT_EQ(nomove(4),b.get());
}

namespace {
    struct nocopy {
        int value;

        nocopy(): value(0) {}
        nocopy(int i): value(i) {}
        nocopy(const nocopy &n) = delete;
        nocopy(nocopy &&n) {
            value=n.value;
            n.value=0;
        }

        nocopy &operator=(const nocopy &n) = delete;
        nocopy &operator=(nocopy &&n) {
            value=n.value;
            n.value=-1;
            return *this;
        }

        bool operator==(const nocopy &them) const { return them.value==value; }
        bool operator!=(const nocopy &them) const { return !(*this==them); }
    };
}
    
TEST(optionalm,ctor_nocopy) {
    optional<nocopy> a(nocopy(5));
    EXPECT_EQ(nocopy(5),a.get());

    optional<nocopy> b(std::move(a));
    EXPECT_EQ(nocopy(5),b.get());
    EXPECT_EQ(0,a.get().value);

    b=optional<nocopy>(nocopy(6));
    EXPECT_EQ(nocopy(6),b.get());
}

namespace {
    optional<double> odd_half(int n) {
        optional<double> h;
        if (n%2==1) h=n/2.0;
        return h;
    }
}

TEST(optionalm,bind) {
    optional<int> a;
    auto b=a.bind(odd_half);

    EXPECT_EQ(typeid(optional<double>),typeid(b));

    a=10;
    b=a.bind(odd_half);
    EXPECT_FALSE((bool)b);

    a=11;
    b=a.bind(odd_half);
    EXPECT_TRUE((bool)b);
    EXPECT_EQ(5.5,b.get());

    b=a >> odd_half >> [](double x) { return (int)x; } >> odd_half;
    EXPECT_TRUE((bool)b);
    EXPECT_EQ(2.5,b.get());
}

TEST(optionalm,void) {
    optional<void> a,b(true),c(a),d=b,e(false);

    EXPECT_FALSE((bool)a);
    EXPECT_TRUE((bool)b);
    EXPECT_FALSE((bool)c);
    EXPECT_TRUE((bool)d);
    EXPECT_TRUE((bool)e);

    auto x=a >> []() { return 1; };
    EXPECT_FALSE((bool)x);

    x=b >> []() { return 1; };
    EXPECT_TRUE((bool)x);
    EXPECT_EQ(1,x.get());
}

TEST(optionalm,bind_to_void) {
    optional<int> a,b(3);
    
    int call_count=0;
    auto vf=[&call_count](int i) -> void { ++call_count; };

    auto x=a >> vf;
    EXPECT_EQ(typeid(optional<void>),typeid(x));
    EXPECT_FALSE((bool)x);
    EXPECT_EQ(0,call_count);

    call_count=0;
    x=b >> vf;
    EXPECT_TRUE((bool)x);
    EXPECT_EQ(1,call_count);
}
    
TEST(optionalm,bind_to_optional_void) {
    optional<int> a,b(3),c(4);
    
    int count=0;
    auto count_if_odd=[&count](int i) { return i%2?(++count,optional<void>(true)):optional<void>(); };

    auto x=a >> count_if_odd;
    EXPECT_EQ(typeid(optional<void>),typeid(x));
    EXPECT_FALSE((bool)x);
    EXPECT_EQ(0,count);

    count=0;
    x=b >> count_if_odd;
    EXPECT_TRUE((bool)x);
    EXPECT_EQ(1,count);

    count=0;
    x=c >> count_if_odd;
    EXPECT_FALSE((bool)x);
    EXPECT_EQ(0,count);
}

TEST(optionalm,bind_with_ref) {
    optional<int> a=10;
    a >> [](int &v) {++v; };
    EXPECT_EQ(11,*a);
}

namespace {
    struct check_cref {
        int operator()(const int &) { return 10; }
        int operator()(int &) { return 11; }
    };
}

TEST(optionalm,bind_constness) {
    check_cref checker;
    optional<int> a=1;
    int v=*(a >> checker);
    EXPECT_EQ(11,v);

    const optional<int> b=1;
    v=*(b >> checker);
    EXPECT_EQ(10,v);
}


TEST(optionalm,conversion) {
    optional<double> a(3),b=5;
    EXPECT_TRUE((bool)a);
    EXPECT_TRUE((bool)b);
    EXPECT_EQ(3.0,a.get());
    EXPECT_EQ(5.0,b.get());

    optional<int> x;
    optional<double> c(x);
    optional<double> d=optional<int>();
    EXPECT_FALSE((bool)c);
    EXPECT_FALSE((bool)d);

    auto doubler=[](double x) { return x*2; };
    auto y=optional<int>(3) >> doubler;
    EXPECT_TRUE((bool)y);
    EXPECT_EQ(6.0,y.get());
}

TEST(optionalm,or_operator) {
    optional<const char *> default_msg="default";
    auto x=nullptr | default_msg;
    EXPECT_TRUE((bool)x);
    EXPECT_STREQ("default",x.get());

    auto y="something" | default_msg;
    EXPECT_TRUE((bool)y);
    EXPECT_STREQ("something",y.get());

    optional<int> a(1),b,c(3);
    EXPECT_EQ(1,*(a|b|c));
    EXPECT_EQ(1,*(a|c|b));
    EXPECT_EQ(1,*(b|a|c));
    EXPECT_EQ(3,*(b|c|a));
    EXPECT_EQ(3,*(c|a|b));
    EXPECT_EQ(3,*(c|b|a));
}

TEST(optionalm,and_operator) {
    optional<int> a(1);
    optional<double> b(2.0);

    auto ab=a&b;
    auto ba=b&a;

    EXPECT_EQ(typeid(ab),typeid(b));
    EXPECT_EQ(typeid(ba),typeid(a));
    EXPECT_EQ(2.0,*ab);
    EXPECT_EQ(1,*ba);

    auto zb=false & b;
    EXPECT_EQ(typeid(zb),typeid(b));
    EXPECT_FALSE((bool)zb);
    
    auto b3=b & 3;
    EXPECT_EQ(typeid(b3),typeid(optional<int>));
    EXPECT_TRUE((bool)b3);
    EXPECT_EQ(3,*b3);
}

TEST(optionalm,provided) {
    std::array<int,3> qs={1,0,3};
    std::array<int,3> ps={14,14,14};
    std::array<int,3> rs;

    std::transform(ps.begin(),ps.end(),qs.begin(),rs.begin(),
        [](int p,int q) { return *( provided(q!=0) >> [=]() { return p/q; } | -1 ); });

    EXPECT_EQ(14,rs[0]);
    EXPECT_EQ(-1,rs[1]);
    EXPECT_EQ(4,rs[2]);
}