#include "ltrsrv_log_proto.h"
#include "ltrsrv_cfg.h"
#include "ltrsrv_defs.h"
#include "ltrsrv_client.h"

#include <string.h>
#include <stdio.h>
#include <stdarg.h>
#include <stddef.h>


#define LTR_LOGPROTO_MSG_SIGN   0xA54C

typedef struct {
    uint16_t sign;
    uint16_t size;
    int32_t  err;
    uint64_t time;
    uint8_t  lvl;
    uint8_t  res[15];
    char     msg[1];
} t_ltr_logproto_msg;

#define LTR_LOGPROTO_MSG_HDR_SIZE (offsetof(t_ltr_logproto_msg, msg))


typedef enum {
    LOG_CLIENT_STATUS_WORK = 0,
    LOG_CLIENT_STATUS_ERR  = 1,
    LOG_CLIENT_STATUS_OVERFLOW = 2
} t_log_client_status;


typedef struct {
    struct st_ltr_client* client;
    unsigned get_pos;
    uint8_t log_lvl;
    uint8_t wait;
    uint8_t status;
} t_logproto_client_info;


static uint8_t f_log_buf[LTRSRV_LOGPROTO_BUF_SIZE];
static unsigned f_put_log_rec_pos = 0;
static unsigned f_get_log_rec_pos = 0;

static t_logproto_client_info f_log_clients[LTRSRV_LOGPROTO_CLIENT_CNT];
static unsigned f_logclient_cnt = 0;

static char f_tmp_buf[512];

static void f_send_to_client(t_logproto_client_info *logclient);

static void f_clean_clients(struct st_ltr_client *cur_client) {
    int i;
    for (i=f_logclient_cnt-1; i >=0; i--) {
        if (f_log_clients[i].status!=LOG_CLIENT_STATUS_WORK) {
            struct st_ltr_client *client = f_log_clients[i].client;
            if (i!=(int)(f_logclient_cnt-1)) {
                memmove(&f_log_clients[i], &f_log_clients[i+1],
                        (f_logclient_cnt-i-1)*sizeof(f_log_clients[0]));
            }
            f_logclient_cnt--;
            ltrsrv_client_close_req(client, 0);
        }
    }
}


static int f_is_overflow(unsigned get_pos, unsigned old_put_pos, unsigned next_put_pos) {
    return  next_put_pos > old_put_pos ?
                (get_pos > old_put_pos) && (get_pos <= next_put_pos) :
                (get_pos > old_put_pos) || (get_pos <= next_put_pos);
}


static void f_update_put_pos(size_t size) {
    unsigned i;
    unsigned next_pos = f_put_log_rec_pos+size;
    if (next_pos == LTRSRV_LOGPROTO_BUF_SIZE)
        next_pos = 0;

    for (i=0; i < f_logclient_cnt; i++) {
        if ((f_log_clients[i].status == LOG_CLIENT_STATUS_WORK) &&
                f_is_overflow(f_log_clients[i].get_pos, f_put_log_rec_pos, next_pos)) {
            /* равенство используется, чтобы обгон записи ровно на буфер
             * отличить от варианта, когда все передано */
            f_log_clients[i].status = LOG_CLIENT_STATUS_OVERFLOW;
        }
    }

    while (f_is_overflow(f_get_log_rec_pos, f_put_log_rec_pos, next_pos)) {
        uint16_t msg_size = 0;
        unsigned size_pos = f_get_log_rec_pos + 2;

        if (size_pos >= LTRSRV_LOGPROTO_BUF_SIZE)
            size_pos -= LTRSRV_LOGPROTO_BUF_SIZE;
        msg_size = f_log_buf[size_pos];

        size_pos++;
        if (size_pos==LTRSRV_LOGPROTO_BUF_SIZE)
            size_pos=0;

        msg_size+= (uint16_t)f_log_buf[size_pos] << 8;

        f_get_log_rec_pos += msg_size;
        if (f_get_log_rec_pos >= LTRSRV_LOGPROTO_BUF_SIZE)
            f_get_log_rec_pos-=LTRSRV_LOGPROTO_BUF_SIZE;
    }

    f_put_log_rec_pos = next_pos;
}


static void f_cpy_to_log(const uint8_t *data, size_t size) {
    unsigned end_size = LTRSRV_LOGPROTO_BUF_SIZE - f_put_log_rec_pos;
    unsigned put_size = MIN(size, end_size);
    unsigned put_pos = f_put_log_rec_pos;

    f_update_put_pos(put_size);
    memcpy(&f_log_buf[put_pos], data, put_size);

    if (put_size != size) {
        f_update_put_pos(size-put_size);
        memcpy(&f_log_buf[0], &data[put_size], size-put_size);
    }
}


