/* -*- Mode: C; c-basic-offset:4 ; -*- */
/*
 * Copyright (c) 2004-2017 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2009      Oak Ridge National Labs.  All rights reserved.
 * Copyright (c) 2015-2018 Research Organization for Information Science
 *                         and Technology (RIST).  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#ifdef HAVE_ALLOCA_H
#include <alloca.h>
#endif

#if !defined(MEM_OP_NAME)
#error
#endif  /* !defined((MEM_OP_NAME) */
#if !defined(MEM_OP)
#error
#endif  /* !defined(MEM_OP) */
#if !defined(MEM_OP_BLOCK_SIZE)
#error
#endif  /* !defined(MEM_OP_BLOCK_SIZE) */

#ifndef STRINGIFY
#  define STRINGIFY_(arg)          #arg
#  define STRINGIFY(arg)           STRINGIFY_(arg)
#endif

#ifndef DT_CONCAT
#  define DT__CONCAT(a, b)        a##b
#  define DT_CONCAT(a, b)         DT__CONCAT(a, b)
#endif


#define _predefined_data        DT_CONCAT(MEM_OP_NAME,_predefined_data)
#define _contiguous_loop        DT_CONCAT(MEM_OP_NAME,_contiguous_loop)
#define _copy_content_same_ddt  DT_CONCAT(MEM_OP_NAME,_copy_content_same_ddt)

static inline void _predefined_data( const dt_elem_desc_t* ELEM,
                                     const opal_datatype_t* DATATYPE,
                                     unsigned char* SOURCE_BASE,
                                     size_t TOTAL_COUNT,
                                     size_t COUNT,
                                     unsigned char* SOURCE,
                                     unsigned char* DESTINATION,
                                     size_t* SPACE )
{
    const ddt_elem_desc_t* _elem = &((ELEM)->elem);
    unsigned char* _source = (SOURCE) + _elem->disp;
    unsigned char* _destination = (DESTINATION) + _elem->disp;
    size_t do_now = _elem->count, do_now_bytes;

    assert( (COUNT) == (do_now * _elem->blocklen));

    /* We don't a prologue and epilogue here as we are __always__ working
     * with full copies of the data description.
     */

    /**
     * Compute how many full blocklen we need to do and do them.
     */
    do_now_bytes = _elem->blocklen * opal_datatype_basicDatatypes[_elem->common.type]->size;
    assert( (do_now * do_now_bytes) <= (*SPACE) );

    for(size_t _i = 0; _i < do_now; _i++ ) {
        OPAL_DATATYPE_SAFEGUARD_POINTER( _source, do_now_bytes, (SOURCE_BASE),
                                         (DATATYPE), (TOTAL_COUNT) );
        DO_DEBUG( opal_output( 0, "copy %s( %p, %p, %" PRIsize_t " ) => space %" PRIsize_t "\n",
                               STRINGIFY(MEM_OP_NAME), (void*)_destination, (void*)_source, do_now_bytes, *(SPACE) - _i * do_now_bytes ); );
        MEM_OP( _destination, _source, do_now_bytes );
        _destination += _elem->extent;
        _source      += _elem->extent;
    }
    *(SPACE) -= (do_now_bytes * do_now);
}

