#ifndef Z64SCHEDULE_H
#define Z64SCHEDULE_H

#include "z64scripts.h"

/**
 *   Schedule is a subsystem that acts as a way to make decisions based on the
 * time and scene (and a limited selection of items). It is utilized by writing
 * a script that is encoded into bytecode and run, returning the result in a
 * struct. The returned result can be a value or a encoded time value.
 *
 * The scripts contain 2 kinds of instructions:
 * - Checks with branches (relative offsets, either 1-byte offsets (short, *_S),
 *                         or 2-byte offsets (long, *_L))
 * - Returns
 *
 * Scripts are stored as u8[]. They are built using the macros are the bottom of
 * this file. The scheduledis.py script can be used to convert any scripts in
 * actor data into the macros.
 */

/**
 * Actors that use this system generally create 3 functions to interact with it.
 *
 * - FollowSchedule: The action function an actor sets to follow the schedule.
 * - ProcessScheduleOutput: Holds the logic of processing the output received by running the schedule script, called by FollowSchedule
 * - HandleSchedule: Holds the actual logic of how to actually follow the schedule based on the processed output, called by FollowSchedule
 */

struct PlayState;

typedef enum ScheduleCommandId {
    /* 0x00 */ SCHEDULE_CMD_ID_CHECK_WEEK_EVENT_REG_S, // Checks if a weekEventReg flag is set and branches if so, short range branch
    /* 0x01 */ SCHEDULE_CMD_ID_CHECK_WEEK_EVENT_REG_L, // Checks if a weekEventReg flag is set and branches if so, long range branch
    /* 0x02 */ SCHEDULE_CMD_ID_CHECK_TIME_RANGE_S,     // Checks if the current time is within the range of the two provided times and branches if so, short range branch
    /* 0x03 */ SCHEDULE_CMD_ID_CHECK_TIME_RANGE_L,     // Checks if the current time is within the range of the two provided times and branches if so, long range branch
    /* 0x04 */ SCHEDULE_CMD_ID_RET_VAL_L,              // Ends script and returns 2-byte value (Note: bugged as the return value size is only 1 byte in the struct)
    /* 0x05 */ SCHEDULE_CMD_ID_RET_NONE,               // Ends script without returning anything
    /* 0x06 */ SCHEDULE_CMD_ID_RET_EMPTY,              // Ends script and indicates return without changing existing value
    /* 0x07 */ SCHEDULE_CMD_ID_NOP,                    // No-Op
    /* 0x08 */ SCHEDULE_CMD_ID_CHECK_MISC_S,           // Special check based on items or masks and branches if check passes, short range branch
    /* 0x09 */ SCHEDULE_CMD_ID_RET_VAL_S,              // Ends script and returns byte value
    /* 0x0A */ SCHEDULE_CMD_ID_CHECK_NOT_IN_SCENE_S,   // Checks if the current scene is not SceneId and branches if so, short range branch
    /* 0x0B */ SCHEDULE_CMD_ID_CHECK_NOT_IN_SCENE_L,   // Checks if the current scene is not SceneId and branches if so, long range branch
    /* 0x0C */ SCHEDULE_CMD_ID_CHECK_NOT_IN_DAY_S,     // Checks if the current day is not Day and branches if so, short range branch
    /* 0x0D */ SCHEDULE_CMD_ID_CHECK_NOT_IN_DAY_L,     // Checks if the current day is not Day and branches if so, long range branch
    /* 0x0E */ SCHEDULE_CMD_ID_RET_TIME,               // Returns 2 time values
    /* 0x0F */ SCHEDULE_CMD_ID_CHECK_BEFORE_TIME_S,    // Branches if the current time is less than the command time, short range branch
    /* 0x10 */ SCHEDULE_CMD_ID_CHECK_BEFORE_TIME_L,    // Branches if the current time is less than the command time, long range branch
    /* 0x11 */ SCHEDULE_CMD_ID_BRANCH_S,               // Always branch, short range branch
    /* 0x12 */ SCHEDULE_CMD_ID_BRANCH_L                // Always branch, long range branch
} ScheduleCommandId;

typedef enum {
    /* 0 */ SCHEDULE_CHECK_MISC_ROOM_KEY,
    /* 1 */ SCHEDULE_CHECK_MISC_LETTER_TO_KAFEI,
    /* 2 */ SCHEDULE_CHECK_MISC_MASK_ROMANI
} ScheduleCheckMisc;

