#pragma once

#ifdef CSQC
#include <client/main.qh>
#endif
#ifndef SVQC
#include <common/util.qh> // for icon_path_from_menuskin
#endif

const int CBC_ORDER_FIRST = 1;
const int CBC_ORDER_LAST = 2;
const int CBC_ORDER_EXCLUSIVE = 3;
const int CBC_ORDER_ANY = 4;

bool CallbackChain_ReturnValue; // read-only field of the current return value

/**
 * Callbacks may be added to zero or more callback chains.
 */
CLASS(Callback, Object)
    /**
     * a callback function is like this:
     * bool mycallback()
     * {
     *     do something
     *     return false;
     * }
     */
    ATTRIB(Callback, cbc_func, bool());
    CONSTRUCTOR(Callback, bool() func) {
        CONSTRUCT(Callback);
        this.cbc_func = func;
    }
ENDCLASS(Callback)

/**
 * Callback chains contain zero or more callbacks.
 */
CLASS(CallbackChain, Object)
    CLASS(CallbackNode, Object)
        ATTRIB(CallbackNode, cbc, Callback);
        ATTRIB(CallbackNode, cbc_next, CallbackNode);
        ATTRIB(CallbackNode, cbc_order, int, 0);
        CONSTRUCTOR(CallbackNode, Callback it, int order) {
            CONSTRUCT(CallbackNode);
            this.cbc = it;
            this.cbc_order = order;
        }
    ENDCLASS(CallbackNode)

    ATTRIB(CallbackChain, cbc_next, CallbackNode);
    ATTRIB(CallbackChain, cbc_order, int, 0);
    CONSTRUCTOR(CallbackChain, string _name) {
        CONSTRUCT(CallbackChain);
        this.netname = _name;
    }

    bool CallbackChain_Add(CallbackChain this, Callback cb, int order)
    {
        if (order & CBC_ORDER_FIRST) {
            if (order & CBC_ORDER_LAST)
                if (this.cbc_order & CBC_ORDER_ANY)
                    return false;
            if (this.cbc_order & CBC_ORDER_FIRST)
                return false;
        } else if (order & CBC_ORDER_LAST) {
            if (this.cbc_order & CBC_ORDER_LAST)
                return false;
        }
        entity node = NEW(CallbackNode, cb, order);
        if (order & CBC_ORDER_FIRST) {
            node.cbc_next = this.cbc_next;
            this.cbc_next = node;
        } else if (order & CBC_ORDER_LAST) {
            CallbackNode prev = NULL, it = this.cbc_next;
            while (it) { prev = it, it = it.cbc_next; }
            if (prev) prev.cbc_next = node;
            else this.cbc_next = node;
        } else {
            // by default we execute last, but before a possible CBC_ORDER_LAST callback
            CallbackNode prev = NULL, it = this.cbc_next;
            while (it && !(it.cbc_order & CBC_ORDER_LAST)) { prev = it, it = it.cbc_next; }
            node.cbc_next = it;
            if (prev) prev.cbc_next = node;
            else this.cbc_next = node;
        }
        this.cbc_order |= (order | CBC_ORDER_ANY);
        return true;
    }
    int CallbackChain_Remove(CallbackChain this, Callback cb)
    {
        int n = 0, order = 0;
        for (Callback prev = NULL, it = this.cbc_next; it; prev = it, it = it.cbc_next) {
            if (it.cbc == cb) {
                // remove it from the chain
                Callback next = it.cbc_next;
                if (prev) prev.cbc_next = next;
                else this.cbc_next = next;
                ++n;
            }
            // it is now something we want to keep
            order |= (it.cbc_order & CBC_ORDER_ANY);
        }
        this.cbc_order = order;
        return n;
    }
    bool CallbackChain_Call(CallbackChain this)
    {
        bool r = false;
        for (Callback it = this.cbc_next; it; it = it.cbc_next) {
            CallbackChain_ReturnValue = r;
            r |= it.cbc.cbc_func();
        }
        return r; // callbacks return an error status, so 0 is default return value
    }
ENDCLASS(CallbackChain)

#define _MUTATOR_HANDLE_NOP(type, id)
#define _MUTATOR_HANDLE_PARAMS(type, id) , type in_##id
#define _MUTATOR_HANDLE_PREPARE(type, id) id = in_##id;
#define _MUTATOR_HANDLE_PUSHTMP(type, id) TC(type, id); type tmp_##id = id;
#define _MUTATOR_HANDLE_PUSHOUT(type, id) type out_##id = id;
#define _MUTATOR_HANDLE_POPTMP(type, id) id = tmp_##id;
#define _MUTATOR_HANDLE_POPOUT(type, id) id = out_##id;

void RegisterHooks() {};
void RegisterCallbacks() {};