static inline void _contiguous_loop( const dt_elem_desc_t* ELEM,
                                     const opal_datatype_t* DATATYPE,
                                     unsigned char* SOURCE_BASE,
                                     size_t TOTAL_COUNT,
                                     size_t COUNT,
                                     unsigned char* SOURCE,
                                     unsigned char* DESTINATION,
                                     size_t* SPACE )
{
    ddt_loop_desc_t *_loop = (ddt_loop_desc_t*)(ELEM);
    ddt_endloop_desc_t* _end_loop = (ddt_endloop_desc_t*)((ELEM) + _loop->items);
    unsigned char* _source = (SOURCE) + _end_loop->first_elem_disp;
    unsigned char* _destination = (DESTINATION) + _end_loop->first_elem_disp;
    size_t _copy_loops = (COUNT);

    if( _loop->extent == (ptrdiff_t)_end_loop->size ) {  /* the loop is contiguous */
        _copy_loops *= _end_loop->size;
        OPAL_DATATYPE_SAFEGUARD_POINTER( _source, _copy_loops, (SOURCE_BASE),
                                    (DATATYPE), (TOTAL_COUNT) );
        MEM_OP( _destination, _source, _copy_loops );
    } else {
        for(size_t _i = 0; _i < _copy_loops; _i++ ) {
            OPAL_DATATYPE_SAFEGUARD_POINTER( _source, _end_loop->size, (SOURCE_BASE),
                                        (DATATYPE), (TOTAL_COUNT) );
            DO_DEBUG( opal_output( 0, "copy 3. %s( %p, %p, %" PRIsize_t " ) => space %" PRIsize_t "\n",
                                   STRINGIFY(MEM_OP_NAME), (void*)_destination, (void*)_source, _end_loop->size, *(SPACE) - _i * _end_loop->size ); );
            MEM_OP( _destination, _source, _end_loop->size );
            _source      += _loop->extent;
            _destination += _loop->extent;
        }
        _copy_loops *= _end_loop->size;
    }
    *(SPACE)      -= _copy_loops;
}

