#include "behaviour_tree.h"

#include "../memory/memory.h"
#include "../util/object_pool.h"

//#define GET_NODE(idx) ((BzAIBTNode *) bzObjectPoolGetObject(bt->nodePool, idx))

struct BzBTNode {
    BzBTNode *parent;
    // Children
    BzBTNode *first;
    BzBTNode *last;
    // Siblings
    BzBTNode *prev;
    BzBTNode *next;

    BzBTNodeType type;
    const char   *name;
    union {
        struct {
            i32 n;
        } repeat;
        struct {
            f32 ms;
        } delay;
        struct {
            BzBTActionFn fn;
        } action;
    } as;
};

struct BzBTNodeState {
    const BzBTNode *node;
    BzBTNodeState *next;
    BzBTNodeState *prev;

    union {
        struct {
            BzBTNode *running;
        } composite;
        struct {
            i32 iter;
        } repeat;
        struct {
            f32 elapsed;
        } delay;
    } as;
};


size_t bzBTGetNodeSize() {
    return sizeof(BzBTNode);
}
size_t  bzBTGetNodeStateSize() {
    return sizeof(BzBTNodeState);
}

const char *bzBTNodeTypeToStr(BzBTNodeType type) {
    switch (type) {
        case BZ_BT_COMP_SELECTOR:
            return "SELECTOR";
        case BZ_BT_COMP_SEQUENCE:
            return "SEQUENCE";
        case BZ_BT_COMP_PARALLEL_SELECTOR:
            return "P_SELECTOR";
        case BZ_BT_COMP_PARALLEL_SEQUENCE:
            return "P_SEQUENCE";
        case BZ_BT_DECOR_DUMMY:
            return "DUMMY";
        case BZ_BT_DECOR_SUCCESS:
            return "RETURN_SUCCESS";
        case BZ_BT_DECOR_FAIL:
            return "RETURN_FAIL";
        case BZ_BT_DECOR_INVERT:
            return "INVERT";
        case BZ_BT_DECOR_UNTIL_SUCCESS:
            return "UNTIL_SUCCESS";
        case BZ_BT_DECOR_UNTIL_FAIL:
            return "UNTIL_FAIL";
        case BZ_BT_DECOR_REPEAT:
            return "REPEAT";
        case BZ_BT_DECOR_DELAY:
            return "DELAY";
        case BZ_BT_ACTION:
            return "ACTION";
    }
}

static BzBTNode *bzBTNodeMake(BzObjectPool *nodePool, BzBTNode *parent, BzBTNodeType type) {
    BZ_ASSERT(nodePool);
    BZ_ASSERT(bzObjectPoolGetObjectSize(nodePool) == bzBTGetNodeSize());
    BzBTNode *node = bzObjectPool(nodePool);
    bzMemSet(node, 0, sizeof(*node));

    node->type = type;

    if (parent && parent->last) {
        parent->last->next = node;
        node->prev = parent->last;
        parent->last = node;
    } else if (parent) {
        parent->first = node;
        parent->last = node;
    }
    node->parent = parent;

    return node;
}

BzBTNode *bzBTMakeRoot(BzObjectPool *nodePool) {
    return bzBTNodeMake(nodePool, NULL, BZ_BT_DECOR_DUMMY);
}

void bzBTDestroyRoot(BzObjectPool *nodePool, BzBTNode *node) {
    BZ_ASSERT(node);
    BzBTNode *pNode = node;

    while (pNode) {
        BzBTNode *next = pNode->next;
        bzBTDestroyRoot(nodePool, pNode);
        pNode = next;
    }

    bzObjectPoolRelease(nodePool, node);
}

BzBTNode *bzBTCompSelector(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) {
    BzBTNodeType type = parallel ?
                        BZ_BT_COMP_PARALLEL_SELECTOR :
                        BZ_BT_COMP_SELECTOR;
    return bzBTNodeMake(nodePool, parent, type);
}
BzBTNode *bzBTCompSequence(BzObjectPool *nodePool, BzBTNode *parent, bool parallel) {
    BzBTNodeType type = parallel ?
                        BZ_BT_COMP_PARALLEL_SEQUENCE :
                        BZ_BT_COMP_SEQUENCE;
    return bzBTNodeMake(nodePool, parent, type);
}