typedef struct {
    /* 0x0 */ u8 result;
    /* 0x4 */ s32 time0;
    /* 0x8 */ s32 time1;
    /* 0xC */ s32 hasResult;
} ScheduleOutput; // size = 0x10

typedef struct {
    /* 0x0 */ u8 cmd;
} ScheduleCmdBase; // size = 0x1

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 flagByte;
    /* 0x2 */ u8 flagMask;
    /* 0x3 */ s8 offset;
} ScheduleCmdCheckWeekEventRegS; // size = 0x4

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 flagByte;
    /* 0x2 */ u8 flagMask;
    /* 0x3 */ u8 offsetH;
    /* 0x4 */ u8 offsetL;
} ScheduleCmdCheckWeekEventRegL; // size = 0x5

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 startHr;
    /* 0x2 */ u8 startMin;
    /* 0x3 */ u8 endHr;
    /* 0x4 */ u8 endMin;
    /* 0x5 */ s8 offset;
} ScheduleCmdCheckTimeRangeS; // size = 0x6

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 startHr;
    /* 0x2 */ u8 startMin;
    /* 0x3 */ u8 endHr;
    /* 0x4 */ u8 endMin;
    /* 0x5 */ u8 offsetH;
    /* 0x6 */ u8 offsetL;
} ScheduleCmdCheckTimeRangeL; // size = 0x7

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 retH;
    /* 0x2 */ u8 retL;
} ScheduleCmdReturnValueL; // size = 0x3

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 unk1;
    /* 0x2 */ u8 unk2;
    /* 0x3 */ u8 unk3;
} ScheduleCmdNop; // size = 0x4

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 which;
    /* 0x2 */ s8 offset;
} ScheduleCmdCheckMiscS; // size = 0x3

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 result;
} ScheduleCmdReturnValueS; // size = 0x2

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 sceneIdH;
    /* 0x2 */ u8 sceneIdL;
    /* 0x3 */ s8 offset;
} ScheduleCmdCheckNotInSceneS; // size = 0x4

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 sceneIdH;
    /* 0x2 */ u8 sceneIdL;
    /* 0x3 */ u8 offsetH;
    /* 0x4 */ u8 offsetL;
} ScheduleCmdCheckNotInSceneL; // size = 0x5

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 dayH;
    /* 0x2 */ u8 dayL;
    /* 0x3 */ s8 offset;
} ScheduleCmdCheckNotInDayS; // size = 0x4

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 dayH;
    /* 0x2 */ u8 dayL;
    /* 0x3 */ u8 offsetH;
    /* 0x4 */ u8 offsetL;
} ScheduleCmdCheckNotInDayL; // size = 0x5

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 time0Hr;
    /* 0x2 */ u8 time0Min;
    /* 0x3 */ u8 time1Hr;
    /* 0x4 */ u8 time1Min;
    /* 0x5 */ u8 result;
} ScheduleCmdReturnTime; // size = 0x6

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 timeHr;
    /* 0x2 */ u8 timeMin;
    /* 0x3 */ s8 offset;
} ScheduleCmdCheckBeforeTimeS; // size = 0x4

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 timeHr;
    /* 0x2 */ u8 timeMin;
    /* 0x3 */ u8 offsetH;
    /* 0x4 */ u8 offsetL;
} ScheduleCmdCheckBeforeTimeL; // size = 0x5

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ s8 offset;
} ScheduleCmdBranchS; // size = 0x2

typedef struct {
    /* 0x0 */ ScheduleCmdBase base;
    /* 0x1 */ u8 offsetH;
    /* 0x2 */ u8 offsetL;
} ScheduleCmdBranchL; // size = 0x3

#define SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_S(flag, offset) \
    SCHEDULE_CMD_ID_CHECK_WEEK_EVENT_REG_S, SCRIPT_UNPACK_16(flag), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_CHECK_WEEK_EVENT_REG_L(flag, offset) \
    SCHEDULE_CMD_ID_CHECK_WEEK_EVENT_REG_L, SCRIPT_UNPACK_16(flag), SCRIPT_UNPACK_16(offset)

