#include <main.h>
#include <usart.h>
#include <show.h>
#include <PontCoopScheduler.h>
#include <logger.h>
#include <ringbuffer.h>
#include <wizHelper.h>
#include <socket.h>

#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <stdio.h>

#ifdef LOGGER_OUTPUT_BY_INTERRUPT
#include <stm32f103xe.h>
#endif //LOGGER_OUTPUT_BY_INTERRUPT

#ifndef LOGGER_OUTPUT_BY_INTERRUPT
#include <stm32f1xx_hal_uart.h>
#endif //LOGGER_OUTPUT_BY_INTERRUPT


#define LOGBUFFER_SIZE 2048
#define MSGBUFFER_SIZE 256


extern const uint8_t SYSLOG_SOCK;
const uint8_t syslogAddr[] = { 172, 16, 11, 15 }; 

uint8_t singleOctetTXBuffer;

static ringbuffer_t logBuffer;

void logInit() {
    ringbufferInit(&logBuffer, LOGBUFFER_SIZE);
}

void logFree() {
    ringbufferFree(&logBuffer);
}

#ifdef LOGGER_OUTPUT_BY_INTERRUPT
void debugTxCpltCallback(UART_HandleTypeDef *huart) {
    int c = ringbufferGetOne(&logBuffer);
    if (c > 0) {
        singleOctetTXBuffer = (uint8_t) c;
        HAL_UART_Transmit_IT(&debugUart, &singleOctetTXBuffer, 1);
    }
} 
#endif //LOGGER_OUTPUT_BY_INTERRUPT

#ifndef LOGGER_OUTPUT_BY_INTERRUPT
int logExec() {
    int c = -1;
    if (__HAL_UART_GET_FLAG(&debugUart, UART_FLAG_TXE)) { // is the TX channel free
        c = ringbufferGetOne(&logBuffer);
        if (c > 0) {
            // transfer to TX channel
            uint8_t cc = (uint8_t) c;
            HAL_UART_Transmit(&debugUart, &cc, 1, HAL_MAX_DELAY);
        }
    }
    return c;
}
#endif //LOGGER_OUTPUT_BY_INTERRUPT

void syslog(char *msg) {
    static uint8_t state = 0;
    int8_t res8 = 0;
    if (isNetworkAvailable()) {
        switch (state) {
            case 0:
                res8 = socket(SYSLOG_SOCK, Sn_MR_UDP, 514, SF_IO_NONBLOCK);
                if (res8 == SYSLOG_SOCK) {
                    state = 1;
                }
                break;
            case 1:
                sendto(SYSLOG_SOCK, (uint8_t*)msg, strlen(msg), syslogAddr, 514);
                break;
        }
    }
}

static int innerLogMsg(const char *pre, const char *post, bool syslogToo, const char *format, va_list vl) {
    const static char SYSLOG_HEADER[] = "<133>1 ";
    #define MAX_PREFIX_SIZE 20
    int res = -1;
    int offset = 0;
    char msgBuffer[MSGBUFFER_SIZE+MAX_PREFIX_SIZE];
    char *bufferStart;
    memset(msgBuffer, 0, MSGBUFFER_SIZE+MAX_PREFIX_SIZE);

    uint16_t syslogHeaderSize = strlen(SYSLOG_HEADER);
    uint16_t preSize = (pre) ? strlen(pre) : 0;
    uint16_t prefixSize = (syslogHeaderSize > preSize) ? syslogHeaderSize : preSize;
    if (prefixSize > MAX_PREFIX_SIZE) {
        return -1;
    }
    bufferStart = msgBuffer + prefixSize;

    int vcnt = vsnprintf(bufferStart, MSGBUFFER_SIZE, format, vl);
    if (vcnt < MSGBUFFER_SIZE) {
        if (syslogToo) {
            memcpy(bufferStart - syslogHeaderSize, SYSLOG_HEADER, syslogHeaderSize);
            syslog(bufferStart - syslogHeaderSize);
        }

        if (pre) {
            memcpy(bufferStart - preSize, pre, preSize);
        }
        if (post) {
            strcat(bufferStart - preSize, post);
        }
#ifdef LOGGER_OUTPUT_BY_INTERRUPT
        HAL_NVIC_DisableIRQ(UART4_IRQn);
#endif //LOGGER_OUTPUT_BY_INTERRUPT

        res = ringbufferPut(&logBuffer, (uint8_t*) (bufferStart - preSize), strlen(bufferStart - preSize));

#ifdef LOGGER_OUTPUT_BY_INTERRUPT        
        HAL_NVIC_EnableIRQ(UART4_IRQn);
#endif //LOGGER_OUTPUT_BY_INTERRUPT        

#ifdef LOGGER_OUTPUT_BY_INTERRUPT
        debugTxCpltCallback(NULL);
#endif LOGGER_OUTPUT_BY_INTERRUPT        
    }

    return res;
}

int logMsg(const char *format, ...) {
    va_list vl;
    va_start(vl, format);
    int res = innerLogMsg(NULL, "\r\n", false, format, vl);
    va_end(vl);
    return res;
}

int coloredMsg(const t_logColor color, bool syslogToo, const char *format, ...) {
    const static char POST[] = "\x1b[0m\r\n";
    const static char HIGH[] = "\x1b[1m";
    const static char RED[] = "\x1b[31;1m";
    const static char GREEN[] = "\x1b[32;1m";
    const static char BLUE[] = "\x1b[34;1m";
    const static char YELLOW[] = "\x1b[33;1m";
    const char *pre = NULL;
    switch (color) {
        case LOG_HIGH:
        pre = HIGH;
        break;
        case LOG_RED:
        pre = RED;
        break;
        case LOG_BLUE:
        pre = BLUE;
        break;
        case LOG_GREEN:
        pre = GREEN;
        break;
        case LOG_YELLOW:
        pre = YELLOW;
        break;
    }
    va_list vl;
    va_start(vl, format);
    int res = innerLogMsg(pre, POST, syslogToo, format, vl);
    va_end(vl);
    return res;
}