BzBTNode *bzBTDecorDummy(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_DUMMY);
}
BzBTNode *bzBTDecorSuccess(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_SUCCESS);
}
BzBTNode *bzBTDecorFail(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_FAIL);
}
BzBTNode *bzBTDecorInvert(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_INVERT);
}
BzBTNode *bzBTDecorUntilSuccess(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_UNTIL_SUCCESS);
}
BzBTNode *bzBTDecorUntilFail(BzObjectPool *nodePool, BzBTNode *parent) {
    return bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_UNTIL_FAIL);
}
BzBTNode *bzBTDecorRepeat(BzObjectPool *nodePool, BzBTNode *parent, i32 n) {
    BzBTNode *node = bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_REPEAT);
    node->as.repeat.n = n;
    return node;
}
BzBTNode *bzBTDecorDelay(BzObjectPool *nodePool, BzBTNode *parent, f32 ms) {
    BzBTNode *node = bzBTNodeMake(nodePool, parent, BZ_BT_DECOR_DELAY);
    node->as.delay.ms = ms;
    return node;
}

BzBTNode *bzBTAction(BzObjectPool *nodePool, BzBTNode *parent, BzBTActionFn fn) {
    BzBTNode *node = bzBTNodeMake(nodePool, parent, BZ_BT_ACTION);
    node->as.action.fn = fn;
    return node;
}

void bzBTNodeSetName(BzBTNode *node, const char *name) {
    node->name = name;
}
const char *bzBTNodeGetName(const BzBTNode *node) {
    return node->name;
}

i32 bzBTDecorGetRepeat(const BzBTNode *node) {
    BZ_ASSERT(node->type == BZ_BT_DECOR_REPEAT);
    return node->as.repeat.n;
}
f32 bzBTDecorGetDelay(const BzBTNode *node) {
    BZ_ASSERT(node->type == BZ_BT_DECOR_DELAY);
    return node->as.delay.ms;
}

BzBTActionFn bzBTActionGetFn(const BzBTNode *node) {
    BZ_ASSERT(node->type == BZ_BT_ACTION);
    return node->as.action.fn;
}

BzBTNodeType bzBTGetNodeType(const BzBTNode *node) {
    return node->type;
}
BzBTNode *bzBTNodeChild(const BzBTNode *node) {
    return node->first;
}
BzBTNode *bzBTNodeNext(const BzBTNode *node) {
    return node->next;
}

const BzBTNodeState *bzBTNodeStateNext(const BzBTNodeState *state) {
    BZ_ASSERT(state);
    return state->next;
}
bool bzBTNodeMatchesState(const BzBTNode *node, const BzBTNodeState *state) {
    return state && state->node == node;
}

BzBTNode *bzBTCompStateGetRunningChild(const BzBTNodeState *state) {
    BZ_ASSERT(state->node);
    BzBTNodeType type = state->node->type;
    bool isComposite = type == BZ_BT_COMP_SELECTOR ||
                       type == BZ_BT_COMP_PARALLEL_SELECTOR ||
                       type == BZ_BT_COMP_SEQUENCE ||
                       type == BZ_BT_COMP_PARALLEL_SEQUENCE;
    BZ_ASSERT(isComposite);
    return state->as.composite.running;
}

i32 bzBTRepeatStateGetIter(const BzBTNodeState *state) {
    BZ_ASSERT(state->node && state->node->type == BZ_BT_DECOR_REPEAT);
    return state->as.repeat.iter;
}
f32 bzBTDelayStateGetElapsed(const BzBTNodeState *state) {
    BZ_ASSERT(state->node && state->node->type == BZ_BT_DECOR_DELAY);
    return state->as.delay.elapsed;
}

