/********************************************************************************
 * Copyright (c) 2023 CEA-List
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0.
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 ********************************************************************************/

#include <fmt/format.h>

#include "aidge/scheduler/MemoryManager.hpp"
#include "aidge/utils/ErrorHandling.hpp"

Aidge::MemoryManager::~MemoryManager() noexcept = default;

std::shared_ptr<Aidge::MemoryManager::MemorySpace> Aidge::MemoryManager::reserve(
    unsigned int size,
    const std::set<std::shared_ptr<Node> >& dependencies)
{
    const unsigned int offset = onStack(size);

    std::shared_ptr<MemorySpace> memSpace
        = std::make_shared<MemorySpace>(mClock, offset, size, dependencies);
    mMemSpaces.push_back(memSpace);
    return memSpace;
}

void Aidge::MemoryManager::expand(
    std::shared_ptr<MemorySpace> memSpace,
    unsigned int requiredSize)
{
    assert(std::find(mMemSpaces.begin(), mMemSpaces.end(), memSpace)
            != mMemSpaces.end());

    memSpace->size = std::max(memSpace->size, requiredSize);

    // Rebuild the stack from the beginning, taking into account the new size.
    // Everything else stay the same.
    mMemStack.clear();

    for (Clock_T clock = 0; clock <= mClock; ++clock) {
        for (std::vector<std::shared_ptr<MemorySpace> >::iterator
            it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd;
            ++it)
        {
            if ((*it)->allocated == clock)
                (*it)->offset = onStack((*it)->size);
        }

        // MemorySpace released at clock are still valid until the next tick;
        // make sure offStack() only append after all onStack() are done.
        for (std::vector<std::shared_ptr<MemorySpace> >::iterator
            it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd;
            ++it)
        {
            if ((*it)->released == clock && (*it)->dependencies.empty())
                offStack((*it)->offset);
        }
    }
}

Aidge::MemoryManager::MemoryPlane Aidge::MemoryManager::allocate(
    unsigned int size,
    const std::set<std::shared_ptr<Node> >& dependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    const unsigned int fullSize = std::max(size, stride) * length * count;
    return MemoryPlane(reserve(fullSize, dependencies),
                       mClock, 0, size, stride, length, count);
}

unsigned int Aidge::MemoryManager::allocate(
    const std::shared_ptr<Node>& node,
    unsigned int size,
    const std::set<std::shared_ptr<Node> >& dependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >::iterator it;
    std::tie(it, std::ignore) = mMemPlanes.insert(std::make_pair(node,
                                                std::vector<MemoryPlane>()));

    (*it).second.push_back(allocate(size, dependencies, stride, length, count));
    return ((*it).second.size() - 1);
}

bool Aidge::MemoryManager::isWrapAround(
    std::shared_ptr<MemorySpace> memSpace,
    unsigned int offset,
    unsigned int size,
    unsigned int stride,
    unsigned int length,
    unsigned int count) const
{
    const unsigned int fullSize = std::max(size, stride) * length * count;
    return (offset + fullSize > memSpace->size);
}

Aidge::MemoryManager::MemoryPlane Aidge::MemoryManager::reallocate(
    std::shared_ptr<MemorySpace> memSpace,
    unsigned int offset,
    unsigned int size,
    bool wrapAround,
    unsigned int extraSize,
    const std::set<std::shared_ptr<Node> >& additionalDependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    const unsigned int fullSize = std::max(size, stride) * length * count;
    unsigned int requiredSize = offset + fullSize;

    if (wrapAround) {
        requiredSize = fullSize + extraSize;

        if (count > 1) {
            // (requiredSize - offset) must be a multiple of (stride * length)
            requiredSize = offset
                + std::ceil((requiredSize - offset)
                    / static_cast<double>(std::max(size, stride) * length))
                        * (std::max(size, stride) * length);
        }
        else if (length > 1) {
            // (requiredSize - offset) must be a multiple of stride
            requiredSize = offset
                + std::ceil((requiredSize - offset)
                    / static_cast<double>(std::max(size, stride)))
                        * std::max(size, stride);
        }
    }

    if (requiredSize > memSpace->size || memSpace->released >= 0) {
        // Expand in size and/or duration.
        // If memSpace was already released, put it back on the stack
        memSpace->released = -1;
        expand(memSpace, requiredSize);
    }

    memSpace->dependencies.insert(additionalDependencies.begin(),
                                  additionalDependencies.end());

    return MemoryPlane(memSpace, mClock, offset, size, stride, length, count);
}

