#include "FreeRTOS.h"
#include "task.h"
#include "gd32f107c_eval.h"
#include "gd32f10x_gpio.h"
#include "stdio.h"
/* System application includes. */
#include "FreeRTOSIPConfig.h"

/* Demo application includes. */
#include "FreeRTOS_IP.h"
#include "FreeRTOS_Sockets.h"
#include "SimpleUDPClientAndServer.h"
#include "SimpleTCPEchoServer.h"
#include "TCPEchoClient_SingleTasks.h"

#define BUTTON_USER GPIO_PIN_14

#define LED2_USER_PORT GPIOC
#define LED5_TICK_PORT GPIOE

#define LED2_USER GPIO_PIN_0
#define LED5_TICK GPIO_PIN_1

#define TASK_HELLO_WORLD_DELAY 500
#define TASK_TOGGLE_LED_DELAY 125

#define TEST_RUNNER_TASK_STACK_SIZE    512

#define mainCREATE_SIMPLE_UDP_CLIENT_SERVER_TASKS     1
#define mainCREATE_TCP_ECHO_TASKS_SINGLE              0
#define mainCREATE_TCP_ECHO_SERVER_TASK               0

/* Simple UDP client and server task parameters. */
#define mainSIMPLE_UDP_CLIENT_SERVER_TASK_PRIORITY    ( tskIDLE_PRIORITY )
#define mainSIMPLE_UDP_CLIENT_SERVER_PORT             ( 5005UL )

/* Echo client task parameters - used for both TCP and UDP echo clients. */
#define mainECHO_CLIENT_TASK_STACK_SIZE               ( configMINIMAL_STACK_SIZE * 2 )      /* Not used in the Windows port. */
#define mainECHO_CLIENT_TASK_PRIORITY                 ( tskIDLE_PRIORITY + 1 )

/* Echo server task parameters. */
#define mainECHO_SERVER_TASK_STACK_SIZE               ( configMINIMAL_STACK_SIZE * 2 )      /* Not used in the Windows port. */
#define mainECHO_SERVER_TASK_PRIORITY                 ( tskIDLE_PRIORITY + 1 )

/* Define a name that will be used for LLMNR and NBNS searches. */
//#define mainHOST_NAME                                 "RTOSDemo"
//#define mainDEVICE_NICK_NAME                          "windows_demo"

/* Default MAC address configuration.  The demo creates a virtual network
 * connection that uses this MAC address by accessing the raw Ethernet data
 * to and from a real network connection on the host PC.  See the
 * configNETWORK_INTERFACE_TO_USE definition for information on how to configure
 * the real network connection to use. */
const uint8_t ucMACAddress[ 6 ] =
{
    configMAC_ADDR0,
    configMAC_ADDR1,
    configMAC_ADDR2,
    configMAC_ADDR3,
    configMAC_ADDR4,
    configMAC_ADDR5
};

/* The default IP and MAC address used by the demo.  The address configuration
 * defined here will be used if ipconfigUSE_DHCP is 0, or if ipconfigUSE_DHCP is
 * 1 but a DHCP server could not be contacted.  See the online documentation for
 * more information.  In both cases the node can be discovered using
 * "ping RTOSDemo". */
static const uint8_t ucIPAddress[ 4 ] =
{
    configIP_ADDR0,
    configIP_ADDR1,
    configIP_ADDR2,
    configIP_ADDR3
};
static const uint8_t ucNetMask[ 4 ] =
{
    configNET_MASK0,
    configNET_MASK1,
    configNET_MASK2,
    configNET_MASK3
};
static const uint8_t ucGatewayAddress[ 4 ] =
{
    configGATEWAY_ADDR0,
    configGATEWAY_ADDR1,
    configGATEWAY_ADDR2,
    configGATEWAY_ADDR3
};
static const uint8_t ucDNSServerAddress[ 4 ] =
{
    configDNS_SERVER_ADDR0,
    configDNS_SERVER_ADDR1,
    configDNS_SERVER_ADDR2,
    configDNS_SERVER_ADDR3
};

/* Use by the pseudo random number generator. */
static UBaseType_t ulNextRand;

/*
 * Just seeds the simple pseudo random number generator.
 */
static void prvSRand( UBaseType_t ulSeed );

/*
 * Miscellaneous initialisation including preparing the logging and seeding the
 * random number generator.
 */
