/******************************************************************************
**
** 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 "board.h"
#include "nxp_os.h"

#include "fsl_pxp.h"
#include "fsl_lpi2c.h"
#include "fsl_gt911.h"
#include "fsl_debug_console.h"
#include "display_support.h"
#if IS_HDMI_TOUCH_SUPPORTED()
  #include "usb_host_config.h"
  #include "usb_host.h"
  #include "usb_host_hid.h"
  #include "host_hid_generic.h"
#endif

#include <limits.h>
#include <time.h>
#include <math.h>
#include <stddef.h>


#if defined(__ICCARM__)
  // The IAR compiler is setup to not include system files so
  // time.h is not included which results in warnings=>errors
  // for missing these two typedefs
  typedef unsigned int   __time32_t;
  //typedef unsigned int   clock_t;
#endif

#ifdef FSL_RTOS_FREE_RTOS
#include "FreeRTOS.h"
#include "task.h"
#endif /* FSL_RTOS_FREE_RTOS */

#define NXP_TIMER_PRECISION configTICK_RATE_HZ

#if IS_MIPI_TOUCH_SUPPORTED()
extern gt911_handle_t s_touchHandle;

void touchDataInterruptHandler(int x, int y, bool pressed);
#elif IS_HDMI_TOUCH_SUPPORTED()
void touchDataInterruptHandler(int x, int y, bool pressed);
#endif

void QUL_CleanInvalidateDCache_by_Addr(void *addr, int size)
{
    uint32_t a = (uint32_t) addr;

    // addr must be 32 byte aligned
    int delta = a & 0x1f;
    a &= ~0x1f;

    SCB_CleanInvalidateDCache_by_Addr((uint32_t *) a, size + delta);
}

void SysTick_Handler(void)
{
    nxp_systick();
}

uint64_t nxp_timestamp()
{
    return xTaskGetTickCount() / (NXP_TIMER_PRECISION / 1000);
}

static volatile bool touchDataAvailable = false;

void BOARD_MIPI_PANEL_TOUCH_IRQ_HANDLER(void)
{
    GPIO_PortClearInterruptFlags(BOARD_MIPI_PANEL_TOUCH_INT_GPIO, 1U << BOARD_MIPI_PANEL_TOUCH_INT_PIN);
    touchDataAvailable = true;
    nxp_resume(MAINLOOP_SEMAPHORE);
}

void nxp_requestTouchData()
{
#if IS_MIPI_TOUCH_SUPPORTED()
    static int lastPressed = false;
    static uint64_t lastTimeStamp = 0;
    const uint64_t touchPeriod = 20;

    uint64_t timeStamp = nxp_timestamp();

    static int lastX = 0, lastY = 0;
    int x, y;

    GPIO_PortDisableInterrupts(BOARD_MIPI_PANEL_TOUCH_INT_GPIO, 1U << BOARD_MIPI_PANEL_TOUCH_INT_PIN);

    if ((lastTimeStamp + touchPeriod > timeStamp) && lastPressed == true) {
        touchDataAvailable = false;
        GPIO_PortEnableInterrupts(BOARD_MIPI_PANEL_TOUCH_INT_GPIO, 1U << BOARD_MIPI_PANEL_TOUCH_INT_PIN);
        return;
    }

    lastTimeStamp = timeStamp;

    status_t status = GT911_GetSingleTouch(&s_touchHandle, &x, &y);

    touchDataAvailable = false;
    GPIO_PortEnableInterrupts(BOARD_MIPI_PANEL_TOUCH_INT_GPIO, 1U << BOARD_MIPI_PANEL_TOUCH_INT_PIN);

    if (status == kStatus_Success) {
        if (lastPressed == false || x != lastX || y != lastY) {
            touchDataInterruptHandler(x, y, true);
            lastX = x;
            lastY = y;
            lastPressed = true;
        }

    } else if (lastPressed == true) {
        touchDataInterruptHandler(lastX, lastY, false);
        lastPressed = false;
    }
#elif IS_HDMI_TOUCH_SUPPORTED()
    static int lastPressed = false;

    static int lastX = 0, lastY = 0;
    int x, y;
    touchDataAvailable = false;

    if (BOARD_HDMI_Touch_GetData(&x, &y) == kStatus_Success) {
        if (lastPressed == false || x != lastX || y != lastY) {
            touchDataInterruptHandler(x, y, true);
            lastX = x;
            lastY = y;
            lastPressed = true;
        }

    } else if (lastPressed == true) {
        touchDataInterruptHandler(lastX, lastY, false);
        lastPressed = false;
    }
#endif
}