Aidge::MemoryManager::MemoryPlane Aidge::MemoryManager::reallocate(
    const MemoryPlane& memPlane,
    unsigned int extraOffset,
    unsigned int size,
    bool wrapAround,
    unsigned int extraSize,
    const std::set<std::shared_ptr<Node> >& additionalDependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    const unsigned int initialOffset = memPlane.getFinalOffset()
        - memPlane.memSpace->offset + extraOffset;
    const unsigned int fullSize = std::max(size, stride) * length * count;
    unsigned int requiredSize = initialOffset + fullSize;

    if (wrapAround) {
        requiredSize = fullSize + extraSize;

        if (count > 1) {
            // (requiredSize - offset) must be a multiple of (stride * length)
            requiredSize = initialOffset
                + std::ceil((requiredSize - initialOffset)
                    / static_cast<double>(std::max(size, stride) * length))
                        * (std::max(size, stride) * length);
        }
        else if (length > 1) {
            // (requiredSize - offset) must be a multiple of stride
            requiredSize = initialOffset
                + std::ceil((requiredSize - initialOffset)
                    / static_cast<double>(std::max(size, stride)))
                        * std::max(size, stride);
        }

        // Make sure that the intended margin with previous memPlane will be
        // respected, as it may actually be lower because of the floor()
        // in the memPlane getLimit() function.
        if (memPlane.count > 1) {
            requiredSize = memPlane.offset
                + std::ceil((requiredSize - memPlane.offset)
                    / static_cast<double>(memPlane.stride * memPlane.length))
                        * (memPlane.stride * memPlane.length);
        }
        else if (memPlane.length > 1) {
            requiredSize = memPlane.offset
                + std::ceil((requiredSize - memPlane.offset)
                    / static_cast<double>(memPlane.stride))
                        * memPlane.stride;
        }
    }

    if (requiredSize > memPlane.memSpace->size
        || memPlane.memSpace->released >= 0)
    {
        // Expand in size and/or duration.
        // If memSpace was already released, put it back on the stack
        memPlane.memSpace->released = -1;
        expand(memPlane.memSpace, requiredSize);
    }

    memPlane.memSpace->dependencies.insert(
        additionalDependencies.begin(),
        additionalDependencies.end());

    const unsigned int finalOffset = memPlane.getFinalOffset()
        - memPlane.memSpace->offset + extraOffset;

    return MemoryPlane(memPlane.memSpace, mClock,
                       finalOffset, size, stride, length, count);
}

unsigned int Aidge::MemoryManager::reallocate(
    const MemoryPlane& memPlane,
    const std::shared_ptr<Node>& node,
    unsigned int extraOffset,
    unsigned int size,
    bool wrapAround,
    unsigned int extraSize,
    const std::set<std::shared_ptr<Node> >& additionalDependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >::iterator it;
    std::tie(it, std::ignore) = mMemPlanes.insert(std::make_pair(node,
                                                std::vector<MemoryPlane>()));

    (*it).second.push_back(reallocate(memPlane, extraOffset, size, wrapAround,
                                      extraSize, additionalDependencies,
                                      stride, length, count));
    return ((*it).second.size() - 1);
}

