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

#include <tim.h>

#include <counter.h>
#include <show.h>
#include <logger.h>
#include <PontCoopScheduler.h>
#include <wizHelper.h>
#include <config.h>
#include <socket.h>
#include <sinkStruct.h>
#include <sha256.h>



const uint32_t COUNTER_FREQUENCY = 1.0e6;

// 100: 2 digits behind decimal point
// 1000: 3 digits behind decimal point
const uint32_t PRECISION = 1000;

volatile static uint32_t mainsCntSum = 0;
volatile static uint32_t mainsCntCnt = 0;

static t_seconds *seconds;



#define NUM_OF_MINUTE_BUFFERS 2
t_minuteBuffer minuteBuffers[NUM_OF_MINUTE_BUFFERS];
uint8_t activeMinuteBuffer = 0;

static t_configBlock *config;

extern uint8_t SINK_SOCK;
const uint16_t SINK_PORT = 20169;

int8_t counterSendMinuteBuffer(t_minuteBuffer *minuteBuffer) {
    uint8_t sinkAddr[4];
    if (! wizDnsQuery(config->sinkServer, sinkAddr)) {
        coloredMsg(LOG_BLUE, "csmb, failed to resolve sink server name");
        return -1;
    } else {
        coloredMsg(LOG_BLUE, "csmb, sink server at %d.%d.%d.%d", sinkAddr[0], sinkAddr[1], sinkAddr[2], sinkAddr[3]);
    }


    socket(SINK_SOCK, Sn_MR_UDP, SINK_PORT, 0);
    uint8_t sockState = getSn_SR(SINK_SOCK);
    if (sockState == SOCK_UDP) {
        sendto(SINK_SOCK, minuteBuffer->b, sizeof(minuteBuffer->b), sinkAddr, SINK_PORT);
        coloredMsg(LOG_BLUE, "csmb, sent");
    } else {
        coloredMsg(LOG_BLUE, "csmb, socket in unexpected state: %d", sockState);
        return -2;
    }

    close(SINK_SOCK);

    return 1;
}

void counterMinuteTick(void *handle) {
    for (uint8_t minuteBufferIdx = 0; minuteBufferIdx < NUM_OF_MINUTE_BUFFERS; minuteBufferIdx++) {
        t_minuteBuffer *minuteBuffer = &(minuteBuffers[minuteBufferIdx]);

        if (minuteBuffer->s.done == 1) {
            coloredMsg(LOG_BLUE, "cmt, buffer %d is done, handle it", minuteBufferIdx);
            memset(minuteBuffer->s.deviceId, 0, sizeof(minuteBuffer->s.deviceId));
            strcpy(minuteBuffer->s.deviceId, config->deviceId);

            memcpy(minuteBuffer->s.hash, config->sharedSecret, SHA256_BLOCK_SIZE);
            SHA256_CTX ctx;
            sha256_init(&ctx);
            sha256_update(&ctx, minuteBuffer->b, sizeof(minuteBuffer->b));
            sha256_final(&ctx, minuteBuffer->s.hash);
            coloredMsg(LOG_BLUE, "cmt, hash, 1. half is %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
                       minuteBuffer->s.hash[0],
                       minuteBuffer->s.hash[1],
                       minuteBuffer->s.hash[2],
                       minuteBuffer->s.hash[3],
                       minuteBuffer->s.hash[4],
                       minuteBuffer->s.hash[5],
                       minuteBuffer->s.hash[6],
                       minuteBuffer->s.hash[7],
                       minuteBuffer->s.hash[8],
                       minuteBuffer->s.hash[9],
                       minuteBuffer->s.hash[10],
                       minuteBuffer->s.hash[11],
                       minuteBuffer->s.hash[12],
                       minuteBuffer->s.hash[13],
                       minuteBuffer->s.hash[14],
                       minuteBuffer->s.hash[15]
                       );
            coloredMsg(LOG_BLUE, "cmt, hash, 2. half is %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x",
                       minuteBuffer->s.hash[16],
                       minuteBuffer->s.hash[17],
                       minuteBuffer->s.hash[18],
                       minuteBuffer->s.hash[19],
                       minuteBuffer->s.hash[20],
                       minuteBuffer->s.hash[21],
                       minuteBuffer->s.hash[22],
                       minuteBuffer->s.hash[23],
                       minuteBuffer->s.hash[24],
                       minuteBuffer->s.hash[25],
                       minuteBuffer->s.hash[26],
                       minuteBuffer->s.hash[27],
                       minuteBuffer->s.hash[28],
                       minuteBuffer->s.hash[29],
                       minuteBuffer->s.hash[30],
                       minuteBuffer->s.hash[31]
                       );

            int8_t res = counterSendMinuteBuffer(minuteBuffer);
            if (res == 1) {
                coloredMsg(LOG_BLUE, "cmt, successfully sent");
                minuteBuffer->s.done = 0;
            } else {
                coloredMsg(LOG_BLUE, "cmt, not successfully sent, try again");
                schAdd(counterMinuteTick, NULL, 1000, 0);
            }
        }
    }
}

