//===----------------------------------------------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

// std::function support for the "blocks" extension

// UNSUPPORTED: c++03

// This test requires the Blocks runtime, which is (only?) available
// on Darwin out-of-the-box.
// REQUIRES: has-fblocks && darwin

// ADDITIONAL_COMPILE_FLAGS: -fblocks

#include <cassert>
#include <cstdlib>
#include <functional>
#include <typeinfo>

#include <Block.h>

#include "test_macros.h"
#include "count_new.h"


struct A {
  static int count;
  int id_;
  explicit A(int id) { ++count; id_ = id; }
  A(const A &a) { id_ = a.id_; ++count; }
  ~A() { id_ = -1; --count; }
  int operator()() const { return -1; }
  int operator()(int i) const { return i; }
  int operator()(int, int) const { return -2; }
  int operator()(int, int, int) const { return -3; }
  int id() const { return id_; }
};

int A::count = 0;

int g(int) { return 0; }

int main(int, char**)
{
    // swap
    {
        std::function<int(int)> f1 = g;
        std::function<int(int)> f2 = ^(int x) { return x + 1; };
        assert(globalMemCounter.checkOutstandingNewEq(0));
        RTTI_ASSERT(*f1.target<int(*)(int)>() == g);
        RTTI_ASSERT(*f2.target<int(^)(int)>() != 0);
        swap(f1, f2);
        assert(globalMemCounter.checkOutstandingNewEq(0));
        RTTI_ASSERT(*f1.target<int(^)(int)>() != 0);
        RTTI_ASSERT(*f2.target<int(*)(int)>() == g);
    }

    // operator bool
    {
        std::function<int(int)> f;
        assert(!f);
        f = ^(int x) { return x+1; };
        assert(f);
    }

    // operator()
    {
        std::function<int ()> r1(^{ return 4; });
        assert(r1() == 4);
    }
    {
        __block bool called = false;
        std::function<void ()> r1(^{ called = true; });
        r1();
        assert(called);
    }
    {
        __block int param = 0;
        std::function<void (int)> r1(^(int x){ param = x; });
        r1(4);
        assert(param == 4);
    }
    {
        std::function<int (int)> r1(^(int x){ return x + 4; });
        assert(r1(3) == 7);
    }
    {
        __block int param1 = 0;
        __block int param2 = 0;
        std::function<void (int, int)> r1(^(int x, int y){ param1 = x; param2 = y; });
        r1(3, 4);
        assert(param1 == 3);
        assert(param2 == 4);
    }
    {
        std::function<int (int, int)> r1(^(int x, int y){ return x + y; });
        assert(r1(3, 4) == 7);
    }

    // swap
    {
        std::function<int(int)> f1 = A(999);
        std::function<int(int)> f2 = ^(int x) { return x + 1; };
        assert(A::count == 1);
        assert(globalMemCounter.checkOutstandingNewEq(1));
        RTTI_ASSERT(f1.target<A>()->id() == 999);
        RTTI_ASSERT((*f2.target<int(^)(int)>())(13) == 14);
        f1.swap(f2);
        assert(A::count == 1);
        assert(globalMemCounter.checkOutstandingNewEq(1));
        RTTI_ASSERT((*f1.target<int(^)(int)>())(13) == 14);
        RTTI_ASSERT(f2.target<A>()->id() == 999);
    }
    assert(globalMemCounter.checkOutstandingNewEq(0));
    assert(A::count == 0);

    // operator== and operator!=
    {
        std::function<int(int)> f;
        assert(f == nullptr);
        assert(nullptr == f);
        f = ^(int x) { return x + 1; };
        assert(f != nullptr);
        assert(nullptr != f);
    }

    // target
    {
        int (^block)(int) = Block_copy(^(int x) { return x + 1; });
        std::function<int(int)> f = block;
        RTTI_ASSERT(*f.target<int(^)(int)>() == block);
        RTTI_ASSERT(f.target<int(*)(int)>() == 0);
        Block_release(block);
    }

    // target_type
    {
        std::function<int(int)> f = ^(int x) { return x + 1; };
        RTTI_ASSERT(f.target_type() == typeid(int(^)(int)));
    }

    return 0;
}
