/******************************************************************************
**
** Copyright (C) 2020 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Quick Ultralite module.
**
** $QT_BEGIN_LICENSE:COMM$
**
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see http://www.qt.io/terms-conditions. For further
** information use the contact form at http://www.qt.io/contact-us.
**
** $QT_END_LICENSE$
**
******************************************************************************/
#include "fsl_device_registers.h"
#include "fsl_debug_console.h"
#include "vglitedrawingengine.h"
#include "vglitesupport.h"
#include "lcdifv2layerengine.h"
#include "lcdifv2layer.h"
#include "display_support.h"
#include "board.h"

#include "nxp_os.h"
#include "power.h"

#include <qul/pixelformat.h>
#include <qul/eventqueue.h>

#include <platform/platform.h>
#include <platform/singlepointtoucheventdispatcher.h>
#include <platforminterface/log.h>
#include <platforminterface/rect.h>
#include <platforminterface/platforminterface.h>
#include <platforminterface/screen.h>
#include <platforminterface/layerengine.h>
#include <platform/private/fixedsizeblockallocator.h>
#include <platform/private/preloadallocator.h>
#include <platform/private/doublequeue.h>

#include <random>

#define __STDC_LIMIT_MACROS
#include <stdint.h>

#ifdef FSL_RTOS_FREE_RTOS
#include <FreeRTOS.h>
#include <task.h>
#endif /* FSL_RTOS_FREE_RTOS */

#ifdef NDEBUG
#define debug_bsp(...) ((void) 0)
#else
#define debug_bsp(fmt, ...) Qul::PlatformInterface::log(fmt, ##__VA_ARGS__)
#endif

#if QUL_COLOR_DEPTH != 32
#error "MIMXRT1170 support only 32 bpp color depth"
#endif

extern "C" {
int init_nxp(void);
void init_nxp_late(void);
void nxp_requestTouchData();
uint64_t nxp_timestamp();
void QUL_CleanInvalidateDCache_by_Addr(void *addr, int size);
void preloadModuleResourceData();
char __StackTop;
char __StackLimit;
}

extern uint8_t __preloadRamEnd, __preloadRamStart;
void *preloadRamEnd = &__preloadRamEnd, *preloadRamStart = &__preloadRamStart;

#if !defined(__ICCARM__)
extern unsigned char __preprocessCacheStart;
extern unsigned char __preprocessCacheEnd;
#endif

namespace {
const Qul::PixelFormat kLcdFormat = Qul::PixelFormat_RGB32;
const uint16_t kLcdWidth = DEMO_PANEL_WIDTH;
const uint16_t kLcdHeight = DEMO_PANEL_HEIGHT;

std::pair<char *, char *> preprocessCacheRange()
{
#if defined(__ICCARM__)
#pragma section = "QulPreprocessCache"
    char *begin = reinterpret_cast<char *>(__section_begin("QulPreprocessCache"));
    char *end = reinterpret_cast<char *>(__section_end("QulPreprocessCache"));
#else
    char *begin = reinterpret_cast<char *>(&__preprocessCacheStart);
    char *end = reinterpret_cast<char *>(&__preprocessCacheEnd);
#endif
    return std::make_pair(begin, end);
}
} // namespace

namespace Qul {
namespace Platform {
namespace Private {

static std::minstd_rand randomEngine;

std::pair<void *, void *> stackRange()
{
    return std::make_pair(&__StackTop, &__StackLimit);
}

size_t preprocessCacheSize()
{
    const auto block = preprocessCacheRange();
    return std::distance(block.first, block.second);
}

using namespace Qul::PlatformInterface;

class SinglePointTouchEventQueue : public EventQueue<SinglePointTouchEvent>
{
public:
    virtual void onEvent(const SinglePointTouchEvent &event) { touchEventDispatcher.dispatch(event); }

private:
    SinglePointTouchEventDispatcher touchEventDispatcher;
};

static SinglePointTouchEventQueue touchEventQueue;
static uint64_t nextUpdate = 0;

extern "C" void touchDataInterruptHandler(int x, int y, bool pressed)
{
    touchEventQueue.postEvent(SinglePointTouchEvent{nxp_timestamp(), x, y, pressed});
}

} // namespace Private

struct NxpRt1170Platform : PlatformContext
{
    void initializeHardware() override { init_nxp(); }

    void initializePlatform() override
    {
        Qul::PlatformInterface::init16bppRendering();
        Qul::PlatformInterface::init32bppRendering();

        preloadModuleResourceData();
        nxp_initSuspension();
    }

