#include <string.h>
#include <xdc/std.h>
#include <xdc/runtime/System.h>
#include <ti/sysbios/BIOS.h>
#include <ti/sysbios/hal/Hwi.h>
#include <ti/sysbios/knl/Semaphore.h>
#include <ti/sysbios/knl/Task.h>
#include <ti/sysbios/knl/Mailbox.h>
#include <ti/ndk/inc/netmain.h>
#include <ti/ndk/inc/stack/inc/resif.h>
#include <ti/ndk/inc/stack/inc/netdf.h>
#include <ti/ndk/inc/stack/inc/listlib.h>
#include <ti/ndk/inc/stack/inc/nimuif.h>
#include <ti/ndk/inc/stack/inc/lliif.h>
//Check whether required
#include <xdc/runtime/Timestamp.h>

#include "user_acd_ndk.h"

#ifdef USE_CPSW_DRIVER
#include "cpsw_ethdriver.h"
#else
#include "icss_miimdio.h"
#include "icss_miimdio_regs.h"
#include "icss_nimu_eth.h"
#include "icss_common.h"
#include "icss_pruInit.h"
#include "icss_ethdriver.h"
#include "icss_dlr.h"
#endif

#define BUFSIZE (ARPHDR_SIZE + ETHHDR_SIZE)
#define ARPBUFSIZE (BUFSIZE * 32)

#define ACDPERIOD 10   /* 5 ms */

/**Message definition */
#define PORT1_MSG_LINKUP    1
#define PORT1_MSG_LINKDOWN  2
#define MSG_ARP             3
#define MSG_NETSTOP         4
#define PORT2_MSG_LINKUP    5
#define PORT2_MSG_LINKDOWN  6
#define PORTS_MSG_LINKDOWN  7

#define USE_ACD_ARP 1
#define USE_NDK_ARP 0

#define NUMMSGS       16
#define NIMUINDEX 1

typedef struct ACD_Object
{
    Mailbox_Handle mbox;
    Task_Handle task;
    Semaphore_Handle validSem;
} ACD_Object;

unsigned int acdEventMissed =0;
extern unsigned int eipAppStat;
static ACD_Object acdObj;
extern int startupMode;
static HANDLE arpq;
/**Flag which informs whether an IP is obtained*/
static int validAddress;
/**Local variable to store MAC ID of the device*/
static unsigned char macAddr[6];
/**Flag to inform the LED task regarding the ACD status*/
unsigned int ledACD = 0;
/**Flag to indicate ACD Stack is started*/
unsigned int acdStart=0;
/**Flag to indicate ACD Link Down event occured*/
unsigned int acdCablePull=0;
/**Flag to indicate IP is assigned to the device*/
unsigned int acdIPAssign = 0;
/**Variable used when ACD work around for gratitous arp is in use*/
extern unsigned int countACD;
/**Variable to indicate ACD or TCP/IP stack to process ARP*/
unsigned int acdARPFlag=USE_ACD_ARP;
 /**Variable which informs whether both link were down or not. Use*/
unsigned int linkDownStat=0;
/**Local array to store Link status*/
Uint8 eipLinkStat[2] ={0};

/**Work around function to block Gratitous ARP from TCP/IP stack*/
extern Uint8 checkGratArp(Uint8 * srcAddress);


void UpdateLinkStatus(Uint32 linkStatus , Uint32 portNum )
{

	Uint8 validMessage=1;
	UInt msg;
	if(linkStatus)
	{
		if (eipLinkStat[portNum]) // Make sure speed or duplexity changes doesnot send ACD ARP
			validMessage =0;
		else
			msg = (portNum *4)+1;

		eipLinkStat[portNum]=1;
		linkDownStat=1;
	}
	else
	{
		eipLinkStat[portNum]=0;
		if(eipLinkStat[0] | eipLinkStat[1])
		{
			msg = (portNum *4)+2;
			linkDownStat=1;
		}
		else
		{
			if(!linkDownStat)
				validMessage =0;
			msg= PORTS_MSG_LINKDOWN;
			linkDownStat=0;
		}
	}
	if(validMessage)
		Mailbox_post(acdObj.mbox, &msg, 0);
}

void EnetIPLinkStatusPort0(Uint32 linkStatus)
{
#ifndef USE_CPSW_DRIVER
	DlrPort0ProcessLinkBrk();//Link status based operations on DLR
#endif
	UpdateLinkStatus(linkStatus , 0);//Link status based operations for ACD
}

void EnetIPLinkStatusPort1(Uint32 linkStatus)
{
#ifndef USE_CPSW_DRIVER
	DlrPort1ProcessLinkBrk();
#endif
	UpdateLinkStatus(linkStatus , 1);
}

static void queueARP(void * buf)
{
    UInt msg = MSG_ARP;
    SBWrite(arpq, BUFSIZE, buf, NULL);
    Mailbox_post(acdObj.mbox, &msg, 0);
}

