#include <stdarg.h>
#include <stddef.h>
#include <assert.h>
#include <lib/rtlog_buf.h>
#include <string.h>
#include <stdio.h>

#include <os_isr.h>
#include <trace_probes.h>

#ifndef trace_rtlog_print
trace_need(rtlog_print)
#endif


// этим макро включаю отладку функций журнала
#ifdef DEBUG_RTLOG
#define RTLOG_printf(...) debug_printf(__VA_ARGS__)
#else
#define RTLOG_printf(...)
#endif

// это настройки проверок строгости фнутри функций журнала
#ifndef RTLOG_STRICT_ARGS
//< проверять границы целостность границ индекса буфера записей журнала
#define RTLOG_STRICT_BOUNDS    1
//< проверять целостность хипа памяти отведенного под записи журнала
#define RTLOG_STRICT_MEM       2
//< проверять корректность количества аргументов
#define RTLOG_STRICT_ARGS      8
#endif

#ifndef RTLOG_STRICTNESS
#define RTLOG_STRICTNESS (RTLOG_STRICT_ARGS)
#endif

#define STRICT( level, body ) if ((RTLOG_STRICTNESS & RTLOG_STRICT_##level) != 0) {body;}

#if 0
void rtlog_validate(rtlog* u){
    assert(uos_valid_memory_address(u->store));
    mem_validate_block(u->store);
    assert(mem_size(u->store) >= (u->idx.mask * sizeof(rtlog_node)) );
}
#else
#define rtlog_validate(u)
#endif

//*\arg size - размер store в байтах. буфера выделяется как массив rtlog_node
//*            размером в степень2
void rtlog_init    ( rtlog* u, void* store, size_t size)
{
    size_t limit = size / sizeof(rtlog_node);
    assert(limit > 0);
    //rount to nearest power2
    size_t count = (1u<<31) >> __builtin_clz(limit) ;
    STRICT(BOUNDS, assert((count <= limit) && ((count*2) > limit) ) );
    ring_uindex_init(&u->idx, count);
    u->store = (rtlog_node*)store;
    u->stamp = 0;
}

//* это эмуляторы аналогичных функций печати. количество сохраняемых аргументов
//*     не более RTLOG_ARGS_LIMIT
int rtlog_printf  ( rtlog* u, unsigned nargs, ...){
    va_list args;
    int err;
    va_start (args, nargs);
    const char* fmt;
    fmt = va_arg(args, const char * );
    err = rtlog_vprintf(u, nargs, fmt, args);
    va_end (args);
    return err;
}


// захватить слот в бфере журнала
static
arch_state_t rtlog_take( rtlog* u ){
    unsigned slot;
    STRICT(MEM, rtlog_validate(u));
    arch_state_t x = arch_intr_off();
    if (ring_uindex_full(&u->idx))
        // drop first message if full
        ring_uindex_get(&u->idx);
    slot = u->idx.write;
    STRICT(BOUNDS, assert(slot <= u->idx.mask));
    rtlog_node* n = u->store + slot;
    n->stamp = u->stamp;

    return x;
}

static
void rtlog_release( rtlog* u, arch_state_t isrsave ){
    ring_uindex_put(&u->idx);
    ++(u->stamp);
    STRICT(BOUNDS, assert(u->idx.write <= u->idx.mask) );
    arch_intr_restore ( isrsave );
}




//  слоты могжно объединять, чтобы добавить количество аргументов к печати.
//  добавочные слоты с аргументами имеют fmt == NULL
//
enum{ slot_couples = RTLOG_ARGS_COUPLES};

int rtlog_vprintf ( rtlog* u, unsigned nargs, const char *fmt, va_list args){
    unsigned slot;
    trace_rtlog_print_on();

    STRICT(ARGS, )
        assert(nargs <= RTLOG_ARGS_LIMIT*slot_couples);
//        assert2(nargs <= RTLOG_ARGS_LIMIT
//                , "rtlog_vprintf passed %d args but supports max %d\n"
//                , nargs, RTLOG_ARGS_LIMIT
//                );
    if (nargs > RTLOG_ARGS_LIMIT*slot_couples)
        nargs = RTLOG_ARGS_LIMIT;

    arch_state_t x = rtlog_take(u);
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    unsigned i;

    if (slot_couples > 1){
        // аргументов слишком много, закину их в добавочные слоты
        unsigned couples = slot_couples;
        while ((nargs > RTLOG_ARGS_LIMIT) && (couples > 1)) {
            for (i = 0; i < RTLOG_ARGS_LIMIT; i++)
                n->args.uL[i] = va_arg(args, unsigned long);
            nargs -= RTLOG_ARGS_LIMIT;
            couples--;
            // размещу добавочный слот
            n->msg = NULL;
            ring_uindex_put(&u->idx);
            // размещу новый слот
            if (ring_uindex_full(&u->idx))
                // drop first message if full
                ring_uindex_get(&u->idx);
            slot = u->idx.write;
            n = u->store + slot;
            n->stamp = u->stamp;
        }
    };

    n->msg = fmt;
    for (i = 0; i < nargs; i++)
        n->args.uL[i] = va_arg(args, unsigned long);
    for (i = nargs; i < RTLOG_ARGS_LIMIT; i++)
        n->args.uL[i] = 0;

    rtlog_release(u, x);
    trace_rtlog_print_off();

    RTLOG_printf("rtlog: printf %s to %d[stamp%x] %d args : %x, %x, %x, %x, %x, %x\n"
                , fmt
                , slot, n->stamp
                , nargs
                , n->args[0], n->args[1], n->args[2], n->args[3]
                , n->args[4], n->args[5]
                );
    return 0;
}

