/* -*- Mode: C; c-basic-offset:4 ; -*- */
/*
 * Copyright (c) 2004-2006 The Trustees of Indiana University and Indiana
 *                         University Research and Technology
 *                         Corporation.  All rights reserved.
 * Copyright (c) 2004-2017 The University of Tennessee and The University
 *                         of Tennessee Research Foundation.  All rights
 *                         reserved.
 * Copyright (c) 2004-2017 High Performance Computing Center Stuttgart,
 *                         University of Stuttgart.  All rights reserved.
 * Copyright (c) 2004-2006 The Regents of the University of California.
 *                         All rights reserved.
 * Copyright (c) 2009      Oak Ridge National Labs.  All rights reserved.
 * Copyright (c) 2017-2018 Research Organization for Information Science
 *                         and Technology (RIST).  All rights reserved.
 * $COPYRIGHT$
 *
 * Additional copyrights may follow
 *
 * $HEADER$
 */

#include "opal_config.h"

#include <stddef.h>
#include <stdlib.h>

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

#include "opal/datatype/opal_convertor.h"
#include "opal/datatype/opal_datatype.h"
#include "opal/datatype/opal_datatype_internal.h"

extern int opal_convertor_create_stack_with_pos_general(opal_convertor_t *convertor,
                                                        size_t starting_point, const size_t *sizes);