static inline int32_t _copy_content_same_ddt( const opal_datatype_t* datatype, int32_t count,
                                              char* destination_base, char* source_base )
{
    dt_stack_t* pStack;       /* pointer to the position on the stack */
    int32_t stack_pos;        /* index of the stack level */
    uint32_t pos_desc;        /* actual position in the description of the derived datatype */
    uint32_t count_desc;      /* the number of items already done in the actual pos_desc */
    dt_elem_desc_t* description;
    dt_elem_desc_t* pElem;
    size_t iov_len_local;
    unsigned char *source = (unsigned char*)source_base,
                  *destination = (unsigned char*)destination_base;

    DO_DEBUG( opal_output( 0, "_copy_content_same_ddt( %p, %d, dst %p, src %p )\n",
                           (void*)datatype, count, (void*)destination_base, (void*)source_base ); );

    iov_len_local = (size_t)count * datatype->size;

    /* If we have to copy a contiguous datatype then simply
     * do a MEM_OP.
     */
    if( datatype->flags & OPAL_DATATYPE_FLAG_CONTIGUOUS ) {
        ptrdiff_t extent = (datatype->ub - datatype->lb);
        /* Now that we know the datatype is contiguous, we should move the 2 pointers
         * source and destination to the correct displacement.
         */
        destination += datatype->true_lb;
        source      += datatype->true_lb;
        if( (ptrdiff_t)datatype->size == extent ) {  /* all contiguous == no gaps around */
            size_t total_length = iov_len_local;
            size_t memop_chunk = MEM_OP_BLOCK_SIZE;
            OPAL_DATATYPE_SAFEGUARD_POINTER( source, iov_len_local,
                                             (unsigned char*)source_base, datatype, count );
            while( total_length > 0 ) {
                if( memop_chunk > total_length ) memop_chunk = total_length;
                DO_DEBUG( opal_output( 0, "copy c1. %s( %p, %p, %lu ) => space %lu\n",
                                       STRINGIFY(MEM_OP_NAME), (void*)destination, (void*)source, (unsigned long)memop_chunk, (unsigned long)total_length ); );
                MEM_OP( destination, source, memop_chunk );
                destination   += memop_chunk;
                source        += memop_chunk;
                total_length  -= memop_chunk;
            }
            return 0;  /* completed */
        }
        for( pos_desc = 0; (int32_t)pos_desc < count; pos_desc++ ) {
            OPAL_DATATYPE_SAFEGUARD_POINTER( destination, datatype->size,
                                        (unsigned char*)destination_base, datatype, count );
            OPAL_DATATYPE_SAFEGUARD_POINTER( source, datatype->size,
                                        (unsigned char*)source_base, datatype, count );
            DO_DEBUG( opal_output( 0, "copy c2. %s( %p, %p, %lu ) => space %lu\n",
                                   STRINGIFY(MEM_OP_NAME), (void*)destination, (void*)source, (unsigned long)datatype->size,
                                   (unsigned long)(iov_len_local - (pos_desc * datatype->size)) ); );
            MEM_OP( destination, source, datatype->size );
            destination += extent;
            source += extent;
        }
        return 0;  /* completed */
    }

    pStack = (dt_stack_t*)alloca( sizeof(dt_stack_t) * (datatype->loops + 1) );
    pStack->count = count;
    pStack->index   = -1;
    pStack->disp    = 0;
    pos_desc = 0;
    stack_pos = 0;

    description = datatype->opt_desc.desc;
    if( NULL == description ) {
        description = datatype->desc.desc;
    }

    UPDATE_INTERNAL_COUNTERS( description, 0, pElem, count_desc );

    while( 1 ) {
        while( OPAL_LIKELY(pElem->elem.common.flags & OPAL_DATATYPE_FLAG_DATA) ) {
            /* now here we have a basic datatype */
            _predefined_data( pElem, datatype, (unsigned char*)source_base, count, count_desc,
                              source, destination, &iov_len_local );
            pos_desc++;  /* advance to the next data */
            UPDATE_INTERNAL_COUNTERS( description, pos_desc, pElem, count_desc );
        }
        if( OPAL_DATATYPE_END_LOOP == pElem->elem.common.type ) { /* end of the current loop */
            DO_DEBUG( opal_output( 0, "copy end_loop count %" PRIsize_t " stack_pos %d pos_desc %d disp %ld space %lu\n",
                                   pStack->count, stack_pos, pos_desc, pStack->disp, (unsigned long)iov_len_local ); );
            if( --(pStack->count) == 0 ) { /* end of loop */
                if( stack_pos == 0 ) {
                    assert( iov_len_local == 0 );
                    return 0;  /* completed */
                }
                stack_pos--;
                pStack--;
                pos_desc++;
            } else {
                pos_desc = pStack->index + 1;
                if( pStack->index == -1 ) {
                    pStack->disp += (datatype->ub - datatype->lb);
                } else {
                    assert( OPAL_DATATYPE_LOOP == description[pStack->index].loop.common.type );
                    pStack->disp += description[pStack->index].loop.extent;
                }
            }
            source      = (unsigned char*)source_base + pStack->disp;
            destination = (unsigned char*)destination_base + pStack->disp;
            UPDATE_INTERNAL_COUNTERS( description, pos_desc, pElem, count_desc );
            DO_DEBUG( opal_output( 0, "copy new_loop count %" PRIsize_t " stack_pos %d pos_desc %d disp %ld space %lu\n",
                                   pStack->count, stack_pos, pos_desc, pStack->disp, (unsigned long)iov_len_local ); );
        }
        if( OPAL_DATATYPE_LOOP == pElem->elem.common.type ) {
            ptrdiff_t local_disp = (ptrdiff_t)source;
            if( pElem->loop.common.flags & OPAL_DATATYPE_FLAG_CONTIGUOUS ) {
                _contiguous_loop( pElem, datatype, (unsigned char*)source_base, count, count_desc,
                                  source, destination, &iov_len_local );
                pos_desc += pElem->loop.items + 1;
                goto update_loop_description;
            }
            local_disp = (ptrdiff_t)source - local_disp;
            PUSH_STACK( pStack, stack_pos, pos_desc, OPAL_DATATYPE_LOOP, count_desc,
                        pStack->disp + local_disp);
            pos_desc++;
        update_loop_description:  /* update the current state */
            source      = (unsigned char*)source_base + pStack->disp;
            destination = (unsigned char*)destination_base + pStack->disp;
            UPDATE_INTERNAL_COUNTERS( description, pos_desc, pElem, count_desc );
            DDT_DUMP_STACK( pStack, stack_pos, pElem, "advance loop" );
            continue;
        }
    }
}