static void prvMiscInitialisation( void );

void vTaskHelloWorld( void *pvParameters);
void vTaskToggleLed( void *pvParameters);
int stdout_putchar (int ch);
int stdin_getchar (void);
void DelayMS(unsigned char ms);

int stdout_putchar (int ch)
{
	usart_data_transmit(EVAL_COM1, (uint16_t)ch);	
	while (!usart_flag_get(EVAL_COM1, USART_FLAG_TBE)){}
	
	return ch;
}
int stdin_getchar (void)
{	
	return usart_data_receive(EVAL_COM1);
}

/**! \brief vInitMCU
*		Initial MCU configuration
*/
static void vInitMCU(void)
{
	SystemInit();	
	//gd_eval_led_init(LED2);
	//gd_eval_led_on(LED2);
	rcu_periph_clock_enable(RCU_GPIOC);
	
	gpio_init(LED2_USER_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED2_USER);		
	gpio_bit_set(LED2_USER_PORT, LED2_USER);
	
	rcu_periph_clock_enable(RCU_GPIOE);
	gpio_init(LED5_TICK_PORT, GPIO_MODE_OUT_PP, GPIO_OSPEED_2MHZ, LED5_TICK);		
	gpio_bit_set(LED5_TICK_PORT, LED5_TICK);
	
	gd_eval_com_init(EVAL_COM1);
	rcu_periph_clock_enable(RCU_GPIOB);
	gpio_init(GPIOB, GPIO_MODE_IPU, GPIO_OSPEED_2MHZ, BUTTON_USER);
}

/**! \brief vTaskHelloWorld procedure
 *         
 *  \param Not Used.
 */
void vTaskHelloWorld( void *pvParameters)
{
	char ButtonState = 0;
	for( ;; )	
	{
			ButtonState = !gpio_input_bit_get(GPIOB, BUTTON_USER);
			if (ButtonState)
			{				
				printf("Hello world\n");	
				fflush( stdout );				
				vTaskDelay(TASK_HELLO_WORLD_DELAY);
			}
	}
}	
/**! \brief vTaskToggleLed procedure
 *         
 *  \param Not Used.
 */
void vTaskToggleLed( void *pvParameters)
{	
	char toggle = 1;
	for( ;; )	
	{			
			if (toggle)
			{
				gpio_bit_reset(LED2_USER_PORT, LED2_USER);				
			}
			else
			{
				gpio_bit_set(LED2_USER_PORT, LED2_USER);				
			}	
			toggle = !toggle;
			vTaskDelay(TASK_TOGGLE_LED_DELAY);			
	}
}	

static void prvMiscInitialisation( void )
{
//    time_t xTimeNow;
    uint32_t ulRandomNumbers[ 4 ];

       /* Seed the random number generator. */
 //   time( &xTimeNow );
    FreeRTOS_debug_printf( ( "Seed for randomiser: %lu\r\n", xTimeNow ) );
 //   prvSRand( ( uint32_t ) xTimeNow );

    ( void ) xApplicationGetRandomNumber( &ulRandomNumbers[ 0 ] );
    ( void ) xApplicationGetRandomNumber( &ulRandomNumbers[ 1 ] );
    ( void ) xApplicationGetRandomNumber( &ulRandomNumbers[ 2 ] );
    ( void ) xApplicationGetRandomNumber( &ulRandomNumbers[ 3 ] );
    FreeRTOS_debug_printf( ( "Random numbers: %08X %08X %08X %08X\r\n",
                             ulRandomNumbers[ 0 ],
                             ulRandomNumbers[ 1 ],
                             ulRandomNumbers[ 2 ],
                             ulRandomNumbers[ 3 ] ) );
}