unsigned int Aidge::MemoryManager::reallocate(
    std::shared_ptr<MemorySpace> memSpace,
    const std::shared_ptr<Node>& node,
    unsigned int offset,
    unsigned int size,
    bool wrapAround,
    unsigned int extraSize,
    const std::set<std::shared_ptr<Node> >& additionalDependencies,
    unsigned int stride,
    unsigned int length,
    unsigned int count)
{
    std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >::iterator it;
    std::tie(it, std::ignore) = mMemPlanes.insert(std::make_pair(node,
                                                std::vector<MemoryPlane>()));

    (*it).second.push_back(reallocate(memSpace, offset, size, wrapAround,
                                      extraSize, additionalDependencies,
                                      stride, length, count));
    return ((*it).second.size() - 1);
}

unsigned int Aidge::MemoryManager::release(std::shared_ptr<MemorySpace> memSpace)
{
    if (memSpace->released == -1) {
        memSpace->released = mClock;

        if (memSpace->dependencies.empty())
            return offStack(memSpace->offset);
    }

    return 0;
}

unsigned int Aidge::MemoryManager::release(const std::shared_ptr<Node>& node)
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::iterator it = mMemPlanes.find(node);

    if (it == mMemPlanes.end()) {
        fmt::print("Warning: release(): there is no allocated memory for node {}\n", node->name());
        return 0;
    }

    unsigned int releasedMemSize = 0;

    for (std::vector<MemoryPlane>::iterator itPlanes = (*it).second.begin(),
        itPlanesEnd = (*it).second.end(); itPlanes != itPlanesEnd; ++itPlanes)
    {
        releasedMemSize += release((*itPlanes).memSpace);
    }

    // Remove dependencies
    releasedMemSize += releaseDependencies(node);

    return releasedMemSize;
}

unsigned int Aidge::MemoryManager::releaseDependencies(
    const std::shared_ptr<Node>& node)
{
    unsigned int releasedMemSize = 0;

    for (std::vector<std::shared_ptr<MemorySpace> >::iterator
        it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd;
        ++it)
    {
        if (!(*it)->dependencies.empty()) {
            (*it)->dependencies.erase(node);

            if ((*it)->released <= mClock
                && (*it)->dependencies.empty())
            {
                (*it)->released = mClock;
                releasedMemSize += offStack((*it)->offset);
            }
        }
    }

    return releasedMemSize;
}

bool Aidge::MemoryManager::MaxLifetimeMinSizeFirst::operator()(
    const std::shared_ptr<MemorySpace>& p0,
    const std::shared_ptr<MemorySpace>& p1)
{
    const Clock_T lifetime0
        = ((p0->released >= 0) ? p0->released : maxLifetime) - p0->allocated;
    const Clock_T lifetime1
        = ((p1->released >= 0) ? p1->released : maxLifetime) - p1->allocated;

    return (lifetime0 > lifetime1
            || (lifetime0 == lifetime1 && p0->size < p1->size));
}

bool Aidge::MemoryManager::MaxLifetimeMaxSizeFirst::operator()(
    const std::shared_ptr<MemorySpace>& p0,
    const std::shared_ptr<MemorySpace>& p1)
{
    const Clock_T lifetime0
        = ((p0->released >= 0) ? p0->released : maxLifetime) - p0->allocated;
    const Clock_T lifetime1
        = ((p1->released >= 0) ? p1->released : maxLifetime) - p1->allocated;

    return (lifetime0 > lifetime1
            || (lifetime0 == lifetime1 && p0->size > p1->size));
}

bool Aidge::MemoryManager::MaxHoleMaxLifetimeFirst::operator()(
    const std::shared_ptr<MemorySpace>& p0,
    const std::shared_ptr<MemorySpace>& p1)
{
    const Clock_T lifetime0
        = ((p0->released >= 0) ? p0->released : maxLifetime) - p0->allocated;
    const Clock_T lifetime1
        = ((p1->released >= 0) ? p1->released : maxLifetime) - p1->allocated;

    const std::pair<Clock_T, unsigned int> maxHole0 = inst->getMaxHole(p0);
    const std::pair<Clock_T, unsigned int> maxHole1 = inst->getMaxHole(p1);

    return (maxHole0.second > maxHole1.second
            || (maxHole0.second == maxHole1.second && lifetime0 > lifetime1));
}