int rtlog_puts( rtlog* u, const char *str){
    return rtlog_printf1(u, str, 0);
}



//------------------------------------------------------------------------------
int rtlog_printf1 ( rtlog* u, const char *fmt, long arg1)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.L[0] = arg1;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf2 ( rtlog* u, const char *fmt, long arg1, long arg2)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.L[0] = arg1;
    n->args.L[1] = arg2;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf4 ( rtlog* u, const char *fmt
                    , long arg1, long arg2, long arg3, long arg4)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->args.L[0] = arg1;
    n->args.L[1] = arg2;

#if RTLOG_ARGS_LIMIT >= 4
    n->msg = fmt;
    n->args.L[2] = arg3;
    n->args.L[3] = arg4;
    rtlog_release(u, x);
    return 1;
#else
    n->msg = NULL;
    // размещу добавочный слот
    assert(slot_couples > 1);
    ring_uindex_put(&u->idx);
    // размещу новый слот
    if (ring_uindex_full(&u->idx))
        // drop first message if full
        ring_uindex_get(&u->idx);
    slot = u->idx.write;
    n = u->store + slot;
    n->stamp = u->stamp;
    n->msg = fmt;
    n->args.L[0] = arg3;
    n->args.L[1] = arg4;
    rtlog_release(u, x);
    return 2;
#endif
}

int rtlog_printfLL( rtlog* u, const char *fmt, int64_t arg1)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.LL[0] = arg1;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf1D( rtlog* u, const char *fmt, double arg1)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.D[0] = arg1;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf1F( rtlog* u, const char *fmt, float arg1)
__noexcept
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.F[0] = arg1;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf2F( rtlog* u, const char *fmt, float arg1, float arg2)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = fmt;
    n->args.F[0] = arg1;
    n->args.F[1] = arg2;
    rtlog_release(u, x);
    return 1;
}

int rtlog_printf4F( rtlog* u, const char *fmt
                    , float arg1, float arg2, float arg3, float arg4)
__noexcept __NOTHROW
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = NULL;
    n->args.F[0] = arg1;
    n->args.F[1] = arg2;

    // размещу добавочный слот
    assert(slot_couples > 1);
    ring_uindex_put(&u->idx);
    // размещу новый слот
    if (ring_uindex_full(&u->idx))
        // drop first message if full
        ring_uindex_get(&u->idx);
    slot = u->idx.write;
    n = u->store + slot;
    n->stamp = u->stamp;
    n->msg = fmt;
    n->args.F[0] = arg3;
    n->args.F[1] = arg4;
    rtlog_release(u, x);
    return 2;
}

int rtlog_printfL3F( rtlog* u, const char *fmt
                    , long arg1, float arg2, float arg3, float arg4)
{
    unsigned slot;
    arch_state_t x = rtlog_take( u );
    slot = u->idx.write;
    rtlog_node* n = u->store + slot;
    n->msg = NULL;
    n->args.L[0] = arg1;
    n->args.F[1] = arg2;

    // размещу добавочный слот
    assert(slot_couples > 1);
    ring_uindex_put(&u->idx);
    // размещу новый слот
    if (ring_uindex_full(&u->idx))
        // drop first message if full
        ring_uindex_get(&u->idx);
    slot = u->idx.write;
    n = u->store + slot;
    n->stamp = u->stamp;
    n->msg = fmt;
    n->args.F[0] = arg3;
    n->args.F[1] = arg4;
    rtlog_release(u, x);
    return 2;
}


//-----------------------------------------------------------------------------
// для корректной печати аргументов, передавать в prinf, надо теми типами которыми
//      они печатаются. Поэтому для разных комбинаций аргументов буду делать вызывать
//      разные вызовы printf.
// выбор типа вызова буду вести на учете анализа строки формата - какие типы печатаются в '%'
enum support_args{
      st2L   = stL + (stL << stwide)
    , st2LL  = stLL + (stLL << stwide)
    , st2F   = stF + (stF << stwide)
    , stFL   = stF + (stL << stwide)
    , stLF   = stL + (stF << stwide)
    , st2D   = stD + (stD << stwide)
    , st3L   = st2L + (stL << stwide*2)
    , st4L   = st3L + (stL << stwide*3)
    , st3F   = st2F + (stF << stwide*2)
    , st4F   = st3F + (stFL << stwide*3)
    , stL3F   = stL + (st3F << stwide)
};

