/*******************************************************************************
 * (c) Copyright 2015 Microsemi Corporation.  All rights reserved.
 *
 *
 *
 * SVN $Revision: $
 * SVN $Date: $
 */

#include <api_oam_sdk/common/constants/inc/mr_constants.h>
#include <api_oam_sdk/metroam_hal/inc/mr_hal.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>

#include "osal.h"
#include "network_settings.h"
//VR#include "mss_ethernet_mac.h"
#include "core_tse.h"


#ifdef USE_LWIP
#	include "gateway_2port_eth.h"

#	include "lwip/tcpip.h"
#	include "lwip/dhcp.h"
#	include "lwip/netif.h"
#endif


#include "syslog.h"

/******************************************************************************
 * External Parameters
 *****************************************************************************/
extern mrDeviceInfo_t	mrDeviceInfo;
extern uint8_t			netIfNum[PHY_COUNT];
/******************************************************************************
 * Global Parameters
 *****************************************************************************/
#ifdef USE_LWIP
struct netif 		g_EMAC_if[PHY_COUNT];
#endif
uint8_t				netIfNum[PHY_COUNT] = {1,2,0};

#if (LWIP_NETIF_HOSTNAME | ENABLE_NETBIOS)
	// only required if lwip support for HOSTNAME is enabled OR if NETBOIS is enabled
	char netHostNames[PHY_COUNT][20];
#endif


/*
 * Returns true if the selected interface is enabled for management
 */
boolean_t is_interface_active(uint8_t ifNum)
{
	return(mrDeviceInfo.mrNetIfEnabled[ifNum]);
}


/*
 * This function tries to read the network settings from Non Volatile Memory (NVM)
 * and copy the values into the supplied storage array. There can be PHY_COUNT
 * network interfaces so the supplied array must have that number of elements.
 * If the there is no network settings in NVM or the NVM data is corrupt then
 * default values are returned and a the NVM updated with the defaults.
 */
int verifyNvmNetworkSettings(networkConfig_t *networkConfigList)
{
	uint8_t 		k;
	int				retVal;
	networkConfig_t *networkConfig;

	// Ensure that the network settings are stored in flash
	for (k = 0; k < PHY_COUNT; k++)
	{
		networkConfig = &networkConfigList[k];
		//Not reading from external flash
		//VRretVal = get_board_network_settings(k,networkConfig);
		//VRif (retVal != 0)
		if(1)
		{
			// The assignment is:
			//		Mgnt = 0		: Enabled by default : 00-A0-69-0C-24-B2
			//		Port0 (WAN) = 1	: Enabled by default : 00-A0-69-0C-24-B1
			//		Port1 (LAN) = 2	: Disabled by default : 00-A0-69-0C-24-B0
			//if (networkConfig->signature != 0x5AFE)
			//{
				networkConfig->hostName[0] = 'E';
				networkConfig->hostName[1] = 'A';
				networkConfig->hostName[2] = 'G';
				networkConfig->hostName[3] = 'L';
				networkConfig->hostName[4] = 'E';
				networkConfig->hostName[5] = '0';
				networkConfig->hostName[6] = 0;
				networkConfig->macAddr[0] = 0x00;
				networkConfig->macAddr[1] = 0xA0;
				networkConfig->macAddr[2] = 0x69;
				networkConfig->macAddr[3] = 0x0C;
				networkConfig->macAddr[4] = 0x24;
				if (k == 0)
				{
					// Mgnt Port:
					networkConfig->hostName[5] = '2';
					networkConfig->macAddr[5] = 0xB3;
					networkConfig->options = MR_NET_SET_NET_EN_MASK;		// MR_NET_SET_DHCP_MASK, MR_NET_SET_NET_EN_MASK enable DHCP = 1, disable DHCP = 0
				}
				else if (k == 1)
				{
					// (WAN) Port0:
					networkConfig->hostName[5] = '0';
					networkConfig->macAddr[5] = 0xB0;
					networkConfig->options = MR_NET_SET_NET_EN_MASK;		// MR_NET_SET_DHCP_MASK, MR_NET_SET_NET_EN_MASK enable DHCP = 1, disable DHCP = 0
				}
				else
				{
					// (LAN) Port1:
					networkConfig->hostName[5] = '1';
					networkConfig->macAddr[5] = 0xB1;
					networkConfig->options = MR_NET_SET_NET_EN_MASK;//0;								// MR_NET_SET_DHCP_MASK, MR_NET_SET_NET_EN_MASK enable DHCP = 1, disable DHCP = 0
				}

				IP4_ADDR_TO_UINT32_T(networkConfig->ipAddr, 192, 168, k, 234);
				IP4_ADDR_TO_UINT32_T(networkConfig->ipGate, 192, 168, k, 1);
				IP4_ADDR_TO_UINT32_T(networkConfig->ipMask, 255, 255, 255, 0);

				/*retVal = store_network_settings(k,networkConfig);
				if (retVal == -1)
				{
					printf("Error: Failed to save Network #%d settings to flash\n",k);
				}*/
				//VR Not using external SPI Flash

			//}
		}
		else
		{
			printf("Read Network #%d Settings from Flash\n",k);
		}
		mrDeviceInfo.mrNetIfEnabled[k] = (networkConfig->options & MR_NET_SET_NET_EN_MASK) ? 1:0;
#if (ENABLE_NETBIOS | LWIP_NETIF_HOSTNAME)
		// When NETBIOS is enabled load the HostNames from NVM
		memcpy(&netHostNames[k],networkConfig->hostName,sizeof(netHostNames[k]));
#endif
	}

	return(retVal);
}