BzBTState bzBTCreateState(const BzBTStateDesc *desc) {
    BZ_ASSERT(desc->pool);
    BZ_ASSERT(bzObjectPoolGetObjectSize(desc->pool) == bzBTGetNodeStateSize());
    BZ_ASSERT(desc->root);
    return (BzBTState) {
            .root = desc->root,
            ._first = NULL,
            ._last = NULL,
            .nodeStatePool = desc->pool,
            .userData = desc->userData
    };
}
void bzBTDestroyState(BzBTState *state) {
    BzBTNodeState *pNodeState = state->_first;
    while (pNodeState) {
        BzBTNodeState *next = pNodeState->next;
        bzObjectPoolRelease(state->nodeStatePool, pNodeState);
        pNodeState = next;
    }
    bzMemSet(state, 0, sizeof(*state));
}

void bzBTStateAppend(BzBTState *state, BzBTNodeState *nodeState) {
    nodeState->next = NULL;
    nodeState->prev = state->_last;
    if (state->_last)
        state->_last->next = nodeState;
    else
        state->_first = nodeState;
    state->_last = nodeState;
}
void bzBTStatePop(BzBTState *state, BzBTNodeState *nodeState) {
    if (state->_first == nodeState) state->_first = nodeState->next;
    if (state->_last == nodeState) state->_last = nodeState->prev;
    BzBTNodeState *next = nodeState->next;
    BzBTNodeState *prev = nodeState->prev;
    if (nodeState->prev)
        nodeState->prev->next = next;
    if (nodeState->next)
        nodeState->next->prev = prev;
    nodeState->next = NULL;
    nodeState->prev = NULL;
}
void bzBTStateRenew(BzBTState *oldState, BzBTState *newState, BzBTNodeState *nodeState) {
    // Pop nodeState and transfer it to the back
    bzBTStatePop(oldState, nodeState);
    bzBTStateAppend(newState, nodeState);
}
BzBTNodeState *bzBTStatePool(BzBTState *state, const BzBTNode *node) {
    BzBTNodeState *nodeState = bzObjectPool(state->nodeStatePool);
    nodeState->next = NULL;
    nodeState->prev = NULL;
    nodeState->node = node;
    return nodeState;
}
void bzBTStateRelease(BzBTState *state, BzBTNodeState *nodeState) {
    bzBTStatePop(state, nodeState);
    bzObjectPoolRelease(state->nodeStatePool, nodeState);
}

bool nodeMatchesState(const BzBTNode *node, const BzBTNodeState *state) {
    return bzBTNodeMatchesState(node, state);
}
BzBTNodeState *getNextNodeState(const BzBTNode *node, BzBTNodeState *nodeState) {
    if (nodeState && nodeMatchesState(node, nodeState))
        return nodeState->next;
    return nodeState;
}

static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
                                         BzBTNodeState *nodeState,
                                         BzBTState *oldState, BzBTState *newState);