// чтобы уменьшить число вариантов печати, сведу эквивалентные по размеру аргументы
//  к нескольким вариантам - long32/64/float/double
static
int fmt_style_reduce(int style){
    enum {
          redS2     = 8   + (8 << stwide)
        , redS4     = redS2  + (redS2 << stwide*2)
        , reduceS   = redS4
    };
    return style & ~ reduceS;
}


__WEAK
const char* rtlog_fmt_of_msg(const char *fmt){
    return fmt;
}

// декодирование и печать записи слота <slot.
// \return - количество декодироанных записей
int rtlog_dumpsn_slot( rtlog* u, char* dst, unsigned bufsz, unsigned slot)  __noexcept __NOTHROW
{
    rtlog_node n[slot_couples];
    long       args[slot_couples*RTLOG_ARGS_LIMIT];
    unsigned last_stamp = u->stamp;
    unsigned couples = 0;

    while (slot != u->idx.write){

        {
            arch_state_t x = arch_intr_off();
            memcpy(&n[0], u->store+slot, sizeof(n));
            arch_intr_restore (x);
        }
        long* arg = args;
#if RTLOG_ARGS_LIMIT == 2
        *arg++ = n[0].args.L[0];
        *arg++ = n[0].args.L[1];
#endif
        RTLOG_printf("rtdump: at %d[stamp%x] %s"
                    , slot, n[0].stamp
                    , n[0].msg
                    );

        if ( (last_stamp+1) != n[0].stamp)
            snprintf(dst, bufsz, ".... droped %d messages ....\n"
                          , (n[0].stamp - 1 - last_stamp)
                          );
        last_stamp = n[0].stamp;
        slot = (slot+1) & u->idx.mask;

        // загружу все связанные записи с аргументами
        while (n[couples].msg == NULL) {
            couples++;
            assert(couples < slot_couples);
            rtlog_node* node = &n[couples];
            //надо загрузить доп слоты аргументов
            arch_state_t x = arch_intr_off();
            memcpy(node, u->store+slot, sizeof(*node));
            arch_intr_restore (x);
            slot = (slot+1) & u->idx.mask;

#if RTLOG_ARGS_LIMIT == 2
            *arg++ = node->args.L[0];
            *arg++ = node->args.L[1];
#endif

            if (last_stamp != n[couples].stamp){
                //видимо журнал перезаписался прямо во время декодирования
                return 0;
            }
        }
        n[0].msg = n[couples].msg;

        const char* fmt = rtlog_fmt_of_msg(n[0].msg);
        // определю стиль печати аргументов
        unsigned style = fmt_style(fmt);
        unsigned max_args = RTLOG_ARGS_LIMIT*(couples+1);

        // строковые аргументы тоже конвертирую из идентификатора в строку
        arg = args;
        for( unsigned s = style; s > 0; s = s>>stwide, arg++){
            if ((s&stMASK) == stS){
                arg[0] = (long)(uintptr_t)(void*)rtlog_fmt_of_msg( (const char*)((void*)arg[0]) );
            }
        }

        unsigned stmask = (1<< (stwide*max_args))-1;
        if (style > stmask ){
            style &= stmask;
        }
        style = fmt_style_reduce( style );

        // под стиль аргументов вызову адекватно printf
        switch (style){
        case stNO   :
            strncpy(dst, fmt, bufsz);
            break;

        case stL    :
        case st2L   :
            snprintf(dst, bufsz, fmt, args[0], args[1] );
            break;

        case st2F   :
            snprintf(dst, bufsz, fmt
                , (double)n[0].args.F[0], (double)n[0].args.F[1]
                );
            break;

        case stFL   :
            snprintf(dst, bufsz, fmt
                , (double)n[0].args.F[0], args[1]
                );
            break;

        case stLF   :
            snprintf(dst, bufsz, n[0].msg
                , n[0].args.L[0], (double)n[0].args.F[1]
                );
            break;

        case stLL   :
            snprintf(dst, bufsz, fmt , n[0].args.LL[0] );
            break;

        case stD    :
            snprintf(dst, bufsz, fmt, n[0].args.D[0] );
            break;

#if RTLOG_ARGS_LIMIT >= 4
        case st3L   :
        case st4L   :
            if (slot_couples > 1)
                snprintf(dst, bufsz, fmt
                            , args[0], args[1], args[2], args[3]
                            );
            break;

        case st3F   :
        case st4F   :
            if (slot_couples > 1)
                snprintf(dst, bufsz, fmt
                            , (double)n[0].args.F[0], (double)n[0].args.F[1]
                            , (double)n[1].args.F[0], (double)n[1].args.F[1]
                            );
            break;

        case stL3F   :
            if (slot_couples > 1)
                snprintf(dst, bufsz, fmt
                            , args[0], (double)n[0].args.F[1]
                            , (double)n[1].args.F[0], (double)n[1].args.F[1]
                            );
            break;

        case st2D   :
            if (slot_couples > 1)
                snprintf(dst, bufsz, fmt, n[0].args.D[0], n[1].args.D[0]);
            break;

        default:{
            int len = snprintf(dst, bufsz, "FMT?%%x:");
            dst += len;
            bufsz -=len;
            if (slot_couples > 1)
                snprintf(dst, bufsz, fmt
                            , args[0], args[1], args[2], args[3]
                            );
            };
            break;
#else
#error  "rtlog: unsuported args printing"
#endif

        }//switch (style)
        return couples+1;
    }
    return 0;
}

