#include "WiFiCtrl.h"
#include "string.h"
#include "driver/source/nmasic.h"
#include <stdint.h>
//#include "socket/include/socket.h"
#include "define.h"
//#include "Terminal.h"
#include "driver/include/m2m_periph.h"
//#include "ProtocolDefs.h"
#include "DigitalIO.h"
//#include "timer.h"



/** IP address of host. */
uint32 gu32HostIp = 0;

/** Retry count. */
uint8 gu8RetryCount = 0;

/** TCP client socket handlers. */
static SOCKET tcp_client_socket = -1;
SOCKET TerminalSocket = -1, TerminalServerSocket = -1;
uint8 TerminalRxBuf[1024];

SOCKET PrintfSocket = -1, PrintfServerSocket = -1;
uint8 PrintfRxBuf[1024];

SOCKET NetworkSocket = -1, NetworkServerSocket = -1;
uint8 NetworkRxBuf[1024];

/** Receive buffer definition. */
static uint8 gau8ReceivedBuffer[MAIN_WIFI_M2M_BUFFER_SIZE] = {0};

/** Wi-Fi status variable. */
static bool gbConnectedWifi = false;

/** Get host IP status variable. */
static bool gbHostIpByName = false;

/** TCP Connection status variable. */
static bool gbTcpConnection = false;

/** Server host name. */
static char server_host_name[] = MAIN_WEATHER_SERVER_NAME;


tstrWifiInitParam param;
uint8 mac_addr[6];
uint8 u8IsMacAddrValid;
struct sockaddr_in addr_in;

tstrM2MIPConfig mModuleIPConfig;

bool mWiFiInitOK = false;
char mWiFiState = WIFI_UNKNOWN_STATE;


/**
 * \brief Callback function of IP address.
 *
 * \param[in] hostName Domain name.
 * \param[in] hostIp Server IP.
 *
 * \return None.
 */
static void resolve_cb(uint8 *hostName, uint32 hostIp)
{
	gu32HostIp     = hostIp;
	gbHostIpByName = true;
	printf("Host IP is %d.%d.%d.%d\r\n",
            (int)IPV4_BYTE(hostIp, 0),
            (int)IPV4_BYTE(hostIp, 1),
            (int)IPV4_BYTE(hostIp, 2),
            (int)IPV4_BYTE(hostIp, 3));
	printf("Host Name is %s\r\n", hostName);
}