void Aidge::MemoryManager::optimize(OptimizeStrategy strategy) {
    if (strategy == None)
        return;

    const unsigned int maxLifetime = getMaxLifetime();

    if (strategy == OptimizeMaxLifetimeMinSizeFirst) {
        std::stable_sort(mMemSpaces.begin(), mMemSpaces.end(),
                        MemoryManager::MaxLifetimeMinSizeFirst(maxLifetime));
    }
    else if (strategy == OptimizeMaxLifetimeMaxSizeFirst) {
        std::stable_sort(mMemSpaces.begin(), mMemSpaces.end(),
                        MemoryManager::MaxLifetimeMaxSizeFirst(maxLifetime));
    }
    else if (strategy == OptimizeMaxHoleMaxLifetimeFirst) {
        std::stable_sort(mMemSpaces.begin(), mMemSpaces.end(),
                        MemoryManager::MaxHoleMaxLifetimeFirst(maxLifetime, this));
    }

    std::vector<std::map<unsigned int, unsigned int> > stacks(maxLifetime + 1,
                                        std::map<unsigned int, unsigned int>());

    for (std::vector<std::shared_ptr<MemorySpace> >::const_iterator
        it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd; ++it)
    {
        const Clock_T maxT = ((*it)->released >= 0
                                && (*it)->dependencies.empty())
                                    ? (*it)->released : maxLifetime;

        // Merge stacks over memSpace lifetime
        std::map<unsigned int, unsigned int> mergedStacks;

        for (Clock_T t = (*it)->allocated; t <= maxT; ++t) {
            for (std::map<unsigned int, unsigned int>::iterator itMem
                = stacks[t].begin(), itMemEnd = stacks[t].end();
                itMem != itMemEnd; ++itMem)
            {
                bool newInsert;
                std::map<unsigned int, unsigned int>::iterator itMergedMem;
                std::tie(itMergedMem, newInsert) = mergedStacks.insert(
                    std::make_pair((*itMem).first, (*itMem).second));

                if (!newInsert) {
                    (*itMergedMem).second = std::max((*itMergedMem).second,
                                                     (*itMem).second);
                }
            }
        }

        std::map<unsigned int, unsigned int> mergedStack;

        if (!mergedStacks.empty()) {
            std::map<unsigned int, unsigned int>::iterator itMem
                = mergedStacks.begin();

            mergedStack.insert(*itMem);
            ++itMem;

            while (itMem != mergedStacks.end()) {
                std::map<unsigned int, unsigned int>::reverse_iterator
                    itMergedMem = mergedStack.rbegin();
                const unsigned int nextOffset = (*itMergedMem).first
                                                + (*itMergedMem).second;

                if ((*itMem).first <= nextOffset) {
                    (*itMergedMem).second
                        = std::max((*itMem).first + (*itMem).second, nextOffset)
                            - (*itMergedMem).first;
                }
                else
                    mergedStack.insert(*itMem);

                ++itMem;
            }
        }

        // Allocate in merged stack
        unsigned int offset = 0;
        std::map<unsigned int, unsigned int>::iterator itMem
            = mergedStack.begin();

        while (true) {
            if (itMem == mergedStack.end()
                || (*itMem).first - offset >= (*it)->size)
            {
                mergedStack.insert(std::make_pair(offset, (*it)->size));
                break;
            }
            else {
                offset = (*itMem).first + (*itMem).second;
                ++itMem;
            }
        }

        (*it)->offset = offset;

        for (Clock_T t = (*it)->allocated; t <= maxT; ++t) {
            const std::map<unsigned int, unsigned int> stack
                = getStack((*it), t);
            stacks[t].insert(stack.begin(), stack.end());

            //stacks[t].insert(std::make_pair(offset, (*it)->size));
        }
    }
}