static inline BzBTStatus bzBTExecuteComposite(const BzBTNode *node, f32 dt,
                                              BzBTNodeState *nodeState,
                                              BzBTState *oldState, BzBTState *newState) {
    BzBTNodeState *nextState = getNextNodeState(node, nodeState);
    BzBTNode *start = node->first;
    bool isParallel = node->type == BZ_BT_COMP_PARALLEL_SEQUENCE ||
                      node->type == BZ_BT_COMP_PARALLEL_SELECTOR;

    if (!isParallel && nodeMatchesState(node, nodeState))
        start = nodeState->as.composite.running;

    // Always push dummy state
    if (nodeMatchesState(node, nodeState)) {
        bzBTStateRenew(oldState, newState, nodeState);
    } else {
        nodeState = bzBTStatePool(oldState, node);
        bzBTStateAppend(newState, nodeState);
    }
    i32 numRunning = 0;
    i32 numSuccessful = 0;
    i32 numFailed = 0;
    i32 numChildren = 0;
    BzBTStatus status = BZ_BT_ERROR;
    BzBTNode *child = start;
    for (;child; child = child->next) {
        BzBTStatus childStatus = bzBTExecuteNode(child, dt, nextState, oldState, newState);
        numChildren++;
        switch (childStatus) {
            case BZ_BT_RUNNING:
                numRunning++;
                break;
            case BZ_BT_SUCCESS:
                numSuccessful++;
                break;
            case BZ_BT_FAIL:
                numFailed++;
                break;
            default:
                break;
        }
        switch (node->type) {
            case BZ_BT_COMP_SELECTOR:
            case BZ_BT_COMP_PARALLEL_SELECTOR:
                if (childStatus == BZ_BT_SUCCESS)
                    status = BZ_BT_SUCCESS;
                break;
            case BZ_BT_COMP_SEQUENCE:
            case BZ_BT_COMP_PARALLEL_SEQUENCE:
                if (childStatus == BZ_BT_FAIL)
                    status = BZ_BT_FAIL;
                break;
            default:
                break;
        }
        if (status == BZ_BT_FAIL || status == BZ_BT_SUCCESS)
            break;
        if (numRunning > 0 && !isParallel) {
            status = BZ_BT_RUNNING;
            break;
        }
    }
    switch (node->type) {
        case BZ_BT_COMP_SELECTOR:
        case BZ_BT_COMP_PARALLEL_SELECTOR:
            if (numFailed == numChildren)
                status = BZ_BT_FAIL;
            break;
        case BZ_BT_COMP_SEQUENCE:
        case BZ_BT_COMP_PARALLEL_SEQUENCE:
            if (numSuccessful == numChildren)
                status = BZ_BT_SUCCESS;
            break;
        default:
            break;
    }

    if (status == BZ_BT_ERROR) {
        bzBTStateRelease(newState, nodeState);
        return BZ_BT_ERROR;
    }

    bool finished = status == BZ_BT_SUCCESS ||
                    status == BZ_BT_FAIL;
    if (finished) {
        // Dummy state is no longer needed
        bzBTStateRelease(newState, nodeState);
    } else {
        BZ_ASSERT(status == BZ_BT_RUNNING);
        nodeState->as.composite.running = child;
    }
    return status;
}
static inline BzBTStatus bzBTExecuteDecorator(const BzBTNode *node, f32 dt,
                                              BzBTNodeState *nodeState,
                                              BzBTState *oldState, BzBTState *newState) {
    // Ensure decorator has only one child, if any
    BZ_ASSERT(!node->first || node->first == node->last);
    BzBTNodeState *nextState = getNextNodeState(node, nodeState);

    switch (node->type) {
        case BZ_BT_DECOR_REPEAT:
            if (!nodeMatchesState(node, nodeState)) {
                BzBTNodeState *newNodeState = bzBTStatePool(oldState, node);
                newNodeState->as.repeat.iter = 0;
                bzBTStateAppend(newState, newNodeState);
                nodeState = newNodeState;
            } else {
                bzBTStateRenew(oldState, newState, nodeState);
            }
            break;
        case BZ_BT_DECOR_DELAY:
            if (!nodeMatchesState(node, nodeState)) {
                BzBTNodeState *newNodeState = bzBTStatePool(oldState, node);
                newNodeState->as.delay.elapsed = dt;
                bzBTStateAppend(newState, newNodeState);
                return BZ_BT_RUNNING;
            }
            nodeState->as.delay.elapsed += dt;
            if (nodeState->as.delay.elapsed < node->as.delay.ms) {
                bzBTStateRenew(oldState, newState, nodeState);
                return BZ_BT_RUNNING;
            }
            break;
        default:
            break;
    }

    // Implicit success, if no children are present
    BzBTStatus inStatus = BZ_BT_SUCCESS;
    if (node->first)
        inStatus = bzBTExecuteNode(node->first, dt, nextState, oldState, newState);

    // Propagate ERROR, RUNNING up
    if (inStatus == BZ_BT_ERROR)
        return BZ_BT_ERROR;
    if (inStatus == BZ_BT_RUNNING)
        return BZ_BT_RUNNING;

    BzBTStatus status = BZ_BT_ERROR;
    switch (node->type) {
        case BZ_BT_DECOR_DUMMY:
        case BZ_BT_DECOR_DELAY: // Delay already handled
            status = inStatus;
            break;
        case BZ_BT_DECOR_SUCCESS:
            status = BZ_BT_SUCCESS;
            break;
        case BZ_BT_DECOR_FAIL:
            status = BZ_BT_FAIL;
            break;
        case BZ_BT_DECOR_INVERT:
            if (inStatus == BZ_BT_FAIL)
                status = BZ_BT_SUCCESS;
            if (inStatus == BZ_BT_SUCCESS)
                status = BZ_BT_FAIL;
            break;
        case BZ_BT_DECOR_UNTIL_SUCCESS:
            if (inStatus == BZ_BT_SUCCESS)
                status = BZ_BT_SUCCESS;
            else
                status = BZ_BT_RUNNING;
            break;
        case BZ_BT_DECOR_UNTIL_FAIL:
            if (inStatus == BZ_BT_FAIL)
                status = BZ_BT_SUCCESS;
            else
                status = BZ_BT_RUNNING;
            break;
        case BZ_BT_DECOR_REPEAT:
            BZ_ASSERT(nodeState->node == node);
            nodeState->as.repeat.iter++;
            if (nodeState->as.repeat.iter >= node->as.repeat.n) {
                bzBTStateRelease(newState, nodeState);
                status = inStatus;
                break;
            }
            status = BZ_BT_RUNNING;
            break;
        default:
            break;
    }
    return status;
}
static inline BzBTStatus bzBTExecuteNode(const BzBTNode *node, f32 dt,
                                         BzBTNodeState *nodeState,
                                         BzBTState *oldState, BzBTState *newState) {
    BzBTStatus status = BZ_BT_ERROR;
    switch (node->type) {
        case BZ_BT_COMP_SELECTOR:
        case BZ_BT_COMP_SEQUENCE:
        case BZ_BT_COMP_PARALLEL_SELECTOR:
        case BZ_BT_COMP_PARALLEL_SEQUENCE:
            status = bzBTExecuteComposite(node, dt, nodeState, oldState, newState);
            break;
        case BZ_BT_DECOR_DUMMY:
        case BZ_BT_DECOR_SUCCESS:
        case BZ_BT_DECOR_FAIL:
        case BZ_BT_DECOR_INVERT:
        case BZ_BT_DECOR_UNTIL_SUCCESS:
        case BZ_BT_DECOR_UNTIL_FAIL:
        case BZ_BT_DECOR_REPEAT:
        case BZ_BT_DECOR_DELAY:
            status = bzBTExecuteDecorator(node, dt, nodeState, oldState, newState);
            break;
        case BZ_BT_ACTION:
            BZ_ASSERT(node->as.action.fn);
            return node->as.action.fn(oldState->userData, dt);
    }
    return status;
}