static int validateIPAddress(IPN ipAddr)
{
	NIMU_IF_REQ ifReq;
    ifReq.name[0] = 0;
    ifReq.index = NIMUINDEX;
    if (NIMUIoctl(NIMU_GET_DEVICE_MAC, &ifReq, macAddr, sizeof(macAddr))) {
        return FALSE;
    }

    /*Do Configs and Callback to be done before IP Assign*/
    SetEthernetLinkConfig();

#ifdef USE_CPSW_DRIVER
    cpsw_register_tx_exception_callback((cpsw_callback_ret_t)checkGratArp);
#else
	RegisterTxExceptionCallback((icssCallBackRet_t)checkGratArp);
	StartSwitch();
#endif
    acdStart = ACD_Start(htonl(ipAddr), macAddr, 0);
    /* wait for the ACD state machine to check address */
	Semaphore_pend(acdObj.validSem, BIOS_WAIT_FOREVER);

    return (validAddress);
}

static void sendArpPacket(unsigned char * arp, IPN IPDst)
{
    PBM_Pkt * pkt;
    ARPHDR * arpHdr;
    NETIF_DEVICE * nimu;
    static UINT8 BROADCAST[] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff };

    if ((nimu = NIMUFindByIndex(NIMUINDEX)) == NULL) {
        return;
    }

    if (!(pkt = NIMUCreatePacket(ARPHDR_SIZE))) {
        return;
    }

    /* ACD module includes an Ethernet header, so skip it */
    arpHdr = (ARPHDR *)(arp + ETHHDR_SIZE);
    memcpy(pkt->pDataBuffer + pkt->DataOffset, arpHdr, ARPHDR_SIZE);
    pkt->ValidLen = ARPHDR_SIZE;

    llEnter();
    if (NIMUAddHeader(nimu, (HANDLE)pkt, BROADCAST, NULL, 0x806) == 0) {
        /* Layer 2 header has been added successfully; now only can we send the 
         * packet through the NIMU Interface. */
        NIMUSendPacket(nimu, pkt);
    }
    else {
        PBM_free (pkt);
    }
    llExit();
}

static readARP(unsigned char * buf)
{
    UInt key;
    int size;

    key = Hwi_disable();
    size = SBRead(arpq, BUFSIZE, 0, buf, NULL, NULL, 0);
    Hwi_restore(key);

    return (size);
}

static void acdMonitor(UArg arg0, UArg arg1)
{
    ACD_Object * acdObj = (ACD_Object *)arg0;
    unsigned char arp[ARPHDR_SIZE + ETHHDR_SIZE];
    UInt msg;
    while (!acdStart) // Do not start ACD monitor task until ACD Stack is started
    {
    	Task_sleep(50);
    }

    Semaphore_Handle clockSem;
    Clock_Params clockParams;
    Semaphore_Params semParams1;
    Semaphore_Params_init(&semParams1);
    semParams1.mode = Semaphore_Mode_BINARY;

    clockSem = Semaphore_create(0, &semParams1, NULL);

    Clock_Params_init(&clockParams);
    clockParams.period = ACDPERIOD;
    clockParams.arg = (UArg)clockSem;
    clockParams.startFlag = TRUE;
    Clock_create((Clock_FuncPtr)Semaphore_post, ACDPERIOD, &clockParams, NULL);
    while (1)
    {
        Semaphore_pend(clockSem, BIOS_WAIT_FOREVER);

        ACD_IncTick();

		if(acdEventMissed)
		{
			if(eipAppStat && (!linkDownStat))
			{
				acdCablePull=1;
				ACD_Link(0, 0);
				ACD_Link(1, 0);
				ACD_Stop();
				acdEIPStatus = EIP_ACD_RESTART;
				acdEventMissed=0;
			}
		}
		if (Mailbox_pend(acdObj->mbox, &msg, BIOS_NO_WAIT))
		{
			switch (msg)
			{
			   case PORT1_MSG_LINKUP:
				   if(startupMode==EIP_STARTUP_DHCP)
					   Task_sleep(6000);
				   ACD_Link(0, 1);
				   acdEventMissed=0;
				   break;
				case PORT1_MSG_LINKDOWN:
					ACD_Link(0, 0);
					break;
				case PORT2_MSG_LINKUP:
				   if(startupMode==EIP_STARTUP_DHCP)
					   Task_sleep(6000);
					ACD_Link(1, 1);
					acdEventMissed=0;
					break;
				case PORT2_MSG_LINKDOWN:
					ACD_Link(1, 0);
					break;
				case PORTS_MSG_LINKDOWN:
					if (eipAppStat) // Donot restart in the when EIP is not UP
					{
						//set flag to indicate cable pull
						// and check instead od dhcp chek in register ppfg
						acdCablePull=1;
						ACD_Link(0, 0);
						ACD_Link(1, 0);
						ACD_Stop();
						acdEIPStatus = EIP_ACD_RESTART;
					}
					else
					{
						acdEventMissed=1;
					}
					break;
				case MSG_ARP:
					readARP(arp);
					ACD_RcvArpFrame(ARPHDR_SIZE + ETHHDR_SIZE, arp);
					break;
				case MSG_NETSTOP:
					Task_exit();
					break;
				default:
					break;
			}
		}
    }
}