#define SCHEDULE_CMD_CHECK_TIME_RANGE_S(startHr, startMin, endHr, endMin, offset) \
    SCHEDULE_CMD_ID_CHECK_TIME_RANGE_S, SCRIPT_UNPACK_8(startHr), SCRIPT_UNPACK_8(startMin), SCRIPT_UNPACK_8(endHr), SCRIPT_UNPACK_8(endMin), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_CHECK_TIME_RANGE_L(startHr, startMin, endHr, endMin, offset) \
    SCHEDULE_CMD_ID_CHECK_TIME_RANGE_L, SCRIPT_UNPACK_8(startHr), SCRIPT_UNPACK_8(startMin), SCRIPT_UNPACK_8(endHr), SCRIPT_UNPACK_8(endMin), SCRIPT_UNPACK_16(offset)

#define SCHEDULE_CMD_RET_VAL_L(result) \
    SCHEDULE_CMD_ID_RET_VAL_L, SCRIPT_UNPACK_16(result)

#define SCHEDULE_CMD_RET_NONE() \
    SCHEDULE_CMD_ID_RET_NONE

#define SCHEDULE_CMD_RET_EMPTY() \
    SCHEDULE_CMD_ID_RET_EMPTY,

#define SCHEDULE_CMD_NOP(unk0, unk1, unk2) \
    SCHEDULE_CMD_ID_NOP, SCRIPT_UNPACK_8(unk0), SCRIPT_UNPACK_8(unk1), SCRIPT_UNPACK_8(unk2)

#define SCHEDULE_CMD_CHECK_MISC_S(which, offset) \
    SCHEDULE_CMD_ID_CHECK_MISC_S, SCRIPT_UNPACK_8(which), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_RET_VAL_S(result) \
    SCHEDULE_CMD_ID_RET_VAL_S, SCRIPT_UNPACK_8(result)

#define SCHEDULE_CMD_CHECK_NOT_IN_SCENE_S(scene, offset) \
    SCHEDULE_CMD_ID_CHECK_NOT_IN_SCENE_S, SCRIPT_UNPACK_16(scene), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_CHECK_NOT_IN_SCENE_L(scene, offset) \
    SCHEDULE_CMD_ID_CHECK_NOT_IN_SCENE_L, SCRIPT_UNPACK_16(scene), SCRIPT_UNPACK_16(offset)

#define SCHEDULE_CMD_CHECK_NOT_IN_DAY_S(day, offset) \
    SCHEDULE_CMD_ID_CHECK_NOT_IN_DAY_S, SCRIPT_UNPACK_16(day), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_CHECK_NOT_IN_DAY_L(day, offset) \
    SCHEDULE_CMD_ID_CHECK_NOT_IN_DAY_L, SCRIPT_UNPACK_16(day), SCRIPT_UNPACK_16(offset)

#define SCHEDULE_CMD_RET_TIME(time0Hr, time0Min, time1Hr, time1Min, result) \
    SCHEDULE_CMD_ID_RET_TIME, SCRIPT_UNPACK_8(time0Hr), SCRIPT_UNPACK_8(time0Min), SCRIPT_UNPACK_8(time1Hr), SCRIPT_UNPACK_8(time1Min), SCRIPT_UNPACK_8(result)

#define SCHEDULE_CMD_CHECK_BEFORE_TIME_S(timeHr, timeMin, offset) \
    SCHEDULE_CMD_ID_CHECK_BEFORE_TIME_S, SCRIPT_UNPACK_8(timeHr), SCRIPT_UNPACK_8(timeMin), SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_CHECK_BEFORE_TIME_L(timeHr, timeMin, offset) \
    SCHEDULE_CMD_ID_CHECK_BEFORE_TIME_L, SCRIPT_UNPACK_8(timeHr), SCRIPT_UNPACK_8(timeMin), SCRIPT_UNPACK_16(offset)

#define SCHEDULE_CMD_BRANCH_S(offset) \
    SCHEDULE_CMD_ID_BRANCH_S, SCRIPT_UNPACK_8(offset)

#define SCHEDULE_CMD_BRANCH_L(offset) \
    SCHEDULE_CMD_ID_BRANCH_L, SCRIPT_UNPACK_16(offset)

typedef u8 ScheduleScript;

s32 Schedule_RunScript(struct PlayState* play, ScheduleScript* script, ScheduleOutput* output);

#endif
