// ******************************************************************************
//
//
//  Version/Date    Comment
//   2026.05.28     - First release
// ******************************************************************************

#include <stdbool.h>
#include <stdio.h>
#include <ustdlib.h>

// TM4C includes
#include "driverlib/flash.h"
#include "driverlib/rom_map.h"

#include <FreeRTOS.h>
#include "task.h"

#ifdef FREERTOS_PLUS_TCP
  #include "FreeRTOS_IP.h"
#endif

#ifdef FREERTOS_PLUS_LWIP
  #include "include/lwiplib.h"
  #include "lwip/netif.h"
#endif

// Task
#include "TaskLocatorServer.h"
#include "TaskWebServer.h"

#ifdef FREERTOS_PLUS_OPCUA
  #include "TaskOpcUaServer.h"
#endif


// LocatorServer task parameters
#define TASKSNETWORKING_TASKLOCATORSERVER_STACK_SIZE     ( configMINIMAL_STACK_SIZE * 2 )
#define TASKSNETWORKING_TASKLOCATORSERVER_PRIORITY       ( TCPIP_THREAD_PRIO - 1 )

// WebServer task parameters
#define TASKSNETWORKING_TASKWEBSERVER_STACK_SIZE         ( configMINIMAL_STACK_SIZE * 2 )
#define TASKSNETWORKING_TASKWEBSERVER_PRIORITY           ( TCPIP_THREAD_PRIO - 1 )

#ifdef FREERTOS_PLUS_OPCUA
  // OpcUaServer task parameters
  // OpcUa requires a large stack
  #define TASKSNETWORKING_TASKOPCUASERVER_STACK_SIZE     ( 4096 )
  #define TASKSNETWORKING_TASKOPCUASERVER_PRIORITY       ( TCPIP_THREAD_PRIO )
#endif


// Ethernet interface
#ifdef FREERTOS_PLUS_TCP
  static const uint8_t GIPAddress       [ 4 ] = { 192, 168, 1, 202};
  static const uint8_t GNetMask         [ 4 ] = { 255, 255, 255, 0};
  static const uint8_t GGatewayAddress  [ 4 ] = {   0,   0,   0, 0};
  static const uint8_t GDNSServerAddress[ 4 ] = {   0,   0,   0, 0};
#endif
#ifdef FREERTOS_PLUS_LWIP
  static const uint32_t GIPAddress        = 0xC0A801CA;
  static const uint32_t GNetMask          = 0xFFFFFF00;
  static const uint32_t GGatewayAddress   = 0x00000000;
#endif

#ifdef FREERTOS_PLUS_TCP
  // There is only 1 physical interface
  static NetworkInterface_t TasksNetworking_Interfaces[ 1 ];

  // It will have several end-points (we only use one though)
  static NetworkEndPoint_t  TasksNetworking_EndPoints[ 4 ];
  #define TCPIP_THREAD_PRIO               3
#endif


#ifdef FREERTOS_PLUS_LWIP
  static bool     TasksNetworking_LastStatus = false;
  static uint32_t TasksNetworking_LastIP     = 0;
#endif



static BaseType_t TasksNetworking_TasksAlreadyCreated = pdFALSE;

void TasksNetworking_SetupTask( void * AParameters );


// ------------------------------------------------------------------------------
//  Descript: Start the networking task
//  Params  : <ASysClock>  System clock setting
//  Returns : -
// ------------------------------------------------------------------------------
void TasksNetworking_StartNetworkingTasks ( uint32_t ui32SysClock )
{
  configPRINTF( ( "Networking task init\n" ) );

  if (xTaskCreate(
      TasksNetworking_SetupTask,       // The function that implements the task
      "TasksNetworkingSetup",          // Just a text name for the task to aid debugging
      configMINIMAL_STACK_SIZE * 2,    // The stack size
      (void *)ui32SysClock,            // The task parameter
      TCPIP_THREAD_PRIO,               // The priority assigned to the task
      NULL ) != pdPASS)
  {
    configPRINTF( ( "Networking task init task failure\n" ) );
    while (1) {}
  }

  // Start the RTOS scheduler
  configPRINTF( ( "vTaskStartScheduler\n" ) );
  vTaskStartScheduler();
  configPRINTF( ( "Should not reach this point after scheduler\n" ) );

  // The following line should never be reached
  for( ; ; ) { }

}


