/* -*- c-file-style: "GNU" -*- */
/*
 * Copyright (C) CNRS, INRIA, Universite Bordeaux 1, Telecom SudParis
 * See COPYING in top-level directory.
 */

#ifndef _REENTRANT
#define _REENTRANT
#endif

#include "mpi_eztrace.h"

#include <dlfcn.h>
#include <eztrace-lib/eztrace.h>
#include <mpi.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include <sys/timeb.h>
#include <unistd.h>
#include <eztrace-core/eztrace_attributes.h>

void mpi_complete_request(MPI_Fint* req MAYBE_UNUSED,
			  MPI_Status* s MAYBE_UNUSED) {
  if(!EZTRACE_SAFE)
    return;

  struct ezt_mpi_request* r =  ezt_mpi_get_request_type((MPI_Request*)req, 0);
  if(!r) { // This may be a persistent request
    r =  ezt_mpi_get_request_type((MPI_Request*)req, 1);
  }
  if(!r) { /* This may happen if the request was cancelled */
    return;
  }

  if(r->type == recv) {
    int received_bytes;
    MPI_Get_count(s, MPI_BYTE, &received_bytes);

    OTF2_ErrorCode err = OTF2_EvtWriter_MpiIrecv( evt_writer,
						  NULL,
						  ezt_get_timestamp(),
						  s->MPI_SOURCE,
						  0, //MPI_TO_OTF_COMMUNICATOR(comm)
						  s->MPI_TAG,
						  received_bytes,
						  (uint64_t)req);
    if(err != OTF2_SUCCESS) {
      eztrace_error("OTF2 error: %s: %s\n", OTF2_Error_GetName(err), OTF2_Error_GetDescription(err));
    }
  } else if(r->type == send  ||
	    r->type == bsend ||
	    r->type == rsend ||
	    r->type == ssend ) {
    /* send: nothing to do */
    OTF2_ErrorCode err = OTF2_EvtWriter_MpiIsendComplete( evt_writer,
							  NULL,
							  ezt_get_timestamp(),
							  (uint64_t)req);	
    if(err != OTF2_SUCCESS) {
      eztrace_error("OTF2 error: %s: %s\n", OTF2_Error_GetName(err), OTF2_Error_GetDescription(err));
    }
  } else {
    /* collective */

    int root = OTF2_UNDEFINED_UINT32;
    OTF2_CollectiveOp coll_type = 0;
    switch(r->type) {
    case ibarrier:        coll_type = OTF2_COLLECTIVE_OP_BARRIER; break;
    case ibcast:          coll_type = OTF2_COLLECTIVE_OP_BCAST; break;
    case igather: 	  coll_type = OTF2_COLLECTIVE_OP_GATHER; break;
    case igatherv: 	  coll_type = OTF2_COLLECTIVE_OP_GATHERV; break;
    case iscatter: 	  coll_type = OTF2_COLLECTIVE_OP_SCATTER; break;	 
    case iscatterv: 	  coll_type = OTF2_COLLECTIVE_OP_SCATTERV; break; 
    case iallgather: 	  coll_type = OTF2_COLLECTIVE_OP_ALLGATHER; break;
    case iallgatherv: 	  coll_type = OTF2_COLLECTIVE_OP_ALLGATHERV; break;
    case ialltoall: 	  coll_type = OTF2_COLLECTIVE_OP_ALLTOALL; break;
    case ialltoallv: 	  coll_type = OTF2_COLLECTIVE_OP_ALLTOALLV; break;
    case ialltoallw: 	  coll_type = OTF2_COLLECTIVE_OP_ALLTOALLW; break;
    case iallreduce: 	  coll_type = OTF2_COLLECTIVE_OP_ALLREDUCE; break;
    case ireduce: 	  coll_type = OTF2_COLLECTIVE_OP_REDUCE; break;
    case ireduce_scatter: coll_type = OTF2_COLLECTIVE_OP_REDUCE_SCATTER; break;
    default:
      eztrace_error("unknown colltype: %x\n", r->type);
    }

    if(r->type == ibcast ||
       r->type == igather ||
       r->type == igatherv ||
       r->type == iscatter ||
       r->type == iscatterv ||
       r->type == ireduce ||
       r->type == ireduce_scatter) {
      root = r->root;
    }

    OTF2_ErrorCode err = OTF2_EvtWriter_MpiCollectiveEnd(evt_writer,
							 NULL,
							 ezt_get_timestamp(),
							 coll_type,
							 MPI_TO_OTF_COMMUNICATOR(r->comm),
							 root,
							 r->send_size,
							 r->recv_size);

    if(err != OTF2_SUCCESS) {
      eztrace_error("OTF2 error: %s: %s\n", OTF2_Error_GetName(err), OTF2_Error_GetDescription(err));
    }

  }
}

static void MPI_Wait_prolog(MPI_Fint* req MAYBE_UNUSED,
                            MPI_Status* s MAYBE_UNUSED,
			    int* isvalid MAYBE_UNUSED) {
}

static int MPI_Wait_core(MPI_Request* req, MPI_Status* s) {
  return libMPI_Wait(req, s);
}


static void MPI_Wait_epilog(MPI_Fint* req MAYBE_UNUSED,
                            MPI_Status* s MAYBE_UNUSED,
			    int* isvalid) {
  if(isvalid)
    mpi_complete_request(req, s);
}

int MPI_Wait(MPI_Request* req, MPI_Status* s) {
  FUNCTION_ENTRY;

  int isvalid = *req != MPI_REQUEST_NULL;
  MPI_Status ezt_mpi_status;
  if(!s || s == MPI_STATUS_IGNORE) 
    s = &ezt_mpi_status;

  MPI_Wait_prolog((MPI_Fint*)req, s, &isvalid);
  int ret = MPI_Wait_core(req, s);
  MPI_Wait_epilog((MPI_Fint*)req, s, &isvalid);
  FUNCTION_EXIT;
  return ret;
}

void mpif_wait_(MPI_Fint* r, MPI_Fint* s, int* error) {
  FUNCTION_ENTRY_("mpi_wait_");
  MPI_Request c_req = MPI_Request_f2c(*r);
  int isvalid = c_req != MPI_REQUEST_NULL;
  MPI_Status c_status;
  MPI_Wait_prolog(r, &c_status, &isvalid);
  *error = MPI_Wait_core(&c_req, &c_status);
  MPI_Status_c2f(&c_status, s);
  MPI_Wait_epilog(r, &c_status, &isvalid);
  FUNCTION_EXIT_("mpi_wait_");
}