ArgStyle fmt1_style(const char* fmt);

// определю sтиль параметров fmt. соберу его из стилей каждого параметра
unsigned fmt_style(const char* fmt){
    int res     = 0;
    int argsn   = 0;
    const char* pc = fmt;
    ArgStyle    pc_style;
    while (pc != NULL){
        pc = strchr(pc, '%');
        if (pc == NULL)
            break;

        pc++;
        // пропущу модификаторы формата
        pc += strspn(pc, ".0123456789+-#");

        if (*pc == '\0')
            break;
        else if (*pc == '%'){
            // skip this %% - not a parameter
            continue;
        }

        pc_style = fmt1_style(pc);
        if (pc_style == stNO)
            continue;

        // добавлю найденый стиль к набору параметров
        res |= pc_style << (argsn*stwide);
        argsn++;

        if (argsn >= slot_couples*RTLOG_ARGS_LIMIT)
            break;
    }
    return res;
}

// определю sтиль параметра на который указывает fmt
ArgStyle fmt1_style(const char* fmt){
    ArgStyle    res = stNO;
    while (*fmt != '\0'){
        switch(*fmt){
            case 'd':
            case 'i':
            case 'u':
            case 'x':
            case 'X':
            case 'o':
            case 'c':
            case 'p':{
                if (res == stNO)
                    return stL;
                return res;
            }

            case 's':{
                if (res == stNO)
                    return stS;
                return res;
            }

            case 'a':
            case 'A':
            case 'e':
            case 'E':
            case 'f':
            case 'F':
            case 'g':
            case 'G':{
                if (res != stF)
                    return stD;
                return res;
            }

            case 'h':
            case 'H':
                res = stF;
                break;

            case 'L':
            case 'z':
            case 'j':
            case 't':
                res = stNO;
                break;

            case 'l':
                fmt++;
                if (*fmt != 'l')
                    continue;
                res = stLL;
                break;

            default:
                return stNO;
        }//switch(*pc)
        fmt++;
    }
    return stNO;
}

// печать декодированной 1й записи в журнале.
int rtlog_dumpsn_first( rtlog* u, char* buf, unsigned bufsz)
    __noexcept __NOTHROW
{
    return rtlog_dumpsn_slot(u, buf, bufsz, u->idx.read);
}

#if 0
//* печатает records_count последних записей журнала в dst
void rtlog_dump_last( rtlog* u, stream_t *dst, unsigned records_count)
{
    unsigned slot = 0;
    rtlog_node n;
    unsigned last_stamp = u->stamp;

    {
        arch_state_t x = arch_intr_off();

        unsigned avail = ring_uindex_avail(&u->idx);
        if (records_count > avail)
            records_count = avail;

        slot = u->idx.write;
        slot = (slot - records_count) & u->idx.mask;
        arch_intr_restore (x);

        if (records_count <= 0)
            return;
    }

    while (slot != u->idx.write){

        {
            arch_state_t x = arch_intr_off();
            memcpy(&n, u->store+slot, sizeof(n));
            arch_intr_restore (x);
        }

        RTLOG_printf("rtdump: at %d[stamp%x] %s"
                    , slot, n.stamp
                    , n.msg
                    );

        if ( (last_stamp+1) != n.stamp)
            stream_printf(dst, ".... droped %d messages ....\n"
                          , (n.stamp - 1 - last_stamp) 
                          );
        last_stamp = n.stamp;

        stream_printf(dst, n.msg
                    , n.args[0], n.args[1], n.args[2], n.args[3]
                    , n.args[4], n.args[5]
                    );

        slot = (slot+1) & u->idx.mask;
        --records_count;
        if (records_count <= 0)
            break;
    }
}
#endif
