#include <map>
#include <iostream>
#include <mkl.h>

#include "magma_mic_bindings.h"

// globals
//std::map<off_t, size_t> gHost_nbyte;
//std::map<off_t, off_t> gHost_offset;

std::vector<maptypemem> gHost_nbyte;
std::vector<maptypeoff> gHost_offset;
std::vector<maptypemem> gDev_nbyte;
std::vector<maptypeoff> gDev_offset;

scif_epd_t gEpd;
int gNumEpd = 0;
int gCurrID = 1;
scif_epd_t *gEpd_array;

// Sync waits for all kernels to complete.
// This does NOT wait for communications to complete.
// To wait for communication, use fence_mark and fence_wait.
void magma_mic_synchronize_kernels()
{
    int err;
    int control_msg = magma_mic_SYNC;
    
    if ((err = scif_send(gEpd, &control_msg, sizeof(control_msg), 1)) <= 0) {
        err = errno;
        printf("scif_send failed with err %d\n", errno);
        fflush(stdout);
    }
    
    // todo: don't need recv; above send is enough to sync
    if ((err = scif_recv(gEpd, &control_msg, sizeof(control_msg), 1)) <= 0) {
        err = errno;
        printf("scif_send failed with err %d\n", errno);
        fflush(stdout);
    }
    return;
}

// Mark all current communications, in order to sync them with fence_wait.
int magma_mic_fence_mark( magma_event_t *mark )
{
    int err;
    if ((err = scif_fence_mark(gEpd, SCIF_FENCE_INIT_SELF, mark)) != 0) {
        err = errno;
        printf("scif_fence_mark failed with err %d\n", errno);
        fflush(stdout);
        return MIC_FAILURE;
    }
    /*printf("Value of mark 0x%x\n", mark);*/
    return MAGMA_SUCCESS;
}

// Wait for all marked communications to complete.
// This does NOT wait for kernels to complete.
// To wait for kernels, use magma_mic_synchronize_kernels.
int magma_mic_fence_wait( magma_event_t mark )
{
    int err;
    if ((err = scif_fence_wait(gEpd, mark)) != 0) {
        err = errno;
        printf("scif_fence_wait failed with err %d\n", errno);
        fflush(stdout);
        return MIC_FAILURE;
    }
    return MAGMA_SUCCESS;
}

