/*
The format is largely equal to that of Pluto, with some changes to reduce
file size, and where necessary due to changes in Lua's architecture.

Pseudo-C is used to express the file format. Padding is assumed to be
nonexistent. The keyword "one_of" is used to express a concept similar to
"union", except that its size is the size of the actual datatype chosen. Thus,
objects which contain, directly or indirectly, a one_of, may vary in size. The
keyword "if" is used to express the fact that the following block may or may
not be present, usually indicated by the type of a previous "one_of".
*/

struct PersistedData {
    Header header;      /* The header used for basic validation. */
    Object rootobj;     /* The root object that was persisted. */
};

struct Header {
    char header[4] = "ERIS";   /* Header signature for rudimentary validation */
    uint8_t sizeof_number;  /* sizeof(lua_Number) to check type compatibility */
    lua_Number test;    /* -1.234567890 to check representation compatibility */
    uint8_t sizeof_int; /* sizeof(int) in persisted data */
    uint8_t sizeof_size_t;  /* sizeof(size_t) in persisted data */
    /* Note that the last two fields determine the size of the int and size_t
     * fields in the following definitions. We write each value in the native
     * "size" and check for truncation when reading, if necessary. */
};

struct Object {
    one_of {
        int type;       /* if the value is one of LUA_TXXX or ERIS_PERMANENT */
        Reference r;    /* otherwise */
        /* Note that the types LUA_TNIL, LUA_TBOOLEAN, LUA_TNUMBER and
         * LUA_TLIGHTUSERDATA will never be "referenced", but always be written
         * directly. */
    }
    if (type) {
        RealObject o;   /* if we have a type, not a reference */
        /* If the object is not primitive (see list above) we remember it and
         * increment the reference counter, and point any future occurrences of
         * it to this one via a reference (see above, Reference r). */
    }
};

struct Reference {
    int ref;            /* The index the object was registered with */
};

struct RealObject {
    one_of {
        uint8_t b;      /* If type == LUA_TBOOLEAN */
        size_t l;       /* If type == LUA_TLIGHTUSERDATA */
        Number n;       /* If type == LUA_TNUMBER */
        String s;       /* If type == LUA_TSTRING */
        Table t;        /* If type == LUA_TTABLE */
        Closure f;      /* If type == LUA_TFUNCTION */
        Userdata u;     /* If type == LUA_TUSERDATA */
        Thread th;      /* If type == LUA_TTHREAD */
        Proto p;        /* If type == LUA_TPROTO (from lobject.h) */
        UpVal uv;       /* If type == LUA_TUPVAL (from lobject.h) */
        PermKey pk;     /* if type == ERIS_PERMANENT */
    };
};

struct Number {
    if(sizeof(lua_Number) == sizeof(uint32_t)) {
        uint32_t rep;   /* binary representation of the number, stored as
                         * integer to re-use endian-agnosticism */
    }
    else if (sizeof(lua_Number) == sizeof(uint64_t)) {
        uint64_t rep;   /* binary representation of the number, stored as
                         * integer to re-use endian-agnosticism */
    }
    else {
        /* unsupported float type -- error if asserts are enabled */
    }
};

struct String {
    size_t length;      /* The length of the string */
    char str[length];   /* The actual string (not always null terminated) */
};

struct Table {
    uint8_t isSpecial;  /* 1 if SP is used; 0 otherwise */
    one_of {
        Object c;       /* if isspecial == 1; closure to refill the table */
        LiteralTable t; /* if isspecial == 0; literal table info */
    };
};

struct LiteralTable {
    Pair p[];           /* key/value pairs */
    Object nil = nil;   /* Nil reference to terminate */
    Object metatable;   /* The metatable (nil for none, otherwise LUA_TTABLE) */
};

struct Pair {
    Object key;         /* never nil, since that indicates the end */
    Object value;       /* never nil, since the entry wouldn't exist then */
};

struct Userdata {
    uint8_t isSpecial;  /* 1 for special persistence, 0 for literal */
    one_of {
        Object c;       /* if isspecial == 1; closure to recreate the udata */
        LiteralUserdata lu; /* if is_special is 0 */
    };
};

struct LiteralUserdata {
    size_t length;      /* Size of the data */
    char data[length];  /* The actual data */
    Object metatable;   /* The metatable (nil for none, otherwise LUA_TTABLE) */
};