int main(void)
{	
	vInitMCU(); 		
	/* Miscellaneous initialisation including preparing the logging and seeding
     * the random number generator. */
  prvMiscInitialisation();
	xTaskCreate( vTaskToggleLed,  "ToggleLed",  configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
	xTaskCreate( vTaskHelloWorld, "HelloWorld", configMINIMAL_STACK_SIZE, NULL, tskIDLE_PRIORITY+1, NULL);
	FreeRTOS_IPInit( ucIPAddress, ucNetMask, ucGatewayAddress, ucDNSServerAddress, ucMACAddress );
	vStartSimpleTCPServerTasks( configMINIMAL_STACK_SIZE*2, tskIDLE_PRIORITY+1 );
	vTaskStartScheduler();
	while(1);
}

/**
 * @brief vApplicationIPNetworkEventHook( eIPCallbackEvent_t eNetworkEvent )
 */
/* Called by FreeRTOS+TCP when the network connects or disconnects.  Disconnect
 * events are only received if implemented in the MAC driver. */
void vApplicationIPNetworkEventHook( eIPCallbackEvent_t eNetworkEvent )
{
    uint32_t ulIPAddress, ulNetMask, ulGatewayAddress, ulDNSServerAddress;
    char cBuffer[ 16 ];
    static BaseType_t xTasksAlreadyCreated = pdFALSE;

    /* If the network has just come up...*/
    if( eNetworkEvent == eNetworkUp )
    {
        /* Create the tasks that use the IP stack if they have not already been
         * created. */
        if( xTasksAlreadyCreated == pdFALSE )
        {
            /* See the comments above the definitions of these pre-processor
             * macros at the top of this file for a description of the individual
             * demo tasks. */
            #if ( mainCREATE_SIMPLE_UDP_CLIENT_SERVER_TASKS == 1 )
                {
                    vStartSimpleUDPClientServerTasks( configMINIMAL_STACK_SIZE, mainSIMPLE_UDP_CLIENT_SERVER_PORT, mainSIMPLE_UDP_CLIENT_SERVER_TASK_PRIORITY );
                }
            #endif /* mainCREATE_SIMPLE_UDP_CLIENT_SERVER_TASKS */

            #if ( mainCREATE_TCP_ECHO_TASKS_SINGLE == 1 )
                {
                    vStartTCPEchoClientTasks_SingleTasks( mainECHO_CLIENT_TASK_STACK_SIZE, mainECHO_CLIENT_TASK_PRIORITY );
                }
            #endif /* mainCREATE_TCP_ECHO_TASKS_SINGLE */

            #if ( mainCREATE_TCP_ECHO_SERVER_TASK == 1 )
                {
                    vStartSimpleTCPServerTasks( mainECHO_SERVER_TASK_STACK_SIZE, mainECHO_SERVER_TASK_PRIORITY );
                }
            #endif

            xTasksAlreadyCreated = pdTRUE;
        }

        /* Print out the network configuration, which may have come from a DHCP
         * server. */
        FreeRTOS_GetAddressConfiguration( &ulIPAddress, &ulNetMask, &ulGatewayAddress, &ulDNSServerAddress );
        FreeRTOS_inet_ntoa( ulIPAddress, cBuffer );
        FreeRTOS_printf( ( "\r\n\r\nIP Address: %s\r\n", cBuffer ) );

        FreeRTOS_inet_ntoa( ulNetMask, cBuffer );
        FreeRTOS_printf( ( "Subnet Mask: %s\r\n", cBuffer ) );

        FreeRTOS_inet_ntoa( ulGatewayAddress, cBuffer );
        FreeRTOS_printf( ( "Gateway Address: %s\r\n", cBuffer ) );

        FreeRTOS_inet_ntoa( ulDNSServerAddress, cBuffer );
        FreeRTOS_printf( ( "DNS Server Address: %s\r\n\r\n\r\n", cBuffer ) );
    }
}

static void prvSRand( UBaseType_t ulSeed )
{
    /* Utility function to seed the pseudo random number generator. */
    ulNextRand = ulSeed;
}



UBaseType_t uxRand( void )
{		
		static UBaseType_t ulNextRand;
    const uint32_t ulMultiplier = 0x015a4e35UL, ulIncrement = 1UL;

    /* Utility function to generate a pseudo random number. */

    ulNextRand = ( ulMultiplier * ulNextRand ) + ulIncrement;
    return( ( int ) ( ulNextRand ) & 0x7fffUL );
}

BaseType_t xApplicationGetRandomNumber( uint32_t * pulNumber )
{
		BaseType_t xReturn; /* Return pdTRUE if successful */
#ifdef RANDOM_NUMBER		
    CK_RV xResult = 0;
    SemaphoreHandle_t xSessionLock = NULL;
    CK_SESSION_HANDLE xPkcs11Session = 0;
    CK_FUNCTION_LIST_PTR pxPkcs11FunctionList = NULL;
    uint32_t ulRandomValue = 0;
   

    xResult = prvSocketsGetCryptoSession( &xSessionLock,
                                          &xPkcs11Session,
                                          &pxPkcs11FunctionList );

    if( 0 == xResult )
    {
        /* Request a sequence of cryptographically random byte values using
         * PKCS#11. */
        xResult = pxPkcs11FunctionList->C_GenerateRandom( xPkcs11Session,
                                                          ( CK_BYTE_PTR ) &ulRandomValue,
                                                          sizeof( ulRandomValue ) );
    }

    /* Check if any of the API calls failed. */
    if( 0 == xResult )
    {
        xReturn = pdTRUE;
        *( pulNumber ) = ulRandomValue;
    }
    else
    {
        xReturn = pdFALSE;
        *( pulNumber ) = 0uL;
    }		
    return xReturn;
}
#else
    *pulNumber = uxRand();
		return pdTRUE;
#endif    
}