    void initializeDisplay(const PlatformInterface::Screen *) override { init_nxp_late(); }

    uint64_t update() override
    {
        uint64_t timestamp = currentTimestamp();

        if (timestamp >= Private::nextUpdate) {
            Qul::PlatformInterface::updateEngine(timestamp);
        }

        return Private::nextUpdate;
    }

    void exec() override
    {
        debug_bsp("exec \r\n");

        while (true) {
            nxp_requestTouchData();

            const uint64_t nextUpdate = this->update();

            if (nextUpdate > currentTimestamp())
                nxp_suspend(MAINLOOP_SEMAPHORE, nextUpdate - currentTimestamp());
        }
    }

    double rand() override { return Private::randomEngine() / (Private::randomEngine.max() + 1.0); }

    void scheduleEngineUpdate(uint64_t timeout) override
    {
        Private::nextUpdate = timeout;

        if (currentTimestamp() >= Private::nextUpdate) {
            nxp_resume(MAINLOOP_SEMAPHORE);
        }
    }

    uint64_t currentTimestamp() override { return nxp_timestamp(); }

    FrameBufferingType frameBufferingType(const PlatformInterface::LayerEngine::ItemLayer *layer) const override
    {
        QUL_UNUSED(layer);
        return FlippedDoubleBuffering;
    }

    Qul::PlatformInterface::Screen *availableScreens(size_t *screenCount) const override
    {
        *screenCount = 1;
        static PlatformInterface::Screen screen(PlatformInterface::Size(kLcdWidth, kLcdHeight), kLcdFormat);
        return &screen;
    }

    void waitUntilAsyncReadFinished(const void *begin, const void *end) override
    {
        QUL_UNUSED(begin);
        QUL_UNUSED(end);
        Private::Vglite::finish();
    }

    void flushCachesForAsyncRead(const void *addr, size_t length) override
    {
        QUL_CleanInvalidateDCache_by_Addr(const_cast<void *>(addr), length);
    }

    PlatformInterface::LayerEngine *layerEngine() override
    {
        static Private::Lcdif2LayerEngine engine;
        return &engine;
    }

    PlatformInterface::DrawingDevice *beginFrame(const PlatformInterface::LayerEngine::ItemLayer *layer,
                                                 const PlatformInterface::Rect &rect,
                                                 int refreshInterval) override
    {
        QUL_UNUSED(rect);

        if (!layer)
            return nullptr;

        auto l = reinterpret_cast<const Qul::Platform::Private::Lcdifv2ItemLayer *>(layer);
        return l->beginFrame(refreshInterval);
    }

    void endFrame(const PlatformInterface::LayerEngine::ItemLayer *layer) override
    {
        if (!layer)
            return;

        Private::Vglite::flush();
        auto l = reinterpret_cast<const Qul::Platform::Private::Lcdifv2ItemLayer *>(layer);
        l->endFrame();
    }

    FrameStatistics presentFrame(const PlatformInterface::Screen *screen, const PlatformInterface::Rect &rect) override
    {
        QUL_UNUSED(screen);
        QUL_UNUSED(rect);

        Private::Vglite::finish();
        Private::Lcdif2LayerEngine::commit();
        return FrameStatistics();
    }

    static PlatformInterface::MemoryAllocator *preprocessAllocator()
    {
        // 16k blocks should be a reasonable trade-off between allocation speed and avoiding wasting memory
        const auto block = preprocessCacheRange();
        static Private::FixedSizeBlockAllocator<16384> allocator(block.first, block.second);
        return &allocator;
    }

    PlatformInterface::MemoryAllocator *memoryAllocator(PlatformInterface::MemoryAllocator::AllocationType type)
    {
        static PlatformInterface::MemoryAllocator defaultAllocator;
        static Private::ReversePreloadAllocator<4> preloadAllocator(preloadRamEnd, preloadRamStart);

        switch (type) {
        case PlatformInterface::MemoryAllocator::DefaultPreload:
            return &preloadAllocator;
        case PlatformInterface::MemoryAllocator::Custom:
            return preprocessAllocator();
        default:
            return &defaultAllocator;
        }
    }

    void consoleWrite(char character) override { DbgConsole_Putchar(character); }
};

PlatformContext *getPlatformInstance()
{
    static NxpRt1170Platform platform;
    return &platform;
}

} // namespace Platform
} // namespace Qul
