#ifndef OBJECT_H
#define OBJECT_H

#include <stdlib.h>

#ifndef STANDALONE
#include <pebble.h>
#undef printf
#define printf(...) APP_LOG(APP_LOG_LEVEL_DEBUG, __VA_ARGS__)
#else
#ifdef DEBUG
#define printd(...) printf(__VA_ARGS__)
#else
#define printd(...) ;
#endif
#endif

#define MAX_TOK_CNT 1024
#define MAX_ENV_ELM 100
#define RESULT_LENGTH 128

#define FOR_POINTER_IN_LIST(_list) \
    for(Object *_element = (_list)->list; \
            _element != NULL;\
            _element = _element->forward)
#define POINTER _element

#define FOR_POINTERS_IN_LISTS(_list, _list2) \
    for(Object *_element = (_list)->list, *_element2 = (_list2)->list; \
            _element != NULL && _element2 != NULL; \
            _element = _element->forward, _element2 = _element2->forward)
#define P1 POINTER
#define P2 _element2

#ifdef PBL_PLATFORM_APLITE
#define LOW_MEM
#endif

#ifdef LOW_MEM
#define SIMPLE_ERRORS
#endif

enum errorCode {
    MISMATCHED_PARENS,
    BAD_LIST_OF_SYMBOL_STRINGS,
    TYPE_LIST_NOT_CAUGHT,
    NULL_ENV,
    EMPTY_ENV,
    BUILT_IN_NOT_FOUND,
    NULL_PARSE,
    NULL_LAMBDA_LIST,
    NULL_MAP_ARGS,
    LAMBDA_ARGS_NOT_LIST,
    DID_NOT_FIND_SYMBOL,
    BAD_TYPE,
    UNEXPECTED_FORM,
    LISTS_NOT_SAME_SIZE,
    BAD_NUMBER,
    UNSUPPORTED_NUMBER_TYPE,
    NOT_A_SYMBOL,
    ONLY_ONE_ARGUMENT,
    NOT_A_LIST,
    SCRIPT_NOT_FOUND,
    NO_CLONE_SPECIFIED,
    CAN_ONLY_EVAL_STRINGS,
    UNEXPECTED_EOF,
    INDEX_PAST_END,
};

typedef enum Type {
    TYPE_NUMBER,
    TYPE_BOOL,
    TYPE_LIST,
    TYPE_SLIST,
    TYPE_STRUCT,
    TYPE_FUNC,
    TYPE_SYMBOL,
    TYPE_LAMBDA,
    TYPE_STRING,
    TYPE_OTHER,
    TYPE_ERROR
} Type;

typedef struct Object Object;

struct Lambda;
struct Environment;
struct Slice;
struct Other;
struct Error {
    enum errorCode code;
    char *context;
};

struct Object {
    Type type;
    Object *forward;

    union {
        int number;
        Object *list;
        char *string;

        Object (*func)(Object, Object, struct Environment *);

        struct StructObject *structObject;
        struct Lambda *lambda;
        struct Other *other;
#ifdef SIMPLE_ERRORS
        enum errorCode error;
#else
        struct Error *error;
#endif
    };
};

struct StructDef {
    int fieldCount;
    char* name;
    char** names;
};

struct StructObject {
    int definition;
    //struct StructDef *definition;
    struct Object* fields; // Order should match that in the definition.
};

struct Lambda {
    Object params;
    Object body;
};

struct Other {
    void (*cleanup)(Object *);

    Object (*clone)(struct Other *);

    void *data;
};

char *stringNObj(char *dest, const Object *obj, size_t len);

char *stringObj(char *dest, const Object *obj);

void printList(const Object *list);

void printType(const Object *obj);

void printObj(const Object *obj);

void _printObj(const Object *obj, int newline);

void debugObj(const Object *obj);

void printErr(const Object *obj);

int isEmpty(const Object *obj);

Object *tail(const Object *listObj);

void insertIntoList(Object *dest, int ind, const Object src);

void replaceListing(Object *list, int i, const Object src);

void nf_addToList(Object *dest, Object src);

void deleteList(Object *dest);

int listLength(const Object *listObj);

Object *itemAt(const Object *listObj, int n);

void copyList(Object *dest, const Object *src);

void cleanObject(Object *target);

void printAndClean(Object *target);

void allocObject(Object **spot, const Object src);

void appendList(Object *dest, const Object *src);

int isListy(const Object test);

int isStringy(const Object test);

int isValidType(const Object test);

int isError(const Object obj, const enum errorCode err);

int bothAre(const enum Type type, const Object *obj1, const Object *obj2);

int eitherIs(const enum Type type, const Object *obj1, const Object *obj2);

int areSameType(const Object *obj1, const Object *obj2);

Object cloneList(const Object src);

Object cloneString(Object obj);

Object cloneLambda(const Object old);

Object cloneObject(const Object src);

Object newObject(Type type);

Object listObject();

Object startList(const Object start);

Object objFromSlice(const char *string, int len);

Object stringFromSlice(const char *string, int len);

Object symFromSlice(const char *string, int len);

Object boolObject(int b);

Object numberObject(int num);

Object structObject(int definition);

Object otherObject();

Object errorObject(enum errorCode err);

enum errorCode getErrorCode(const Object obj);

#ifdef SIMPLE_ERRORS
#define errorWithContext(code, context) errorObject(code)
#define errorAddContext(x, y) ;
#else

Object errorWithContext(enum errorCode err, const char *context);

void errorAddContext(Object *o, const char *context);

#endif

struct Error noError();

Object constructLambda(const Object *params, const Object *body);

// Object version of listLength()
Object len(Object obj1, Object, struct Environment *);

#endif