/*    DISTRGEN2
TESTING distribute and redistribute CLAUSE
for arrays distributed with GEN-block*/

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

#define Min(a, b) ((a) < (b) ? (a) : (b))

#ifndef _DVMH
#define dvmh_get_num_procs(X) 1
#endif

static void distrg21();
static void distrg22();
static void distrg23();
static void distrg24();
static void distrg25();
static void distrg26();
static void distrg261();
static void distrg27();
static void distrg28();
static void distrg29();
static void distrg210();

static void ansyes(const char tname[]);
static void ansno(const char tname[]);

static int NL = 1000;
static int ER = 10000;
static int erri, i, j, ia, ib;

int main(int an, char **as)
{
    printf("=== START OF DISTRGEN2 ===================\n");
    /* DISTRIBUTE arrA2[BLOCK][*] REDISTRIBUTE arrA2[*][GENBLOCK]*/
    distrg21();

    /* DISTRIBUTE arrA2[*][BLOCK] REDISTRIBUTE arrA2[GENBLOCK][*]*/
    distrg22();

    /* DISTRIBUTE arrA2[*][GENBLOCK] REDISTRIBUTE arrA2[*][*]*/
    distrg23();

    /* DISTRIBUTE arrA2[*][*] REDISTRIBUTE arrA2[GENBLOCK][*]*/
    distrg24();

    /* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
              REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK] other blocks*/
    distrg25();

    /* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
                             REDISTRIBUTE arrA2[BLOCK][BLOCK]
                             REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]*/
    distrg26();

    /* DISTRIBUTE arrA2[GEN_BLOCK][BLOCK]
                         REDISTRIBUTE arrA2[BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[GEN_BLOCK][BLOCK]*/
    distrg27();

    /* DISTRIBUTE arrA2[BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[BLOCK][BLOCK]
                         REDISTRIBUTE arrA2[GEN_BLOCK][BLOCK]*/
    distrg28();

    /* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[*][*]
                         REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]*/
    distrg29();

    /* DISTRIBUTE arrA2[GEN_BLOCK][*]
                         REDISTRIBUTE arrA2[*][*]
                         REDISTRIBUTE arrA2[*][GEN_BLOCK]*/
    distrg210();

    printf("=== END OF DISTRGEN2 =====================\n");
    return 0;
}

static int myRand() {
    const unsigned a = 1103515245U;
    const unsigned c = 12345U;
    const unsigned m = ((unsigned)RAND_MAX) + 1U;
    static unsigned prev = 5;
    prev = (a * prev + c) % m;
    return prev;
}