static void socket_cb(SOCKET sock, uint8 u8Msg, void *pvMsg)
{
	/* Check for socket event on TCP socket. */
    
    //if(sock == TerminalSocket)
    {
        switch(u8Msg)
        {
            case SOCKET_MSG_BIND:
            {
                tstrSocketBindMsg *pstrBind = (tstrSocketBindMsg*)pvMsg;
                if(pstrBind->status == 0)
                {
                    if(sock == TerminalServerSocket)
                    {
                        listen(TerminalServerSocket, 0);
                    }
                    else if(sock == PrintfServerSocket)
                    {
                        listen(PrintfServerSocket,0);                                
                    }
                    else if(sock == NetworkServerSocket)
                    {
                        listen(NetworkServerSocket,0);
                    }
                }
                else
                {
                    printf("Bind Failed\n");
                }
                break;
            }
            case SOCKET_MSG_LISTEN:
            {
                
                tstrSocketListenMsg *pstrListen = (tstrSocketListenMsg*)pvMsg;
                if(pstrListen->status != 0)
                {
                    printf("socket %d listen Failed\n",socket);;
                    break;
                }
                m2m_periph_gpio_set_val(M2M_PERIPH_GPIO3,1);
                break;
            }
            case SOCKET_MSG_ACCEPT:
            {
                // New Socket is accepted.
                tstrSocketAcceptMsg *pstrAccept = (tstrSocketAcceptMsg *)pvMsg;
                if(pstrAccept->sock >= 0)
                {
                    if(sock == TerminalServerSocket)
                    {
                        memset(TerminalRxBuf,0,sizeof(TerminalRxBuf));
                        // Get the accepted socket.
                        TerminalSocket = pstrAccept->sock;
                        recv(TerminalSocket, TerminalRxBuf, sizeof(TerminalRxBuf), 0);
                        SendTerminalData("Bienvenue au chalet!\nLe chalet parle en anglais comme Mr. Pepin\nIf you need help... type help\n\n",strlen("Bienvenue au chalet!\nLe chalet parle en anglais comme Mr. Pepin\nIf you need help... type help\n\n"));
                    }
                    else if(sock == PrintfServerSocket)
                    {
                        memset(PrintfRxBuf,0,sizeof(PrintfRxBuf));
                        // Get the accepted socket.
                        PrintfSocket = pstrAccept->sock;
                        recv(PrintfSocket, PrintfRxBuf, sizeof(PrintfRxBuf), 0);
                    }
                    else if(sock == NetworkServerSocket)
                    {
                        memset(NetworkRxBuf,0,sizeof(NetworkRxBuf));
                        // Get the accepted socket.
                        NetworkSocket = pstrAccept->sock;
                        recv(NetworkSocket, NetworkRxBuf, sizeof(NetworkRxBuf), 0);
                    }
                    
                }
                else
                {
                    printf("Socket %d : Accept Failed\n", sock);
                }
                break;
                
            }
            case SOCKET_MSG_RECV:
            {
                tstrSocketRecvMsg *pstrRecvMsg = (tstrSocketRecvMsg*)pvMsg;
                if((pstrRecvMsg->pu8Buffer != NULL) && (pstrRecvMsg->s16BufferSize > 0))
                {
                    // Process the received message
                    if(sock == TerminalSocket)
                    {
                        //Fwd data to Terminal...
 //                       RxTerminalBuf(pstrRecvMsg->pu8Buffer, pstrRecvMsg->s16BufferSize);
                        recv(TerminalSocket, TerminalRxBuf, sizeof(TerminalRxBuf), 0);
                    }
                    else if(sock == PrintfSocket)
                    {
                        //Fwd data to stdin...
                        recv(PrintfSocket, PrintfRxBuf, sizeof(PrintfRxBuf), 0);
                    }
                    else if(sock == NetworkSocket)
                    {
                        //Fwd data to Network...
                        recv(NetworkSocket, NetworkRxBuf, sizeof(NetworkRxBuf), 0);
                    }
                }
                else    //Socket must be closed.
                {
                     if(sock == TerminalSocket)
                    {
                         close(TerminalSocket);
                         TerminalSocket = -1;
                    }
                    else if(sock == PrintfSocket)
                    {
                        close(PrintfSocket);
                        PrintfSocket = -1;
                    }
                    else if(sock == NetworkSocket)
                    {
                        close(NetworkSocket);
                        NetworkSocket = -1;
                    }
                }
                break;
            }
        }
    }
    
}

static void set_dev_name_to_mac(uint8 *name, uint8 *mac_addr)
{
	/* Name must be in the format WINC1500_00:00 */
	uint16 len;
    
	len = m2m_strlen(name);
	if (len >= 5) {
		name[len - 1] = MAIN_HEX2ASCII((mac_addr[5] >> 0) & 0x0f);
		name[len - 2] = MAIN_HEX2ASCII((mac_addr[5] >> 4) & 0x0f);
		name[len - 4] = MAIN_HEX2ASCII((mac_addr[4] >> 0) & 0x0f);
		name[len - 5] = MAIN_HEX2ASCII((mac_addr[4] >> 4) & 0x0f);
	}
}

/**
 * \brief Callback to get the Wi-Fi status update.
 *
 * \param[in] u8MsgType Type of Wi-Fi notification.
 * \param[in] pvMsg A pointer to a buffer containing the notification parameters.
 *
 * \return None.
 */
