/*
 * Copyright (c) 2015, Freescale Semiconductor, Inc.
 * Copyright 2016-2017 NXP
 * All rights reserved.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */

#include <stdio.h>
#include <string.h>
#include "fsl_sd.h"
#include "fsl_debug_console.h"
#include "ff.h"
#include "diskio.h"
#include "fsl_sd_disk.h"
#include "board.h"
#include "fsl_trng.h"

#include "pin_mux.h"
#include "clock_config.h"
#include "fsl_common.h"

#include "ewrte.h"
#include "ewmain.h"
#include "ew_bsp_system.h"

#include "image.h"
#include "limits.h"
#include "time.h"
/*******************************************************************************
 * Definitions
 ******************************************************************************/

/* buffer size (in byte) for read/write operations */
#define BUFFER_SIZE (784U) // 28x28 = 784

#define VERBOSE 0

/*******************************************************************************
 * Prototypes
 ******************************************************************************/
/*!
 * @brief wait card insert function.
 */
static status_t sdcardWaitCardInsert(void);
int writeFile( uint8_t * , int );

/*******************************************************************************
 * Variables
 ******************************************************************************/
static FATFS g_fileSystem; /* File system object */
static FIL g_fileObject;   /* File object */

/* @brief decription about the read/write buffer
 * The size of the read/write buffer should be a multiple of 512, since SDHC/SDXC card uses 512-byte fixed
 * block length and this driver example is enabled with a SDHC/SDXC card.If you are using a SDSC card, you
 * can define the block length by yourself if the card supports partial access.
 * The address of the read/write buffer should align to the specific DMA data buffer address align value if
 * DMA transfer is used, otherwise the buffer address is not important.
 * At the same time buffer address/size should be aligned to the cache line size if cache is supported.
 */
SDK_ALIGN(uint8_t g_bufferWrite[SDK_SIZEALIGN(BUFFER_SIZE, SDMMC_DATA_BUFFER_ALIGN_CACHE)],
          MAX(SDMMC_DATA_BUFFER_ALIGN_CACHE, SDMMCHOST_DMA_BUFFER_ADDR_ALIGN));
SDK_ALIGN(uint8_t g_bufferRead[SDK_SIZEALIGN(BUFFER_SIZE, SDMMC_DATA_BUFFER_ALIGN_CACHE)],
          MAX(SDMMC_DATA_BUFFER_ALIGN_CACHE, SDMMCHOST_DMA_BUFFER_ADDR_ALIGN));
/*! @brief SDMMC host detect card configuration */
static const sdmmchost_detect_card_t s_sdCardDetect = {
#ifndef BOARD_SD_DETECT_TYPE
    .cdType = kSDMMCHOST_DetectCardByGpioCD,
#else
    .cdType = BOARD_SD_DETECT_TYPE,
#endif
    .cdTimeOut_ms = (~0U),
};

/*! @brief SDMMC card power control configuration */
#if defined DEMO_SDCARD_POWER_CTRL_FUNCTION_EXIST
static const sdmmchost_pwr_card_t s_sdCardPwrCtrl = {
    .powerOn          = BOARD_PowerOnSDCARD,
    .powerOnDelay_ms  = 500U,
    .powerOff         = BOARD_PowerOffSDCARD,
    .powerOffDelay_ms = 0U,
};
#endif
/*******************************************************************************
 * Code
 ******************************************************************************/
static void BOARD_USDHCClockConfiguration(void)
{
    /*configure system pll PFD0 fractional divider to 24, output clock is 528MHZ * 18 / 24 = 396 MHZ*/
    CLOCK_InitSysPfd(kCLOCK_Pfd0, 24U);
    /* Configure USDHC clock source and divider */
    CLOCK_SetDiv(kCLOCK_Usdhc1Div, 0U);
    CLOCK_SetMux(kCLOCK_Usdhc1Mux, 1U);
}


/*!
 * @brief Main function
 */
