#include <stdio.h>
#include <stddef.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include "definitions.h"
#include "../src/ioLibrary_Driver-master/ioLibrary_Driver-master/Ethernet/W5500/w5500.h"
#include "../src/ioLibrary_Driver-master/ioLibrary_Driver-master/Ethernet/socket.h"
#include "../src/ioLibrary_Driver-master/ioLibrary_Driver-master/Internet/DHCP/dhcp.h"
#include <sys/attribs.h>

// Constants
#define DATASOCKET          1
#define portNumber          23
#define _WIZCHIP_           W5500

// Global variables
uint8_t SOCK_DHCP = 0;
uint8_t gDATABUF[600];
/*int8_t retVal;
uint8_t sockStatus;*/
char message[500] = " ";
volatile bool clientConnected = false;  // Flag for ISR

// Network information structure
wiz_NetInfo networkInfo = {
    .mac = {0x00, 0x08, 0xdc,0x76, 0xec, 0xd1},
    .ip = {192, 168, 1, 20},
    .sn = {255, 255, 255, 0},
    .gw = {192, 168, 1, 1},
    .dns = {0,0,0,0},
    .dhcp = NETINFO_DHCP
};

// Timeout configuration structure
wiz_NetTimeout gWIZNETTIME = {
    .retry_cnt = 1,         // RCR = 1
    .time_100us = 1000      // RTR = 2000
};

// Function prototypes
void TIMER2_InterruptSvcRoutine(uint32_t status, uintptr_t context);
void network_init(wiz_NetInfo net);
void openTCPSocket(void);
void handleDHCP(void);
void handleClientCommunication(uint8_t socket);
void configure_INT0_on_RB7(void);
void __ISR(_EXTERNAL_0_VECTOR, IPL4SRS) _INT0Interrupt(void);
void writeIMR(uint8_t imr);

// Main
int main(void) {
    // Initialization
    SYS_Initialize(NULL);
    wizInit();

    // Set MAC address
    setSHAR(networkInfo.mac);

    // Initialize DHCP
    DHCP_init(SOCK_DHCP, gDATABUF);

    // Set up a timer interrupt to be called every second
    TMR2_CallbackRegister(TIMER2_InterruptSvcRoutine, (uintptr_t)NULL);
    TMR2_Start();

    // Set network timeout
    ctlnetwork(CN_SET_TIMEOUT, (void *)&gWIZNETTIME);   
    
    configure_INT0_on_RB7();

    // Main Loop
    while (1) {
        if (getPHYCFGR() & PHYCFGR_LNK_ON) {
            handleDHCP();

            if (clientConnected) {
                UART1_Write(">> Calling handleClientCommunication()\r\n", 41);
                handleClientCommunication(DATASOCKET);
                clientConnected = false;
            }
        } else {
            // Handle error connecting
            memset(message, 0, sizeof(message));
            strcpy(message, ">> Error Connecting \r\n");
            UART1_Write(message, sizeof(message));
            disconnect(DATASOCKET);
            close(DATASOCKET);
            clientConnected = false;
        }
    }

    return EXIT_SUCCESS;
}

// This function is called after a period matches in Timer 3 (32-bit timer)
void TIMER2_InterruptSvcRoutine(uint32_t status, uintptr_t context) {
    LED2_Toggle();
    DHCP_time_handler();
}