static void wifi_cb(uint8 u8MsgType, void *pvMsg)
{
	switch (u8MsgType) {
        case M2M_WIFI_RESP_CON_STATE_CHANGED: {
            tstrM2mWifiStateChanged *pstrWifiState = (tstrM2mWifiStateChanged *)pvMsg;
            if (pstrWifiState->u8CurrState == M2M_WIFI_CONNECTED) 
            {
                printf("Wi-Fi connected\r\n");
#ifndef USE_STATIC_IP
                m2m_wifi_request_dhcp_client();
#else
                m2m_wifi_set_static_ip(&mModuleIPConfig);
                gbConnectedWifi = true;
#endif
                mWiFiState = WIFI_CONNECTED_STATE;
            } else if (pstrWifiState->u8CurrState == M2M_WIFI_DISCONNECTED) 
            {
                printf("Wi-Fi disconnected\r\n");
                gbConnectedWifi = false;
                mWiFiState = WIFI_DISCONNECTED_STATE;
                CloseSockets();
                m2m_periph_gpio_set_val(M2M_PERIPH_GPIO3,0);
            }
            
            break;
        }
        
        case M2M_WIFI_REQ_DHCP_CONF: 
        {
            uint8 *pu8IPAddress = (uint8 *)pvMsg;
            /* Turn LED0 on to declare that IP address received. */
            printf("Wi-Fi IP is %u.%u.%u.%u\r\n", pu8IPAddress[0], pu8IPAddress[1], pu8IPAddress[2], pu8IPAddress[3]);
            gbConnectedWifi = true;
            /* Obtain the IP Address by network name */
            gethostbyname((uint8 *)server_host_name);
            break;
        }
        
        case M2M_WIFI_RESP_PROVISION_INFO: 
        {
            tstrM2MProvisionInfo *pstrProvInfo = (tstrM2MProvisionInfo *)pvMsg;
            printf("wifi_cb: M2M_WIFI_RESP_PROVISION_INFO.\r\n");
            
            if (pstrProvInfo->u8Status == M2M_SUCCESS) {
                m2m_wifi_connect((char *)pstrProvInfo->au8SSID,
                        strlen((char *)pstrProvInfo->au8SSID),
                        pstrProvInfo->u8SecType,
                        pstrProvInfo->au8Password,
                        M2M_WIFI_CH_ALL);
            } else {
                printf("wifi_cb: Provision failed.\r\n");
            }
        } break;
        
        default: {
            break;
        }
	}
}