// ------------------------------------------------------------------------------
//  Descript: Start the networking task
//  Params  : <AParameters>  System clock setting
//  Returns : -
// ------------------------------------------------------------------------------
void TasksNetworking_SetupTask( void * AParameters )
{
#ifdef FREERTOS_PLUS_TCP
  BaseType_t LReturn;
#endif
  uint8_t  LMACAddress     [ 6 ];
  uint32_t LMacAddressWords[ 2 ];

  // Initialise the network interface
  configPRINTF( ( "FreeRTOS_IPInit\n" ) );

#ifdef FREERTOS_PLUS_TCP
  /* Initialise the interface descriptor for WinPCap. */
  extern NetworkInterface_t * pxTM4C_FillInterfaceDescriptor( BaseType_t xEMACIndex, NetworkInterface_t * pxInterface );
  pxTM4C_FillInterfaceDescriptor( 0, &( TasksNetworking_Interfaces[ 0 ]) );
#endif

  // Read MAC address from flash
  MAP_FlashUserGet( &LMacAddressWords[ 0 ], &LMacAddressWords[ 1 ] );
  LMACAddress[ 0 ] = ( LMacAddressWords[ 0 ] >>  0 ) & 0xFF;
  LMACAddress[ 1 ] = ( LMacAddressWords[ 0 ] >>  8 ) & 0xFF;
  LMACAddress[ 2 ] = ( LMacAddressWords[ 0 ] >> 16 ) & 0xFF;
  LMACAddress[ 3 ] = ( LMacAddressWords[ 1 ] >>  0 ) & 0xFF;
  LMACAddress[ 4 ] = ( LMacAddressWords[ 1 ] >>  8 ) & 0xFF;
  LMACAddress[ 5 ] = ( LMacAddressWords[ 1 ] >> 16 ) & 0xFF;

#ifdef FREERTOS_PLUS_TCP
  FreeRTOS_FillEndPoint( &( TasksNetworking_Interfaces[ 0 ] ), &( TasksNetworking_EndPoints[ 0 ] ), GIPAddress, GNetMask, GGatewayAddress, GDNSServerAddress, LMACAddress );
  // Initialzia the network stack (vApplicationIPNetworkEventHook_Multi is called when done)
  LReturn = FreeRTOS_IPInit_Multi();
  configASSERT( LReturn == pdTRUE );
#endif

#ifdef FREERTOS_PLUS_LWIP
  lwIPInit( (uint32_t)AParameters, &LMACAddress[0], GIPAddress, GNetMask, GGatewayAddress, IPADDR_USE_STATIC);
#endif


  vTaskDelete(NULL);
}


// ------------------------------------------------------------------------------
//  Descript: Start the individual networking tasks
//  Params  : -
//  Returns : -
// ------------------------------------------------------------------------------
void TasksNetworking_StartTasks()
{
  // Create the tasks that use the IP stack if they have not already been created
  if( TasksNetworking_TasksAlreadyCreated == pdFALSE )
  {
    // Start our tasks
    TaskLocatorServer_StartLocatorTasks( TASKSNETWORKING_TASKLOCATORSERVER_STACK_SIZE, TASKSNETWORKING_TASKLOCATORSERVER_PRIORITY );
    TaskWebServer_StartWebServerTasks  ( TASKSNETWORKING_TASKWEBSERVER_STACK_SIZE    , TASKSNETWORKING_TASKWEBSERVER_PRIORITY     );
#ifdef FREERTOS_PLUS_OPCUA
    TaskOpcUaServer_StartOpcUaTasks    ( TASKSNETWORKING_TASKOPCUASERVER_STACK_SIZE  , TASKSNETWORKING_TASKOPCUASERVER_PRIORITY   );
#endif
    // Only create them once
    TasksNetworking_TasksAlreadyCreated = pdTRUE;
  }
}


