/*
 * Copyright (c) 2001,2002 Florian Schulze.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the authors nor the names of the contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHORS AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * app_main.c - This file is part of lwIP test
 *
 */

/* ========================================================================== */
/*                             Include Files                                  */
/* ========================================================================== */

#include <stdio.h>
#include <stdarg.h>
#include <string.h>
/* lwIP core includes */
#include "lwip/opt.h"
#include "lwip/sys.h"
#include "lwip/init.h"
#include "lwip/dhcp.h"
#include "lwip/timeouts.h"
#include <apps/tcpecho_raw/tcpecho_raw.h>

#include <kernel/dpl/ClockP.h>
#include <kernel/dpl/EventP.h>
#include <enet_apputils.h>
#include <enet_board.h>
#include "ti_board_config.h"
#include "ti_board_open_close.h"
#include "ti_drivers_open_close.h"
#include "ti_enet_config.h"
#include "ti_enet_open_close.h"
#include "app_cpswconfighandler.h"
#include "ti_enet_lwipif.h"

/* ========================================================================== */
/*                           Macros & Typedefs                                */
/* ========================================================================== */

static const uint8_t BROADCAST_MAC_ADDRESS[ENET_MAC_ADDR_LEN] = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF };

/* ========================================================================== */
/*                         Structure Declarations                             */
/* ========================================================================== */

/* ========================================================================== */
/*                          Function Declarations                             */
/* ========================================================================== */
static void App_printCpuLoad();

static void App_tcpipInitCompleteCb(void *pArg);

static void App_setupNetif();

static void App_allocateIPAddress();

static void App_setupNetworkStack();

static void App_shutdownNetworkStack();

static void App_netifStatusChangeCb(struct netif *state_netif);

static void App_netifLinkChangeCb(struct netif *state_netif);

static inline int32_t App_isNetworkUp(struct netif* netif_);

static void App_handleEvent(const uint32_t eventMask);

static uint32_t App_receiveEvents(EventP_Object* pEvent);

void httpd_init(void);
/* ========================================================================== */
/*                            Global Variables                                */
/* ========================================================================== */

/* dhcp struct for the ethernet netif */
static struct dhcp g_netifDhcp[ENET_SYSCFG_NETIF_COUNT];
struct netif *g_pNetif[ENET_SYSCFG_NETIF_COUNT];

EventP_Object hEvent;

/* Handle to the Application interface for the LwIPIf Layer
 */
LwipifEnetApp_Handle hlwipIfApp = NULL;
/* ========================================================================== */
/*                          Function Definitions                              */
/* ========================================================================== */