void counterSecondTick(void *handle) {
    static uint8_t bufferIdx = 0;
    uint32_t tmpSum = 0;
    uint32_t tmpCnt = 0;

    HAL_NVIC_DisableIRQ(TIM1_CC_IRQn);
    tmpSum = mainsCntSum;
    mainsCntSum = 0;
    tmpCnt = mainsCntCnt;
    mainsCntCnt = 0;
    HAL_NVIC_EnableIRQ(TIM1_CC_IRQn);

    uint32_t cnt = tmpSum / tmpCnt;
    uint32_t freq = PRECISION * COUNTER_FREQUENCY / cnt;

    coloredMsg(LOG_GREEN, "cst, Freq: %lu", freq);
    coloredMsg(LOG_GREEN, "cst, Tick: %d %lu %lu", seconds->valid, seconds->missedUpdates, seconds->seconds);

    if (! seconds->valid) {
        coloredMsg(LOG_RED, "cst, time is not yet valid");
    } else if (minuteBuffers[activeMinuteBuffer].s.done == 1) {
        coloredMsg(LOG_RED, "cst, minute buffer overrun");
    } else {
        minuteBuffers[activeMinuteBuffer].s.events[bufferIdx].timestamp = seconds->seconds;
        minuteBuffers[activeMinuteBuffer].s.events[bufferIdx].frequency = freq;
        coloredMsg(LOG_GREEN, "cst, added to buffer %d, slot %d", activeMinuteBuffer, bufferIdx);
        bufferIdx += 1;
        if (bufferIdx == SECONDS_PER_MINUTE) {
            coloredMsg(LOG_GREEN, "cst, buffer %d is done", activeMinuteBuffer);
            minuteBuffers[activeMinuteBuffer].s.done = 1;
            activeMinuteBuffer += 1;
            bufferIdx = 0;
            if (activeMinuteBuffer >= NUM_OF_MINUTE_BUFFERS) {
                activeMinuteBuffer = 0;
            }
            schAdd(counterMinuteTick, NULL, 1000, 0);
        }
    }
}

void mainsCntsInputCaptureCallback(TIM_HandleTypeDef *htim) {
    static uint8_t state = 0;
    static uint32_t savedV = 0;

    uint32_t v = HAL_TIM_ReadCapturedValue(htim, TIM_CHANNEL_1);
    if (state == 0) {
        show(DEBUG_2, ON);
        savedV = v;
        state = 1;
    } else if (state == 1) {
        show(DEBUG_2, OFF);
        uint32_t captured = (savedV < v) ? (v - savedV) : ((htim->Init.Period - savedV) + v);
        mainsCntSum += captured;
        mainsCntCnt += 1;
        state = 0;
    } else {
        state = 0;
    }
}


void counterInit() {
    seconds = wizGetSeconds();
    minuteBuffers[0].s.done = 0;
    minuteBuffers[1].s.done = 0;

    config = getConfig();

    schAdd(counterSecondTick, NULL, 0, 1000);

    HAL_TIM_IC_Start_IT(&mainsCnt, TIM_CHANNEL_1);
}