/*
 * Copyright (C) by Argonne National Laboratory
 *     See COPYRIGHT in top-level directory
 */

/* This test checks accumulate ordering in three cases:
 * 1) Default (most restricted)
 * 2) None (no ordering)
 * 3) Mixture of REPLACE and MIN_LOC/MAX_LOC.
 *    (In CH4/OFI, REPLACE may go through OFI but MIN_LOC/MAX_LOC has to go through active message.)
 */

#include <stdio.h>
#include <stdlib.h>
#include <mpi.h>
#include <limits.h>
#include "mpitest.h"

typedef struct {
    int val;
    int loc;
} twoint_t;

#define ARRAY_LEN (8192/(sizeof(twoint_t)))

static int errs = 0;

static void verify_result(twoint_t * data, int len, twoint_t expected, const char *test_name)
{
    int i;
    for (i = 0; i < len; i++) {
        if ((data[i].val != expected.val) || (data[i].loc != expected.loc)) {
            errs++;
            printf("%s: Expected: { loc = %d, val = %d }  Actual: { loc = %d, val = %d }\n",
                   test_name, expected.loc, expected.val, data[i].loc, data[i].val);
        }
    }
}

/* Check non-deterministic result of none ordering.
 * Expected result has two possibilities. */
static void verify_nondeterministic_result(twoint_t * data,
                                           int len, twoint_t * expected, const char *test_name)
{
    int i;
    for (i = 0; i < len; i++) {
        if (!((data[i].loc == expected[0].loc && data[0].val == expected[0].val)
              || (data[i].loc == expected[1].loc && data[i].val == expected[1].val))) {
            errs++;
            printf
                ("%s: Expected: { loc = %d, val = %d } or { loc = %d, val = %d }; Actual: { loc = %d, val = %d }\n",
                 test_name, expected[0].loc, expected[0].val, expected[1].loc, expected[1].val,
                 data[i].loc, data[i].val);
        }
    }
}