static void f_send_to_clients(void) {
    unsigned i;
    for (i=0; i < f_logclient_cnt; i++) {
        f_send_to_client(&f_log_clients[i]);
    }
}

static int f_client_snd_rdy(struct st_ltr_client *client) {
    unsigned i;
    for (i=0; i < f_logclient_cnt; i++) {
        if (f_log_clients[i].client==client) {
            f_log_clients[i].wait = 0;
            f_send_to_client(&f_log_clients[i]);
        }
    }
    return 0;
}


static void f_send_to_client(t_logproto_client_info *logclient) {
    unsigned client_pos;
    while (!logclient->wait && (logclient->status == LOG_CLIENT_STATUS_WORK) &&
           ((client_pos=logclient->get_pos) != f_put_log_rec_pos)) {
        int sent_size, send_size = client_pos < f_put_log_rec_pos ?
                    f_put_log_rec_pos - client_pos :
                    LTRSRV_LOGPROTO_BUF_SIZE - client_pos;

        sent_size = ltrsrv_client_put_bytes(logclient->client,
                                            &f_log_buf[client_pos],
                                            send_size);
        if (sent_size>=0) {
            client_pos+=sent_size;
            if (client_pos==LTRSRV_LOGPROTO_BUF_SIZE)
                client_pos = 0;

            logclient->get_pos = client_pos;
            logclient->wait = sent_size != send_size;
            /* если не удалось передать все данные, то ждем, пока сокет станет
             * доступен на запись */
            if (logclient->wait) {
                ltrsrv_client_wait_snd_rdy(logclient->client, f_client_snd_rdy);
            }
        } else {
            logclient->status = LOG_CLIENT_STATUS_ERR;
        }
    }
}

/* добавление записей в буфер лога */
void ltrsrv_logproto_add_rec(t_ltrsrv_log_level lvl, int err,
                             const char *fmt, ...) {

    t_ltr_logproto_msg hdr;
    unsigned msg_size = 0;
    va_list args;
    va_start(args, fmt);
    vsprintf(f_tmp_buf, fmt, args);
    va_end(args);

    msg_size = strlen(f_tmp_buf) + 1;

    hdr.sign = LTR_LOGPROTO_MSG_SIGN;
    hdr.size = (uint16_t)(LTR_LOGPROTO_MSG_HDR_SIZE + msg_size);
    UINT64_ASSIGN32(hdr.time, time(NULL));
    hdr.lvl = lvl;
    hdr.err = err;
    memset(hdr.res, 0, sizeof(hdr.res));
    f_cpy_to_log((uint8_t*)&hdr, LTR_LOGPROTO_MSG_HDR_SIZE);
    f_cpy_to_log((uint8_t*)f_tmp_buf, msg_size);


    f_send_to_clients();
    f_clean_clients(0);
}

int ltrsrv_logproto_add_client(struct st_ltr_client *client) {
    if (f_logclient_cnt==LTRSRV_LOGPROTO_CLIENT_CNT) {
        return LTRSRV_ERR_NO_FREE_LOGPROTO_CLIENTS;
    }

    f_log_clients[f_logclient_cnt].client = client;
    f_log_clients[f_logclient_cnt].get_pos = f_get_log_rec_pos;
    f_log_clients[f_logclient_cnt].wait = 0;
    f_log_clients[f_logclient_cnt].status = LOG_CLIENT_STATUS_WORK;
    f_logclient_cnt++;

    f_send_to_client(&f_log_clients[f_logclient_cnt-1]);
    return 0;
}




int ltrsrv_logproto_notify_data_rdy(struct st_ltr_client *client) {
    uint8_t byte;
    int ret = ltrsrv_client_get_bytes(client, &byte, 1);
    if (ret <= 0) {
        unsigned i;
        for (i=0; i < f_logclient_cnt; i++) {
            if (f_log_clients[i].client == client) {
                f_log_clients[i].status = LOG_CLIENT_STATUS_ERR;
            }
        }
        /* этот вызов идет из обработчика для клиента, поэтому
         * клиента сразу удалять нельзя, а только устанавлвиваем
         * запрос на удаление, для удаления в конце обработчика */
        f_clean_clients(client);
    }
    return 0;
}


void ltrsrv_logproto_close(void) {
    f_logclient_cnt = 0;
}
