AUTOTEST {
    test_obj_core();
}

// ----------------------------------------------------------------------------

typedef struct orc { OBJ
    char name[8];
} orc;

//#pragma pack(push, 1)
typedef struct boy { OBJ
    char name[8];
    float hp;
} boy;
//#pragma pack(pop)

OBJTYPEDEF(orc,100)
int   orc_ctor(orc* self) { strcpy(self->name, "orc!"); printf("i'm orc %p\n", self); return 0; }
int   orc_tick(orc* self) { printf("%p orc tick\n", self); return 0; }
char* orc_save(orc *self) { return obj_saveini(self); } // PACKMSG("ss", "orc_v1", self->name); }

OBJTYPEDEF(boy,101)
int   boy_ctor(boy* self) { strcpy(self->name, "boy!"); printf("i'm boy %p\n", self); self->hp = 0x80; return 0; }
int   boy_tick(boy* self) { printf("%p boy tick, hp:%f\n", self, self->hp); return 0; }
char* boy_save(boy *self) { return obj_saveini(self); } // PACKMSG("ssf", "boy_v1", self->name, self->hp); }

AUTOTEST {
    EXTEND(orc, ctor,tick,save);
    EXTEND(boy, ctor,tick,save);

    // instance gameobjs

    boy *obj1 = obj_new(boy, "boy", 123);
    orc *obj2 = obj_new(orc, "orc");
    orc *obj3 = obj_new(orc, "orc");

    printf("%p\n", obj_datac(obj1));
    printf("%d vs %d vs %d\n", (int)sizeof(boy), obj_sizeof(obj1), obj_size(obj1));

    // generics
    obj_tick(obj1);
    obj_tick(obj2);
    obj_tick(obj3);

    obj_hexdump(obj1);

    obj_free(obj1); // will free

    obj_ref(obj2);
    obj_unref(obj2); // will free

    obj_ref(obj3);
    obj_free(obj3); // will do nothing
    obj_unref(obj3); // will free

    // make a dangling reference. this will be printed at end of program.
    static int dangling;
    obj1 = obj_new(boy, "boy", 123);
    obj_ref(obj1);
}

// --- scene

AUTOTEST {
    test_obj_scene();
}

// --- comps

struct WorldTravellerComponent {
    unsigned world_source;
    unsigned world_target;
};

struct TransformComponent {
    vec3 position;
    quat rotation;
    vec3 scale;
};

struct VelocityComponent {
    float speed;
};

struct LookComponent {
    float sensitivity; // _and_polarity;
    vec2 rotation;
};

struct PhysicsComponent {
    float gravity;
    vec3 velocity;
    vec3 acceleration;
    vec3 rotationVelocity;
    vec3 rotationAcceleration;
};

struct RenderComponent {
    aabb box;
    mesh_t mesh;
    texture_t texture;
};

AUTOTEST {
    test_obj_ecs();
}

// generics

// --- example: new class

// declare new object

TYPEDEF_STRUCT(box,102,
    int x,y,w,h;
);

// /* same as: */
// typedef struct box { OBJ
//     int x,y,w,h;
// } box;
// OBJTYPEDEF(box,102)

// implement a few built-in interfaces

#define box(...)         obj(box, __VA_ARGS__)
char*   box_save(const box *b) { return obj_saveini(b); } // PACKMSG("siiii", "box_v1", b->x, b->y, b->w, b->h); }
bool    box_load(box *b, const char* s) { return !!obj_loadini(b, s); } // char *header; return UNPACKMSG(s, "siiii", &header, &b->x, &b->y, &b->w, &b->h) && !strcmp(header, "box_v1"); }
int     box_test(box *b) { return b->w > 0 && b->h > 0; }
void    box_dtor(box *b) { puts("bye box!"); }

// create a new obj interface (not only for box! valid for every other obj that would extend on this)

obj_vtable(area, float, { return 0; });

#define obj_area(o,...) obj_method(area, o, ##__VA_ARGS__)

// implement area interface for box object

float   box_area(box *b) { return b->w * b->h; }