int opal_convertor_create_stack_with_pos_general(opal_convertor_t *pConvertor,
                                                 size_t starting_point, const size_t *sizes)
{
    dt_stack_t *pStack; /* pointer to the position on the stack */
    int pos_desc;       /* actual position in the description of the derived datatype */
    size_t lastLength = 0;
    const opal_datatype_t *pData = pConvertor->pDesc;
    size_t loop_length, *remoteLength, remote_size;
    size_t resting_place = starting_point;
    dt_elem_desc_t *pElems;
    size_t count;

    assert(0 != starting_point);
    assert(pConvertor->bConverted != starting_point);
    assert(starting_point <= (pConvertor->count * pData->size));

    /*opal_output( 0, "Data extent %d size %d count %d total_size %d starting_point %d\n",
                 pData->ub - pData->lb, pData->size, pConvertor->count,
                 pConvertor->local_size, starting_point );*/
    pConvertor->stack_pos = 0;
    pStack = pConvertor->pStack;
    /* Fill the first position on the stack. This one correspond to the
     * last fake OPAL_DATATYPE_END_LOOP that we add to the data representation and
     * allow us to move quickly inside the datatype when we have a count.
     */
    pElems = pConvertor->use_desc->desc;

    if ((pConvertor->flags & CONVERTOR_HOMOGENEOUS)
        && (pData->flags & OPAL_DATATYPE_FLAG_CONTIGUOUS)) {
        /* Special case for contiguous datatypes */
        int32_t cnt = (int32_t)(starting_point / pData->size);
        ptrdiff_t extent = pData->ub - pData->lb;

        loop_length = GET_FIRST_NON_LOOP(pElems);
        pStack[0].disp = pElems[loop_length].elem.disp;
        pStack[0].type = OPAL_DATATYPE_LOOP;
        pStack[0].count = pConvertor->count - cnt;
        cnt = (int32_t)(starting_point - cnt * pData->size); /* number of bytes after the loop */
        pStack[1].index = 0;
        pStack[1].type = OPAL_DATATYPE_UINT1;
        pStack[1].disp = pStack[0].disp;
        pStack[1].count = pData->size - cnt;

        if ((ptrdiff_t) pData->size == extent) { /* all elements are contiguous */
            pStack[1].disp += starting_point;
        } else { /* each is contiguous but there are gaps inbetween */
            pStack[1].disp += (pConvertor->count - pStack[0].count) * extent + cnt;
        }

        pConvertor->bConverted = starting_point;
        pConvertor->stack_pos = 1;
        return OPAL_SUCCESS;
    }

    /* remove from the main loop all the complete datatypes */
    assert(!(pConvertor->flags & CONVERTOR_SEND));
    remote_size = opal_convertor_compute_remote_size(pConvertor);
    count = starting_point / remote_size;
    resting_place -= (remote_size * count);
    pStack->count = pConvertor->count - count;
    pStack->index = -1;

    loop_length = GET_FIRST_NON_LOOP(pElems);
    pStack->disp = count * (pData->ub - pData->lb) + pElems[loop_length].elem.disp;

    pos_desc = 0;
    remoteLength = (size_t *) alloca(sizeof(size_t) * (pConvertor->pDesc->loops + 1));
    remoteLength[0] = 0; /* initial value set to ZERO */
    loop_length = 0;

    /* The only way to get out of this loop is when we reach the desired position or
     * when we finish the whole datatype.
     */
    while (pos_desc < (int32_t) pConvertor->use_desc->used) {
        if (OPAL_DATATYPE_END_LOOP == pElems->elem.common.type) { /* end of the current loop */
            ddt_endloop_desc_t *end_loop = (ddt_endloop_desc_t *) pElems;
            ptrdiff_t extent;

            if ((loop_length * pStack->count) > resting_place) {
                /* We will stop somewhere on this loop. To avoid moving inside the loop
                 * multiple times, we can compute the index of the loop where we will
                 * stop. Once this index is computed we can then reparse the loop once
                 * until we find the correct position.
                 */
                int32_t cnt = (int32_t)(resting_place / loop_length);
                if (pStack->index == -1) {
                    extent = pData->ub - pData->lb;
                } else {
                    assert(OPAL_DATATYPE_LOOP == (pElems - end_loop->items)->loop.common.type);
                    extent = ((ddt_loop_desc_t *) (pElems - end_loop->items))->extent;
                }
                pStack->count -= (cnt + 1);
                resting_place -= cnt * loop_length;
                pStack->disp += (cnt + 1) * extent;
                /* reset the remoteLength as we act as restarting the last loop */
                pos_desc -= (end_loop->items - 1); /* go back to the first element in the loop */
                pElems -= (end_loop->items - 1);
                remoteLength[pConvertor->stack_pos] = 0;
                loop_length = 0;
                continue;
            }
            /* Not in this loop. Cleanup the stack and advance to the
             * next data description.
             */
            resting_place -= (loop_length * (pStack->count - 1)); /* update the resting place */
            pStack--;
            pConvertor->stack_pos--;
            pos_desc++;
            pElems++;
            remoteLength[pConvertor->stack_pos] += (loop_length * pStack->count);
            loop_length = remoteLength[pConvertor->stack_pos];
            continue;
        }
        if (OPAL_DATATYPE_LOOP == pElems->elem.common.type) {
            remoteLength[pConvertor->stack_pos] += loop_length;
            PUSH_STACK(pStack, pConvertor->stack_pos, pos_desc, OPAL_DATATYPE_LOOP,
                       pElems->loop.loops, pStack->disp);
            pos_desc++;
            pElems++;
            remoteLength[pConvertor->stack_pos] = 0;
            loop_length = 0; /* starting a new loop */
        }
        while (pElems->elem.common.flags & OPAL_DATATYPE_FLAG_DATA) {
            /* now here we have a basic datatype */
            const opal_datatype_t *basic_type = BASIC_DDT_FROM_ELEM((*pElems));
            lastLength = (size_t) pElems->elem.count * basic_type->size;
            if (resting_place < lastLength) {
                int32_t cnt = (int32_t)(resting_place / basic_type->size);
                loop_length += (cnt * basic_type->size);
                resting_place -= (cnt * basic_type->size);
                PUSH_STACK(pStack, pConvertor->stack_pos, pos_desc, pElems->elem.common.type,
                           pElems->elem.count - cnt, pElems->elem.disp + cnt * pElems->elem.extent);
                pConvertor->bConverted = starting_point - resting_place;
                DDT_DUMP_STACK(pConvertor->pStack, pConvertor->stack_pos,
                               pConvertor->pDesc->desc.desc, pConvertor->pDesc->name);
                return OPAL_SUCCESS;
            }
            loop_length += lastLength;
            resting_place -= lastLength;
            pos_desc++; /* advance to the next data */
            pElems++;
        }
    }

    /* Correctly update the bConverted field */
    pConvertor->flags |= CONVERTOR_COMPLETED;
    pConvertor->bConverted = pConvertor->local_size;
    return OPAL_SUCCESS;
}
