#include <stdint.h>
#include <string.h>

#include <networkAbstractionLayer_impl.h>
#include <logger.h>
#include <PontCoopScheduler.h>

#include <wizHelper.h>
#include <wizchip_conf.h>
#include <socket.h>
#include <config.h>
#include <sntp.h>


static t_configBlock *config;



static const uint64_t UNIX_NTP_EPOCH_DIFF = 2208988800;
static const uint16_t NTP_PORT = 123;
static const uint16_t MAX_SNTP_RETRIES = 100;
extern const uint8_t SNTP_SOCK;



const uint8_t SEND_LI_VN_MODE = 0xe3; // LI: unknown (3), VN: 4, Mode: Client (3)
typedef struct s_ntpStruct {
    uint8_t   li_vn_mode;
    uint8_t   stratum;
    uint8_t   poll;
    uint8_t   precision;
    uint32_t  rootdelay;
    uint32_t  rootdisp;
    uint32_t  refid;
    //uint64_t  reftime;
    uint32_t reftime_h;
    uint32_t reftime_l;
    //uint64_t  org;
    uint32_t org_h;
    uint32_t org_l;
    //uint64_t  rec;
    uint32_t rec_h;
    uint32_t rec_l;
    //uint64_t  xmt;
    uint32_t xmt_h;
    uint32_t xmt_l;
} t_ntpStruct;

typedef union __attribute__((__packed__)) {
    t_ntpStruct s;
    uint8_t b[sizeof(t_ntpStruct)];
} ntpMsg_t;

typedef struct {
    ntpMsg_t ntpMsg;
    uint8_t ntpAddr[4];
    uint16_t retryCount;
    uint64_t seconds;
    enum {
        SNTP_STATE_IDLE,
        SNTP_STATE_START, 
        SNTP_STATE_SEND,
        SNTP_STATE_RECV,
        SNTP_STATE_DONE,
        SNTP_STATE_ERROR
    } sntpState;
} sntpEngineHandle_t;

sntpEngineHandle_t sntpEngineHandle = { 
    .seconds = 0, 
    .retryCount = 0, 
    .sntpState = SNTP_STATE_IDLE 
};


void networkSntpEngine(void *handle) {
    sntpEngineHandle_t *localHandle = (sntpEngineHandle_t*) handle;
    if (isNetworkAvailable()) {
        switch (localHandle->sntpState) {
            case SNTP_STATE_START:
                coloredMsg(LOG_BLUE, "nes, resolve ntp server");
                if (! wizDnsQuery(config->ntpServer, localHandle->ntpAddr)) {
                    coloredMsg(LOG_BLUE, "nes, failed to resolve ntp server");
                    localHandle->sntpState = SNTP_STATE_ERROR;
                } else {
                    localHandle->sntpState = SNTP_STATE_SEND;
                    schAdd(networkSntpEngine, (void*) localHandle, 10, 0);
                }
                break;
            case SNTP_STATE_SEND:
                coloredMsg(LOG_BLUE, "nes, about to call SNTP");
                localHandle->retryCount = 0;
                socket(SNTP_SOCK, Sn_MR_UDP, NTP_PORT, 0);
                uint8_t sockState = getSn_SR(SNTP_SOCK);
                if (sockState == SOCK_UDP) {
                    memset(&(localHandle->ntpMsg), 0, sizeof(localHandle->ntpMsg));
                    localHandle->ntpMsg.s.li_vn_mode = SEND_LI_VN_MODE;
                    localHandle->ntpMsg.s.xmt_l = 1;
                    sendto(SNTP_SOCK, localHandle->ntpMsg.b, 
                           sizeof(localHandle->ntpMsg.b), localHandle->ntpAddr, NTP_PORT);
                    coloredMsg(LOG_BLUE, "nes, sent");
                    localHandle->sntpState = SNTP_STATE_RECV;
                    schAdd(networkSntpEngine, (void*) localHandle, 100, 0);
                } else {
                    coloredMsg(LOG_BLUE, "nes, socket in unexpected state %d", sockState);
                    localHandle->sntpState = SNTP_STATE_ERROR;
                }
                break;
            case SNTP_STATE_RECV:
                coloredMsg(LOG_BLUE, "nes, check receive");
                localHandle->retryCount += 1;
                uint16_t recvLen = getSn_RX_RSR(SNTP_SOCK);
                if (recvLen == 0) {
                    if (localHandle->retryCount > MAX_SNTP_RETRIES) {
                        coloredMsg(LOG_BLUE, "nes, max retry count reached, failed");
                        localHandle->sntpState = SNTP_STATE_ERROR;
                    } else {
                        coloredMsg(LOG_BLUE, "nes, nothing received yet, try again");
                        schAdd(networkSntpEngine, (void*) localHandle, 100, 0);
                    }
                } else if (recvLen >= sizeof(localHandle->ntpMsg)) {
                    memset(&(localHandle->ntpMsg), 0, sizeof(localHandle->ntpMsg));
                    uint8_t srcAddr[4];
                    uint16_t srcPort;
                    
                    recvfrom(SNTP_SOCK, localHandle->ntpMsg.b,
                             sizeof(localHandle->ntpMsg.b), srcAddr, &srcPort);
                    
                    
                    uint8_t *buf = localHandle->ntpMsg.b;
                    uint8_t x = 0;
                    coloredMsg(LOG_BLUE, "%02x %02x %02x %02x %02x %02x %02x %02x  %02x %02x %02x %02x %02x %02x %02x %02x",
                               buf[x+0], buf[x+1], buf[x+2], buf[x+3], buf[x+4], buf[x+5], buf[x+6], buf[x+7],
                               buf[x+8], buf[x+9], buf[x+10], buf[x+11], buf[x+12], buf[x+13], buf[x+14], buf[x+15]);
                    x += 16;
                    coloredMsg(LOG_BLUE, "%02x %02x %02x %02x %02x %02x %02x %02x  %02x %02x %02x %02x %02x %02x %02x %02x",
                               buf[x+0], buf[x+1], buf[x+2], buf[x+3], buf[x+4], buf[x+5], buf[x+6], buf[x+7],
                               buf[x+8], buf[x+9], buf[x+10], buf[x+11], buf[x+12], buf[x+13], buf[x+14], buf[x+15]);
                    x += 16;
                    coloredMsg(LOG_BLUE, "%02x %02x %02x %02x %02x %02x %02x %02x  %02x %02x %02x %02x %02x %02x %02x %02x",
                               buf[x+0], buf[x+1], buf[x+2], buf[x+3], buf[x+4], buf[x+5], buf[x+6], buf[x+7],
                               buf[x+8], buf[x+9], buf[x+10], buf[x+11], buf[x+12], buf[x+13], buf[x+14], buf[x+15]);
                    x += 16;
                    coloredMsg(LOG_BLUE, "%02x %02x %02x %02x %02x %02x %02x %02x  %02x %02x %02x %02x %02x %02x %02x %02x",
                               buf[x+0], buf[x+1], buf[x+2], buf[x+3], buf[x+4], buf[x+5], buf[x+6], buf[x+7],
                               buf[x+8], buf[x+9], buf[x+10], buf[x+11], buf[x+12], buf[x+13], buf[x+14], buf[x+15]);

                    close(SNTP_SOCK);
                    coloredMsg(LOG_BLUE, "nes, msg received from %d.%d.%d.%d:%d", 
                               srcAddr[0], srcAddr[1], srcAddr[2], srcAddr[3],
                               srcPort);
                    
                    coloredMsg(LOG_BLUE, "nes, received in the %d. cycles", localHandle->retryCount);
                    uint32_t xmt_h = localHandle->ntpMsg.s.xmt_h;
                    localHandle->seconds = ((uint64_t)xmt_h) - UNIX_NTP_EPOCH_DIFF;
                    coloredMsg(LOG_BLUE, "nes, xmt: %08x", xmt_h);
                    coloredMsg(LOG_BLUE, "nes, seconds: %lu", localHandle->seconds);
                    localHandle->sntpState = SNTP_STATE_DONE;
                } else {
                    coloredMsg(LOG_BLUE, "nes, invalid number of octets received: %d", recvLen);
                    localHandle->sntpState = SNTP_STATE_ERROR;
                }
                break;
            default:
                coloredMsg(LOG_BLUE, "nes, unexpected state");
        }
    } else {
        coloredMsg(LOG_BLUE, "nes, no network yet, try again");
        schAdd(networkSntpEngine, (void*) localHandle, 100, 0);
    }
}