AUTOTEST {
    // reflect
    STRUCT(box,int,x);
    STRUCT(box,int,y);
    STRUCT(box,int,w);
    STRUCT(box,int,h);

    // extend
    obj_extend(box,save);
    obj_extend(box,load);
    obj_extend(box,test);
    obj_extend(box,dtor);
    obj_extend(box,area);

    // -- example

    box b = box(0,0,2,3);
    box *c = obj_new(box, 1,1,3,4);

    test( obj_test(&b) );
    test( obj_test(c) );

    test( obj_area(&b) == 6 );
    test( obj_area(c) == 12 );

    // serialization tests
    test_obj_serialization(&b, c);

    test_obj_similarity(&b, c);
    char *sav = obj_save(c);
    obj_load(&b, sav);
    // obj_load(&b, obj_save(c)); // @fixme: this expression in mingw
    test_obj_equality(&b, c);
}

// --- reflection

typedef struct MyVec3 { OBJ
    float x,y,z;
} MyVec3;

OBJTYPEDEF(MyVec3,77)

typedef struct MyTransform {
    MyVec3 location;          ///R @todo: ideally, we would want self-reflection to inscribe these members
    MyVec3 rotation;          ///R @todo: ideally, we would want self-reflection to inscribe these members
    float  scale;             ///R @todo: ideally, we would want self-reflection to inscribe these members
} MyTransform;

/*
// @todo: which is technically like doing
AUTORUN {
    STRUCT(MyTransform, MyVec3, location);
    STRUCT(MyTransform, MyVec3, rotation);
    STRUCT(MyTransform, float, scale);
}
*/

OBJTYPEDEF(MyTransform,78);

typedef struct MyObject { OBJ
    char *id;
    int spawnx,spawny;
    float time;
    struct MyObject *next;
    MyTransform tr;
} MyObject;

OBJTYPEDEF(MyObject,79);


AUTOTEST {
    STRUCT( MyVec3, float, x, "Right" );
    STRUCT( MyVec3, float, y, "Forward" );
    STRUCT( MyVec3, float, z, "Up" );

    STRUCT( MyTransform, MyVec3, location, "World location (absolute)" );
    STRUCT( MyTransform, MyVec3, rotation, "Local rotation (in degrees)" );
    STRUCT( MyTransform, float, scale, "Local scale (in centimeters)" );

    STRUCT( MyObject, int, spawnx, "Actor Spawn X" );
    STRUCT( MyObject, int, spawny, "Actor Spawn Y" );
    STRUCT( MyObject, string, id, "Actor name" );
    STRUCT( MyObject, MyTransform, tr, "Actor transform" );
    STRUCT( MyObject, float, time, "Actor time" );
    STRUCT( MyObject, MyObject *, next, "Next actor in seq" );

    // reflect_print("MyVec3");
    // reflect_print("MyTransform");
    // reflect_print("MyObject");

    // construct a type from a reflected struct
    MyVec3 *o = obj_new(MyVec3, 1,2,-3);
    test( 12 == obj_size(o) );
    MyVec3 *o2 = obj_make("[MyVec3]\nfloat.y=2\nfloat.x=1\nfloat.z=-3");
    test( !obj_comp(o,o2) ) || obj_hexdump(o), obj_hexdump(o2);

    for each_objmember(o,type,name,ptr) {
        /**/ if( !strcmp(type, "float") )  printf("%s %s = %f\n", type, name, *(float*)ptr );
        else if( !strcmp(type, "double") ) printf("%s %s = %f\n", type, name, *(double*)ptr );
    }

    test_obj_console(o);
    test_obj_console(o2);

    // Setup objects
    MyObject *root = obj_make("[MyObject]");
    obj_hexdump(root);

    MyObject *oo = obj_make("[MyObject]\nid=\"An identifier!\"\nx=123\ny=256\nrotation=90\nnext=root\n");
    obj_hexdump(oo);

    // Dump contents of our objects

    obj_print(oo);
    puts("---");

    obj_hexdump(oo);
    puts("---");

    // Save to mem

    char *sav = obj_savebin(oo);
    test( sav && strlen(sav) > 0 );

    // Clear

    obj_zero(oo);
    obj_hexdump(oo);
    puts("---");

    // Reload

    obj_loadbin(oo, sav);
    obj_hexdump(oo);
}

// --- Benchmarks for call overhead.