unsigned int Aidge::MemoryManager::getOffset(const std::shared_ptr<Node>& node,
                                            unsigned int plane) const
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.find(node);

    if (it == mMemPlanes.end()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getOffset(): no memory allocated for node name {}", node->name());
    }

    if (plane >= (*it).second.size()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getOffset(): plane out of range for node name {}", node->name());
    }

    return ((*it).second[plane].memSpace->offset + (*it).second[plane].offset);
}

unsigned int Aidge::MemoryManager::getSize(const std::shared_ptr<Node>& node,
                                          unsigned int plane) const
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.find(node);

    if (it == mMemPlanes.end()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getSize(): no memory allocated for node name {}", node->name());
    }

    if (plane >= (*it).second.size()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getSize(): plane out of range for node name {}", node->name());
    }

    return (*it).second[plane].getSize();
}

unsigned int Aidge::MemoryManager::getSize(const std::shared_ptr<Node>& node)
    const
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.find(node);

    if (it == mMemPlanes.end()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getSize(): no memory allocated for node name {}", node->name());
    }

    unsigned int size = 0;

    for (std::vector<MemoryPlane>::const_iterator itPlanes
        = (*it).second.begin(), itPlanesEnd = (*it).second.end();
        itPlanes != itPlanesEnd; ++itPlanes)
    {
        size += (*itPlanes).getSize();
    }

    return size;
}

unsigned int Aidge::MemoryManager::getNbPlanes(const std::shared_ptr<Node>& node)
    const
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.find(node);
    return (it == mMemPlanes.end()) ? 0 : (*it).second.size();
}

unsigned int Aidge::MemoryManager::getPeakUsage() const {
    unsigned int peakUsage = 0;

    for (std::vector<std::shared_ptr<MemorySpace> >::const_iterator
        it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd; ++it)
    {
        peakUsage = std::max(peakUsage,
                             (*it)->offset + (*it)->size);
    }

    return peakUsage;
}

Aidge::MemoryManager::Clock_T Aidge::MemoryManager::getMaxLifetime() const {
    Clock_T maxLifetime = 0;

    for (std::vector<std::shared_ptr<MemorySpace> >::const_iterator
        it = mMemSpaces.begin(), itEnd = mMemSpaces.end(); it != itEnd; ++it)
    {
        maxLifetime = std::max(maxLifetime,
            std::max((*it)->allocated, (*it)->released));
    }

    return maxLifetime;
}

const std::vector<Aidge::MemoryManager::MemoryPlane>&
Aidge::MemoryManager::getPlanes(const std::shared_ptr<Node>& node) const
{
    const std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.find(node);

    if (it == mMemPlanes.end()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "getSize(): no memory allocated for node name {}", node->name());
    }

    return (*it).second;
}

Aidge::MemoryManager::MemMap_T
Aidge::MemoryManager::getPlanes(std::shared_ptr<MemorySpace> memSpace)
    const
{
    MemMap_T planes;

    for (MemMap_T::const_iterator itNode = mMemPlanes.begin(),
        itNodeEnd = mMemPlanes.end(); itNode != itNodeEnd; ++itNode)
    {
        for (std::vector<MemoryPlane>::const_iterator itPlane
             = (*itNode).second.begin(), itPlaneEnd = (*itNode).second.end();
             itPlane != itPlaneEnd; ++itPlane)
        {
            if ((*itPlane).memSpace == memSpace) {
                std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
                    ::iterator it;
                std::tie(it, std::ignore) = planes.insert(
                    std::make_pair((*itNode).first,
                                   std::vector<MemoryPlane>()));

                (*it).second.push_back((*itPlane));
            }
        }
    }

    return planes;
}

