#include <stdio.h>
#include <stdlib.h>
#include <omp.h>

long long count_unprotected(long long n, int thread_count) {
    long long count = 0;
#   pragma omp parallel for num_threads(thread_count)
    for (long long i = 0; i < n; i++) {
        count++;
    }
    return count;
}

long long count_critical(long long n, int thread_count) {
    long long count = 0;
#   pragma omp parallel for num_threads(thread_count)
    for (long long i = 0; i < n; i++) {
#       pragma omp critical
        count++;
    }
    return count;
}

long long count_atomic(long long n, int thread_count) {
    long long count = 0;
#   pragma omp parallel for num_threads(thread_count)
    for (long long i = 0; i < n; i++) {
#       pragma omp atomic
        count++;
    }
    return count;
}

long long count_lock(long long n, int thread_count) {
    long long count = 0;
    omp_lock_t count_lock;
    omp_init_lock(&count_lock);
#   pragma omp parallel for num_threads(thread_count)
    for (long long i = 0; i < n; i++) {
        omp_set_lock(&count_lock);
        count++;
        omp_unset_lock(&count_lock);
    }
    omp_destroy_lock(&count_lock);
    return count;
}

long long count_reduction(long long n, int thread_count) {
    long long count = 0;
#   pragma omp parallel for num_threads(thread_count) reduction(+: count)
    for (long long i = 0; i < n; i++) {
        count++;
    }
    return count;
}

void report(const char *method, long long count, long long n, double seconds) {
    printf("%-12s %12lld   %-8s %8.3f\n", method, count,
           count == n ? "yes" : "NO", seconds);
}

int main(int argc, char *argv[]) {
    int thread_count = 4;
    long long n = 10000000;
    if (argc > 1) {
        thread_count = (int)strtol(argv[1], NULL, 10);
    }
    if (argc > 2) {
        n = strtoll(argv[2], NULL, 10);
    }

    printf("Threads: %d\n", thread_count);
    printf("%-12s %12s   %-8s %8s\n", "Method", "Count", "Correct?", "Time (s)");

    double start = omp_get_wtime();
    long long count = count_unprotected(n, thread_count);
    report("unprotected", count, n, omp_get_wtime() - start);

    start = omp_get_wtime();
    count = count_critical(n, thread_count);
    report("critical", count, n, omp_get_wtime() - start);

    start = omp_get_wtime();
    count = count_atomic(n, thread_count);
    report("atomic", count, n, omp_get_wtime() - start);

    start = omp_get_wtime();
    count = count_lock(n, thread_count);
    report("lock", count, n, omp_get_wtime() - start);

    start = omp_get_wtime();
    count = count_reduction(n, thread_count);
    report("reduction", count, n, omp_get_wtime() - start);
    return 0;
}
