#include <cstdio>
#include <cstdint>

#include "FreeRTOS.h"
#include "task.h"

#include "ff.h"
#include "fatfs.h"
#include "main_task.h"
#include "utils.h"

#include "wizchip_conf.h"
#include "dhcp.h"

static constexpr unsigned ETH_MAX_BUF_SIZE = 2048U;
static constexpr unsigned SOCKET_DHCP = 1;

static const char* const TAG = "MAIN";

static constexpr uint16_t delay_ms = 5000;
static constexpr unsigned MAX_CHARS_IN_VOLUME_NAME = 12U;
static uint8_t volumeName[MAX_CHARS_IN_VOLUME_NAME];

//UINT ret;

static FATFS fs;// __attribute__((section(".DTCM_RAM")));     // Filesystem object
static uint8_t dhcp_buffer[ETH_MAX_BUF_SIZE];

wiz_NetInfo gWIZNETINFO = {
		.mac = {0x00, 0x08, 0xdc, 0x6f, 0x00, 0x8a},
		.ip = {192, 168, 11, 109},
		.sn = {255, 255, 255, 0},
		.gw = {192, 168, 11, 1},
		.dns = {8, 8, 8, 8},
		.dhcp = NETINFO_DHCP
};


FRESULT scan_files (TCHAR* path);
FRESULT list_dir (const char* path);

static void ip_assigned(void);
static void ip_updated(void);
static void ip_conflict(void);
static void wizchip_initialize(void);
static void wizchip_reset(void);
static void wizchip_check(void);
static void wizchip_dhcp_init(void);

//------------------------------------------------------------------------------

[[noreturn]] void mainTaskStart(void *argument)
{
	(void)argument;

	wizchip_initialize();

	if (gWIZNETINFO.dhcp == NETINFO_DHCP) // DHCP
	{
		wizchip_dhcp_init();

		while (DHCP_run() != DHCP_IP_LEASED)
	      wiz_delay_ms(1000);
	}
	else // static
	{
		ctlnetwork(CN_SET_NETINFO, &gWIZNETINFO);
		printf("\r\n----------STATIC Net Information--------------\r\n");
		//print_network_information();
	}

	//DHCP_init(0, dhcp_buffer);
	//reg_dhcp_cbfunc(ip_assigned, ip_updated, ip_conflict);

	//MX_LWIP_Init();

    FRESULT  r = f_mount(&fs, (const TCHAR*)"", 1);            // Mount the default drive
	if (r != FR_OK) printf("Cannot mount SD-card!\n");
	else
	{
		printf("SD-card was mounted.\n");

		static TCHAR buff[256];
		strcpy (buff, "/");
		list_dir(buff);
	}

	while(1)
	{
		vTaskDelay(pdMS_TO_TICKS(delay_ms));

		//DHCP_run();
		//printThreadStackInfo(TAG);
	}
}

//------------------------------------------------------------------------------

void wizchip_reset(void)
{
	HAL_GPIO_WritePin(ETH_SPI_PWR_GPIO_Port, ETH_SPI_PWR_Pin, GPIO_PIN_RESET);
	vTaskDelay(pdMS_TO_TICKS(100U));
	HAL_GPIO_WritePin(ETH_SPI_RST_GPIO_Port, ETH_SPI_RST_Pin, GPIO_PIN_SET);
	vTaskDelay(pdMS_TO_TICKS(65U));	  // Min 60.3ms
}

//------------------------------------------------------------------------------

void wizchip_initialize(void)
{
	uint8_t W5100S_AdrSet[2][4]= {{2,2,2,2},{2,2,2,2}};
    uint8_t tmp1, tmp2;
	intr_kind temp= IK_DEST_UNREACH;

	wizchip_reset();

	if (ctlwizchip(CW_INIT_WIZCHIP, (void*)W5100S_AdrSet) == -1)
		printf(">>>>W5100s memory initialization failed\r\n");

	if(ctlwizchip(CW_SET_INTRMASK,&temp) == -1)
		printf("W5100S interrupt\r\n");

	wizchip_check();

	while(1)
	{
		ctlwizchip(CW_GET_PHYLINK, &tmp1 );
		ctlwizchip(CW_GET_PHYLINK, &tmp2 );
		if (tmp1 == PHY_LINK_ON && tmp2 == PHY_LINK_ON) break;
	}
}