int appMain(void *args)
{
    Enet_Type enetType;
    uint32_t instId;
    int32_t status;
    status = EventP_construct(&hEvent);

    DebugP_log("==========================\r\n");
    DebugP_log("  CPSW LWIP HTTP WEB SERVER \r\n");
    DebugP_log("==========================\r\n");

    EnetApp_getEnetInstInfo(CONFIG_ENET_CPSW0, &enetType, &instId);

    EnetAppUtils_enableClocks(enetType, instId);

    EnetApp_driverInit();

    status = EnetApp_driverOpen(enetType, instId);
    EnetApp_initPhyStateHandlerTask(&hEvent);
    if (ENET_SOK != status)
    {
        EnetAppUtils_print("Failed to open ENET: %d\r\n", status);
        EnetAppUtils_assert(false);
        return -1;
    }

    EnetApp_addMCastEntry(enetType,
                          instId,
                          EnetSoc_getCoreId(),
                          BROADCAST_MAC_ADDRESS,
                          CPSW_ALE_ALL_PORTS_MASK);
    //call createPhyHandler here


    App_setupNetworkStack();

    if (ENET_SOK != status)
    {
        EnetAppUtils_print("Failed to construct Event: %d\r\n", status);
        EnetAppUtils_assert(false);
        return -1;
    }

    /* Wait for ALL network interfaces to be UP */
    uint32_t netupMask = 0;
    uint32_t prevNetupMask = 0;
    uint32_t allInterfacesMask = (1U << ENET_SYSCFG_NETIF_COUNT) - 1U;

    DebugP_log("Waiting for all %d network interfaces to be UP...\r\n", ENET_SYSCFG_NETIF_COUNT);

    while (netupMask != allInterfacesMask)
    {
        for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
        {
            if (App_isNetworkUp(g_pNetif[i]))
            {
                if (!(netupMask & (1 << i)))
                {
                    /* Interface just came UP, log it */
                    DebugP_log("[%d] Network is UP - IP: %s\r\n", i, ip4addr_ntoa(netif_ip4_addr(g_pNetif[i])));
                }
                netupMask |= (1 << i);
            }
        }

        /* Periodic status update if not all interfaces are up yet */
        if (netupMask != prevNetupMask)
        {
            DebugP_log("Network interfaces status: %d/%d UP\r\n",
                       __builtin_popcount(netupMask), ENET_SYSCFG_NETIF_COUNT);
            prevNetupMask = netupMask;
        }

        sys_check_timeouts();

        const uint32_t recvdEventsMask = App_receiveEvents(&hEvent);

        if (recvdEventsMask != AppEventId_NONE)
        {
            App_handleEvent(recvdEventsMask);
        }
    }

    DebugP_log("All %d network interfaces are UP!\r\n", ENET_SYSCFG_NETIF_COUNT);
    ClockP_sleep(1);

//    AppTcp_startServer();
    tcpecho_raw_init();
    httpd_init();

    while (1)
    {
        sys_check_timeouts();

        const uint32_t recvdEventsMask = App_receiveEvents(&hEvent);

        if (recvdEventsMask != AppEventId_NONE)
        {
            App_handleEvent(recvdEventsMask);
        }

    }

    App_shutdownNetworkStack();
    EnetApp_driverDeInit();
    EventP_destruct(&hEvent);
    return 0;
}


static void App_handleEvent(const uint32_t eventMask)
{
    /* In dual MAC mode, the lwip2enet layer internally routes packets to the
     * correct netif based on the rxPortNum from hardware. RX/TX handlers are
     * called once - they handle routing internally. However, periodic polling
     * MUST be called for each netif to update its individual link status. */

    if (AppEventId_NETIFMNGR_RXPKT & eventMask)
    {
        /* RX packet handler - routes packets internally to correct netif */
        LWIPIF_LWIP_rxPktHandler(g_pNetif[ENET_SYSCFG_DEFAULT_NETIF_IDX]);
    }

    if (AppEventId_NETIFMNGR_POLL & eventMask)
    {
        /* Periodic polling - MUST call for each netif to update link status */
        for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
        {
            LWIPIF_LWIP_periodic_polling(g_pNetif[i]);
        }
    }

    if (AppEventId_NETIFMNGR_TXPKT & eventMask)
    {
        /* TX packet handler - processes TX for all netifs */
        LWIPIF_LWIP_txPktHandler(g_pNetif[ENET_SYSCFG_DEFAULT_NETIF_IDX]);
    }

    if (AppEventId_CPSW_PERIODIC_POLL & eventMask)
    {
        EnetApp_phyStateHandler();
    }
}

static uint32_t App_receiveEvents(EventP_Object* pEvent)
{
    int32_t status = SystemP_SUCCESS;
    uint32_t recvdEventsMask = AppEventId_NONE;

    status = EventP_waitBits(pEvent,
                        AppEventId_ANY_EVENT, // bitsToWaitFor
                        1,
                        0,
                        SystemP_NO_WAIT,
                        &recvdEventsMask);

     if ((status != SystemP_SUCCESS) && (status != SystemP_TIMEOUT))
     {
         EnetAppUtils_print("Failed to receive Event handle\r\n");
         EnetAppUtils_assert(false);
     }

     return recvdEventsMask;
}

static void App_setupNetworkStack()
{
    lwip_init();

    srand(ClockP_getTicks()/1000);

    App_setupNetif();

    App_allocateIPAddress();

    return;
}