int main(int argc, char **argv)
{
    int me, nproc, i;
    twoint_t *data;
    twoint_t *mine = NULL;
    twoint_t *mine_plus = NULL;
    twoint_t *expected = NULL;
    MPI_Win win;
    MPI_Info info_in;

    MTest_Init(&argc, &argv);

    MPI_Comm_rank(MPI_COMM_WORLD, &me);
    MPI_Comm_size(MPI_COMM_WORLD, &nproc);

    if (nproc < 2) {
        printf("Run this program with 2 or more processes\n");
        MPI_Abort(MPI_COMM_WORLD, 1);
    }

    data = (twoint_t *) calloc(ARRAY_LEN, sizeof(twoint_t));
    if (me == 0) {
        /* length of 2 in order to store all results for none ordering. */
        expected = (twoint_t *) malloc(2 * sizeof(twoint_t));
    }
    if (me == nproc - 1) {
        mine = (twoint_t *) malloc(ARRAY_LEN * sizeof(twoint_t));
        mine_plus = (twoint_t *) malloc(ARRAY_LEN * sizeof(twoint_t));
        for (i = 0; i < ARRAY_LEN; i++) {
            mine[i].val = me + 1;
            mine[i].loc = me;
            mine_plus[i].val = me + 2;
            mine_plus[i].loc = me + 1;
        }
    }

    /* 1. Default ordering */
    MPI_Win_create(data, sizeof(twoint_t) * ARRAY_LEN, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &win);
    MPI_Win_fence(0, win);

    /* Rank np-1 performs 2 WRITE to rank 0. */
    /* 1.a. Single data test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine_plus, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        expected[0].loc = nproc;
        expected[0].val = nproc + 1;
        verify_result(data, 1, expected[0], "Single data test case for default ordering");
    }

    if (me == 0) {
        data[0].loc = 0;
        data[0].val = 0;
    }
    MPI_Win_fence(0, win);
    /* 1.b. Large array test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine_plus, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        verify_result(data, ARRAY_LEN, expected[0], "Large array test case for default ordering");
    }
    /* MPI Implementation may ignore window info changes once created.
     * Thus we should free current window and create a new window with required info. */
    MPI_Win_free(&win);

    /* 2. None ordering */
    if (me == 0) {
        /* reset data on rank 0. */
        memset((void *) data, 0, ARRAY_LEN * sizeof(twoint_t));
    }

    MPI_Info_create(&info_in);
    MPI_Info_set(info_in, "accumulate_ordering", "none");
    MPI_Win_create(data, sizeof(twoint_t) * ARRAY_LEN, 1, info_in, MPI_COMM_WORLD, &win);
    MPI_Info_free(&info_in);

    MPI_Win_fence(0, win);

    /* Rank np-1 performs 2 WRITE to rank 0. */
    /* 2.a. Single data test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine_plus, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        expected[0].loc = nproc - 1;
        expected[0].val = nproc;
        expected[1].loc = nproc;
        expected[1].val = nproc + 1;
        verify_nondeterministic_result(data, 1, expected,
                                       "Single data test case for none ordering");
    }

    if (me == 0) {
        data[0].loc = 0;
        data[0].val = 0;
    }
    MPI_Win_fence(0, win);
    /* 2.b. Large array test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine_plus, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        verify_nondeterministic_result(data, ARRAY_LEN, expected,
                                       "Large array test case for none ordering");
    }

    MPI_Win_free(&win);

    /* 3. Mix MAX_LOC/MIN_LOC and MPI_REPLACE */
    if (me == 0) {
        /* reset data on rank 0. */
        memset((void *) data, 0, ARRAY_LEN * sizeof(twoint_t));
    }

    MPI_Win_create(data, sizeof(twoint_t) * ARRAY_LEN, 1, MPI_INFO_NULL, MPI_COMM_WORLD, &win);

    MPI_Win_fence(0, win);

    /* Rank np-1 performs 2 WRITE to rank 0. */
    /* Test MAXLOC */
    /* 3.a. Single data test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine_plus, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_MAXLOC, win);
        MPI_Accumulate(mine, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        expected[0].loc = nproc - 1;
        expected[0].val = nproc;
        verify_result(data, 1, expected[0], "Single data test case for MAXLOC and REPLACE");
    }

    if (me == 0) {
        data[0].loc = 0;
        data[0].val = 0;
    }
    MPI_Win_fence(0, win);
    /* 3.b. Large array test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine_plus, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_MAXLOC, win);
        MPI_Accumulate(mine, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        verify_result(data, ARRAY_LEN, expected[0], "Large array test case for MAXLOC and REPLACE");
    }

    /* Test MINLOC */
    if (me == 0) {
        for (i = 0; i < ARRAY_LEN; i++) {
            data[i].loc = INT_MAX;
            data[i].val = INT_MAX;
        }
    }
    MPI_Win_fence(0, win);
    /* Rank np-1 performs 2 WRITE to rank 0. */
    /* 3.c. Single data test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine_plus, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine, 1, MPI_2INT, 0, 0, 1, MPI_2INT, MPI_MINLOC, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        expected[0].loc = nproc - 1;
        expected[0].val = nproc;
        verify_result(data, 1, expected[0], "Single data test case for REPLACE and MINLOC");
    }

    if (me == 0) {
        data[0].loc = INT_MAX;
        data[0].val = INT_MAX;
    }
    MPI_Win_fence(0, win);
    /* 3.d. Large array test */
    if (me == nproc - 1) {
        MPI_Accumulate(mine_plus, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_REPLACE, win);
        MPI_Accumulate(mine, ARRAY_LEN, MPI_2INT, 0, 0, ARRAY_LEN, MPI_2INT, MPI_MINLOC, win);
    }

    MPI_Win_fence(0, win);
    if (me == 0) {
        verify_result(data, ARRAY_LEN, expected[0], "Single data test case for REPLACE and MINLOC");
    }

    MPI_Win_free(&win);
    if (me == nproc - 1) {
        free(mine);
        free(mine_plus);
    }
    if (me == 0)
        free(expected);

    MTest_Finalize(errs);
    free(data);

    return MTestReturnValue(errs);
}