struct Closure {
    uint8_t isCClosure; /* 1 if the closure is a C closure; 0 otherwise */
    uint8_t nups;       /* Number of upvalues the function uses */
    one_of {
        CClosure ccl;   /* if isCClosure == 1 */
        LClosure lcl;   /* if isCClosure == 0; it's a Lua closure */
    };
};

struct CClosure {
    Object f;           /* The actual C function. Must be available via the
                         * permanents table on persist and unpersist. */
    Object upvals[Closure.nups]; /* All upvalues */
    /* Note that here the upvalues are the actual objects, i.e. these are not
     * of type LUA_TUPVAL, since C closures' upalues are always closed. */
};

struct LClosure {
    Object proto;       /* The proto this function uses */
    Object upvals[Closure.nups]; /* All upvalues */
};

struct UpVal {
    Object obj;         /* The object this upval refers to; we proxy it with
                         * the LUA_TUPVAL type to keep shared upvalues intact */
}

struct Proto {
    int linedefined;    /* Start of line range */
    int lastlinedefined;    /* End of line range */
    uint8_t numparams;  /* Number of parameters taken */
    uint8_t is_vararg;  /* 1 if function accepts varargs, 0 otherwise */
    uint8_t maxstacksize;   /* Size of stack reserved for the function */

    int sizecode;       /* Number of instructions in code */
    Instruction code[sizecode]; /* The proto's code */

    int sizek;          /* Number of constants referenced */
    Object k[sizek];    /* Constants referenced */

    int sizep;          /* Number of inner Protos referenced */
    Object p[sizep];    /* Inner Protos referenced */

    int sizeupvalues;   /* Number of upvalues used */
    Upvaldesc upvalues[sizeupvalues]; /* The locations of the upvalues */

    uint8_t debug;      /* 1 if debug data is present; 0 otherwise */
    if (debug) {
        Object source;  /* The source code string */

        int sizelineinfo;   /* Number of opcode-line mappings */
        int lineinfo[sizelineinfo]; /* opcode-line mappings */

        int sizelocvars;    /* Number of local variable names */
        LocVar[sizelocvars];    /* Local variable names */

        Object upvalnames[sizeupvalues];    /* Upvalue names */
    }
};

struct Upvaldesc {
    uint8_t instack;    /* whether it is in stack */
    uint8_t idx;   /* index of upvalue (in stack or in outer function's list) */
};

struct LocVar {
    int startpc;        /* Point where variable is active */
    int endpc;          /* Point where variable is dead */
    Object name;        /* Name of the local variable */
};

struct Thread {
    int stacksize;      /* The overall size of the stack filled with objects,
                         * including all stack frames. */
    size_t top;         /* top = L->top - L->stack; */
    Object stack[stacksize];    /* All stack values, bottom up */

    uint8_t status;     /* current thread status (ok, yield) */
    size_t errfunc;     /* current error handling function (stack index) */

    CallInfo ci[];      /* The CallInfo stack, starting with base_ci */
    if (status == LUA_YIELD) {
        size_t extra;   /* value of thread->ci->extra, which is the original
                         * value of thread->ci->func */
    }

    OpenUpval openupvals[]; /* Upvalues to open */
};

struct CallInfo {
    size_t func;        /* func = ci->func - thread->stack */
    size_t top;         /* top = ci->top - thread->stack */
    int16_t nresults;   /* expected number of results from this function */
    uint8_t callstatus;
    if (callstatus & CIST_YPCALL) {
        size_t extra;   /* the stack level of the function being pcalled */
    }
    if (callstatus & CIST_LUA) {
        size_t base;    /* base = ci->u.l.base - thread-stack */
        size_t savedpc; /* savedpc = ci->u.l.savedpc - ci_func(ci)->p->code */
    }
    else {
        uint8_t status;
        if (callstatus & (CIST_YPCALL | CIST_YIELDED)) {
            int ctx;    /* context info. in case of yields */
            Object k;   /* C function, callback for resuming */
        }
    }
    uint8_t hasNext;    /* 1 if there's another CI to read; 0 otherwise */
};

struct OpenUpval {
    size_t idx;         /* stack index of the value + 1; 0 if end of list */
    Object upval;       /* The upvalue */
};

struct PermKey {
    uint8_t type;   /* The actual LUA_TXXX of the original value. */
    Object key;     /* The value to use as a key when unpersisting. */
    /* Note that we store the type of the original value (replaced by the
     * permanent table value used as a key when unpersisting) to ensure the
     * value in the permanents table when unpersisting has the correct type. */
};