unsigned int Aidge::MemoryManager::getNbPlanes(
    std::shared_ptr<MemorySpace> memSpace) const
{
    unsigned int count = 0;

    for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator itNode = mMemPlanes.begin(),
        itNodeEnd = mMemPlanes.end(); itNode != itNodeEnd; ++itNode)
    {
        for (std::vector<MemoryPlane>::const_iterator itPlane
             = (*itNode).second.begin(), itPlaneEnd = (*itNode).second.end();
             itPlane != itPlaneEnd; ++itPlane)
        {
            if ((*itPlane).memSpace == memSpace)
                ++count;
        }
    }

    return count;
}

void Aidge::MemoryManager::tick()
{
    ++mClock;
}

void Aidge::MemoryManager::log(const std::string& fileName) const
{
    auto memData = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen(fileName.c_str(), "w"), &std::fclose);

    if (!memData) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "Could not create memory layout log file: {}", fileName);
    }

    auto gnuplot = std::unique_ptr<FILE, decltype(&std::fclose)>(std::fopen((fileName + "_plot.gnu").c_str(), "w"), &std::fclose);

    if (!gnuplot) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "Could not create memory layout log file: {}", (fileName + "_plot.gnu"));
    }

    const Clock_T maxLifetime = getMaxLifetime();
    const unsigned int peakUsage = getPeakUsage();

    fmt::print(gnuplot.get(), "#!/usr/bin/gnuplot\n");
    fmt::print(gnuplot.get(), "set term pngcairo size 1280,768 noenhanced\n");
    fmt::print(gnuplot.get(), "set output \"{}\"\n", fileName + "_plot.png");
    fmt::print(gnuplot.get(), "set xrange [{}:{}]\n", 0, maxLifetime + 1);
    fmt::print(gnuplot.get(), "set yrange [{}:{}]\n", 0, 1.05 * (peakUsage / 1024.0));
    fmt::print(gnuplot.get(), "set xlabel \"Time\"\n");
    fmt::print(gnuplot.get(), "set ylabel \"Memory usage (KWords)\"\n");
    fmt::print(gnuplot.get(), "set grid\n");
    fmt::print(gnuplot.get(), "set xtics 1\n");
    fmt::print(gnuplot.get(), "unset key\n");
    fmt::print(gnuplot.get(), "set palette rgbformulae 30,31,32\n");
    fmt::print(gnuplot.get(), "unset colorbox\n");
    fmt::print(gnuplot.get(), "N={}\n", mMemPlanes.size() + 1);

    unsigned int objectId = 1;
    unsigned int labelId = 1;

    for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator it = mMemPlanes.begin(), itEnd = mMemPlanes.end();
        it != itEnd; ++it)
    {
        const std::string name = (*it).first->name();
        fmt::print(memData.get(), "{}\n", name);

        double minX = -1;
        unsigned int maxY = 0;

        for (std::vector<MemoryPlane>::const_iterator itPlanes
             = (*it).second.begin(), itPlanesBegin = (*it).second.begin(),
            itPlanesEnd = (*it).second.end(); itPlanes != itPlanesEnd;
            ++itPlanes)
        {
            const unsigned int contiguousOffset
                = (*itPlanes).getContiguousOffset();
            const unsigned int contiguousSize = (*itPlanes).getContiguousSize();
            const unsigned int wrappedOffset = (*itPlanes).getWrappedOffset();
            const unsigned int wrappedSize = (*itPlanes).getWrappedSize();

            const Clock_T allocated = (*itPlanes).allocated;
            const Clock_T released = (*itPlanes).memSpace->released;
            const bool isReleased = (released >= 0
                                && (*itPlanes).memSpace->dependencies.empty());

            fmt::print(memData.get(), "  {} {} ({:#08x}U) -> {} ({:#08x}U)",
                (itPlanes - itPlanesBegin), contiguousOffset, contiguousOffset,
                (contiguousOffset + contiguousSize), (contiguousOffset + contiguousSize));

            if (wrappedSize > 0) {
                fmt::print(memData.get(), " + {} ({:#08x}U) -> {} ({:#08x}U)",
                    wrappedOffset, wrappedOffset,
                    (wrappedOffset + wrappedSize), (wrappedOffset + wrappedSize));
            }

            fmt::print(memData.get(), " [{}] @ {}", (*itPlanes).getSize(), allocated);

            if (isReleased) {
                fmt::print(memData.get(), " to {}", released);
            }

            fmt::print(memData.get(), "\n");

            // Gnuplot
            const double startX = allocated;

            if (startX < minX || minX < 0) {
                minX = startX;
                maxY = contiguousOffset + contiguousSize;
            }

            if ((*itPlanes).size != (*itPlanes).stride) {
                for (unsigned int offset = contiguousOffset;
                    offset < contiguousOffset + contiguousSize;
                    offset += (*itPlanes).stride)
                {
                    fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
                        (allocated * 100 + objectId), startX, (offset / 1024.0),
                        (((isReleased) ? released : maxLifetime) + 1),
                        (std::min((offset + (*itPlanes).size),
                                        contiguousOffset + contiguousSize) / 1024.0),
                        labelId);
                    ++objectId;
                }
            }
            else {
                fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
                    (allocated * 100 + objectId), startX, (contiguousOffset / 1024.0),
                    (((isReleased) ? released : maxLifetime) + 1),
                    ((contiguousOffset + contiguousSize) / 1024.0),
                    labelId);
                ++objectId;
            }

            if (wrappedSize > 0) {
                fmt::print(gnuplot.get(), "set object {} rectangle from {},{} to {},{} fc palette frac ({} * 1./N)\n",
                    (allocated * 100 + objectId), startX, (wrappedOffset / 1024.0),
                    (((isReleased) ? released : maxLifetime) + 1),
                    ((wrappedOffset + contiguousSize) / 1024.0),
                    labelId);
                ++objectId;

                fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n",
                    startX, (contiguousOffset / 1024.0),
                    (startX + 0.1), (contiguousOffset / 1024.0));

                fmt::print(gnuplot.get(), "set arrow from {},{} to {},{} nohead\n",
                    (startX + 0.05), ((contiguousOffset + contiguousSize) / 1024.0),
                    (startX + 0.05), (wrappedOffset / 1024.0));
            }
        }

        fmt::print(gnuplot.get(), "set label {} '{}' at {},{} rotate by 30 font \",8\" offset char 0.5,0.5\n",
            labelId, name, minX, (maxY / 1024.0));
        ++labelId;

        fmt::print(memData.get(), "\n");
    }

    fmt::print(gnuplot.get(), "set arrow from 0,{} to {},{} nohead lc rgb \"red\"\n",
        (peakUsage / 1024.0), (maxLifetime + 1),
        (peakUsage / 1024.0));

    fmt::print(gnuplot.get(), "set label {} 'Peak usage = {} KWords' at 0,{} textcolor rgb \"red\" offset char 0.5,0.5\n",
        labelId, (peakUsage / 1024.0), (peakUsage / 1024.0));

    fmt::print(gnuplot.get(), "plot 0\n");
}

