#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <math.h>

#include <munit/munit.h>

#include <echidna.h>
#include <ll.h>
#include <macros.h>
#include <runtime.h>
#include <symbol.h>

#include <suite.h>


//  xxd -i src/timers.txt

static char _Source[] = {

    0x70, 0x72, 0x6f, 0x67, 0x72, 0x61, 0x6d, 0x20, 
    0x74, 0x65, 0x73, 0x74, 0x0a, 0x76, 0x61, 0x72, 
    0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x3a, 0x20, 
    0x75, 0x69, 0x6e, 0x74, 0x20, 0x3a, 0x3d, 0x20, 
    0x30, 0x3b, 0x20, 0x65, 0x6e, 0x64, 0x5f, 0x76, 
    0x61, 0x72, 0x0a, 0x76, 0x61, 0x72, 0x20, 0x69, 
    0x6e, 0x70, 0x75, 0x74, 0x3a, 0x20, 0x62, 0x6f, 
    0x6f, 0x6c, 0x3b, 0x20, 0x65, 0x6e, 0x64, 0x5f, 
    0x76, 0x61, 0x72, 0x0a, 0x76, 0x61, 0x72, 0x20, 
    0x66, 0x62, 0x3a, 0x20, 0x25, 0x73, 0x3b, 0x20, 
    0x65, 0x6e, 0x64, 0x5f, 0x76, 0x61, 0x72, 0x0a, 
    0x0a, 0x61, 0x64, 0x64, 0x20, 0x63, 0x6f, 0x75, 
    0x6e, 0x74, 0x2c, 0x20, 0x31, 0x0a, 0x73, 0x74, 
    0x20, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x0a, 0x6d, 
    0x6f, 0x64, 0x20, 0x31, 0x30, 0x0a, 0x65, 0x71, 
    0x20, 0x31, 0x0a, 0x6a, 0x6d, 0x70, 0x63, 0x6e, 
    0x20, 0x74, 0x69, 0x6d, 0x65, 0x72, 0x0a, 0x78, 
    0x6f, 0x72, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 
    0x2c, 0x20, 0x74, 0x72, 0x75, 0x65, 0x0a, 0x73, 
    0x74, 0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x0a, 
    0x74, 0x69, 0x6d, 0x65, 0x72, 0x3a, 0x20, 0x63, 
    0x61, 0x6c, 0x20, 0x66, 0x62, 0x28, 0x0a, 0x20, 
    0x20, 0x20, 0x20, 0x69, 0x6e, 0x20, 0x3a, 0x3d, 
    0x20, 0x69, 0x6e, 0x70, 0x75, 0x74, 0x2c, 0x0a, 
    0x20, 0x20, 0x20, 0x20, 0x70, 0x74, 0x20, 0x3a, 
    0x3d, 0x20, 0x74, 0x23, 0x32, 0x35, 0x30, 0x6d, 
    0x73, 0x0a, 0x29, 0x0a, 0x0a, 0x65, 0x71, 0x20, 
    0x63, 0x6f, 0x75, 0x6e, 0x74, 0x2c, 0x20, 0x33, 
    0x30, 0x0a, 0x5f, 0x65, 0x78, 0x69, 0x74, 0x63, 
    0x0a, 0x0a, 0x65, 0x6e, 0x64, 0x5f, 0x70, 0x72, 
    0x6f, 0x67, 0x72, 0x61, 0x6d, 0x0a, 0x0a, 0x63, 
    0x6f, 0x6e, 0x66, 0x69, 0x67, 0x75, 0x72, 0x61, 
    0x74, 0x69, 0x6f, 0x6e, 0x20, 0x63, 0x6f, 0x6e, 
    0x66, 0x69, 0x67, 0x31, 0x0a, 0x74, 0x61, 0x73, 
    0x6b, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x31, 0x20, 
    0x28, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x76, 0x61, 
    0x6c, 0x20, 0x3a, 0x3d, 0x20, 0x74, 0x23, 0x35, 
    0x30, 0x6d, 0x73, 0x2c, 0x20, 0x70, 0x72, 0x69, 
    0x6f, 0x72, 0x69, 0x74, 0x79, 0x20, 0x3a, 0x3d, 
    0x20, 0x31, 0x29, 0x3b, 0x0a, 0x70, 0x72, 0x6f, 
    0x67, 0x72, 0x61, 0x6d, 0x20, 0x70, 0x72, 0x6f, 
    0x67, 0x72, 0x61, 0x6d, 0x31, 0x20, 0x77, 0x69, 
    0x74, 0x68, 0x20, 0x74, 0x61, 0x73, 0x6b, 0x31, 
    0x3a, 0x20, 0x74, 0x65, 0x73, 0x74, 0x3b, 0x0a, 
    0x65, 0x6e, 0x64, 0x5f, 0x63, 0x6f, 0x6e, 0x66, 
    0x69, 0x67, 0x75, 0x72, 0x61, 0x74, 0x69, 0x6f, 
    0x6e, 0x0a, 0x00
};