// DHCP + fallback logic
void handleDHCP(void) {
    //UART1_Write("handleDHCP()\r\n", 15);
    switch (DHCP_run()) {
        case DHCP_IP_ASSIGN:
        case DHCP_IP_CHANGED:
            break;
            
        case DHCP_IP_LEASED:
            getIPfromDHCP(networkInfo.ip);
            getSNfromDHCP(networkInfo.sn);
            getGWfromDHCP(networkInfo.gw);
            getDNSfromDHCP(networkInfo.dns);

            // Format and send network information via UART
            char message[300];
            memset(message, 0, sizeof(message));
            sprintf(message, "Network Information:\r\n"
                              "  Assigned IP: %d.%d.%d.%d\r\n"
                              "  Subnet Mask: %d.%d.%d.%d\r\n"
                              "  Gateway: %d.%d.%d.%d\r\n"
                              "  DNS Server: %d.%d.%d.%d\r\n"
                              "  MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
                    networkInfo.ip[0], networkInfo.ip[1],
                    networkInfo.ip[2], networkInfo.ip[3],
                    networkInfo.sn[0], networkInfo.sn[1],
                    networkInfo.sn[2], networkInfo.sn[3],
                    networkInfo.gw[0], networkInfo.gw[1],
                    networkInfo.gw[2], networkInfo.gw[3],
                    networkInfo.dns[0], networkInfo.dns[1],
                    networkInfo.dns[2], networkInfo.dns[3],
                    networkInfo.mac[0], networkInfo.mac[1],
                    networkInfo.mac[2], networkInfo.mac[3],
                    networkInfo.mac[4], networkInfo.mac[5]);
            UART1_Write(message, strlen(message));

            // Initialize network and handle client communication
            network_init(networkInfo);
            openTCPSocket();
            break;

        case DHCP_FAILED:
            memset(message, 0, sizeof(message));
            strcpy(message, ">> DHCP failed, using static IP\r\n");
            UART1_Write(message, sizeof(message));

            DHCP_stop();
            // Notify applying default network settings
            memset(message, 0, sizeof(message));
            strcpy(message, ">> Applying Default Network Settings\r\n");
            UART1_Write(message, sizeof(message));

            // Display default network information
            memset(message, 0, sizeof(message));
            sprintf(message, "Network Information:\r\n"
                              "  Assigned IP: %d.%d.%d.%d\r\n"
                              "  Subnet Mask: %d.%d.%d.%d\r\n"
                              "  Gateway: %d.%d.%d.%d\r\n"
                              "  DNS Server: %d.%d.%d.%d\r\n"
                              "  MAC Address: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
                    networkInfo.ip[0], networkInfo.ip[1],
                    networkInfo.ip[2], networkInfo.ip[3],
                    networkInfo.sn[0], networkInfo.sn[1],
                    networkInfo.sn[2], networkInfo.sn[3],
                    networkInfo.gw[0], networkInfo.gw[1],
                    networkInfo.gw[2], networkInfo.gw[3],
                    networkInfo.dns[0], networkInfo.dns[1],
                    networkInfo.dns[2], networkInfo.dns[3],
                    networkInfo.mac[0], networkInfo.mac[1],
                    networkInfo.mac[2], networkInfo.mac[3],
                    networkInfo.mac[4], networkInfo.mac[5]);
            UART1_Write(message, sizeof(message));
            
            char ipbuf[32];
            memset(ipbuf, 0, sizeof(ipbuf));
            sprintf(ipbuf, ">> DHCP IP: %d.%d.%d.%d\r\n", 
                    networkInfo.ip[0], networkInfo.ip[1], 
                    networkInfo.ip[2], networkInfo.ip[3]);
            UART1_Write(ipbuf, strlen(ipbuf));
                    
            network_init(networkInfo);
            openTCPSocket();
            TMR2_Stop();
            TMR2_CallbackRegister(TIMER2_InterruptSvcRoutine, (uintptr_t)NULL);
            TMR2_Start();
            DHCP_init(SOCK_DHCP, gDATABUF);
            break;

        default:
            break;
    }
}

// Initializes network settings
void network_init(wiz_NetInfo net) {
    setSn_KPALVTR(DATASOCKET, 1); // Set keep alive, 1 = 5 sec, 10 = 50 sec
    ctlnetwork(CN_SET_NETINFO, (void*)&net);
}

// Open TCP sockets and enable interrupts
void openTCPSocket(void) {
    UART1_Write("Waiting for connection...\r\n", 27);
    if (socket(DATASOCKET, Sn_MR_TCP, portNumber, 0) == DATASOCKET) {
        if (listen(DATASOCKET) == SOCK_OK) {

            // Clear previous interrupt flags
            uint8_t sock_mask = Sn_IR_CON | Sn_IR_RECV | Sn_IR_DISCON;
            ctlsocket(DATASOCKET, CS_CLR_INTERRUPT, &sock_mask);
            ctlsocket(DATASOCKET, CS_SET_INTMASK, &sock_mask);
            
            // Enable W5500 global interrupt for this socket
#if (_WIZCHIP_ == W5500)
            uint16_t wiz_mask = (1 << DATASOCKET) << 8;
#else
            uint16_t wiz_mask = (1 << DATASOCKET);
#endif
            ctlwizchip(CW_SET_INTRMASK, &wiz_mask);

            UART1_Write(">> Socket listening on port 23\r\n", 31);
            
            // Enable socket interrupts
            /*setSn_IMR(DATASOCKET, (Sn_IR_CON | Sn_IR_RECV | Sn_IR_DISCON));
               
            writeIMR(0xFF);  // Enable interrupts on all sockets (bits 0-7)
            
            uint8_t status = getSn_SR(DATASOCKET);
            char status_msg[32];
            sprintf(status_msg, "Socket status: 0x%02X\r\n", status);
            UART1_Write(status_msg, strlen(status_msg));
            
            // Delay for 1ms for the connection process
            while ((sockStatus = getSn_SR(DATASOCKET)) == SOCK_LISTEN) {
                tmp = 0xFFFF;
                while (tmp--);
                //clientConnected = true;
            }

            memset(message, 0, sizeof(message));
            strcpy(message, ">> Connected\r\n");
            UART1_Write(message, sizeof(message));*/

        }
    }
}