int main(void)
{
	trng_config_t trngConfig;
    uint32_t data = 0;

    FRESULT error;
    const TCHAR driverNumberBuffer[3U] = {SDDISK + '0', ':', '/'};

    /* initialize system */
    EwBspConfigSystem();

    BOARD_USDHCClockConfiguration();

    TRNG_GetDefaultConfig(&trngConfig);
	trngConfig.sampleMode = kTRNG_SampleModeVonNeumann;
	TRNG_Init(TRNG, &trngConfig);
	TRNG_GetRandomData(TRNG, &data, sizeof(data));

	srand(data);

    if (sdcardWaitCardInsert() != kStatus_Success)
    {
        return -1;
    }

    if (f_mount(&g_fileSystem, driverNumberBuffer, 0U))
    {
        PRINTF("Mount volume failed.\r\n");
        return -1;
    }

#if (FF_FS_RPATH >= 2U)
    error = f_chdrive((char const *)&driverNumberBuffer[0U]);
    if (error)
    {
        PRINTF("Change drive failed.\r\n");
        return -1;
    }
#endif

    /* initialize Embedded Wizard application */
    if ( EwInit() == 0 )
      return 0;

    /* process the Embedded Wizard main loop */
    while( EwProcess())
      ;

    /* de-initialize Embedded Wizard application */
    EwDone();
    TRNG_Deinit(TRNG);

    return 0;
}

/*!
 * @brief Process the input image, prepare the global interpreter and call inference
 *
 * @param pointer to the input image
 * @param image width in pixels
 * @param image height in pixels
 *
 * @return void
 */
void processImage( uint8_t *src, int image_width, int image_height, int digit )
{
	Image pre_sca;
	pre_sca.channels = 1;
	double dx = 0, dy = 0;

	int m = 0;
	int minY = image_height, minX = image_width;
	int maxY = 0, maxX = 0;
	for (int h = 0; h < image_height; h++)
	{
		for (int w = 0; w < image_width; w++)
		{
			if (src[m] == 0xFF)
			{
				maxY = h;
				if (maxX < w) maxX = w;

				if (minY > h) minY = h;
				if (minX > w) minX = w;
			}

			m++;
		}
	}

	/* By incrementing the max values one more time, it is ensured that even the
     last pixel is always included after the cropping. Otherwise it would be
     necessary to use them as (maxX + 1) everywhere.*/
	maxX++;
	maxY++;

	/* Adding an empty border around the drawing to make the resulting picture more
     similar in layout to the MNIST dataset pictures. */
	int border_thickness = 12; // in pixels

	int cropped_centered_width = maxX - minX + (border_thickness * 2);
	int cropped_centered_height = maxY - minY + (border_thickness * 2);

	if (VERBOSE)
	{
		PRINTF("src\n\r");
		int i = 0;
		for (int h = 0; h < image_height; h++)
		{
			for (int w = 0; w < image_width; w++)
			{
				if (src[i] == 0)
					PRINTF("0");
				else
					PRINTF("1");
				i++;
			}
			PRINTF("\n\r");
		}
		PRINTF("\n\n\n\r");
	}

	uint8_t *cropped_centered_src = (uint8_t*) malloc(sizeof(uint8_t) * cropped_centered_width * cropped_centered_height);

	if (cropped_centered_src == NULL)
	{
		PRINTF("Out of memory!\n\r");
		return;
	}

	memset(cropped_centered_src, 0, cropped_centered_width * cropped_centered_height);

	int n = cropped_centered_width * border_thickness; // skip the top border
	n += border_thickness; // skip the left border
	for (int h = minY; h < maxY; h++, n += 2 * border_thickness) // skip the right and the left borders
	{
		for (int w = minX; w < maxX; w++, n++)
		{
			cropped_centered_src[n] = src[w + (h * image_width)];
		}
	}

	if (VERBOSE)
	{
		PRINTF("cropped_centered_src\n\r");
		int i = 0;
		for (int h = 0; h < cropped_centered_height; h++)
		{
		for (int w = 0; w < cropped_centered_width; w++)
			{
				if (cropped_centered_src[i] == 0)
					PRINTF("0");
				else
					PRINTF("1");
				i++;
			}
			PRINTF("\n\r");
		}
		PRINTF("\n\n\n\r");
	}

	pre_sca.width = cropped_centered_width;
	pre_sca.height = cropped_centered_height;
	dx = 1.0*28/pre_sca.width, dy = 1.0*28/pre_sca.height;
	Image *img = ImCreate(&pre_sca, dx, dy);

	pre_sca.imageData = cropped_centered_src;
	img = ImScale(&pre_sca, img, dx, dy);

	if (VERBOSE)
	{
		PRINTF("scaled\n\r");
		int i = 0;
		for (int h = 0; h < 28; h++)
		{
			for (int w = 0; w < 28; w++)
			{
				if (img->imageData[i] == 0)
					PRINTF("0");
				else
					PRINTF("1");
				i++;
			}
			PRINTF("\n\r");
		}
		PRINTF("\n\n\n\r");
	}

	writeFile(img->imageData, digit);
	free(cropped_centered_src);
	free(img->imageData);
	free(img);
}