static void genBlocksAxis(const int procCount, const int weight, const int withoutZero, int **blocks) {
    *blocks = (int *)malloc(procCount * sizeof(int));
    int restWeight = weight, i, zeroind = -1;
    if (!withoutZero && procCount != 1)
        zeroind = myRand() % (procCount - 1);
    for (i = 0; i < (procCount - 1); i++) {
        if (i == zeroind)
            (*blocks)[i] = 0;
        else
            (*blocks)[i] = 1 * withoutZero + myRand() % (restWeight - (procCount - (i + 1)));
        restWeight -= (*blocks)[i];
    }
    (*blocks)[i] = restWeight;
}
/* ---------------------------------------------DISTR21*/
/* DISTRIBUTE arrA2[BLOCK][*] REDISTRIBUTE arrA2[*][GENBLOCK]*/
void distrg21()
{
    #define AN1 8
    #define AN2 8
    int *BS;
    genBlocksAxis(dvmh_get_num_procs(1), AN2, 1, &BS);
    #pragma dvm array distribute[block][*]
    int A1[AN1][AN2];
    char tname[] = "distrg21";

    erri = ER;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A1[i][j] = i*NL + j;
    }
    #pragma dvm redistribute(A1[*][genblock(BS)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A1[i][j] != i*NL + j)
                    erri = Min(erri, i*NL/10 + j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR22*/
/* DISTRIBUTE arrA2[*][BLOCK] REDISTRIBUTE arrA2[GENBLOCK][*]*/
void distrg22()
{
    #define AN1 8
    #define AN2 8
    int *BS;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS);
    #pragma dvm array distribute[*][block]
    int A1[AN1][AN2];
    char tname[] = "distrg22";

    erri = ER;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A1[i][j] = i*NL + j;
    }
    #pragma dvm redistribute(A1[genblock(BS)][*])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A1[i][j] != i*NL + j)
                    erri = Min(erri, i*NL/10 + j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR23*/
/* DISTRIBUTE arrA2[*][GENBLOCK] REDISTRIBUTE arrA2[*][*]*/
void distrg23()
{
    #define AN1 8
    #define AN2 8
    int *BS;
    genBlocksAxis(dvmh_get_num_procs(1), AN2, 1, &BS);
    #pragma dvm array distribute[*][genblock(BS)]
    int A1[AN1][AN2];
    char tname[] = "distrg23";

    erri = ER;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A1[i][j] = i*NL + j;
    }
    #pragma dvm redistribute(A1[*][*])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A1[i][j] != i*NL + j)
                    erri = Min(erri, i*NL/10 + j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR24*/
/* DISTRIBUTE arrA2[*][*] REDISTRIBUTE arrA2[GENBLOCK][*]*/
void distrg24()
{
    #define AN1 8
    #define AN2 8
    int *BS;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS);
    #pragma dvm array distribute[*][*]
    int A1[AN1][AN2];
    char tname[] = "distrg24";

    erri = ER;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A1[i][j] = i*NL + j;
    }
    #pragma dvm redistribute(A1[genblock(BS)][*])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A1[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A1[i][j] != i*NL + j)
                    erri = Min(erri, i*NL/10 + j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR25*/
/* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
              REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK] other blocks*/
void distrg25()
{
    #define AN1 10
    #define AN2 12
    int *BS1i, *BS1j, *BS2i, *BS2j;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS1i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS1j);
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS2i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS2j);
    //int BS1i[2] = {5, 5}, BS1j[2] = {7, 5};
    //int BS2i[2] = {6, 4}, BS2j[2] = {5, 7};
    #pragma dvm array distribute[genblock(BS1i)][genblock(BS1j)]
    int A2[AN1][AN2];
    char tname[] = "distrg25";

    erri = ER;

    for (i = 0; i < AN1; i++)
        for (j = 0; j < AN2; j++)
            A2[i][j] = 1;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }

    #pragma dvm redistribute(A2[genblock(BS2i)][genblock(BS2j)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 1)
                    erri = Min(erri, i*NL/10+j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS1i);
    free(BS1j);
    free(BS2i);
    free(BS2j);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR26*/
/* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
                             REDISTRIBUTE arrA2[BLOCK][BLOCK]
                             REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]*/
void distrg26()
{
    #define AN1 10
    #define AN2 12
    int *BS1i, *BS1j, *BS2i, *BS2j;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS1i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS1j);
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS2i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS2j);
    #pragma dvm array distribute[genblock(BS1i)][genblock(BS1j)]
    int A2[AN1][AN2];
    char tname[] = "distrg26";
    erri = ER;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] = 1;
    }
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }
    #pragma dvm redistribute(A2[block][block])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }
    #pragma dvm redistribute(A2[genblock(BS2i)][genblock(BS2j)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 3)
                    erri = Min(erri, i*NL/10 + j + 3);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS1i);
    free(BS1j);
    free(BS2i);
    free(BS2j);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR27*/
 /* DISTRIBUTE arrA2[GEN_BLOCK][BLOCK]
                         REDISTRIBUTE arrA2[BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[GEN_BLOCK][BLOCK]*/

void distrg27()
{
    #define AN1 10
    #define AN2 12
    int *BS1i, *BS2j, *BS3i;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS1i);
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS3i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS2j);
    #pragma dvm array distribute[genblock(BS1i)][block]
    int A2[AN1][AN2];
    char tname[] = "distrg27";

    erri = ER;

    for (i = 0; i < AN1; i++)
        for (j = 0; j < AN2; j++)
            A2[i][j] = 1;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }
    #pragma dvm redistribute(A2[block][genblock(BS2j)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }
    #pragma dvm redistribute(A2[genblock(BS3i)][block])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;

        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 3)
                    erri = Min(erri, i*NL/10+j + 3);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS1i);
    free(BS3i);
    free(BS2j);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR28*/