static void App_shutdownNetworkStack()
{
    return;
}

static void App_setupNetif()
{
    ip4_addr_t ipaddr, netmask, gw;

    ip4_addr_set_zero(&gw);
    ip4_addr_set_zero(&ipaddr);
    ip4_addr_set_zero(&netmask);

    DebugP_log("Starting lwIP with DHCP enabled for dual MAC mode\r\n");
    hlwipIfApp = LwipifEnetApp_getHandle();

    /* Open all netifs first */
    for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
    {
        DebugP_log("[%d]: Opening lwIP interface, IP is dhcp-enabled\r\n", i);
        g_pNetif[i] = LwipifEnetApp_netifOpen(hlwipIfApp, NETIF_INST_ID0 + i, &ipaddr, &netmask, &gw);
    }

    /* Start schedule after all netifs are opened */
    LwipifEnetApp_startSchedule(hlwipIfApp, g_pNetif[ENET_SYSCFG_DEFAULT_NETIF_IDX], &hEvent);

    /* Set callbacks for all netifs */
    for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
    {
        netif_set_status_callback(g_pNetif[i], App_netifStatusChangeCb);
        netif_set_link_callback(g_pNetif[i], App_netifLinkChangeCb);
    }

    /* Bring up all netifs before starting DHCP */
    for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
    {
        netif_set_up(g_pNetif[i]);
        DebugP_log("[%d]: Interface brought up\r\n", i);
    }
}

static void App_allocateIPAddress()
{
    DebugP_log("Starting DHCP for all interfaces in dual MAC mode\r\n");

    /* Set DHCP structures for all netifs */
    for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
    {
        dhcp_set_struct(g_pNetif[i], &g_netifDhcp[i]);
    }

    /* Start DHCP on all netifs */
    for (uint32_t i = 0U; i < ENET_SYSCFG_NETIF_COUNT; i++)
    {
        const err_t err = dhcp_start(g_pNetif[i]);
        EnetAppUtils_assert(err == ERR_OK);
        DebugP_log("[%d]: DHCP started\r\n", i);
    }
    return;
}

static void App_netifStatusChangeCb(struct netif *pNetif)
{
    if (netif_is_up(pNetif))
    {
        DebugP_log("[%d] Enet IF UP Event. Local interface IP:%s\r\n",
                    pNetif->num,
                    ip4addr_ntoa(netif_ip4_addr(pNetif)));
    }
    else
    {
        DebugP_log("[%d] Enet IF DOWN Event\r\n", pNetif->num);
    }
    return;
}

static void App_netifLinkChangeCb(struct netif *pNetif)
{
    if (netif_is_link_up(pNetif))
    {
        DebugP_log("[%d] Network Link UP Event\r\n", pNetif->num);
    }
    else
    {
        DebugP_log("[%d] Network Link DOWN Event\r\n", pNetif->num);
    }
    return;
}

static int32_t App_isNetworkUp(struct netif* netif_)
{
    return (netif_is_up(netif_) && netif_is_link_up(netif_) && !ip4_addr_isany_val(*netif_ip4_addr(netif_)));
}

#ifndef NO_SYS
static void App_printCpuLoad()
{
    static uint32_t startTime_ms = 0;
    const  uint32_t currTime_ms  = ClockP_getTimeUsec()/1000;
    const  uint32_t printInterval_ms = 5000;

    if (startTime_ms == 0)
    {
        startTime_ms = currTime_ms;
    }
    else if ( (currTime_ms - startTime_ms) > printInterval_ms )
    {
        const uint32_t cpuLoad = TaskP_loadGetTotalCpuLoad();

        DebugP_log(" %6d.%3ds : CPU load = %3d.%02d %%\r\n",
                    currTime_ms/1000, currTime_ms%1000,
                    cpuLoad/100, cpuLoad%100 );

        startTime_ms = currTime_ms;
        TaskP_loadResetAll();
    }
    return;
}
#endif