void preloadModuleResourceData()
{
#if defined(__ICCARM__)
#pragma section = "QulModuleResourceData"
#pragma section = "QulModuleResourceData_init"
    char *__ModuleResourceDataStart = (__section_begin("QulModuleResourceData_init"));
    char *__ModuleResourceDataCacheStart = (__section_begin("QulModuleResourceData"));
    char *__ModuleResourceDataCacheEnd = (__section_end("QulModuleResourceData"));

    memcpy(__ModuleResourceDataCacheStart,
           __ModuleResourceDataStart,
           __ModuleResourceDataCacheEnd - __ModuleResourceDataCacheStart);
#else
    extern unsigned char __ModuleResourceDataStart;
    extern unsigned char __ModuleResourceDataCacheStart;
    extern unsigned char __ModuleResourceDataCacheEnd;

    memcpy(&__ModuleResourceDataCacheStart,
           &__ModuleResourceDataStart,
           &__ModuleResourceDataCacheEnd - &__ModuleResourceDataCacheStart);
#endif
}

#if defined(__ICCARM__)
/*
 * Note from the IAR C/C++ Development Guide:
 * To make the time and date functions work, you must implement the three functions
 * clock, time, and __getzone.
 */

// The number of times an internal timing event occurs per second
int const CLOCKS_PER_SECOND = 1000;

clock_t clock(void)
{
    // This function should return a value, which after division by CLOCKS_PER_SECOND,
    // is the processor time in seconds.
    return (clock_t) nxp_timestamp();
}

__time32_t __time32(__time32_t *t)
{
    uint64_t timestamp = nxp_timestamp();
    // same timestamp as _gettimeofday
    __time32_t curtime = (__time32_t)(60 * (60 * 13 + 33) + timestamp / NXP_TIMER_PRECISION);

    if (t)
        *t = curtime;

    return curtime;
}

char const *__getzone()
{
    // See <IAR>/src/lib/time/getzone.c for documentation
    // For Germany as a default timezone
    return ":GMT+1:GMT+2:0100:032502+0:102502+0";
}

__ATTRIBUTES char *_DstMalloc(size_t);
__ATTRIBUTES void _DstFree(char *);

char *_DstMalloc(size_t s)
{
    // Return a buffer that can hold the maximum number of DST entries of
    // of any timezone available on the device.
    // Each DST entry takes up a structure of 5 bytes plus regular alignment.
    // Instead of a static buffer a dynamically allocated memory can be used as well.

    // With the two entries shown above the required buffer size would be
    // 2 * (5 bytes size + 3 bytes alignment) = 16 bytes

    static char buffert[8 * 4];
    return buffert;
}

void _DstFree(char *p)
{
    // Nothing required here because of static buffer in _DstMalloc
}

#else
int _gettimeofday(struct timeval *tp, void *ts)
{
    uint64_t timestamp = nxp_timestamp();
    // set the time to begin somewhere during the Berlin afternoon,
    // to make the clock example more interesting
    tp->tv_sec = 60 * (60 * 13 + 33) + timestamp / NXP_TIMER_PRECISION;
    tp->tv_usec = (timestamp % NXP_TIMER_PRECISION) * (1000000 / NXP_TIMER_PRECISION);
    return 0;
}
#endif

// Note: wrap for atexit is only for testing purposes
// Calls for malloc are checked by wrapping malloc function
// Default implementation of atexit uses malloc so it needs to be wrapped
int __wrap_atexit(void __attribute__((__unused__)) (*function)(void) )
{
    return -1;
}
