/*
 * Distributed under the OSI-approved Apache License, Version 2.0.  See
 * accompanying file Copyright.txt for details.
 *
 * adiosMemory.cpp
 *
 *  Created on: Oct 31, 2018
 *      Author: William F Godoy godoywf@ornl.gov
 */

#include "adiosMemory.h"

#include <algorithm>
#include <stddef.h> // max_align_t

#include "adios2/helper/adiosType.h"

#ifdef ADIOS2_HAVE_CUDA
#include <cuda_runtime.h>
#endif

namespace adios2
{
namespace helper
{

namespace
{

void CopyPayloadStride(const char *src, const size_t payloadStride, char *dest,
                       const bool endianReverse, const DataType destType)
{
#ifdef ADIOS2_HAVE_ENDIAN_REVERSE
    if (endianReverse)
    {
        if (destType == DataType::None)
        {
        }
#define declare_type(T)                                                        \
    else if (destType == GetDataType<T>())                                     \
    {                                                                          \
        CopyEndianReverse<T>(src, payloadStride, reinterpret_cast<T *>(dest)); \
    }

        ADIOS2_FOREACH_PRIMITIVE_STDTYPE_1ARG(declare_type)
#undef declare_type
    }
    else
    {
        std::copy(src, src + payloadStride, dest);
    }
#else
    (void)endianReverse;
    (void)destType;
    std::copy(src, src + payloadStride, dest);
#endif
}

Dims DestDimsFinal(const Dims &destDims, const bool destRowMajor,
                   const bool srcRowMajor)
{
    Dims destDimsFinal = destDims;
    if (srcRowMajor != destRowMajor)
    {
        std::reverse(destDimsFinal.begin(), destDimsFinal.end());
    }
    return destDimsFinal;
}

void ClipRowMajor(char *dest, const Dims &destStart, const Dims &destCount,
                  const bool destRowMajor, const char *src,
                  const Dims &srcStart, const Dims &srcCount,
                  const Dims & /*destMemStart*/, const Dims & /*destMemCount*/,
                  const Dims &srcMemStart, const Dims &srcMemCount,
                  const bool endianReverse, const DataType destType)
{
    const Dims destStartFinal = DestDimsFinal(destStart, destRowMajor, true);
    const Dims destCountFinal = DestDimsFinal(destCount, destRowMajor, true);
    const Box<Dims> intersectionBox = IntersectionStartCount(
        destStartFinal, destCountFinal, srcStart, srcCount);

    const Dims &interStart = intersectionBox.first;
    const Dims &interCount = intersectionBox.second;
    // loop through intersection start and end and check if it's equal to the
    // srcBox contiguous part
    const size_t dimensions = interStart.size();

    size_t stride = interCount.back();
    size_t startCoord = dimensions - 2;
    //    bool isWholeCopy = false;
    //
    //    for (size_t i = dimensions - 1; i >= 0; --i)
    //    {
    //        // same as source
    //        if (interCount[i] == srcCount[i])
    //        {
    //            stride *= interCount[i - 1];
    //            if (startCoord > 0)
    //            {
    //                --startCoord;
    //            }
    //            if (startCoord == 0)
    //            {
    //                isWholeCopy = true;
    //            }
    //        }
    //        else
    //        {
    //            break;
    //        }
    //    }

    /// start iteration
    Dims currentPoint(interStart); // current point for memory copy
    const size_t interOffset =
        LinearIndex(srcStart, srcCount, interStart, true);

    bool run = true;

    while (run)
    {

        // here copy current linear memory between currentPoint and end
        const size_t srcBeginOffset =
            srcMemStart.empty()
                ? LinearIndex(srcStart, srcCount, currentPoint, true) -
                      interOffset
                : LinearIndex(Dims(srcMemCount.size(), 0), srcMemCount,
                              VectorsOp(std::plus<size_t>(),
                                        VectorsOp(std::minus<size_t>(),
                                                  currentPoint, interStart),
                                        srcMemStart),
                              true);

        const size_t destBeginOffset = helper::LinearIndex(
            destStartFinal, destCountFinal, currentPoint, true);

        CopyPayloadStride(src + srcBeginOffset, stride, dest + destBeginOffset,
                          endianReverse, destType);

        size_t p = startCoord;
        while (true)
        {
            ++currentPoint[p];
            if (currentPoint[p] > interStart[p] + interCount[p] - 1)
            {
                if (p == 0)
                {
                    run = false; // we are done
                    break;
                }
                else
                {
                    currentPoint[p] = interStart[p];
                    --p;
                }
            }
            else
            {
                break; // break inner p loop
            }
        } // dimension index update
    }
}

void ClipColumnMajor(char *dest, const Dims &destStart, const Dims &destCount,
                     const bool destRowMajor, const char *src,
                     const Dims &srcStart, const Dims &srcCount,
                     const Dims & /*destMemStart*/,
                     const Dims & /*destMemCount*/, const Dims &srcMemStart,
                     const Dims &srcMemCount, const bool endianReverse,
                     const DataType destType)
{
    const Dims destStartFinal = DestDimsFinal(destStart, destRowMajor, false);
    const Dims destCountFinal = DestDimsFinal(destCount, destRowMajor, false);
    const Box<Dims> intersectionBox = IntersectionStartCount(
        destStartFinal, destCountFinal, srcStart, srcCount);

    const Dims &interStart = intersectionBox.first;
    const Dims &interCount = intersectionBox.second;
    // loop through intersection start and end and check if it's equal to the
    // srcBox contiguous part
    const size_t dimensions = interStart.size();
    size_t stride = interCount.front();
    size_t startCoord = 1;
    //    for (size_t i = 0; i < dimensions; ++i)
    //    {
    //        // same as source
    //        if (interCount[i] == srcCount[i])
    //        {
    //            stride *= interCount[i];
    //            // startCoord = i + 1;
    //        }
    //        else
    //        {
    //            break;
    //        }
    //    }

    /// start iteration
    Dims currentPoint(interStart); // current point for memory copy
    const size_t interOffset =
        LinearIndex(srcStart, srcCount, interStart, false);

    bool run = true;

    while (run)
    {
        // here copy current linear memory between currentPoint and end
        const size_t srcBeginOffset =
            srcMemStart.empty()
                ? LinearIndex(srcStart, srcCount, currentPoint, false) -
                      interOffset
                : LinearIndex(Dims(srcMemCount.size(), 0), srcMemCount,
                              VectorsOp(std::plus<size_t>(),
                                        VectorsOp(std::minus<size_t>(),
                                                  currentPoint, interStart),
                                        srcMemStart),
                              false);

        const size_t destBeginOffset = helper::LinearIndex(
            destStartFinal, destCountFinal, currentPoint, false);

        CopyPayloadStride(src + srcBeginOffset, stride, dest + destBeginOffset,
                          endianReverse, destType);
        size_t p = startCoord;

        while (true)
        {
            ++currentPoint[p];
            if (currentPoint[p] > interStart[p] + interCount[p] - 1)
            {
                if (p == dimensions - 1)
                {
                    run = false; // we are done
                    break;
                }
                else
                {
                    currentPoint[p] = interStart[p];
                    ++p;
                }
            }
            else
            {
                break; // break inner p loop
            }
        } // dimension index update
    }
}

} // end empty namespace

int NdCopy(const char *in, const Dims &inStart, const Dims &inCount,
           const bool inIsRowMajor, const bool inIsLittleEndian, char *out,
           const Dims &outStart, const Dims &outCount, const bool outIsRowMajor,
           const bool outIsLittleEndian, const int typeSize,
           const Dims &inMemStart, const Dims &inMemCount,
           const Dims &outMemStart, const Dims &outMemCount,
           const bool safeMode, MemorySpace MemSpace)

{

    // use values of ioStart and ioCount if ioMemStart and ioMemCount are
    // left as default
    Dims inMemStartNC = inMemStart.empty() ? inStart : inMemStart;
    Dims inMemCountNC = inMemCount.empty() ? inCount : inMemCount;
    Dims outMemStartNC = outMemStart.empty() ? outStart : outMemStart;
    Dims outMemCountNC = outMemCount.empty() ? outCount : outMemCount;

    Dims inEnd(inStart.size());
    Dims outEnd(inStart.size());
    Dims ovlpStart(inStart.size());
    Dims ovlpEnd(inStart.size());
    Dims ovlpCount(inStart.size());
    Dims inStride(inStart.size());
    Dims outStride(inStart.size());
    Dims inOvlpGapSize(inStart.size());
    Dims outOvlpGapSize(inStart.size());
    Dims inRltvOvlpStartPos(inStart.size());
    Dims outRltvOvlpStartPos(inStart.size());
    size_t minContDim, blockSize;
    const char *inOvlpBase = nullptr;
    char *outOvlpBase = nullptr;
    auto GetInEnd = [](Dims &inEnd, const Dims &inStart, const Dims &inCount) {
        for (size_t i = 0; i < inStart.size(); i++)
        {
            inEnd[i] = inStart[i] + inCount[i] - 1;
        }
    };
    auto GetOutEnd = [](Dims &outEnd, const Dims &outStart,
                        const Dims &output_count) {
        for (size_t i = 0; i < outStart.size(); i++)
        {
            outEnd[i] = outStart[i] + output_count[i] - 1;
        }
    };
    auto GetOvlpStart = [](Dims &ovlpStart, const Dims &inStart,
                           const Dims &outStart) {
        for (size_t i = 0; i < ovlpStart.size(); i++)
        {
            ovlpStart[i] = inStart[i] > outStart[i] ? inStart[i] : outStart[i];
        }
    };
    auto GetOvlpEnd = [](Dims &ovlpEnd, Dims &inEnd, Dims &outEnd) {
        for (size_t i = 0; i < ovlpEnd.size(); i++)
        {
            ovlpEnd[i] = inEnd[i] < outEnd[i] ? inEnd[i] : outEnd[i];
        }
    };
    auto GetOvlpCount = [](Dims &ovlpCount, Dims &ovlpStart, Dims &ovlpEnd) {
        for (size_t i = 0; i < ovlpCount.size(); i++)
        {
            ovlpCount[i] = ovlpEnd[i] - ovlpStart[i] + 1;
        }
    };
    auto HasOvlp = [](Dims &ovlpStart, Dims &ovlpEnd) {
        for (size_t i = 0; i < ovlpStart.size(); i++)
        {
            if (ovlpEnd[i] < ovlpStart[i])
            {
                return false;
            }
        }
        return true;
    };

    auto GetIoStrides = [](Dims &ioStride, const Dims &ioCount,
                           size_t elmSize) {
        // ioStride[i] holds the total number of elements under each element
        // of the i'th dimension
        ioStride[ioStride.size() - 1] = elmSize;
        if (ioStride.size() > 1)
        {
            ioStride[ioStride.size() - 2] =
                ioCount[ioStride.size() - 1] * elmSize;
        }
        if (ioStride.size() > 2)
        {
            size_t i = ioStride.size() - 3;
            while (true)
            {
                ioStride[i] = ioCount[i + 1] * ioStride[i + 1];
                if (i == 0)
                {
                    break;
                }
                else
                {
                    i--;
                }
            }
        }
    };

    auto GetInOvlpBase = [](const char *&inOvlpBase, const char *in,
                            const Dims &inStart, Dims &inStride,
                            Dims &ovlpStart) {
        inOvlpBase = in;
        for (size_t i = 0; i < inStart.size(); i++)
        {
            inOvlpBase = inOvlpBase + (ovlpStart[i] - inStart[i]) * inStride[i];
        }
    };
    auto GetOutOvlpBase = [](char *&outOvlpBase, char *out,
                             const Dims &outStart, Dims &outStride,
                             Dims &ovlpStart) {
        outOvlpBase = out;
        for (size_t i = 0; i < outStart.size(); i++)
        {
            outOvlpBase =
                outOvlpBase + (ovlpStart[i] - outStart[i]) * outStride[i];
        }
    };
    auto GetIoOvlpGapSize = [](Dims &ioOvlpGapSize, Dims &ioStride,
                               const Dims &ioCount, Dims &ovlpCount) {
        for (size_t i = 0; i < ioOvlpGapSize.size(); i++)
        {
            ioOvlpGapSize[i] = (ioCount[i] - ovlpCount[i]) * ioStride[i];
        }
    };
    auto GetMinContDim = [](const Dims &inCount, const Dims outCount,
                            Dims &ovlpCount) {
        //    note: minContDim is the first index where its input box and
        //    overlap box
        //    are not fully match. therefore all data below this branch is
        //    contingous
        //    and this determins the Biggest continuous block size - Each
        //    element of the
        //    current dimension.
        size_t i = ovlpCount.size() - 1;
        while (true)
        {
            if (i == 0)
            {
                break;
            }
            if ((inCount[i] != ovlpCount[i]) || (outCount[i] != ovlpCount[i]))
            {
                break;
            }
            i--;
        }
        return i;
    };
    auto GetBlockSize = [](Dims &ovlpCount, size_t minContDim, size_t elmSize) {
        size_t res = elmSize;
        for (size_t i = minContDim; i < ovlpCount.size(); i++)
        {
            res *= ovlpCount[i];
        }
        return res;
    };

    auto GetRltvOvlpStartPos = [](Dims &ioRltvOvlpStart, const Dims &ioStart,
                                  Dims &ovlpStart) {
        for (size_t i = 0; i < ioStart.size(); i++)
        {
            ioRltvOvlpStart[i] = ovlpStart[i] - ioStart[i];
        }
    };

    // main flow
    // row-major ==> row-major mode
    // algrithm optimizations:
    // 1. contigous data copying
    // 2. mem pointer arithmetics by sequential padding. O(1) overhead/block
    if (inIsRowMajor && outIsRowMajor)
    {
        GetInEnd(inEnd, inStart, inCount);
        GetOutEnd(outEnd, outStart, outCount);
        GetOvlpStart(ovlpStart, inStart, outStart);
        GetOvlpEnd(ovlpEnd, inEnd, outEnd);
        GetOvlpCount(ovlpCount, ovlpStart, ovlpEnd);
        if (!HasOvlp(ovlpStart, ovlpEnd))
        {
            return 1; // no overlap found
        }
        GetIoStrides(inStride, inMemCountNC, typeSize);
        GetIoStrides(outStride, outMemCountNC, typeSize);
        GetIoOvlpGapSize(inOvlpGapSize, inStride, inMemCountNC, ovlpCount);
        GetIoOvlpGapSize(outOvlpGapSize, outStride, outMemCountNC, ovlpCount);
        GetInOvlpBase(inOvlpBase, in, inMemStartNC, inStride, ovlpStart);
        GetOutOvlpBase(outOvlpBase, out, outMemStartNC, outStride, ovlpStart);
        minContDim = GetMinContDim(inMemCountNC, outMemCountNC, ovlpCount);
        blockSize = GetBlockSize(ovlpCount, minContDim, typeSize);
        // same endianess mode: most optimized, contiguous data copying
        // algorithm used.
        if (inIsLittleEndian == outIsLittleEndian)
        {
#ifdef ADIOS2_HAVE_CUDA
            if (MemSpace == MemorySpace::CUDA)
            {
                helper::NdCopyCUDA(inOvlpBase, outOvlpBase, inOvlpGapSize,
                                   outOvlpGapSize, ovlpCount, minContDim,
                                   blockSize);
                return 0;
            }
#endif
            // most efficient algm
            // warning: number of function stacks used is number of dimensions
            // of data.
            if (!safeMode)
            {
                NdCopyRecurDFSeqPadding(0, inOvlpBase, outOvlpBase,
                                        inOvlpGapSize, outOvlpGapSize,
                                        ovlpCount, minContDim, blockSize);
            }
            else // safeMode
            {
                //      //alternative iterative version, 10% slower then
                //      recursive
                //      //use it when very high demension is used.
                NdCopyIterDFSeqPadding(inOvlpBase, outOvlpBase, inOvlpGapSize,
                                       outOvlpGapSize, ovlpCount, minContDim,
                                       blockSize);
            }
        }
        // different endianess mode
        else
        {
#ifdef ADIOS2_HAVE_CUDA
            if (MemSpace == MemorySpace::CUDA)
            {
                helper::Throw<std::invalid_argument>(
                    "Helper", "Memory", "CopyContiguousMemory",
                    "Direct byte order reversal not supported for GPU buffers");
            }
#endif
            if (!safeMode)
            {
                NdCopyRecurDFSeqPaddingRevEndian(
                    0, inOvlpBase, outOvlpBase, inOvlpGapSize, outOvlpGapSize,
                    ovlpCount, minContDim, blockSize, typeSize,
                    blockSize / typeSize);
            }
            else
            {
                NdCopyIterDFSeqPaddingRevEndian(
                    inOvlpBase, outOvlpBase, inOvlpGapSize, outOvlpGapSize,
                    ovlpCount, minContDim, blockSize, typeSize,
                    blockSize / typeSize);
            }
        }
    }

    // Copying modes involing col-major
    // algorithm optimization:
    // 1. mem ptr arithmetics: O(1) overhead per block, dynamic/non-sequential
    // padding
    else
    {
#ifdef ADIOS2_HAVE_CUDA
        if (MemSpace == MemorySpace::CUDA)
        {
            helper::Throw<std::invalid_argument>(
                "Helper", "Memory", "CopyContiguousMemory",
                "Direct byte order reversal not supported for GPU buffers");
        }
#endif
        //        Dims revInCount(inCount);
        //        Dims revOutCount(outCount);
        //
        // col-major ==> col-major mode
        if (!inIsRowMajor && !outIsRowMajor)
        {

            GetInEnd(inEnd, inStart, inCount);
            GetOutEnd(outEnd, outStart, outCount);
            GetOvlpStart(ovlpStart, inStart, outStart);
            GetOvlpEnd(ovlpEnd, inEnd, outEnd);
            GetOvlpCount(ovlpCount, ovlpStart, ovlpEnd);
            if (!HasOvlp(ovlpStart, ovlpEnd))
            {
                return 1; // no overlap found
            }

            GetIoStrides(inStride, inCount, typeSize);
            GetIoStrides(outStride, outCount, typeSize);

            GetRltvOvlpStartPos(inRltvOvlpStartPos, inMemStartNC, ovlpStart);
            GetRltvOvlpStartPos(outRltvOvlpStartPos, outMemStartNC, ovlpStart);
        }
        // row-major ==> col-major mode
        else if (inIsRowMajor && !outIsRowMajor)
        {
            Dims revOutStart(outStart);
            Dims revOutCount(outCount);

            std::reverse(outMemStartNC.begin(), outMemStartNC.end());
            std::reverse(outMemCountNC.begin(), outMemCountNC.end());

            GetInEnd(inEnd, inStart, inCount);
            GetOutEnd(outEnd, revOutStart, revOutCount);
            GetOvlpStart(ovlpStart, inStart, revOutStart);
            GetOvlpEnd(ovlpEnd, inEnd, outEnd);
            GetOvlpCount(ovlpCount, ovlpStart, ovlpEnd);
            if (!HasOvlp(ovlpStart, ovlpEnd))
            {
                return 1; // no overlap found
            }

            // get normal order inStride
            GetIoStrides(inStride, inMemCountNC, typeSize);

            // calulate reversed order outStride
            GetIoStrides(outStride, outMemCountNC, typeSize);
            // reverse outStride so that outStride aligns to inStride
            std::reverse(outStride.begin(), outStride.end());

            // get normal order inOvlpStart
            GetRltvOvlpStartPos(inRltvOvlpStartPos, inMemStartNC, ovlpStart);

            // get reversed order outOvlpStart
            Dims revOvlpStart(ovlpStart);
            std::reverse(revOvlpStart.begin(), revOvlpStart.end());
            GetRltvOvlpStartPos(outRltvOvlpStartPos, outMemStartNC,
                                revOvlpStart);
        }
        // col-major ==> row-major mode
        else if (!inIsRowMajor && outIsRowMajor)
        {
            Dims revInStart(inStart);
            Dims revInCount(inCount);
            std::reverse(inMemStartNC.begin(), inMemStartNC.end());
            std::reverse(inMemCountNC.begin(), inMemCountNC.end());

            GetInEnd(inEnd, revInStart, revInCount);
            GetOutEnd(outEnd, outStart, outCount);
            GetOvlpStart(ovlpStart, revInStart, outStart);
            GetOvlpEnd(ovlpEnd, inEnd, outEnd);
            GetOvlpCount(ovlpCount, ovlpStart, ovlpEnd);
            if (!HasOvlp(ovlpStart, ovlpEnd))
            {
                return 1; // no overlap found
            }

            // get normal order outStride
            GetIoStrides(outStride, outMemCountNC, typeSize);

            // calculate reversed inStride
            GetIoStrides(inStride, inMemCountNC, typeSize);
            // reverse inStride so that inStride aligns to outStride
            std::reverse(inStride.begin(), inStride.end());

            // get reversed order inOvlpStart
            Dims revOvlpStart(ovlpStart);
            std::reverse(revOvlpStart.begin(), revOvlpStart.end());
            GetRltvOvlpStartPos(inRltvOvlpStartPos, inMemStartNC, revOvlpStart);
            // get normal order outOvlpStart
            GetRltvOvlpStartPos(outRltvOvlpStartPos, outMemStartNC, ovlpStart);
        }

        inOvlpBase = in;
        outOvlpBase = out;
        // Same Endian"
        if (inIsLittleEndian == outIsLittleEndian)
        {
            if (!safeMode)
            {
                NdCopyRecurDFNonSeqDynamic(0, inOvlpBase, outOvlpBase,
                                           inRltvOvlpStartPos,
                                           outRltvOvlpStartPos, inStride,
                                           outStride, ovlpCount, typeSize);
            }
            else
            {
                NdCopyIterDFDynamic(inOvlpBase, outOvlpBase, inRltvOvlpStartPos,
                                    outRltvOvlpStartPos, inStride, outStride,
                                    ovlpCount, typeSize);
            }
        }
        // different Endian"
        else
        {
            if (!safeMode)
            {
                NdCopyRecurDFNonSeqDynamicRevEndian(
                    0, inOvlpBase, outOvlpBase, inRltvOvlpStartPos,
                    outRltvOvlpStartPos, inStride, outStride, ovlpCount,
                    typeSize);
            }
            else
            {
                NdCopyIterDFDynamicRevEndian(inOvlpBase, outOvlpBase,
                                             inRltvOvlpStartPos,
                                             outRltvOvlpStartPos, inStride,
                                             outStride, ovlpCount, typeSize);
            }
        }
    }
    return 0;
}
//*************** End of NdCopy() and its 8 helpers ***************

void CopyPayload(char *dest, const Dims &destStart, const Dims &destCount,
                 const bool destRowMajor, const char *src, const Dims &srcStart,
                 const Dims &srcCount, const bool srcRowMajor,
                 const Dims &destMemStart, const Dims &destMemCount,
                 const Dims &srcMemStart, const Dims &srcMemCount,
                 const bool endianReverse, const DataType destType) noexcept
{
    if (srcStart.size() == 1) // 1D copy memory
    {
        const Box<Dims> intersectionBox =
            IntersectionStartCount(destStart, destCount, srcStart, srcCount);
        const Dims &interStart = intersectionBox.first;
        const Dims &interCount = intersectionBox.second;

        const size_t srcBeginOffset =
            srcMemStart.empty()
                ? interStart.front() - srcStart.front()
                : interStart.front() - srcStart.front() + srcMemStart.front();

        const size_t stride = interCount.front();
        const size_t destBeginOffset = interStart.front() - destStart.front();

        CopyPayloadStride(src + srcBeginOffset, stride, dest + destBeginOffset,
                          endianReverse, destType);
        return;
    }

    if (srcRowMajor) // stored with C, C++, Python
    {
        ClipRowMajor(dest, destStart, destCount, destRowMajor, src, srcStart,
                     srcCount, destMemStart, destMemCount, srcMemStart,
                     srcMemCount, endianReverse, destType);
    }
    else // stored with Fortran, R
    {
        ClipColumnMajor(dest, destStart, destCount, destRowMajor, src, srcStart,
                        srcCount, destMemStart, destMemCount, srcMemStart,
                        srcMemCount, endianReverse, destType);
    }
}

size_t PaddingToAlignPointer(const void *ptr)
{
    auto memLocation = reinterpret_cast<std::uintptr_t>(ptr);
    size_t padSize = sizeof(max_align_t) - (memLocation % sizeof(max_align_t));
    if (padSize == sizeof(max_align_t))
    {
        padSize = 0;
    }
    return padSize;
}

uint64_t PaddingToAlignOffset(uint64_t offset, uint64_t alignment_size)
{
    uint64_t padSize = alignment_size - (offset % alignment_size);
    if (padSize == alignment_size)
    {
        padSize = 0;
    }
    return padSize;
}

#ifdef ADIOS2_HAVE_CUDA
void MemcpyGPUToBuffer(void *dst, const char *GPUbuffer, size_t byteCount)
{
    cudaMemcpy(dst, GPUbuffer, byteCount, cudaMemcpyDeviceToHost);
}

void MemcpyBufferToGPU(char *GPUbuffer, const char *src, size_t byteCount)
{
    cudaMemcpy(GPUbuffer, src, byteCount, cudaMemcpyHostToDevice);
}
#endif

} // end namespace helper
} // end namespace adios2