BzBTStatus bzBTExecute(BzBTState *state, f32 dt) {
    BZ_ASSERT(state->nodeStatePool);
    BZ_ASSERT(bzObjectPoolGetObjectSize(state->nodeStatePool) == bzBTGetNodeStateSize());
    BZ_ASSERT(state);
    if (state->root == NULL) {
        return BZ_BT_FAIL;
    }

    BzBTState newState = {
            ._first = NULL,
            ._last = NULL,
            .nodeStatePool = state->nodeStatePool
    };

    BzBTNodeState *first = state->_first;
    const BzBTNode *firstNode = first ? first->node : state->root;
    BzBTStatus status = bzBTExecuteNode(firstNode, dt, first, state, &newState);

    // Release leftover states
    BzBTNodeState *pState = state->_first;
    while (pState) {
        BzBTNodeState *next = pState->next;
        bzBTStateRelease(state, pState);
        pState = next;
    }
    state->_first = newState._first;
    state->_last = newState._last;

    switch (status) {
        case BZ_BT_SUCCESS:
            state->onSuccess && state->onSuccess(state->userData, dt);
            break;
        case BZ_BT_FAIL:
            state->onFailure && state->onFailure(state->userData, dt);
            break;
        case BZ_BT_ERROR:
            state->onError && state->onError(state->userData, dt);
            break;
        default:
            break;
    }

    return status;
}