Void UserACDInit(Void)
{
    arpq = SBNew(ARPBUFSIZE, 0, SB_MODE_LINEAR);
    if (arpq == NULL) {

    }

    LLI_setARPHook(queueARP);
    NS_setAddrHook(validateIPAddress);
}

void RegisterLinkCallBack()
{
#ifdef USE_CPSW_DRIVER
    cpsw_register_link_change_notification(UpdateLinkStatus);
#else
	//EthdriverRegisterLinkChangeNotification(UpdateLinkStatus);

	RegisterPort0ISRCallback((icssCallBack_t)EnetIPLinkStatusPort0,NULL);
	RegisterPort1ISRCallback((icssCallBack_t)EnetIPLinkStatusPort1,NULL);
#endif
}

Bool UserACDStart(Void)
{
    Task_Params taskParams;

    acdObj.mbox = Mailbox_create(sizeof(UInt), NUMMSGS, NULL, NULL);

    Task_Params_init(&taskParams);
    taskParams.instance->name = "UserACD_Monitor";
    taskParams.arg0 = (UArg)&acdObj;
    taskParams.priority = 8;
    acdObj.task = Task_create(acdMonitor, &taskParams, NULL);

    acdObj.validSem = Semaphore_create(0, NULL, NULL);

    return (TRUE);
}



Void UserACDStop()
{
    UInt msg = MSG_NETSTOP;
    System_flush();

    Mailbox_post(acdObj.mbox, &msg, 0);
    do {
        Task_sleep(1);
    } while(Task_getMode(acdObj.task) != Task_Mode_TERMINATED);

    Mailbox_delete(&acdObj.mbox);
    Semaphore_delete(&acdObj.validSem);

    memset(&acdObj, 0, sizeof(acdObj));
}

static void SignalAddress(int flag)
{
    validAddress = flag;
    Semaphore_post(acdObj.validSem);
}


EIP_WORD GetACDLEDStat()
{
	return ledACD;
}

/**
 *  \name USER_ACD_Event
 *  \brief Function will be called by ACD stack to inform the application about the ACD events
 *  \param
 *  \return
 *
 */
EIP_BOOL USER_ACD_Event(EIP_DWORD dwEvent)
{  
    UInt event = dwEvent & 0xffff;
    switch (event)
    {
        case ACD_EVENT_IP_LOST:
        	acdARPFlag=USE_ACD_ARP;
        	ledACD = ACD_LED_CONFLICT; /*Set led flag to indicate IP Address conflict*/
            SignalAddress(FALSE);
            ACD_Link(0, 0);
		    ACD_Link(1, 0);
            ACD_Stop();
            ACD_Exit();
            EIPACDStop();
            acdEIPStatus = EIP_ACD_STOP;
            break;
        case ACD_EVENT_IP_ANNOUNCED:
            acdARPFlag=USE_NDK_ARP;
            SignalAddress(TRUE); /*Signal to NDK to use the IP Address*/
            acdEIPStatus =EIP_ACD_START;
            break;
        case ACD_EVENT_LINK_INTEGRITY:
            break;
        case ACD_EVENT_DEFENSE:
        	acdARPFlag=USE_ACD_ARP;
            break;
        case ACD_EVENT_NO_LINK:
            break;
        default:
            break;
    }
    System_flush();
    return EIP_TRUE;
}

/**
 *  \name USER_ACD_ConflictDetected
 *  \brief Function will be called by ACD stack once Conflict is detected
 *  \param
 *  \return
 *
 */
EIP_BOOL USER_ACD_ConflictDetected(ST_ACD_CONFLICT_DETECTED stAcdConflictDetected)
{
	/*Writes the ACD conflict parameters into non volatile memory*/
	WritePermanentMemory(&(stAcdConflictDetected.byAcdActivity),sizeof(ST_ACD_CONFLICT_DETECTED),SPI_EEPROM_ACD_CONFLICT_OFFSET);
    return EIP_TRUE;
}

/**
 *  \name USER_ACD_SendArpFrame
 *  \brief Function will be called by ACD stack to send ARP
 *  \param
 *  \return
 *
 */
EIP_BOOL USER_ACD_SendArpFrame(EIP_WORD wFrameSize, EIP_BYTE * byFrame)
{
	countACD++;/*Counted as a part of blocking Gratitous ARP blocking workaorund*/
    sendArpPacket(byFrame, 0);
    return EIP_TRUE;
}

/**
 *  \name   USER_ACD_SIM_Print
 *  \brief  This API will be called by ACD stack to print messages
 *  \param  dwFlag
 *  \param  lpString  String to be printed
 *  \return EIP_TRUE
 *
 */
EIP_BOOL USER_ACD_SIM_Print(EIP_DWORD dwFlag, EIP_CHAR * lpString)
{
#ifdef EIP_ACD_DEBUG
    System_printf("%s\n", lpString);
    System_flush();
#endif
    
    return EIP_TRUE;
}