int magma_mic_memcpy2D_async(
          void *dst, size_t dpitch,
    const void *src, size_t spitch,
    magma_int_t width, magma_int_t height, magma_int_t kind,
    magma_queue_t queue, magma_event_t* event )
{
    int err;
    off_t base_host_address;
    off_t base_device_address;
    off_t base_host_offset;
    off_t base_device_offset;
    size_t col;
    
    const void *h_A, *d_A;
    off_t byte_in_ldda;
    off_t byte_in_ldha;
    
    if (kind == WRITETO_DEV) {
        d_A = dst;
        h_A = src;
        byte_in_ldda = dpitch;
        byte_in_ldha = spitch;
    }
    else if (kind == READFROM_DEV) {
        h_A = dst;
        d_A = src;
        byte_in_ldda = spitch;
        byte_in_ldha = dpitch;
    }
    
    std::map<off_t, size_t>::iterator dev_nbit;
    std::map<off_t, size_t>::iterator host_nbit;
    
    for(dev_nbit = (gDev_nbyte[gCurrID]).begin(); dev_nbit != (gDev_nbyte[gCurrID]).end(); ++dev_nbit) {
        off_t base = dev_nbit->first;
        size_t size = dev_nbit->second;
        if ((off_t)d_A >= base && (off_t)d_A <= (base + size)) {
            base_device_address = base;
            base_device_offset = (gDev_offset[gCurrID])[base];
            break;
        }
    }
    
    //return if not found in dev pointer
    //if(dev_nbit == (gDev_nbyte[gCurrID]).end()) {
    //    printf("For mic-%d couldn't find address: %lx\n", gCurrID, (off_t)d_A); 
    //}
    
    for(host_nbit = (gHost_nbyte[gCurrID]).begin(); host_nbit != (gHost_nbyte[gCurrID]).end(); ++host_nbit) {
        off_t base = host_nbit->first;
        size_t size = host_nbit->second;
        if ((off_t)h_A >= base && (off_t)h_A <= (base + size)) {
            base_host_address = base;
            base_host_offset = (gHost_offset[gCurrID])[base];
            break;
        }
    }
    
    //printf("mic %d ha: 0x%lx da: 0x%lx hoff: 0x%lx doff: 0x%lx\n", gCurrID, base_host_address, base_device_address, base_host_offset, base_device_offset);
    //printf("mic %d dst: 0x%lx dpitch: %ld src: 0x%lx spitch: %ld width: %d height; %d\n", gCurrID, (off_t)(dst), dpitch, (off_t)(src), spitch, width, height);
    //return if not found in host pointer
    //if(host_nbit == gHost_nbyte.end()) {
    //    printf("For mic-%d couldn't find host address: %lx\n", gCurrID, (off_t)h_A); 
    //}
    
    off_t byte_in_column = width;
    
    off_t dev_shift_byte = (off_t)d_A - base_device_address;
    off_t host_shift_byte = (off_t)h_A - base_host_address;
    
    off_t current_dev_offset = base_device_offset + dev_shift_byte;
    off_t current_host_offset = base_host_offset + host_shift_byte;
    
    if (kind == WRITETO_DEV) {
	    if(spitch == dpitch && dpitch == width) {
		    if ((err = scif_writeto(gEpd, current_host_offset,
						    spitch * height,
						    current_dev_offset, 0)))
			    {
				    err = errno;
				    printf("scif_writeto failed for mic-%d with err %d\n", gCurrID, errno);
				    fflush(stdout);
				    return MIC_FAILURE;
			    }
	    } else {
		    for(col = 0; col < height; col++) {
			    if ((err = scif_writeto(gEpd, current_host_offset + col*byte_in_ldha,
							    byte_in_column,
							    current_dev_offset + col*byte_in_ldda, /*SCIF_RMA_SYNC*/ 0)))
			    {
				    err = errno;
				    printf("scif_writeto failed for mic-%d with err %d\n", gCurrID, errno);
				    /*if(err == EACCESS)
				      printf("writeto: EACCESS\n");
				      else */if(err == EBADF)
				    printf("writeto: EBADF\n");
				    else if(err == ECONNRESET)
					    printf("writeto: ECONNRESET\n");
				    else if(err == EINVAL)
					    printf("writeto: EINVAL\n");
				    else if(err == ENODEV)
					    printf("writeto: ENODEV\n");
				    else if(err == ENOTCONN)
					    printf("writeto: ENOTCONN\n");
				    else if(err == ENOTTY)
					    printf("writeto: ENOTTY\n");
				    else if(err == ENXIO)
					    printf("writeto: ENXIO\n");
				    fflush(stdout);
				    return MIC_FAILURE;
			    }
		    }
	    }
    }
    else if (kind == READFROM_DEV) {
	    if(spitch == dpitch && dpitch == width) {
		    if ((err = scif_readfrom(gEpd, current_host_offset,
						    spitch * height,
						    current_dev_offset, SCIF_RMA_SYNC)))
		    {
			    err = errno;
			    printf("scif_readfrom failed for mic-%d with err %d\n", gCurrID, errno);
			    fflush(stdout);
			    return MIC_FAILURE;
		    }
	    } else if(byte_in_column <= (8 * 8192)) {
		    for(col = 0; col < height; col++) {
			    if ((err = scif_readfrom(gEpd, current_host_offset + col*byte_in_ldha,
							    byte_in_column,
							    current_dev_offset + col * byte_in_ldda, 0)))
			    {
				    err = errno;
				    printf("scif_readfrom failed for mic-%d with err %d\n", gCurrID, errno);
				    fflush(stdout);
				    return MIC_FAILURE;
			    }
		    }
	    } else {
		    for(col = 0; col < height; col++) {
			    if ((err = scif_readfrom(gEpd, current_host_offset + col*byte_in_ldha,
							    byte_in_column,
							    current_dev_offset + col * byte_in_ldda, SCIF_RMA_SYNC)))
			    {
				    err = errno;
				    printf("scif_readfrom failed for mic-%d with err %d\n", gCurrID, errno);
				    fflush(stdout);
				    return MIC_FAILURE;
			    }
		    }
	    }
    }
    // mark this transfer to sync on event later
    magma_mic_fence_mark( event );
    
    return MAGMA_SUCCESS;
}