uint64_t networkSntpQuery() {
    uint64_t res = 0;

    if (sntpEngineHandle.sntpState == SNTP_STATE_IDLE) {
        coloredMsg(LOG_BLUE, "nsq, start sntp request");
        sntpEngineHandle.sntpState = SNTP_STATE_START;
        schAdd(networkSntpEngine, (void*) &sntpEngineHandle, 1, 0);
    } else if (sntpEngineHandle.sntpState == SNTP_STATE_DONE) {
        coloredMsg(LOG_BLUE, "nsq, start sntp done");
        res = sntpEngineHandle.seconds;
        coloredMsg(LOG_BLUE, "nsq, time is %lu", res);
        sntpEngineHandle.sntpState = SNTP_STATE_IDLE;
    } else if (sntpEngineHandle.sntpState == SNTP_STATE_ERROR) {
        coloredMsg(LOG_BLUE, "nsq, start sntp failed");
        sntpEngineHandle.sntpState = SNTP_STATE_IDLE;
    }

    return res;
}

extern uint8_t SINK_SOCK;

int8_t networkUdpSend(char *hostname, uint16_t port, uint8_t *buf, uint16_t bufLen) {
    uint8_t sinkAddr[4];
    if (! wizDnsQuery(hostname, sinkAddr)) {
        coloredMsg(LOG_BLUE, "nus, failed to resolve sink server name");
        return -1;
    } else {
        coloredMsg(LOG_BLUE, "nus, sink server at %d.%d.%d.%d", sinkAddr[0], sinkAddr[1], sinkAddr[2], sinkAddr[3]);
    }

    socket(SINK_SOCK, Sn_MR_UDP, port, 0);
    uint8_t sockState = getSn_SR(SINK_SOCK);
    if (sockState == SOCK_UDP) {
        sendto(SINK_SOCK, buf, bufLen, sinkAddr, port);
        coloredMsg(LOG_BLUE, "nus, sent");
    } else {
        coloredMsg(LOG_BLUE, "nus, socket in unexpected state: %d", sockState);
        return -2;
    }

    close(SINK_SOCK);

    return 1;
}

bool isNetworkAvailable() {
    return wizIsNetworkAvailable();
}

void networkImplInit() {
    config = getConfig();
    wizInit();
}