AUTOTEST {
    // Here, we're using a blank ctor call as a method to test/stress call overhead.
    //
    // results (old i5-4300/1.90Ghz laptop):
    // v1: 427 million calls/s. compiled with "cl /Ox     /Os /MT /DNDEBUG /GL /GF /arch:AVX2"
    // v2: 450 million calls/s. compiled with "cl /Ox /O2     /MT /DNDEBUG     /GF /arch:AVX2"

    double t;
    enum { N = 100000000 };

    t = -time_ss();
        MyVec3 o = obj(MyVec3, 1,2,3); obj_setname(&o, "MyVec3");
        for( unsigned i = 0; i < N; ++i ) {
            obj_ctor(&o);
        }
    t += time_ss();
    printf("Benchmark: %5.2f objcalls/s %5.2fM objcalls/s\n", N/(t), (N/1000)/(t*1000)); // ((N+N)*5) / (t) );

    t = -time_ss();
        MyVec3 *op = obj_new(MyVec3, 1,2,3);
        for( unsigned i = 0; i < N; ++i ) {
            obj_ctor(op);
        }
    t += time_ss();
    printf("Benchmark: %5.2f objcalls/s %5.2fM objcalls/s\n", N/(t), (N/1000)/(t*1000)); // ((N+N)*5) / (t) );
}

// --- metas

AUTOTEST {
    box b = box(1,2,3,4);

    test( !strcmp("box", obj_type(&b)) );
    test( !strcmp("box", obj_name(&b)) );

    b = box(1,2,3,4);
    obj_setname(&b, "MyBox1");

    test( !strcmp("box", obj_type(&b)) );
    test( !strcmp("MyBox1", obj_name(&b)) );

    test_obj_metadatas(&b);

    obj_free(&b);
}



TYPEDEF_STRUCT(test_vec3_serial, __COUNTER__,
    vec3 position;
    vec3 accel;
);
AUTOTEST {
    STRUCT(test_vec3_serial, vec3, position);
    STRUCT(test_vec3_serial, vec3, accel);
    test_vec3_serial v = obj(test_vec3_serial, {1,2,3},{4,5,6}), z = obj(test_vec3_serial);
    test(obj_comp(&v,&z) != 0) || obj_print(&v) & obj_print(&z);
    obj_loadini(&z, obj_saveini(&v));
    test(obj_comp(&v,&z) == 0) || obj_print(&z);
}

TYPEDEF_STRUCT(MyObject2, __COUNTER__,
    const char* id;
    int x,y;
    float rotation;
    struct MyObject2 *next;
);
AUTORUN {
    STRUCT(MyObject2, const char *, id);
    STRUCT(MyObject2, int, x);
    STRUCT(MyObject2, int, y);
    STRUCT(MyObject2, float, rotation);
    STRUCT(MyObject2, struct MyObject2*, next);

    // Construct two objects
    MyObject2 *root = obj_new(MyObject2);
    MyObject2 *o = obj_new(MyObject2, "An identifier!", 0x11, 0x22, 3.1415f, root );
    // Copy tests
    {
        printf("%d vs %d vs %d\n", (int)sizeof(MyObject2), obj_size(o), (int)sizeof(obj) + obj_size(o) + (int)sizeof(array(void*)));
        MyObject2 *clone = obj_clone(o);
        test(obj_comp(clone,o) == 0) || obj_print(o) & obj_print(clone) & obj_hexdump(o) & obj_hexdump(clone);
        test(obj_free(clone) == 0);
    }

    {
        MyObject2 *copy = obj_new(MyObject2);
        test(obj_copy(copy, o));
        test(obj_comp(copy,o)==0) || obj_print(copy);
        test(obj_free(copy) == 0);
    }

    {
        MyObject2 *copy = obj_new(MyObject2, "A different identifier!", 0x33, 0x44, 0.0f, root );
        test(obj_copy(copy, o));
        test(obj_comp(copy,o)==0) || obj_print(copy);
        test(obj_free(copy) == 0);
    }

    {
        void *copy = obj_malloc(100); // untyped class
        obj_mutate(copy, o);
        obj_print(copy);
        obj_copy(copy, o);
        obj_print(copy);
        obj_free(copy);
    }
}