#define MUTATOR_HOOKABLE(id, params) _MUTATOR_HOOKABLE(id, params)
#define _MUTATOR_HOOKABLE(id, params) \
    CallbackChain HOOK_##id;  \
    bool __Mutator_Send_##id(int params(_MUTATOR_HANDLE_PARAMS, _MUTATOR_HANDLE_NOP)) { \
        params(_MUTATOR_HANDLE_PUSHTMP, _MUTATOR_HANDLE_NOP) \
        params(_MUTATOR_HANDLE_PREPARE, _MUTATOR_HANDLE_NOP) \
        bool ret = CallbackChain_Call(HOOK_##id); \
        params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_PUSHOUT) \
        params(_MUTATOR_HANDLE_POPTMP,  _MUTATOR_HANDLE_NOP) \
        params(_MUTATOR_HANDLE_NOP,     _MUTATOR_HANDLE_POPOUT) \
        return ret; \
    } \
    ACCUMULATE void RegisterHooks() { HOOK_##id = NEW(CallbackChain, #id); }

#define MUTATOR_CALLHOOK(id, ...) _MUTATOR_CALLHOOK(id, __VA_ARGS__)
#ifdef __STDC__
    #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0 P99_IF_EMPTY(__VA_ARGS__)()(, __VA_ARGS__))
#else
    #define _MUTATOR_CALLHOOK(id, ...) APPLY(__Mutator_Send_##id, 0, ##__VA_ARGS__)
#endif

enum {
    MUTATOR_REMOVING,
    MUTATOR_ADDING,
    MUTATOR_ROLLING_BACK
};

USING(mutatorfunc_t, bool(int));

#ifdef MENUQC
#include <menu/draw.qh>
#endif

CLASS(Mutator, Object)
    ATTRIB(Mutator, m_id, int, 0);
    ATTRIB(Mutator, message, string);
    ATTRIB(Mutator, m_color, vector, '1 1 1');
    ATTRIB(Mutator, m_icon, string);
    ATTRIB(Mutator, m_name, string);
    ATTRIB(Mutator, mutatorfunc, mutatorfunc_t);
    ATTRIB(Mutator, mutatorcheck, bool());
#ifdef MENUQC
    METHOD(Mutator, describe, string(Mutator this))
    {
        TC(Mutator, this);
        return SUPER(Mutator).describe(this);
    }
    METHOD(Mutator, display, void(Mutator this, void(string name, string icon) returns))
    {
        TC(Mutator, this);
        returns(this.message, icon_path_from_menuskin(this.m_icon));
    }
#endif
ENDCLASS(Mutator)

REGISTRY(Mutators, BITS(7))

REGISTRY_DEFINE_GET(Mutators, NULL)
bool Mutator_Add(Mutator mut);
void Mutator_Remove(Mutator mut);
bool mutator_log = false;
.bool m_added;

#define _MUTATOR_IS_ENABLED(this) this.mutatorcheck()
#define MUTATOR_IS_ENABLED(this) _MUTATOR_IS_ENABLED(MUTATOR_##this)

#ifdef GAMEQC
/** server mutators activate corresponding client mutators for all clients */
REGISTER_NET_LINKED(Mutator)

#ifdef SVQC
bool Mutator_SendEntity(entity this, entity to, int sf)
{
    int chan = MSG_ENTITY;
    WriteHeader(chan, Mutator);
    WriteString(chan, this.registered_id);
    return true;
}
#endif

#ifdef CSQC
void NET_Mutator_Remove(entity this)
{
    string s = this.netname;
    WITH(bool, mutator_log, true, {
        FOREACH(Mutators, it.registered_id == s, Mutator_Remove(it));
    });
}
NET_HANDLE(Mutator, bool isNew)
{
    make_pure(this);
    string s = this.netname = ReadString();
    return = true;
    if (isNew)
    {
        make_pure(this);
        this.entremove = NET_Mutator_Remove;
        int added = 0;
        WITH(bool, mutator_log, true, {
            FOREACH(Mutators, it.registered_id == s, { Mutator_Add(it); ++added; });
        });
        if (added > 1) LOG_WARNF("Added more than one mutator for %s", s);
    }
}
#endif

#endif

bool Mutator_Add(Mutator mut)
{
    if(mut.m_added)
        return true; // already added

    mut.m_added = true;
    mutatorfunc_t func = mut.mutatorfunc;
    if (!func(MUTATOR_ADDING)) {
        // good
        if (mutator_log) LOG_TRACEF("Mutator: added %s", mut.m_name);
#ifdef SVQC
        Net_LinkEntity(mut, false, 0, Mutator_SendEntity);
#endif
        return true;
    }
    backtrace("WARNING: when adding mutator: adding failed, rolling back\n");
    if (func(MUTATOR_ROLLING_BACK)) {
        // baaaaad
        error("WARNING: when adding mutator: rolling back failed");
    }
    return false;
}

void Mutator_Remove(Mutator mut)
{
    if(!mut.m_added)
    {
        backtrace("WARNING: removing not-added mutator\n");
        return;
    }

    mut.m_added = false;
    mutatorfunc_t func = mut.mutatorfunc;
    if (func(MUTATOR_REMOVING)) {
        // baaaaad
        error("Mutator_Remove: removing mutator failed");
    }
    if (mutator_log) LOG_TRACEF("Mutator: removed %s", mut.m_name);
#ifdef SVQC
    Net_UnlinkEntity(mut);
#endif
}

#define _REGISTER_MUTATOR(id, dependence, cname) \
    bool MUTATORFUNC_##id##_hooks(int mode) { return = false; } \
    bool MUTATORFUNC_##id(int mode) { \
        return = false; \
        bool ret = MUTATORFUNC_##id##_hooks(mode); if (ret) return ret; \
    } \
    bool MUTATOR_##id##_check() { return dependence; } \
    REGISTER(Mutators, MUTATOR, id, m_id, NEW(cname)) \
    { \
        this.m_name = #id; \
        this.mutatorfunc = MUTATORFUNC_##id; \
        this.mutatorcheck = MUTATOR_##id##_check; \
    } \
    ACCUMULATE bool MUTATORFUNC_##id(int mode)

