#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#ifndef _MSC_VER
#include <strings.h>
#else
#include "deps.h"
#endif
#include <errno.h>
#include <assert.h>

#include <callback.h>
#include <echidna.h>
#include <ll.h>
#include <log.h>

/* As type is not shared with another module and there is a type conflict with
 * msvc, definition has been moved here */
typedef struct _CALLBACK {

  CALLBACK_TYPE Type;

  char* Name;

  CALLBACK_CALLBACK Function;

  void* User;
}
CALLBACK;

int 
_callback_register(size_t Arg, ECHIDNA *Context, CALLBACK_TYPE Type, ...) {
    CALLBACK *pCallback;
    va_list sArg;
    int nResult;

    errno = 0;
    if((pCallback = calloc(1, sizeof(CALLBACK))) == NULL) {
        log_critical("Failed to allocate memory: %s", strerror(errno));
        return -1;
    }
    pCallback->Type = Type;

    switch(Type) {
        case CALLBACK_CYCLE_START:
        case CALLBACK_CYCLE_FINISH:
        case CALLBACK_TASK_START:
        case CALLBACK_TASK_STOP:
            if(Arg < 4)
                goto error;
            va_start(sArg, Type);
            pCallback->Name = va_arg(sArg, char *);
            pCallback->Function = va_arg(sArg, CALLBACK_CALLBACK);
            pCallback->User = (Arg > 4) ? va_arg(sArg, void *) : NULL;
            va_end(sArg);
            break;

        default:
            goto error;
    }

    if((nResult = ll_insert(&Context->Callbacks, pCallback)) != 0) {
        callback_destroy(pCallback);
        return nResult;
    }

    return 0;

error:
    callback_destroy(pCallback);
    errno = EINVAL;
    return -1;
}


void
callback_destroy(void *Arg) {
    CALLBACK *pCallback;

    if((pCallback = (CALLBACK *) Arg) == NULL)
        return;
    free(pCallback);
}


int
callback_execute(ECHIDNA *Context, CALLBACK_TYPE Type, void *Arg) {
    CALLBACK *pCallback;
    RUNTIME_CONTEXT *pContext;
    
    assert(Context != NULL);
    ll_reset(&Context->Callbacks);
    while((pCallback = ll_iterate(&Context->Callbacks)) != NULL) {
        if(pCallback->Type != Type)
            continue;
        switch(pCallback->Type) {
            case CALLBACK_CYCLE_FINISH:
                pContext = (RUNTIME_CONTEXT *) Arg;
                assert(pContext != NULL);
                if((pCallback->Name != NULL) &&
                        (strcasecmp(pCallback->Name, pContext->Name) == 0))
                    continue;
                pCallback->Function(Context, &pContext->Stats, pCallback->User);
                break;

            case CALLBACK_CYCLE_START:
            case CALLBACK_TASK_START:
            case CALLBACK_TASK_STOP:
            default:
                break;
        }
    }
    return 0;
}