#ifdef FREERTOS_PLUS_TCP
  // ------------------------------------------------------------------------------
  //  Descript: Called by FreeRTOS+TCP when the network connects or disconnects
  //            Disconnect events are only received if implemented in the MAC driver
  //  Params  : <ANetworkEvent>  Network event triggering the callback
  //            <AEndPoint<      Endpoint associated with event
  //  Returns : -
  // ------------------------------------------------------------------------------
  void vApplicationIPNetworkEventHook_Multi( eIPCallbackEvent_t ANetworkEvent, struct xNetworkEndPoint * AEndPoint )
  {
    uint32_t LIPAddress;
    uint32_t LNetMask;
    uint32_t LGatewayAddress;
    uint32_t LDNSServerAddress;
    char     LBuffer[ 16 ];

    // If the network has just come up
    if( ANetworkEvent == eNetworkUp )
    {
      configPRINTF( ( "FreeRTOS_Plus_TCP event hook, network is up\n" ) );
      // Print out the network configuration
      FreeRTOS_GetEndPointConfiguration( &LIPAddress, &LNetMask, &LGatewayAddress, &LDNSServerAddress, pxNetworkEndPoints );
      FreeRTOS_inet_ntoa( LIPAddress, LBuffer );
      configPRINTF( ( "IP Address : %s\n", LBuffer ) );

      FreeRTOS_inet_ntoa( LNetMask, LBuffer );
      configPRINTF( ( "Subnet Mask: %s\n", LBuffer ) );

      FreeRTOS_inet_ntoa( LGatewayAddress, LBuffer );
      configPRINTF( ( "Gateway    : %s\n", LBuffer ) );

      FreeRTOS_inet_ntoa( LDNSServerAddress, LBuffer );
      configPRINTF( ( "DNS Server : %s\n", LBuffer ) );

      // The MAC address
      configPRINTF( ( "MAC Address: %02X.%02X.%02X.%02X.%02X.%02X\n",
        AEndPoint->xMACAddress.ucBytes[0],
        AEndPoint->xMACAddress.ucBytes[1],
        AEndPoint->xMACAddress.ucBytes[2],
        AEndPoint->xMACAddress.ucBytes[3],
        AEndPoint->xMACAddress.ucBytes[4],
        AEndPoint->xMACAddress.ucBytes[5]) );

      configPRINTF( ( "FreeRTOS_Plus_TCP event hook, starting network tasks\n" ) );
      TasksNetworking_StartTasks();
    }
    else
      configPRINTF( ( "FreeRTOS_Plus_TCP event hook, network is down\n" ) );
  }
#endif


#ifdef FREERTOS_PLUS_TCP
  // Callback that provides the inputs necessary to generate a randomized TCP Initial Sequence Number per RFC 6528
  uint32_t ulApplicationGetNextSequenceNumber( uint32_t ASourceAddress,
                                               uint16_t ASourcePort,
                                               uint32_t ADestinationAddress,
                                               uint16_t ADestinationPort )
  {
    ( void ) ASourceAddress;
    ( void ) ASourcePort;
    ( void ) ADestinationAddress;
    ( void ) ADestinationPort;

    return urand();
  }
#endif


#ifdef FREERTOS_PLUS_TCP
  // Supply a random number to FreeRTOS+TCP stack
  BaseType_t xApplicationGetRandomNumber( uint32_t * ANumber )
  {
    *( ANumber ) = urand();
    return pdTRUE;
  }
#endif


#ifdef FREERTOS_PLUS_LWIP

  void lwIPHostTimerHandler( void ) {
    // Use pointer to the one and only interface
    // Note that this is not actually true, since we should also have a 127.0.0.1 interface
    struct netif *LNif = netif_default;

    // Check for a valid interface
    if (LNif == NULL) {
      return;
    }

    // Check link status
    bool     LCurrentStatus = netif_is_up(LNif) && netif_is_link_up(LNif);
    uint32_t LCurrentIP     = LNif->ip_addr.addr;

    // If chanaged
    if (LCurrentStatus != TasksNetworking_LastStatus || LCurrentIP != TasksNetworking_LastIP) {
      if (LCurrentStatus) {
        char LStr  [ 16 ];
        configPRINTF( ( "LwIp timer hook\n" ) );

        // Check if the loopback interface was actually created
        struct netif *LI;
        for (LI = netif_list; LI != NULL; LI = LI->next)
            configPRINTF( ("Interface  : %c%c%d is %s\n", LI->name[0], LI->name[1], LI->num, netif_is_up(LI) ? "UP" : "DOWN") );

        strncpy(LStr, ipaddr_ntoa(&(LNif->ip_addr)), sizeof(LStr));
        configPRINTF( ( "IP Address : %s\n", LStr  ) );
        strncpy(LStr, ipaddr_ntoa(&(LNif->netmask)), sizeof(LStr));
        configPRINTF( ( "Subnet Mask: %s\n", LStr) );
        strncpy(LStr, ipaddr_ntoa(&(LNif->gw     )), sizeof(LStr));
        configPRINTF( ( "Gateway    : %s\n", LStr  ) );
        configPRINTF( ( "MAC Address: %02X.%02X.%02X.%02X.%02X.%02X\n",
          LNif->hwaddr[0],
          LNif->hwaddr[1],
          LNif->hwaddr[2],
          LNif->hwaddr[3],
          LNif->hwaddr[4],
          LNif->hwaddr[5]) );

        configPRINTF( ( "LwIp timer hook, starting tasks\n" ) );
        TasksNetworking_StartTasks();

      } else {
        configPRINTF( ( "LwIp timer hook, network is down\n" ) );
      }

      TasksNetworking_LastStatus = LCurrentStatus;
      TasksNetworking_LastIP  = LCurrentIP;
    }
  }
#endif