/**
 * @brief Generate a TCP Initial Sequence Number that is reasonably difficult
 * to predict, per https://tools.ietf.org/html/rfc6528. 
 */
extern uint32_t ulApplicationGetNextSequenceNumber( uint32_t ulSourceAddress,
                                                    uint16_t usSourcePort,
                                                    uint32_t ulDestinationAddress,
                                                    uint16_t usDestinationPort )
{
    ( void ) ulSourceAddress;
    ( void ) usSourcePort;
    ( void ) ulDestinationAddress;
    ( void ) usDestinationPort;
#ifdef RANDOM_NUMBER
	  CK_RV xResult = CKR_OK;
    SemaphoreHandle_t xSessionLock = NULL;
    CK_SESSION_HANDLE xPkcs11Session = 0;
    CK_FUNCTION_LIST_PTR pxPkcs11FunctionList = NULL;
    CK_MECHANISM xMechSha256 = { 0 };
    uint8_t ucSha256Result[ cryptoSHA256_DIGEST_BYTES ];
    CK_ULONG ulLength = sizeof( ucSha256Result );
    uint32_t ulNextSequenceNumber = 0;
    static uint64_t ullKey;
    static CK_BBOOL xKeyIsInitialized = CK_FALSE;
		
    /* Acquire a crypto session handle. */
    xResult = prvSocketsGetCryptoSession( &xSessionLock,
                                          &xPkcs11Session,
                                          &pxPkcs11FunctionList );

    if( CKR_OK == xResult )
    {
        xSemaphoreTake( xSessionLock, portMAX_DELAY );

        if( CK_FALSE == xKeyIsInitialized )
        {
            /* One-time initialization, per boot, of the random seed. */
            xResult = pxPkcs11FunctionList->C_GenerateRandom( xPkcs11Session,
                                                              ( CK_BYTE_PTR ) &ullKey,
                                                              sizeof( ullKey ) );

            if( xResult == CKR_OK )
            {
                xKeyIsInitialized = CK_TRUE;
            }
        }

        xSemaphoreGive( xSessionLock );
    }

    /* Lock the shared crypto session. */
    xSemaphoreTake( xSessionLock, portMAX_DELAY );

    /* Start a hash. */
    if( CKR_OK == xResult )
    {
        xMechSha256.mechanism = CKM_SHA256;
        xResult = pxPkcs11FunctionList->C_DigestInit( xPkcs11Session, &xMechSha256 );
    }

    /* Hash the seed. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ullKey,
                                                        sizeof( ullKey ) );
    }

    /* Hash the source address. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ulSourceAddress,
                                                        sizeof( ulSourceAddress ) );
    }

    /* Hash the source port. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &usSourcePort,
                                                        sizeof( usSourcePort ) );
    }

    /* Hash the destination address. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &ulDestinationAddress,
                                                        sizeof( ulDestinationAddress ) );
    }

    /* Hash the destination port. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestUpdate( xPkcs11Session,
                                                        ( CK_BYTE_PTR ) &usDestinationPort,
                                                        sizeof( usDestinationPort ) );
    }

    /* Get the hash. */
    if( CKR_OK == xResult )
    {
        xResult = pxPkcs11FunctionList->C_DigestFinal( xPkcs11Session,
                                                       ucSha256Result,
                                                       &ulLength );
    }

    xSemaphoreGive( xSessionLock );

    /* Use the first four bytes of the hash result as the starting point for
     * all initial sequence numbers for connections based on the input 4-tuple. */
    if( CKR_OK == xResult )
    {
        memcpy( &ulNextSequenceNumber,
                ucSha256Result,
                sizeof( ulNextSequenceNumber ) );

        /* Add the tick count of four-tick intervals. In theory, per the RFC
         * (see above), this approach still allows server equipment to optimize
         * handling of connections from the same device that haven't fully timed out. */
        ulNextSequenceNumber += xTaskGetTickCount() / 4;
    }		
    return ulNextSequenceNumber;
#else
		/* THIS IS ONLY A DUMMY IMPLEMENTATION
		* THAT RETURNS A PSEUDO RANDOM NUMBER SO IS NOT INTENDED FOR USE IN PRODUCTION
		* SYSTEMS. */
		return uxRand();
#endif	    
}