unsigned int Aidge::MemoryManager::onStack(unsigned int size)
{
    unsigned int offset = 0;
    std::map<unsigned int, unsigned int>::iterator itMem = mMemStack.begin();

    while (true) {
        if (itMem == mMemStack.end()
            || (*itMem).first - offset >= size)
        {
            mMemStack.insert(std::make_pair(offset, size));
            break;
        }
        else {
            offset = (*itMem).first + (*itMem).second;
            ++itMem;
        }
    }

    return offset;
}

unsigned int Aidge::MemoryManager::offStack(unsigned int offset)
{
    std::map<unsigned int, unsigned int>::iterator itMem
        = mMemStack.find(offset);

    if (itMem == mMemStack.end()) {
        AIDGE_THROW_OR_ABORT(std::runtime_error,
            "offStack(): offset not found in stack");
    }
    else {
        const unsigned int size = (*itMem).second;
        mMemStack.erase(offset);
        return size;
    }
}

std::map<unsigned int, unsigned int> Aidge::MemoryManager::getStack(
    std::shared_ptr<MemorySpace> memSpace,
    Clock_T clock) const
{
    // Find all planes associated to memSpace and index them by their allocated
    // value in a map
    std::map<Clock_T, std::vector<MemoryPlane> > planes;

    for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator itNode = mMemPlanes.begin(),
        itNodeEnd = mMemPlanes.end(); itNode != itNodeEnd; ++itNode)
    {
        for (std::vector<MemoryPlane>::const_iterator itPlane
             = (*itNode).second.begin(), itPlaneEnd = (*itNode).second.end();
             itPlane != itPlaneEnd; ++itPlane)
        {
            if ((*itPlane).memSpace == memSpace) {
                std::map<Clock_T, std::vector<MemoryPlane> >::iterator it;
                std::tie(it, std::ignore) = planes.insert(
                    std::make_pair((*itPlane).allocated,
                                   std::vector<MemoryPlane>()));

                (*it).second.push_back((*itPlane));
            }
        }
    }

    // Find the planes allocated at time clock or the one just before
    // => obtain all the planes that are considered valid at the time clock
    Clock_T c = clock;
    std::map<Clock_T, std::vector<MemoryPlane> >::iterator itPlanes;

    do
        itPlanes = planes.find(c);
    while (itPlanes == planes.end() && (c--) > 0);

    assert(itPlanes != planes.end());

    // Fill the stack at time clock
    std::map<unsigned int, unsigned int> stack;

    for (std::vector<MemoryPlane>::const_iterator
        it = (*itPlanes).second.begin(), itEnd = (*itPlanes).second.end();
        it != itEnd; ++it)
    {
        stack.insert(std::make_pair((*it).getContiguousOffset(),
                                    (*it).getContiguousSize()));

        if ((*it).getWrappedSize() > 0) {
            stack.insert(std::make_pair((*it).getWrappedOffset(),
                                        (*it).getWrappedSize()));
        }
    }

    return stack;
}