int InitWiFi()
{
	tstrWifiInitParam param;
	int8_t            ret;
    
    gbTcpConnection = false;
    
    
    memset(&mModuleIPConfig,0,sizeof(mModuleIPConfig));
    mModuleIPConfig.u32StaticIP = IP_TO_U32(STATIC_IP_ADDRESS_1,STATIC_IP_ADDRESS_2,STATIC_IP_ADDRESS_3,STATIC_IP_ADDRESS_4);
    mModuleIPConfig.u32DNS = IP_TO_U32(DEFAULT_DNS_ADD_1,DEFAULT_DNS_ADD_2,DEFAULT_DNS_ADD_3,DEFAULT_DNS_ADD_4);
    mModuleIPConfig.u32AlternateDNS  = IP_TO_U32(ALT_DNS_ADD_1,ALT_DNS_ADD_2,ALT_DNS_ADD_3,ALT_DNS_ADD_4);
    mModuleIPConfig.u32Gateway  = IP_TO_U32(GATEWAY_ADDRESS_1,GATEWAY_ADDRESS_2,GATEWAY_ADDRESS_3,GATEWAY_ADDRESS_4);
    mModuleIPConfig.u32SubnetMask  = IP_TO_U32(SUBNET_MASK_1,SUBNET_MASK_2,SUBNET_MASK_3,SUBNET_MASK_4);
    
    /* Initialize the BSP. */
	nm_bsp_init();
	/* Initialize Wi-Fi parameters structure. */
	memset((uint8_t *)&param, 0, sizeof(tstrWifiInitParam));
	/* Initialize Wi-Fi driver with data and status callbacks. */
	param.pfAppWifiCb = wifi_cb;
	ret               = m2m_wifi_init(&param);
	if (M2M_SUCCESS != ret) 
    {
        mWiFiInitOK = false;
        mWiFiState = WIFI_INIT_ERROR_STATE;
        return RET_ERROR;
	}
    mWiFiInitOK = true;
    
    mWiFiState = WIFI_DISCONNECTED_STATE;
    
    socketInit();
	registerSocketCallback(socket_cb, resolve_cb);
    
    //Get MAC address from the module
	m2m_wifi_get_otp_mac_address(mac_addr, &u8IsMacAddrValid);
	if (!u8IsMacAddrValid) {
		m2m_wifi_set_mac_address(gau8MacAddr);
	}
    
    //Get the working MAC address from driver.
	m2m_wifi_get_mac_address(gau8MacAddr);
    
    //Initialize the human readable MAC address based on the working MAC
	set_dev_name_to_mac((uint8 *)gacDeviceName, gau8MacAddr);
    
    //Use the MAC to define the SSID of the module
	set_dev_name_to_mac((uint8 *)gstrM2MAPConfig.au8SSID, gau8MacAddr);
    
	m2m_wifi_set_device_name((uint8 *)gacDeviceName, (uint8)m2m_strlen((uint8 *)gacDeviceName));
    
#ifdef USE_STATIC_IP
    //Use static ip --> disable dhcp client before connecting
    m2m_wifi_enable_dhcp(0);
#endif
    if(m2m_wifi_connect(HOME_AP_NAME,sizeof(HOME_AP_NAME),HOME_AP_SEC_TYPE,HOME_AP_PWD,M2M_WIFI_CH_ALL) != M2M_SUCCESS)
    {
        //wifi connect error...
        printf("error");
    }
    
    tstrPerphInitParam tst;
    
    m2m_periph_init(&tst);
	
    //Set all IOs as inputs except the LED (IO3)..
    m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO3,1);
    m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO4,0);
    m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO5,0);
    m2m_periph_gpio_set_dir(M2M_PERIPH_GPIO6,0);
    
    m2m_periph_gpio_set_val(M2M_PERIPH_GPIO3,0);
    m2m_periph_gpio_set_val(M2M_PERIPH_GPIO4,0);
    m2m_periph_gpio_set_val(M2M_PERIPH_GPIO5,0);
    m2m_periph_gpio_set_val(M2M_PERIPH_GPIO6,0);
    
    
//    TimerStart(WIFI_RECONNECT_TIMER,WIFI_CONNECT_TIMEOUT);
    
    
    //	m2m_wifi_start_provision_mode((tstrM2MAPConfig *)&gstrM2MAPConfig, (char *)gacHttpProvDomainName, 1);
    //	printf("Provision Mode started.\r\nConnect to [%s] via AP[%s] and fill up the page.\r\n",
    //	       MAIN_HTTP_PROV_SERVER_DOMAIN_NAME,
    //	       gstrM2MAPConfig.au8SSID);
}

int TurnOFFWiFi()
{
    int shit;
    
    m2m_wifi_disconnect();
    m2m_wifi_deinit(&shit);
    CloseSockets();
    socketDeinit();
    WIFI_CHP_EN_PIN = 0;
    WIFI_CHP_RST_PIN = 0;
    mWiFiInitOK = false;
    gbConnectedWifi = false;
    HEARTBEAT_LED_2_PIN = LED_OFF;
    
    //    WIFI
    mWiFiState = WIFI_MODULE_OFF_STATE;
    return RET_OK;
}

int CloseSockets()
{
    gbTcpConnection = false;
    close(TerminalServerSocket);
}


char GetWiFiSate()
{
    return mWiFiState;
}