#define REGISTER_MUTATOR(...) EVAL(OVERLOAD(REGISTER_MUTATOR, __VA_ARGS__))
#define REGISTER_MUTATOR_2(id, dependence)        _REGISTER_MUTATOR(id, dependence, Mutator)
#define REGISTER_MUTATOR_3(id, dependence, cname) _REGISTER_MUTATOR(id, dependence, cname)

STATIC_INIT(Mutators) {
    RegisterHooks();
    RegisterCallbacks();
    RegisterMutators();
}

STATIC_INIT_LATE(Mutators) {
    FOREACH(Mutators, _MUTATOR_IS_ENABLED(it), Mutator_Add(it));
}

#define MUTATOR_ONADD                   if (mode == MUTATOR_ADDING)
#define MUTATOR_ONREMOVE                if (mode == MUTATOR_REMOVING)
#define MUTATOR_ONROLLBACK_OR_REMOVE    if (mode == MUTATOR_REMOVING || mode == MUTATOR_ROLLING_BACK)

#define MUTATOR_STATIC() MACRO_BEGIN \
    MUTATOR_ONADD { \
        /* game loads at time 1 */ \
        if (time > 1) { \
            error("This is a gametype and it cannot be added at runtime."); \
        } \
    } \
	MUTATOR_ONREMOVE { \
		LOG_INFO("This is a gametype and it cannot be removed at runtime."); \
		return -1; \
	} \
MACRO_END

#define MUTATOR_ADD(name)               Mutator_Add(MUTATOR_##name)
#define MUTATOR_REMOVE(name)            Mutator_Remove(MUTATOR_##name)
#define MUTATOR_RETURNVALUE             CallbackChain_ReturnValue

#define _MUTATOR_CALLBACK(name, func) \
    Callback CB_##name; \
    bool func(); \
    ACCUMULATE void RegisterCallbacks() { CB_##name = NEW(Callback, func); }

#define MUTATOR_HOOKFUNCTION(...) \
    EVAL_MUTATOR_HOOKFUNCTION(OVERLOAD(MUTATOR_HOOKFUNCTION, __VA_ARGS__))
#define EVAL_MUTATOR_HOOKFUNCTION(...) __VA_ARGS__

#define MUTATOR_HOOKFUNCTION_2(mut, cb) \
    MUTATOR_HOOKFUNCTION_3(mut, cb, CBC_ORDER_ANY)

#define MUTATOR_HOOKFUNCTION_3(mut, cb, order) \
    _MUTATOR_CALLBACK(mut##_##cb, mut##_##cb) \
    ACCUMULATE bool MUTATORFUNC_##mut##_hooks(int mode) { MUTATOR_HOOK(cb, mut##_##cb, order); } \
    bool mut##_##cb() { return = false; } \
    ACCUMULATE bool mut##_##cb()

void _mutPrintFail(string cb, string func)
{
	// this is inside a function to avoid expanding it on compilation everytime
	LOG_INFO("HOOK FAILED: ", cb, ":", func);
}

#define MUTATOR_HOOK(cb, func, order) MACRO_BEGIN \
    MUTATOR_ONADD { \
        if (!CallbackChain_Add(HOOK_##cb, CB_##func, order)) { \
            _mutPrintFail(#cb, #func); \
            return true; \
        } \
    } \
    MUTATOR_ONROLLBACK_OR_REMOVE { \
        CallbackChain_Remove(HOOK_##cb, CB_##func); \
    } \
MACRO_END

#include "events.qh"