/**! \func	 void vApplicationMallocFailedHook( void )
 * \brief This hook function is called when allocation failed.
 */
void vApplicationMallocFailedHook( void )
{
#if defined (DEBUG)
		printf("Malloc Failed Hook\n");
		__ASM("BKPT #0\n");
#endif
		NVIC_SystemReset();
		while(TRUE){}
}

	
/**! \func	 vApplicationTickHook( void )
 *  \brief Toggles LED5_TICK to indicate RTOS running well         
 */
void vApplicationTickHook( void )
{
	static uint16_t tickCounter = 0;	
	if(++tickCounter == configTICK_RATE_HZ)	
	{
		gpio_bit_reset(LED5_TICK_PORT, LED5_TICK);	
		tickCounter = 0;		
	}
	else if(tickCounter == configTICK_RATE_HZ/2)
	{
		gpio_bit_set(LED5_TICK_PORT, LED5_TICK);						
	}	
}

/**! \func vApplicationIdleHook( void )
 *  \brief Idle task Hook
 */
void vApplicationIdleHook( void )
{	
#if defined (DEBUG)
		int32_t delta = (configTICK_RATE_HZ-(TASK_TOGGLE_LED_DELAY + TASK_HELLO_WORLD_DELAY)) % configTICK_RATE_HZ;
		if (delta < 0) delta = -delta;		
									 
		static uint32_t idleHookCounter = 0;
		uint32_t currentTick = xTaskGetTickCount();		
		if(currentTick > idleHookCounter + delta)
		{			
			printf("Idle Hook %d\n", idleHookCounter);//__ASM("BKPT #0\n");				
		}
		idleHookCounter = currentTick;
#endif		
}	

/**! \func	vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
 *  \brief	Stack overflow Hook
 */
void vApplicationStackOverflowHook( TaskHandle_t pxTask, char *pcTaskName )
{	
#if defined (DEBUG)
		printf("Stack Overflow Hook\n");
		__ASM("BKPT #0\n");
#endif
		NVIC_SystemReset();
		while(TRUE){}	
}


__attribute__((used)) void prvGetRegistersFromStack( uint32_t *pulFaultStackAddress ) 
{
/* These are volatile to try and prevent the compiler/linker optimising them
away as the variables never actually get used.  If the debugger won't show the
values of the variables, make them global my moving their declaration outside
of this function. */
static volatile uint32_t r0;
static volatile uint32_t r1;
static volatile uint32_t r2;
static volatile uint32_t r3;
static volatile uint32_t r12;
static volatile uint32_t lr; /* Link register. */
static volatile uint32_t pc; /* Program counter. */
static volatile uint32_t psr;/* Program status register. */

    r0 = pulFaultStackAddress[ 0 ];
    r1 = pulFaultStackAddress[ 1 ];
    r2 = pulFaultStackAddress[ 2 ];
    r3 = pulFaultStackAddress[ 3 ];

    r12 = pulFaultStackAddress[ 4 ];
    lr = pulFaultStackAddress[ 5 ];
    pc = pulFaultStackAddress[ 6 ];
    psr = pulFaultStackAddress[ 7 ];
#ifdef DEBUG		
		printf("Hard Fault Hook\n");
		__asm  volatile("BKPT #0\n") ;
#endif
		NVIC_SystemReset();
    /* When the following line is hit, the variables contain the register values. */
    for( ;; );
}

void DelayMS(unsigned char ms)
{
unsigned long us = 1000*ms;

while (us--)
  {
  __NOP();
  }
}