void TickWiFi()
{
    if(mWiFiInitOK == false)
    {
        return;
    }
    if(mWiFiState == WIFI_DISCONNECTED_STATE && gbConnectedWifi == false)//we should be connected.. if the timer is expired, retry
    {        
 //       if(IsTimerExpired(WIFI_RECONNECT_TIMER))
        {
            m2m_wifi_connect(HOME_AP_NAME,sizeof(HOME_AP_NAME),HOME_AP_SEC_TYPE,HOME_AP_PWD,M2M_WIFI_CH_ALL);
  //          TimerStart(WIFI_RECONNECT_TIMER,WIFI_CONNECT_TIMEOUT);
        }
    }
    
    m2m_wifi_handle_events(NULL);
    
    if (gbConnectedWifi && !gbTcpConnection) 
    {
        OpenTerminalServer();
        OpenNetworkServer();
#ifdef USE_WIFI_PRINTF
        OpenPrintfServer();
#endif
        gbTcpConnection = true;
    }
}


//Terminal server implementation

int OpenTerminalServer()
{
    struct sockaddr_in strAddr;
    
    TerminalServerSocket = socket(AF_INET, SOCK_STREAM,0);
    uint16 TerminalPort = TERMINAL_SERVER_PORT;
    if(TerminalServerSocket >= 0)
    {
        strAddr.sin_family = AF_INET;
        strAddr.sin_port = _htons(TerminalPort);
        strAddr.sin_addr.s_addr = 0;
        
        bind(TerminalServerSocket, (struct sockaddr*)&strAddr, sizeof(struct sockaddr_in));
        return RET_OK;
    }
    else
    {
        return RET_ERROR;
    }
}
void SendTerminalData(uint8 *data, int size)
{
    if(TerminalSocket != -1)
    {
        send(TerminalSocket,data,size,0);
        recv(TerminalSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0);
    }
}
void SentTerminalByte(uint8 data)
{
    if(TerminalSocket != -1)
    {
        send(TerminalSocket,&data,1,0);
        recv(TerminalSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0);
    }
}

int OpenNetworkServer()
{
    struct sockaddr_in strAddr;
    
    NetworkServerSocket = socket(AF_INET, SOCK_STREAM,0);
    uint16 TerminalPort = NETWORK_SERVER_PORT;
    if(NetworkServerSocket >= 0)
    {
        strAddr.sin_family = AF_INET;
        strAddr.sin_port = _htons(TerminalPort);
        strAddr.sin_addr.s_addr = 0;
        
        bind(NetworkServerSocket, (struct sockaddr*)&strAddr, sizeof(struct sockaddr_in));
        return RET_OK;
    }
    else
    {
        return RET_ERROR;
    }
}
void SendNetworkData(uint8 *data, int size)
{
    if(NetworkSocket != -1)
    {
        send(NetworkSocket,data,size,0);
        recv(NetworkSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0);
    }
}
void SentNetworkByte(uint8 data)
{
  if(NetworkSocket != -1)
    {
        send(NetworkSocket,&data,1,0);
        recv(NetworkSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0);
    }  
}

//Printf Server Implementation
#ifdef USE_WIFI_PRINTF
int OpenPrintfServer()
{
    struct sockaddr_in strAddr;
    
    PrintfServerSocket = socket(AF_INET, SOCK_STREAM,0);
    uint16 TerminalPort = 5589;
    if(PrintfServerSocket >= 0)
    {
        strAddr.sin_family = AF_INET;
        strAddr.sin_port = _htons(TerminalPort);
        strAddr.sin_addr.s_addr = 0;
        
        bind(PrintfServerSocket, (struct sockaddr*)&strAddr, sizeof(struct sockaddr_in));
        return RET_OK;
    }
    else
    {
        return RET_ERROR;
    }
}
void SendPrintfData(uint8 *data, int size)
{
    if(PrintfSocket != -1);
    {
        send(PrintfSocket,data,size,0);
        recv(PrintfSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0); 
    }
}
void SendPrintfByte(uint8 data)
{
    if(PrintfSocket != -1)
    {
        send(PrintfSocket,&data,1,0);
        recv(PrintfSocket,TerminalRxBuf,sizeof(TerminalRxBuf),0);
    }
}
#endif