static int _Cycle = 0;


static void
_test_timers_callback(ECHIDNA *Context, void *Arg, void *User) {
    RUNTIME *pRun;
    RUNTIME_CONTEXT *pContext;
    SYMBOL *pSymbol;
    VALUE *pValue, sValues[3];
    char *pName;
    int nIndex;

    /* Arg == &RUNTIME_CONTEXT.Stats */
    munit_assert_not_null(Arg);
    munit_assert_not_null(pContext = container_of(Arg, RUNTIME_CONTEXT, Stats));
    munit_assert_not_null(pRun = (RUNTIME *) pContext->Parent);

    char * sNames[] = {
        "input",
        "fb.q", 
        "fb.et",
    };

    for(nIndex = 0; nIndex < 3; ++nIndex) {
        pValue = &sValues[nIndex];
        value_initialise(pValue);
        munit_assert_not_null(sNames[nIndex]);
        munit_assert_not_null(pSymbol = symbol_search(Context, "config1", "__Single", "program1", sNames[nIndex]));
        value_copy(pValue, &pSymbol->Value);
        memcpy((char *) &pValue->Value.Pointer, &pRun->Memory[pSymbol->Offset], pValue->Length);
        switch(nIndex) {
            case 2:
                munit_assert_uint32(pValue->Type, ==, TYPE_TIME);
                break;

            case 0:
            case 1:
            default:
                munit_assert_uint32(pValue->Type, ==, TYPE_BOOL);
                break;
        }
    }

    munit_assert_not_null(pName = (char *) User);
    if(strcasecmp(pName, "tof") == 0) {
        if(!sValues[0].Value.B1)
            munit_assert_uint8(sValues[1].Value.B1, ==, (sValues[2].Value.Time < 250.0));
        else 
            munit_assert_uint8(sValues[1].Value.B1, ==, 1);
    }
    else if(strcasecmp(pName, "ton") == 0) {
        if(sValues[0].Value.B1)
            munit_assert_uint8(sValues[1].Value.B1, ==, (sValues[2].Value.Time >= 250.0));
        else
            munit_assert_uint8(sValues[1].Value.B1, ==, 0);
    }
    else if(strcasecmp(pName, "tp") == 0) {
        if(!sValues[0].Value.B1)
            munit_assert_uint8(sValues[1].Value.B1, ==, 0);
       
        //  TODO: Add test for tp function block output
    }
    else
        munit_assert_true(0);

    ++_Cycle;
}


static MunitResult 
_test_timers(const char *Name, const MunitParameter Parameters[], void *Fixture) {
    ECHIDNA *pContext;
    char sSource[1024];

    pContext = (ECHIDNA *) Fixture;
    munit_assert_not_null(pContext);

    _Cycle = 0;
    snprintf(sSource, sizeof(sSource), _Source, Name);
    munit_assert_int(test_parse(pContext, sSource), ==, 0);
    pContext->Option |= (OPTION_COMPILE | OPTION_RUN);
    pContext->Output = NULL;
    munit_assert_int(echidna_compile(pContext), ==, 0);
    munit_assert_int(echidna_callback(pContext, CALLBACK_CYCLE_FINISH, NULL, _test_timers_callback, Name), ==, 0);
    munit_assert_int(echidna_run(pContext), ==, 0);
    munit_assert_int(_Cycle, ==, 30);

    return MUNIT_OK;
}


MunitResult
test_timers_tof(const MunitParameter Parameters[], void *Fixture) {
    return _test_timers("tof", Parameters, Fixture);
}


MunitResult
test_timers_ton(const MunitParameter Parameters[], void *Fixture) {
    return _test_timers("ton", Parameters, Fixture);
}


MunitResult
test_timers_tp(const MunitParameter Parameters[], void *Fixture) {
    return _test_timers("tp", Parameters, Fixture);
}