/*
 * This function must only be called by tcpip_callback. It will
 * dynamically reconfigure the selected network interface.
 */
#ifdef USE_LWIP
static void reconfigureLwIpIF(void *param) {
	struct netif	*net_if = (struct netif *)param;
	networkConfig_t networkConfig;

	// Read the latest settings. Exit this function if there are any errors reading the settings
	//VRif (get_board_network_settings(net_if->num,&networkConfig))
		//Not reading from external flash
		return;

	if ((networkConfig.options & MR_NET_SET_NET_EN_MASK) == MR_NET_SET_NET_EN_MASK)
	{
		// network interface should be Enabled

		// in case it was running stop DHCP
		dhcp_stop(net_if);

		if (net_if->flags & NETIF_FLAG_UP)
			netif_set_down(net_if);				// set the interface as Down

		// restart the interface in DHCP or Fixed mode with the new settings
		if ((networkConfig.options & MR_NET_SET_DHCP_MASK) == MR_NET_SET_DHCP_MASK)
		{
			// Use DHCP
			net_if->gw.addr = 0;
			net_if->ip_addr.addr = 0;
			dhcp_start(net_if);
		}
		else
		{
			// Use Fixed IP address
			netif_set_addr(net_if,(ip_addr_t *)&networkConfig.ipAddr,(ip_addr_t *)&networkConfig.ipMask,(ip_addr_t *)&networkConfig.ipGate);
			netif_set_up(net_if);
		}
	}
	else
	{
		// network interface should be Disabled
		// To prevent a user accidentally disabling all ports....
#if SFP == 1
		// Do not allow disabling of the WAN port in the SFP!
		if (net_if->num != 1)
		{
#else
		// Do not allow disabling of the MGNT port in the NID!
		if (net_if->num != 0)
		{
#endif
			netif_set_down(net_if);
		}
	}
}
#endif

/*
 * This function can be called to reconfigure the selected network interface
 * after the network settings for that interface have been changes by the
 * user.
 */
void reconfigNetIf(uint8_t ifNum)
{
#ifdef USE_LWIP
	tcpip_callback(reconfigureLwIpIF, &g_EMAC_if[ifNum]);
#endif
}

/*
 * Use this function to print an IP address
 */
const char* printfIPAddr(uint32_t ip, char* buf)
{
	uint8_t bip[4];
	uint32_t idx = 0;

	for (; idx < sizeof(bip); idx++) {
		bip[idx] = ip & 0xFF;
		ip >>= 8;
	}

	snprintf(buf, 17, "%d.%d.%d.%d", bip[0], bip[1], bip[2], bip[3]);

	return buf;
}

/*
 * This function is called whenever the interface goes up or down
 * i.e. when the DHCP subsystem acquires an IP address.
 */
#ifdef USE_LWIP
static void StatusCallback(struct netif* netif)
{
	char buf[20];

	//if ((netif->flags & (NETIF_FLAG_LINK_UP | NETIF_FLAG_UP)) == (NETIF_FLAG_LINK_UP | NETIF_FLAG_UP))
	if ((netif->flags & (NETIF_FLAG_UP)) == (NETIF_FLAG_UP))
	{
		// get IP and stuff
#ifdef DEBUG_PRINT_L2
		ts_printf("[ethernet] Acquired IP address for client interface: %c%c\n", netif->name[0],netif->name[1]);

		ts_printf("[ethernet] IP address : %s\n", printfIPAddr(netif->ip_addr.addr,buf));
		ts_printf("[ethernet] Subnet     : %s\n", printfIPAddr(netif->netmask.addr,buf));
		ts_printf("[ethernet] Gateway    : %s\n", printfIPAddr(netif->gw.addr,buf));
#endif
	}
}
#endif

/*
 * This function is called whenever the link goes up or down
 */
#ifdef USE_LWIP
static void LinkCallback(struct netif* netif)
{

	ts_printf("[ethernet] %c%c Link %s\n",netif->name[0],netif->name[1],((netif->flags & NETIF_FLAG_LINK_UP) ? "up":"down"));

	// if the link goes down and DHCP is enabled on the interface then clear the IP address
	if (((netif->flags & NETIF_FLAG_LINK_UP) != NETIF_FLAG_LINK_UP) && ((netif->flags & NETIF_FLAG_DHCP) == NETIF_FLAG_DHCP))
	{
		netif->gw.addr = 0;
		netif->ip_addr.addr = 0;
	}
}
#endif

#ifdef USE_LWIP
/*
 * This function must only be called by tcpip_callback, because it must run in
 * the LWIP tcpip thread context.
 * It should be called whenever the Physical link is detected as down
 */
static void do_netif_set_link_down(void *netif)
{
	struct netif *net_if = netif;

	netif_set_link_down(netif);
	if (net_if->dhcp != NULL)
	{
		// LWIP doesn't seem to do this but if the interface is not also set down then
		// the LinkCallback is not called when the link comes back up!
		netif_set_down(netif);
	}
}

/*
 * This function must only be called by tcpip_callback, because it must run in
 * the LWIP tcpip thread context.
 * It should be called whenever the Physical link is detected as up
 */
static void do_netif_set_link_up(void *netif)
{
	struct netif *net_if = netif;

	// If the link is up and DHCP was previously enabled then clear the IP addresses
	// on the interface before letting LWIP know the link is up.
	// LWIP doesn't seem to do this but DHCP seems to need it when it runs!
	if (net_if->dhcp != NULL)
	{
		net_if->gw.addr = 0;
		net_if->ip_addr.addr = 0;
		dhcp_start(net_if);
	}
	netif_set_link_up(netif);
}
#endif

#ifdef USE_LWIP
struct static_ip_params
{
	struct netif 	*netif;
	uint32_t		ip_addr;
	uint32_t		sn_mask;
	uint32_t		gw_addr;
};
#endif

/*
 * This function must only be called by tcpip_callback, because it must run in
 * the LWIP tcpip thread context.
 * This function is called ~10 seconds after the link comes up. It checks to
 * see if DHCP was enabled on the interface but no IP was yet assigned. If
 * that is the case then DHCP is disabled and the interface reverts to a
 * fixed IP.
 */
static void revert_to_static_ip(void *param) {
#ifdef USE_LWIP
	struct static_ip_params *ip_params = (struct static_ip_params *)param;

	if ((ip_params->netif->flags & NETIF_FLAG_DHCP ) && (ip_params->netif->ip_addr.addr == 0))
	{
		dhcp_stop(ip_params->netif);
		netif_set_down(ip_params->netif);
		netif_set_addr(ip_params->netif,(ip_addr_t *)&ip_params->ip_addr,(ip_addr_t *)&ip_params->sn_mask,(ip_addr_t *)&ip_params->gw_addr);
		netif_set_up(ip_params->netif);
		dhcp_inform(ip_params->netif);
	}
#endif
}

/*
 * This functions is intended to be called as part of a periodic polling loop
 * to monitor the status of all the Ethernet links. If a link is detected a
 * having gone up or down the lwip Stack is notified accordingly.
 */
void netMonitorLinkStatus(uint32_t *lastStatus, networkConfig_t *networkConfig)
{
#ifdef USE_LWIP
	struct static_ip_params	dhcp_timeout_param[PHY_COUNT];
	uint8_t					dhcp_timeout[PHY_COUNT] = {1};
	//VRMAC_speed_t 			speed;
	// not used
	uint8_t 				fullduplex;
	uint8_t					k;
	uint32_t				phyStatus;

	// Monitor the link status of all Ethernet links
	for (k=0;k<PHY_COUNT;k++)
	{
		if (is_interface_active(netIfNum[k]))
		{

			if (k==0)
			{
				mrHalRamRead(0x81, CARRMAC_SEL,&phyStatus);
				phyStatus &= 0x20;
			}
			else if (k==1)
			{
				mrHalRamRead(0x81, SYSMAC_SEL,&phyStatus);
				phyStatus &= 0x20;
			}
			else
			{
				//VR Need to pass pointer to tse instance
			    //VRphyStatus = MSS_MAC_get_link_status(&speed,&fullduplex);
			    //VR Disabled by default see verifyNvmNetworkSettings
			    phyStatus &= 0x00;
			}

			if (lastStatus[k] != phyStatus)
			{
				lastStatus[k] = phyStatus;
				if (phyStatus)
				{
					// Link Up
					tcpip_callback(do_netif_set_link_up, &g_EMAC_if[k]);
					//VRsyslog(LOG_WARNING, "%s Link is up",k==2?"MGNT":(k==0?"WAN":"LAN"));
					//syslog unavailable in FreeRTOS
					dhcp_timeout[k] = 1;						// Reset our timer for DHCP timeout
				}
				else
				{
					// link Down
					tcpip_callback(do_netif_set_link_down, &g_EMAC_if[k]);
					//VRsyslog(LOG_WARNING, "%s Link is down",k==2?"MGNT":(k==0?"WAN":"LAN"));
					//syslog unavailable in FreeRTOS
				}
			}
			if ((phyStatus) && (dhcp_timeout[k] < 10))
			{
				dhcp_timeout[k]++;
				if (dhcp_timeout[k] == 10)
				{
					dhcp_timeout_param[k].netif = &g_EMAC_if[k];
					dhcp_timeout_param[k].ip_addr = networkConfig[k].ipAddr;
					dhcp_timeout_param[k].sn_mask = networkConfig[k].ipMask;
					dhcp_timeout_param[k].gw_addr = networkConfig[k].ipGate;
					tcpip_callback(revert_to_static_ip, &dhcp_timeout_param[k]);
				}
			}
		}
	}
#endif
}

void setLinkUp(uint8_t ifNum)
{
#ifdef USE_LWIP
	tcpip_callback(do_netif_set_link_up, &g_EMAC_if[ifNum]);
#endif
}

/*
 * This function is intended to be called as a parameter of the LWIP initialisation function 'tcpip_init'
 * It will carry out the initialisation necessary for the MetRoam Ethernet interfaces to
 * operate with lwip. It is possible for one or both of these interfaces to be disabled
 * for management.
 */

void configureEthernetInterfaces(void * param)
{
#ifdef USE_LWIP
    struct ip_addr 	xIpAddr, xNetMast, xGateway;
    uint8_t			k;
    networkConfig_t *networkConfigList;
    networkConfig_t *networkConfig;
    int8_t			defaultIF = -1;

    extern err_t ethernetif_init( struct netif *netif );

#ifdef DEBUG_PRINT_L2
    ts_printf("Inside configureEthernetInterfaces\n\r");
#endif
    // The params are a pointer to a list of struct netif
    networkConfigList = (networkConfig_t *)param;

#if SFP == 1
    for (k=0;k<2;k++)
#else
    for (k=0;k<PHY_COUNT;k++)
#endif
    {
    	networkConfig = &networkConfigList[netIfNum[k]];
    	if (networkConfig->options & MR_NET_SET_NET_EN_MASK)
    	{
    		// The port is enabled
    		if ((networkConfig->options & MR_NET_SET_DHCP_MASK) == MR_NET_SET_DHCP_MASK )
    		{
    			memset(&xIpAddr,0,sizeof(xIpAddr));
    		}
    		else
    		{
    			memcpy(&xIpAddr,&(networkConfig->ipAddr),sizeof(xIpAddr));
    		}
    		memcpy(&xGateway,&(networkConfig->ipAddr),sizeof(xGateway));
    		IP4_ADDR( &xNetMast, 255, 255, 255, 0 );

    		//VR - Skip TSE init performed using bare metal code
			if (k == 2)
			{
				netif_add( &g_EMAC_if[k], &xIpAddr, &xNetMast, &xGateway, (void*)&netIfNum[k], ethernetif_init, tcpip_input );
			}
			else
			{
				netif_add( &g_EMAC_if[k], &xIpAddr, &xNetMast, &xGateway, (void*)&netIfNum[k], gateway_ethernetif_init, tcpip_input );
			}

		    // register callback functions for the Network Status and Link Status
			netif_set_status_callback(&g_EMAC_if[k],StatusCallback);
			netif_set_link_callback(&g_EMAC_if[k],LinkCallback);

			if ((networkConfig->options & MR_NET_SET_DHCP_MASK) == MR_NET_SET_DHCP_MASK )
			{
				dhcp_start(&g_EMAC_if[k]);
			}
			else
			{
				netif_set_up(&g_EMAC_if[k]);
			}

			// Select the first enabled IF as the default. The order is
			// 0: Wan
			// 1: Lan
			// 2: Ext Mgnt
    		if (defaultIF < 0)
    			defaultIF = k;
    	}
    	else
    	{
    		// The port is disabled
    		g_EMAC_if[k].flags = 0;
    		ts_printf("Ethernet IF E%d is Disabled\n",k);


    	}
    }

    /* Set the default interface */
    if (defaultIF >= 0)
    	netif_set_default(&g_EMAC_if[defaultIF]);
    else
    {
    	ts_printf("Error - No Default Ethernet interface\n");

    }
#endif

}