std::pair<Aidge::MemoryManager::Clock_T, unsigned int>
Aidge::MemoryManager::getMaxHole(std::shared_ptr<MemorySpace> memSpace) const
{
    std::map<Clock_T, unsigned int> holesSize;

    for (std::map<std::shared_ptr<Node>, std::vector<MemoryPlane> >
        ::const_iterator itNode = mMemPlanes.begin(),
        itNodeEnd = mMemPlanes.end(); itNode != itNodeEnd; ++itNode)
    {
        for (std::vector<MemoryPlane>::const_iterator itPlane
             = (*itNode).second.begin(), itPlaneEnd = (*itNode).second.end();
             itPlane != itPlaneEnd; ++itPlane)
        {
            if ((*itPlane).memSpace == memSpace) {
                const unsigned int holeSize = memSpace->size
                    - (*itPlane).getContiguousSize()
                    - (*itPlane).getWrappedSize();

                std::map<Clock_T, unsigned int>::iterator it;
                bool newInsert;
                std::tie(it, newInsert) = holesSize.insert(
                    std::make_pair((*itPlane).allocated, holeSize));

                if (!newInsert) {
                    // Another plane exists at the same time, one must substract
                    // the size of this other plane from the hole size
                    (*it).second = std::max(0, static_cast<int>((*it).second)
                        - static_cast<int>((*itPlane).getContiguousSize())
                        - static_cast<int>((*itPlane).getWrappedSize()));
                }
            }
        }
    }

    return *std::max_element(holesSize.begin(),
                             holesSize.end(),
                             [](const auto& left, const auto& right) {
                                return std::max(left.second, right.second);
                             });
}