#pragma once
#include "fsm.h"


/**
 * @brief Initialize new Finite State Machine class
 *
 * @param self FSM instance (may be stack based) 
 * @param fsm_type type
 * @param initial_handler starting state handler
 * @param super_handler super state handler
 * @return 
 */
static Exception
fsm_create(
    fsm_c* self,
    unsigned int fsm_type,
    FsmStateHandler_f initial_handler,
    FsmStateHandler_f super_handler
)
{
    uassert(self != NULL);
    uassert(fsm_type > 0);
    uassert(initial_handler != NULL && "initial_handler unset");
    uassert(super_handler != initial_handler && "super_handler must not be the same");

    *self = (fsm_c){
        ._fsm_type = fsm_type,
        ._handler = initial_handler,
        ._handler_super = super_handler,
    };

    return EOK;
}

static Exception
fsm_dispatch(fsm_c* self, const fsm_event_s* e)
{
    uassert(self != NULL);
    uassert(self->_handler != NULL);
    uassert(e != NULL);
    uassert(e->sig > 0);

    FsmStateHandler_f prev_state = self->_handler;
    e$ret(self->_handler(self, e));

    if (unlikely(prev_state != self->_handler)) {
        FsmStateHandler_f next_state = self->_handler;

        while (prev_state != next_state) {
            e$ret(prev_state(self, &fsm_event_exit));
            uassert(self->_handler == next_state && "signal exit: state change is not allowed");

            prev_state = self->_handler;
            e$ret(self->_handler(self, &fsm_event_entry));
            next_state = self->_handler;
        }
    }

    return EOK;
}

static Exception
fsm_init(fsm_c* self)
{
    uassert(self != NULL);
    return fsm_dispatch(self, &fsm_event_entry);
}

static Exception
fsm_super(fsm_c* self, const fsm_event_s* e)
{
    uassert(self != NULL);
    uassert(e != NULL);

    e$assert(self->_handler_super != NULL && "_handler_super not set / or double call of super");

    if (e->sig < FsmSignal__user) {
        // Never handle generic signals in base!
        return EOK;
    }

    FsmStateHandler_f prev_state = self->_handler;
    (void)prev_state;

    // HACK: preventing double call of super() from self->_super_handler
    // set to NULL, and if it's called from prev_super, the assert self->_handler_super will fail
    FsmStateHandler_f prev_super = self->_handler_super;
    self->_handler_super = NULL;

    e$except(err, prev_super(self, e))
    {
        self->_handler_super = prev_super;
        return err;
    }

    uassert(
        (self->_handler_super == NULL || self->_handler_super == prev_super) &&
        "super state change not allowed"
    );
    self->_handler_super = prev_super;

    uassert(prev_state == self->_handler && "state transition in _handler_super not allowed");

    return EOK;
}

static Exception
fsm_trans(fsm_c* self, FsmStateHandler_f new_state)
{
    uassert(self != NULL);
    uassert(new_state != NULL);
    uassert(new_state != self->_handler_super && "transition to _handler_super not allowed");

    self->_handler = new_state;
    return EOK;
}

const struct __cex_namespace__fsm fsm = {
    // Autogenerated by CEX
    // clang-format off

    .create = fsm_create,
    .dispatch = fsm_dispatch,
    .init = fsm_init,
    .super = fsm_super,
    .trans = fsm_trans,

    // clang-format on
};