/* DISTRIBUTE arrA2[BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[BLOCK][BLOCK]
                         REDISTRIBUTE arrA2[GEN_BLOCK][BLOCK]*/
void distrg28()
{
    #define AN1 10
    #define AN2 12
    int *BS1j, *BS2i;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS2i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS1j);
    #pragma dvm array distribute[block][genblock(BS1j)]
    int A2[AN1][AN2];
    char tname[] = "distrg28";

    erri = ER;

    for (i = 0; i < AN1; i++)
        for (j = 0; j < AN2; j++)
            A2[i][j] = 1;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }
    #pragma dvm redistribute(A2[block][block])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }

    #pragma dvm redistribute(A2[genblock(BS2i)][block])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;

        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 3)
                    erri = Min(erri, i*NL/10+j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS2i);
    free(BS1j);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR29*/
/* DISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]
                         REDISTRIBUTE arrA2[*][*]
                         REDISTRIBUTE arrA2[GEN_BLOCK][GEN_BLOCK]*/
void distrg29()
{
    #define AN1 10
    #define AN2 12
    int *BS1i, *BS1j, *BS2i, *BS2j;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS1i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS1j);
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS2i);
    genBlocksAxis(dvmh_get_num_procs(2), AN2, 1, &BS2j);
    #pragma dvm array distribute[genblock(BS1i)][genblock(BS1j)]
    int A2[AN1][AN2];
    char tname[] = "distrg29";

    erri = ER;

    for (i = 0; i < AN1; i++)
        for (j = 0; j < AN2; j++)
            A2[i][j] = 1;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }
    #pragma dvm redistribute(A2[*][*])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }
    #pragma dvm redistribute(A2[genblock(BS2i)][genblock(BS2j)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;

        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 3)
                    erri = Min(erri, i*NL/10+j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS1i);
    free(BS1j);
    free(BS2i);
    free(BS2j);
    #undef AN1
    #undef AN2
}

/* ---------------------------------------------DISTR210*/
/* DISTRIBUTE arrA2[GEN_BLOCK][*]
                         REDISTRIBUTE arrA2[*][*]
                         REDISTRIBUTE arrA2[*][GEN_BLOCK]*/
void distrg210()
{
    #define AN1 10
    #define AN2 12
    int *BS1i, *BS2i;
    genBlocksAxis(dvmh_get_num_procs(1), AN1, 1, &BS1i);
    genBlocksAxis(dvmh_get_num_procs(1), AN2, 1, &BS2i);
    #pragma dvm array distribute[genblock(BS1i)][*]
    int A2[AN1][AN2];
    char tname[] = "distrg210";

    erri = ER;

    for (i = 0; i < AN1; i++)
        for (j = 0; j < AN2; j++)
            A2[i][j] = 1;
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += i*NL+j;
    }
    #pragma dvm redistribute(A2[*][*])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j]) cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;
    }
    #pragma dvm redistribute(A2[*][genblock(BS2i)])
    #pragma dvm region
    {
        #pragma dvm parallel([i][j] on A2[i][j])
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                A2[i][j] += 1;

        #pragma dvm parallel([i][j] on A2[i][j]) reduction(min(erri)), cuda_block(256)
        for (i = 0; i < AN1; i++)
            for (j = 0; j < AN2; j++)
                if (A2[i][j] != i*NL+j + 3)
                    erri = Min(erri, i*NL/10+j);
    }

    #pragma dvm get_actual(erri)
    if (erri == ER)
        ansyes(tname);
    else
        ansno(tname);
    free(BS1i);
    free(BS2i);
    #undef AN1
    #undef AN2
}

void ansyes(const char name[])
{
    printf("%s  -  complete\n", name);
}

void ansno(const char name[])
{
    printf("%s  -  ***error\n", name);
}
