//------------------------------------------------------------------------------
//  test C++11 atomics
//  compile native version with:
//  clang -std=c++11 -Wno-format atomic_test.cpp 
//  compile emscripten version with:
//  emcc -std=c++11 -Wno-format atomic_test.cpp
//------------------------------------------------------------------------------
#include <atomic>
#include <cstdio>

template<typename TYPE, typename UNSIGNED_TYPE> void test(TYPE mask0, TYPE mask1, TYPE mask2) {
    typedef TYPE dog;
    
    const TYPE numMemoryOrders = 6;
    std::memory_order memoryOrder[numMemoryOrders] = {
        std::memory_order_relaxed,
        std::memory_order_consume,
        std::memory_order_acquire,
        std::memory_order_release,
        std::memory_order_acq_rel,
        std::memory_order_seq_cst,
    };

    // test atomic<int>
    std::atomic<dog> atomicDog(5);
    printf("atomic<int>.is_lock_free(): %s\n", atomicDog.is_lock_free() ? "true" : "false");
    printf("atomic<int> value: %lld\n", (long long)TYPE(atomicDog));

    // test store/load
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        atomicDog.store(i, memoryOrder[i]);
        printf("store/load %lld: %lld\n", (long long)i, (long long)atomicDog.load(memoryOrder[i]));
    }

    // test exchange
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        TYPE old = atomicDog.exchange(i, memoryOrder[i]);
        printf("exchange %lld: old=%lld new=%lld\n", (long long)i, (long long)old, (long long)TYPE(atomicDog));
    }

    // compare_exchange_weak
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        bool success = atomicDog.compare_exchange_weak(i, i + 1, memoryOrder[i], memoryOrder[i]);
        printf("compare_exchange_weak %lld: success = %s\n", (long long)i, success ? "true" : "false");
    }

    // compare_exchange_strong
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        bool success = atomicDog.compare_exchange_strong(i, i + 1, memoryOrder[i], memoryOrder[i]);
        printf("compare_exchange_strong %lld: success = %s\n", (long long)i, success ? "true" : "false");
    }

    // fetch_add
    atomicDog = 0;
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        TYPE old = atomicDog.fetch_add(1, memoryOrder[i]);
        printf("fetch_add %lld: old=%lld new=%lld\n", (long long)i, (long long)old, (long long)TYPE(atomicDog));
    }

    // fetch_sub
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        TYPE old = atomicDog.fetch_sub(1, memoryOrder[i]);
        printf("fetch_sub %lld: old=%lld new=%lld\n", (long long)i, (long long)old, (long long)TYPE(atomicDog));
    }

    // fetch_and
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        atomicDog.store(mask0, memoryOrder[i]);
        TYPE old = atomicDog.fetch_and((1<<i), memoryOrder[i]);
        printf("fetch_and %lld: old=%llx, new=%llx\n", (long long)i, (unsigned long long)UNSIGNED_TYPE(old), (unsigned long long)UNSIGNED_TYPE(atomicDog));
    }

    // fetch_or
    atomicDog = 0;
    for (TYPE i = 0; i < numMemoryOrders; i++) {
        TYPE old = atomicDog.fetch_or((1<<i), memoryOrder[i]);
        printf("fetch_or %lld: old=%llx, new=%llx\n", (long long)i, (unsigned long long)UNSIGNED_TYPE(old), (unsigned long long)UNSIGNED_TYPE(atomicDog));
    }

    // fetch_xor
    atomicDog = 0;
    for (int i = 0; i < numMemoryOrders; i++) {
        int old = atomicDog.fetch_xor((1<<i), memoryOrder[i]);
        printf("fetch_xor %lld: old=%llx, new=%llx\n", (long long)i, (unsigned long long)UNSIGNED_TYPE(old), (unsigned long long)UNSIGNED_TYPE(atomicDog));
    }

    // operator++, --
    atomicDog = 0;
    atomicDog++;
    printf("operator++: %lld\n", (long long)TYPE(atomicDog));
    atomicDog--;
    printf("operator--: %lld\n", (long long)TYPE(atomicDog));

    // operator +=, -=, &=, |=, ^=
    atomicDog += 10;
    printf("operator+=: %lld\n", (long long)TYPE(atomicDog));
    atomicDog -= 5;
    printf("operator-=: %lld\n", (long long)TYPE(atomicDog));
    atomicDog |= mask0;
    printf("operator|=: %llx\n", (unsigned long long)UNSIGNED_TYPE(atomicDog));
    atomicDog &= mask1;
    printf("operator&=: %llx\n", (unsigned long long)UNSIGNED_TYPE(atomicDog));
    atomicDog ^= mask2;
    printf("operator^=: %llx\n", (unsigned long long)UNSIGNED_TYPE(atomicDog));

}

int main() {

    // test 8, 16, 32 and 64-bit data types
    printf("\n8 bits\n\n");
    test<char, unsigned char>(0xFF, 0xF0, 0x0F);
    printf("\n16 bits\n\n");
    test<short, unsigned short>(0xFFFF, 0xF0F0, 0x0F0F);
    printf("\n32 bits\n\n");
    test<int, unsigned int>(0xFFFFFFFF, 0xF0F0F0F0, 0x0F0F0F0F);
    printf("\n64 bits\n\n");
    test<long long, unsigned long long>(0xFFFFFFFFFFFFFFFF, 0xF0F0F0F0F0F0F0F0, 0x0F0F0F0F0F0F0F0F);

    // test atomic_flag (should also have memory_orders, but probably doesn't matter 
    // to find the missing atomic functions)
    std::atomic_flag af;
    af.clear();
    bool b = af.test_and_set();
    printf("atomic_flag: %s\n", b ? "true" : "false");

    printf("done.\n");
    return 0;
}

