/**
 * @file state_machine.h
 *
 * @brief State Machine
 *
 * This file is the interface into a generic state machine system.
 *
 * State machines are defined as an array of function callbacks where each state
 * is an index in the array with three functions per state: `on_init()`,
 * `on_update()`, and `on_exit()`.
 *
 * **`on_init()`** Ran once when transitioning into the new state
 * **`on_update()`** Ran once every frame
 * **`on_exit()`** Ran once when exiting a state, for cleanup.
 *
 * The state machine `on_update()` function is "registered" to a linked-list of
 * other state machine update functions. This list will call the state machines
 * active `on_update()` function. This allows much of the complexity of state
 * transitions to remain central to `state_machine.c`. Also, multiple state
 * machines can be registered to this list. This allows using substates within
 * states, or have one off state machines like animation controllers.
 *
 * When a state machine is finished, it can "remove" itself from the main update
 * callback list. This can be done within the update method of its own state
 * machine. This can be used to start a self destructing state machine.
 */
#ifndef STATE_MACHINE_H
#define STATE_MACHINE_H

/**
 * @brief State machine callback function pointer type
 */
typedef void (*StateCallback)(void);

/**
 * @brief State machine callbacks
 */
typedef struct
{
    StateCallback on_init;
    StateCallback on_update;
    StateCallback on_exit;
} StateInfo;

/**
 * @brief State machine instance
 */
typedef struct
{
    /**
     * @brief Pointer to the active update function in `state_infos`
     */
    StateCallback active_update;

    /**
     * @brief Array of @ref StateCallbacks , one entry per state
     */
    StateInfo* state_infos;

    /**
     * @brief Number of elements in the `state_infos` array
     */
    unsigned int num_infos;

    /**
     * @brief The current state of the state machine, the offset into state_infos
     */
    int state;

    /**
     * @brief Flag to determine if statemachine is already running it's active_update function
     */
    bool registered;
} StateMachine;

/**
 * @brief Register a statemachine to run it's update function once per frame
 *
 * @param state_machine pointer to @ref StateMachine to register, cannot be NULL
 */
void state_machine_register(StateMachine* state_machine);

/**
 * @brief Remove a statemachine's update function
 *
 * @param state_machine pointer to @ref StateMachine to remove, cannot be NULL
 */
void state_machine_remove(StateMachine* state_machine);

/**
 * @brief Update registered state machines' update functions
 */
void state_machine_update(void);

/**
 * @brief Calls the current state's on_exit, the new state's on_init, and sets the active update fn
 *
 * @param state_machine pointer to @ref StateMachine, cannot be NULL
 * @param new_state offset into `state_infos` array to transition to
 */
void state_machine_change_state(StateMachine* state_machine, int new_state);

/**
 * @brief no operation
 */
void noop(void);

// clang-format off
#define STATE_INFO_UPDATE_FN_ONLY(fn) {.on_init = noop, .on_update = fn, .on_exit = noop}

#define STATE_MACHINE_DEFINE(infos, num) \
{                                        \
    .state_infos = &infos[0],            \
    .num_infos = num,                    \
    .registered = false,                 \
};

// clang-format on

#endif // STATE_MACHINE_H