//------------------------------------------------------------------------------

static void ip_assigned(void)
{
	printf("IP-address was assigned.\n");

	uint8_t ip[4];
	getIPfromDHCP(ip);
	printf("IP-address: %u.%u.%u.%u\n", ip[0], ip[1], ip[2], ip[3]);

	uint8_t nm[4];
	getSNfromDHCP(nm);
	printf("Subnet mask: %u.%u.%u.%u\n", nm[0], nm[1], nm[2], nm[3]);

	uint8_t gw[4];
	getGWfromDHCP(gw);
	printf("Gateway address: %u.%u.%u.%u\n", gw[0], gw[1], gw[2], gw[3]);

	uint8_t dns[4];
	getDNSfromDHCP(dns);
	printf("DNS address: %u.%u.%u.%u\n", dns[0], dns[1], dns[2], dns[3]);

	printf("Lease time: %u\n", getDHCPLeasetime());
}

//------------------------------------------------------------------------------

static void ip_updated(void)
{
	printf("IP-address was updated.\n");
}

//------------------------------------------------------------------------------

static void ip_conflict(void)
{
	printf("IP-address conflict!.\n");
}

//------------------------------------------------------------------------------

void wizchip_check(void)
{
	// Read version register
    if (getVER() != 0x51) // W5100S
    {
        printf(" ACCESS ERR : VERSIONR != 0x51, read value = 0x%02x\n", getVER());
        while (1);
    }
}

//------------------------------------------------------------------------------

void wizchip_dhcp_init(void)
{
    DHCP_init(SOCKET_DHCP, dhcp_buffer);
    reg_dhcp_cbfunc(ip_assigned, ip_updated, ip_conflict);
}

//------------------------------------------------------------------------------

/* List contents of a directory */

FRESULT list_dir (const TCHAR *path)
{
    FRESULT res;
    DIR dir;
    FILINFO fno;
    int nfile, ndir;


    res = f_opendir(&dir, path);                       /* Open the directory */
    if (res == FR_OK) {
        nfile = ndir = 0;
        for (;;) {
            res = f_readdir(&dir, &fno);                   /* Read a directory item */
            if (res != FR_OK || fno.fname[0] == 0) break;  /* Error or end of dir */
            if (fno.fattrib & AM_DIR) {            /* Directory */
                printf("   <DIR>   %s\n", fno.fname);
                ndir++;
            } else {                               /* File */
                printf("%10u %s\n", fno.fsize, fno.fname);
                nfile++;
            }
        }
        f_closedir(&dir);
        printf("%d dirs, %d files.\n", ndir, nfile);
    } else {
        printf("Failed to open \"%s\". (%u)\n", path, res);
    }
    return res;
}

//------------------------------------------------------------------------------

/* Recursive scan of all items in the directory */

FRESULT scan_files (
    TCHAR* path        /* Start node to be scanned (***also used as work area***) */
)
{
    FRESULT res;
    DIR dir;
    UINT i;
    static FILINFO fno;


    res = f_opendir(&dir, path);                       /* Open the directory */
    if (res == FR_OK) {
        for (;;) {
            res = f_readdir(&dir, &fno);                   /* Read a directory item */
            if (res != FR_OK || fno.fname[0] == 0) break;  /* Break on error or end of dir */
            if (fno.fattrib & AM_DIR) {                    /* It is a directory */
                i = strlen(path);
                sprintf(&path[i], "/%s", fno.fname);
                res = scan_files(path);                    /* Enter the directory */
                if (res != FR_OK) break;
                path[i] = 0;
            } else {                                       /* It is a file. */
                printf("%s/%s\n", path, fno.fname);
            }
        }
        f_closedir(&dir);
    }

    return res;
}