int writeFile(uint8_t *img, int digit)
{
    FRESULT error;
    UINT bytesWritten;
	int32_t random_int = rand() % 100000;
	char random_name[13];

	memset(random_name, 0, 13);
	sprintf(random_name, "/%d/%d.dat", digit, random_int);

	if (VERBOSE)
	{
		PRINTF("writefile\n\r");
		int i = 0;
		for (int h = 0; h < 28; h++)
		{
			for (int w = 0; w < 28; w++)
			{
				if (img[i] == 0)
					PRINTF("0");
				else
					PRINTF("1");
				i++;
			}
			PRINTF("\n\r");
		}
		PRINTF("\n\n\n\r");
	}

    error = f_open(&g_fileObject, random_name, (FA_WRITE | FA_READ | FA_CREATE_NEW));
	if (error)
	{
		while (error == FR_EXIST)
		{
			random_int = rand() % 100000;
			memset(random_name, 0, 13);
			sprintf(random_name, "/%d/%d.dat", digit, random_int);

			error = f_open(&g_fileObject, random_name, (FA_WRITE | FA_READ | FA_CREATE_NEW));
		}

		if (error) // not FR_EXIST
		{
			PRINTF("Open file failed.\r\n");
			return -1;
		}
	}

	memcpy(g_bufferWrite, img, 784);

	error = f_write(&g_fileObject, g_bufferWrite, sizeof(g_bufferWrite), &bytesWritten);
	if ((error) || (bytesWritten != sizeof(g_bufferWrite)))
	{
		PRINTF("Write file failed. \r\n");
	}

	if (f_close(&g_fileObject))
	{
		PRINTF("\r\nClose file failed.\r\n");
		return -1;
	}

	memset(g_bufferWrite, 0, sizeof(g_bufferWrite));
	return 0;
}

static status_t sdcardWaitCardInsert(void)
{
    /* Save host information. */
    g_sd.host.base           = SD_HOST_BASEADDR;
    g_sd.host.sourceClock_Hz = SD_HOST_CLK_FREQ;
    /* card detect type */
    g_sd.usrParam.cd = &s_sdCardDetect;
#if defined DEMO_SDCARD_POWER_CTRL_FUNCTION_EXIST
    g_sd.usrParam.pwr = &s_sdCardPwrCtrl;
#endif
    /* SD host init function */
    if (SD_HostInit(&g_sd) != kStatus_Success)
    {
       // PRINTF("\r\nSD host init fail\r\n");
        return kStatus_Fail;
    }
    /* power off card */
    SD_PowerOffCard(g_sd.host.base, g_sd.usrParam.pwr);
    /* wait card insert */
    if (SD_WaitCardDetectStatus(SD_HOST_BASEADDR, &s_sdCardDetect, true) == kStatus_Success)
    {
        PRINTF("\r\nCard inserted.\r\n");
        /* power on the card */
        SD_PowerOnCard(g_sd.host.base, g_sd.usrParam.pwr);
    }
    else
    {
        PRINTF("\r\nCard detect fail.\r\n");
        return kStatus_Fail;
    }

    return kStatus_Success;
}