// INT0 setup for RB7
void configure_INT0_on_RB7(void) {
    TRISBbits.TRISB7 = 1;      // Set RB7 as input
    INTCONbits.INT0EP = 1;     // Falling edge trigger
    IFS0bits.INT0IF = 0;       // Clear INT0 flag
    IEC0bits.INT0IE = 1;       // Enable INT0 interrupt
    __builtin_enable_interrupts();
}

// INT0 ISR for W5500 interrupt
void __ISR(_EXTERNAL_0_VECTOR, IPL4SRS) _INT0Interrupt(void) {
    UART1_Write("INT0 ISR triggered\r\n", 20);
    IFS0bits.INT0IF = 0;  // Clear interrupt flag

    uint8_t sir;
    ctlwizchip(CW_GET_INTERRUPT, &sir); // Get global socket interrupt flags

    sprintf(message, "ISR: sir=0x%02X\r\n", sir);
    UART1_Write(message, strlen(message));

    for (int i = 0; i < _WIZCHIP_SOCK_NUM_; i++) {
        if (sir & (1 << i)) {
            uint8_t ir = getSn_IR(i);

            if (ir & Sn_IR_CON) {
                UART1_Write(">> Sn_IR_CON triggered\r\n", 24);
                setSn_IR(i, Sn_IR_CON); // Clear connection flag
                clientConnected = true;
                sprintf(message, ">> Socket %d connected\r\n", i);
                UART1_Write(message, strlen(message));
            }

            if (ir & Sn_IR_RECV) {
                setSn_IR(i, Sn_IR_RECV);  // Clear RECV flag
                clientConnected = true;
                uint8_t buffer[150];
                memset(buffer, 0, sizeof(buffer));
                int16_t bytes = recv(i, buffer, sizeof(buffer));

                if (bytes > 0) {
                    send(i, buffer, bytes);  // Echo back
                }
            }

            if (ir & Sn_IR_DISCON) {
                setSn_IR(i, Sn_IR_DISCON);  // Clear DISCON flag
                close(i);
                sprintf(message, ">> Socket %d disconnected\r\n", i);
                UART1_Write(message, strlen(message));
                openTCPSocket();
            }
        }
    }

    ctlwizchip(CW_CLR_INTERRUPT, &sir); // Clear global socket interrupts
}


// Handle client data
void handleClientCommunication(uint8_t socket) {
    // Now wait to receive a message
    uint8_t receivedMsg[150]; // Adjust the size based on expected message length
    int16_t bytesRead;
    
    memset(receivedMsg, 0, sizeof(receivedMsg));
    bytesRead = recv(DATASOCKET, receivedMsg, sizeof(receivedMsg));

    if (bytesRead > 0) {
        receivedMsg[bytesRead] = '\0';
        sprintf(message, "Client >> %s\r\n", receivedMsg);
        UART1_Write(message, strlen(message));

        switch (receivedMsg[0]) {
            case 'I': {
                uint8_t ip[4];
                getSIPR(ip);
                char ipStr[32];
                sprintf(ipStr, "Assigned IP: %d.%d.%d.%d\r\n", 
                        ip[0], ip[1], ip[2], ip[3]);
                send(socket, (uint8_t *)ipStr, strlen(ipStr) + 1);
                sprintf(message, "Wiznet >> %s\r\n",ipStr);
                break;
            }
            case 'M': {
                uint8_t mac[6];
                getSHAR(mac);
                char macStr[40];
                sprintf(macStr, "MAC: %02X:%02X:%02X:%02X:%02X:%02X\r\n",
                        mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]);
                send(socket, (uint8_t *)macStr, strlen(macStr) + 1);
                sprintf(message, "Wiznet >> %s\r\n",macStr);
                break;
            }
            default: {
                send(socket, receivedMsg, bytesRead);  // Echo back
                sprintf(message, "Wiznet >> %s\r\n",receivedMsg);
                break;
            }
        }
    }
}
