#include "vec_func.h"
#include <cassert>
#include <cstring>
#include <iostream>
#include <mpi.h>                                  // MPI
#include <string>
using namespace std;

// see  http://www.open-mpi.org/doc/current
// for details on MPI functions

void vecdebug(vector<double> const & x, const MPI_Comm &icomm)
{
	int myrank, size;
    MPI_Comm_rank(icomm, &myrank);
    MPI_Comm_size(icomm, &size);
    int ierr;
    
    int n = x.size();
    
    int take_process;
    for(int i=0; i<size; ++i)
    {
		MPI_Barrier(icomm);
		if(myrank == 0)	// only one process for input
		{
			cout << "\nChoose next process for printing vector (0-3): ";
			cout.flush();
			cin >> take_process;	// no check for wrong input
		}
	
		ierr = MPI_Bcast(&take_process, 1, MPI_INT, 0, icomm); // broadcast value of "take_process" to all processes -> all know what's going on
		// information comes from process 0
		assert(ierr == 0);
		
		MPI_Barrier(icomm);
		
		if(take_process == myrank)
		{
			for(int k=0; k<n; ++k)
			{
				cout << "x_" << k << " = " << x[k] << " (" << myrank << ")\t";
				cout.flush();
				// in brackets we have the actual process
			}
			cout << endl;
			cout.flush();
		}
		
		MPI_Barrier(icomm);
	}
	// to avoid some output chaos with cout you could also do it with MPI_Send and MPI_Recv
	
	return;
}


double par_scalar(vector<double> const & x, vector<double> const & y, const MPI_Comm &icomm)
{
	assert(x.size()==y.size());
	
	double sum_local = 0;
	double sum_global = 0;
	
	for(size_t k=0; k<x.size(); ++k)
	{
		sum_local += x[k]*y[k];
	}
	
	auto ierr = MPI_Allreduce(&sum_local, &sum_global, 1, MPI_DOUBLE, MPI_SUM, icomm);  // reduce local sums to global sum
    assert(ierr == 0);
	
	return sum_global;
}


void min_max_exch(vector<double> &x, double &min_val, double &max_val, const MPI_Comm &icomm)
{
	int myrank;
    MPI_Comm_rank(icomm, &myrank);
	int local_n = x.size();
	int global_offset = myrank * local_n;	// for interchanging
	
	struct {double value; int index;} local_min, local_max, global_min, global_max;	// global index

	local_min.value = x[0];
	local_max.value = x[0];
	local_min.index = global_offset;
	local_max.index = global_offset;

	// finding local min/max with the corresponding global index
	for (int i = 1; i < local_n; ++i)
	{
	    if (x[i] < local_min.value)
	    {
	        local_min.value = x[i];
	        local_min.index = global_offset + i;
	    }
	    if (x[i] > local_max.value)
	    {
	        local_max.value = x[i];
	        local_max.index = global_offset + i;
	    }
	}
	
	// reduction to the global one including the global index (need it later for interchanging)
	MPI_Allreduce(&local_min, &global_min, 1, MPI_DOUBLE_INT, MPI_MINLOC, icomm);
	MPI_Allreduce(&local_max, &global_max, 1, MPI_DOUBLE_INT, MPI_MAXLOC, icomm);
	min_val = global_min.value;
	max_val = global_max.value;
	
	// calculating the process and the local index for interchanging the min and max value
	int rank_min = global_min.index / local_n;
	int rank_max = global_max.index / local_n;
	int local_min_idx = global_min.index % local_n;
	int local_max_idx = global_max.index % local_n;
	
	// interchanging
	if (rank_min != rank_max)
	{
	    double recv_value;
	    if (myrank == rank_min)
	    {
	        MPI_Sendrecv(&x[local_min_idx], 1, MPI_DOUBLE, rank_max, 0, &recv_value, 1, MPI_DOUBLE, rank_max, 0, icomm, MPI_STATUS_IGNORE);	// from last it gets received, first send it to
	        x[local_min_idx] = recv_value;
	    }
	
	    if (myrank == rank_max)
	    {
	        MPI_Sendrecv(&x[local_max_idx], 1, MPI_DOUBLE, rank_min, 0, &recv_value, 1, MPI_DOUBLE, rank_min, 0, icomm, MPI_STATUS_IGNORE);
	        x[local_max_idx] = recv_value;
	    }

	}
	else // min and max value are on same process
	{
		swap(x[local_min_idx], x[local_max_idx]);
	}

	return;
}
