/* $Id$ 
 * 
 * Licensed to the Apache Software Foundation (ASF) under one or more 
 * contributor license agreements. See the NOTICE file distributed with  
 * this work for additional information regarding copyright ownership. 
 * The ASF licenses this file to you under the Apache License, Version  
 * 2.0 (the "License"); you may not use this file except in compliance  
 * with the License. You may obtain a copy of the License at 
 * 
 * http://www.apache.org/licenses/LICENSE-2.0 
 * 
 * Unless required by applicable law or agreed to in writing, software 
 * distributed under the License is distributed on an "AS IS" BASIS, 
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License. 
 */ 

/*
 * test_assignment.c -- test etch object assignability and assignment
 */
#include "apr_time.h" /* some apr must be included first */
#include "etchthread.h"
#include <tchar.h>
#include <stdio.h>
#include <conio.h>

#include "cunit.h"
#include "basic.h"
#include "automated.h"

#include "etch_global.h"
#include "etchobj.h"


int apr_setup(void);
int apr_teardown(void);
int this_setup();
int this_teardown();
apr_pool_t* g_apr_mempool;
const char* pooltag = "etchpool";


/* - - - - - - - - - - - - - - 
 * unit test infrastructure
 * - - - - - - - - - - - - - -
 */

int init_suite(void)
{
    apr_setup();
    etch_runtime_init(TRUE);
    return this_setup();
}

int clean_suite(void)
{
    this_teardown();
    etch_runtime_cleanup(0,0); /* free memtable and cache etc */
    apr_teardown();
    return 0;
}

int g_is_automated_test, g_bytes_allocated;

#define IS_DEBUG_CONSOLE FALSE

/*
 * apr_setup()
 * establish apache portable runtime environment
 */
int apr_setup(void)
{
    int result = apr_initialize();
    if (result == 0)
    {   result = etch_apr_init();
        g_apr_mempool = etch_apr_mempool;
    }
    if (g_apr_mempool)
        apr_pool_tag(g_apr_mempool, pooltag);
    else result = -1;
    return result;
}

/*
 * apr_teardown()
 * free apache portable runtime environment
 */
int apr_teardown(void)
{
    if (g_apr_mempool)
        apr_pool_destroy(g_apr_mempool);
    g_apr_mempool = NULL;
    apr_terminate();
    return 0;
}

int this_setup()
{
    etch_apr_mempool = g_apr_mempool;
    return 0;
}

int this_teardown()
{    
    return 0;
}
#define THISTEST_OBJTYPE      0xeee0
#define CLASSID_CLASS_A       0xeee1
#define CLASSID_CLASS_B       0xeee2
#define CLASSID_CLASS_C       0xeee3
#define CLASSID_CLASS_X       0xeee4
#define CLASSID_CLASS_Y       0xeee5
#define CLASSID_CLASSA_VTABLE 0xeed1
#define CLASSID_CLASSB_VTABLE 0xeed2
#define CLASSID_CLASSC_VTABLE 0xeed3
#define CLASSID_CLASSX_VTABLE 0xeed4
#define CLASSID_CLASSY_VTABLE 0xeed5


/**
 * class_a: base class
 */
typedef struct class_a
{
    unsigned int    hashkey;    
    unsigned short  obj_type;  
    unsigned short  class_id;   
    vtabmask*       vtab;       
    int  (*destroy)(void*);     
    void*(*clone)  (void*); 
    obj_gethashkey  get_hashkey;   
    struct objmask* parent;    
    etchresult*     result;    
    unsigned int    refcount;      
    unsigned int    length;     
    unsigned char   is_null;   
    unsigned char   is_copy;  
    unsigned char   is_static;  
    unsigned char   reserved;

    etch_string* a_string;
     
} class_a;


/**
 * class_a destructor
 */
int destroy_class_a(class_a* thisp)
{
    if (thisp->a_string)
        thisp->a_string->destroy(thisp->a_string);

    destroy_object((objmask*) thisp);
    return 0;
}

/**
 * class_a copy consttructor
 */
class_a* clone_class_a(class_a* origobj)
{
    class_a* newobj  = (class_a*) new_object(sizeof(class_a), THISTEST_OBJTYPE, CLASSID_CLASS_A);
    memcpy(newobj, origobj, origobj->length);
    newobj->a_string = new_string(origobj->a_string->v.valw, ETCH_ENCODING_UTF16);  
    newobj->is_copy  = TRUE; 
    return newobj; 
}


/**
 * class_a constructor
 */
class_a* new_class_a(const wchar_t* strval)
{
    vtabmask* vtab   = NULL;
    class_a* newobj  = (class_a*) new_object(sizeof(class_a), THISTEST_OBJTYPE, CLASSID_CLASS_A);
    newobj->destroy  = destroy_class_a;
    newobj->clone    = clone_class_a;
    newobj->a_string = new_string(strval, ETCH_ENCODING_UTF16);

    if(!(vtab = cache_find(get_vtable_cachehkey(CLASSID_CLASSA_VTABLE), 0)))  
    {    vtab = new_vtable(NULL, sizeof(vtabmask), CLASSID_CLASSA_VTABLE);
         cache_insert(vtab->hashkey, vtab, FALSE);
    } 
 
    newobj->vtab = vtab;   
    return newobj;
}


/**
 * class_b: inherits from class_a
 */
typedef struct class_b
{
    unsigned int    hashkey;    
    unsigned short  obj_type; 
    unsigned short  class_id;
    vtabmask*       vtab;       
    int  (*destroy)(void*);
    void*(*clone)  (void*); 
    obj_gethashkey  get_hashkey;
    class_a*        parent;
    etchresult*     result;
    unsigned int    refcount;
    unsigned int    length;     
    unsigned char   is_null;
    unsigned char   is_copy;
    unsigned char   is_static;
    unsigned char   reserved;

    char* data; 
    int   datasize;  

} class_b;


/**
 * class_b destructor
 */
int destroy_class_b(class_b* thisp)
{
    etch_free(thisp->data);
    destroy_object((objmask*) thisp);
    return 0;
}


/**
 * class_b copy consttructor
 */
class_b* clone_class_b(class_b* origobj)
{
    class_b* newobj  = (class_b*) new_object(sizeof(class_b), THISTEST_OBJTYPE, CLASSID_CLASS_B);
    memcpy(newobj, origobj, origobj->length);

    newobj->parent = origobj->parent? 
        origobj->parent->clone(origobj->parent): 
        new_class_a(NULL);

    if (origobj->data)
    {   newobj->data     = etch_malloc(origobj->datasize, ETCHTYPEB_BYTES); 
        newobj->datasize = origobj->datasize;
        memcpy(newobj->data, origobj->data, origobj->datasize); 
    }
 
    newobj->is_copy = TRUE;
    return newobj;
}


/**
 * class_b constructor
 */
class_b* new_class_b(class_a* parent, const int datalen)
{
   vtabmask* vtab  = NULL;
   etchparentinfo* inheritlist = NULL;
   class_b* newobj = (class_b*) new_object(sizeof(class_b), THISTEST_OBJTYPE, CLASSID_CLASS_B);
   newobj->parent  = parent? parent: new_class_a(NULL);
   newobj->destroy = destroy_class_b;
   newobj->clone   = clone_class_b;
   newobj->data    = etch_malloc(datalen, ETCHTYPEB_BYTES);
   memset(newobj->data, 'x', datalen);
   newobj->datasize = datalen;

   if (NULL == (vtab = cache_find(get_vtable_cachehkey(CLASSID_CLASSB_VTABLE), 0)))  
   {   
       vtab = new_vtable(parent->vtab, sizeof(vtabmask), CLASSID_CLASSB_VTABLE);
       cache_insert(vtab->hashkey, vtab, FALSE);
        
       inheritlist = get_vtab_inheritance_list((objmask*)newobj,
           2, 1, CLASSID_CLASSB_VTABLE); /* create inheritance list */ 
       inheritlist[1].obj_type = THISTEST_OBJTYPE;
       inheritlist[1].class_id = CLASSID_CLASS_A;
    } 
   newobj->vtab = vtab;  
   return newobj;
}


/**
 * class_c: inherits from class_b
 */
typedef struct class_c
{
    unsigned int    hashkey;    
    unsigned short  obj_type; 
    unsigned short  class_id;
    vtabmask*       vtab;       
    int  (*destroy)(void*);
    void*(*clone)  (void*); 
    obj_gethashkey  get_hashkey;
    class_b*        parent;
    etchresult*     result;
    unsigned int    refcount;
    unsigned int    length;
    unsigned char   is_null;
    unsigned char   is_copy;
    unsigned char   is_static;
    unsigned char   reserved;

    int* intarray;
    int  numitems;

} class_c;


/**
 * class_c destructor
 */
int destroy_class_c(class_c* thisp)
{
    etch_free(thisp->intarray);
    destroy_object((objmask*) thisp);
    return 0;
}


/**
 * class_c copy consttructor
 */
class_c* clone_class_c(class_c* origobj)
{
    class_c* newobj  = (class_c*) new_object(sizeof(class_c), THISTEST_OBJTYPE, CLASSID_CLASS_C);
    memcpy(newobj, origobj, sizeof(objmask));

    if  (origobj->parent)
         newobj->parent = origobj->parent->clone(origobj->parent);
    else newobj->parent = new_class_b(NULL, 0);

    if (origobj->intarray)
    {
        newobj->intarray = etch_malloc(origobj->numitems * sizeof(int), ETCHTYPEB_BYTES);
        newobj->numitems = origobj->numitems;
        memcpy(newobj->intarray, origobj->intarray, origobj->numitems * sizeof(int)); 
    }

    newobj->is_copy = TRUE;
    return newobj; 
}


/**
 * class_c constructor
 */
class_c* new_class_c(class_b* parent)
{
    int i = 0;
    vtabmask* vtab   = NULL;
    etchparentinfo*  inheritlist = NULL;
    class_c* newobj  = (class_c*) new_object(sizeof(class_c), THISTEST_OBJTYPE, CLASSID_CLASS_C);
    newobj->parent   = parent? parent: new_class_b(NULL, 0);
    newobj->destroy  = destroy_class_c;
    newobj->clone    = clone_class_c;

    newobj->numitems = 4;
    newobj->intarray = etch_malloc(4 * sizeof(int), ETCHTYPEB_BYTES);
    for(; i < 4; i++) newobj->intarray[i] = i;

    if (NULL == (vtab = cache_find(get_vtable_cachehkey(CLASSID_CLASSC_VTABLE), 0)))  
    {   
       vtab = new_vtable(parent->vtab, sizeof(vtabmask), CLASSID_CLASSC_VTABLE);
       cache_insert(vtab->hashkey, vtab, FALSE);
        
       inheritlist = get_vtab_inheritance_list((objmask*)newobj,
           3, 2, CLASSID_CLASSC_VTABLE); /* create inheritance list */ 
       inheritlist[1].obj_type = THISTEST_OBJTYPE;
       inheritlist[1].class_id = CLASSID_CLASS_B;
       inheritlist[2].obj_type = THISTEST_OBJTYPE;
       inheritlist[2].class_id = CLASSID_CLASS_A;
    } 
    newobj->vtab = vtab;  
    return newobj;
}


/**
 * class_y: inherits from class_x, but is flattened to contain class_x data.
 * inheritance chain is identified through the object vtable inheritance list
 */
typedef struct class_y
{
    unsigned int    hashkey;    
    unsigned short  obj_type; 
    unsigned short  class_id;
    vtabmask*       vtab;       
    int  (*destroy)(void*);
    void*(*clone)  (void*); 
    obj_gethashkey  get_hashkey;
    class_b*        parent;
    etchresult*     result;
    unsigned int    refcount;
    unsigned int    length;
    unsigned char   is_null;
    unsigned char   is_copy;
    unsigned char   is_static;
    unsigned char   reserved;

    int class_x_instance_data;
    int class_y_instance_data;

} class_y;


/**
 * class_x: parent of class_y
 */
typedef struct class_x
{
    unsigned int    hashkey;    
    unsigned short  obj_type; 
    unsigned short  class_id;
    vtabmask*       vtab;       
    int  (*destroy)(void*);
    void*(*clone)  (void*); 
    obj_gethashkey  get_hashkey;
    class_b*        parent;
    etchresult*     result;
    unsigned int    refcount;
    unsigned int    length;
    unsigned char   is_null;
    unsigned char   is_copy;
    unsigned char   is_static;
    unsigned char   reserved;

    int class_x_instance_data;

} class_x;


/**
 * class_x constructor
 */
class_x* new_class_x(int classx_data)
{
    class_x* newobj = (class_x*) new_object(sizeof(class_x), THISTEST_OBJTYPE, CLASSID_CLASS_X);

    newobj->class_x_instance_data = classx_data;
 
    /* class_x has no parent so we can omit vtable and inheritance list if we want */
    return newobj;
}


/**
 * class_y constructor
 * inherits from class_x via inheritance type 2 - flat object
 */
class_y* new_class_y(int classx_data, int classy_data)
{
    etchparentinfo* inheritlist = NULL;
    vtabmask* vtab  = NULL;
    class_y* newobj = (class_y*) new_object(sizeof(class_y), THISTEST_OBJTYPE, CLASSID_CLASS_Y);

    newobj->class_x_instance_data = classx_data;
    newobj->class_y_instance_data = classy_data;
 
    if (NULL == (vtab = cache_find(get_vtable_cachehkey(CLASSID_CLASSY_VTABLE), 0)))  
    {   
        vtab = new_vtable(NULL, sizeof(vtabmask), CLASSID_CLASSY_VTABLE);
        cache_insert(vtab->hashkey, vtab, FALSE);
        
        inheritlist = get_vtab_inheritance_list((objmask*)newobj,
            2, 1, CLASSID_CLASSY_VTABLE); /* create inheritance list */ 
        inheritlist[1].obj_type = THISTEST_OBJTYPE;
        inheritlist[1].class_id = CLASSID_CLASS_X; 
    } 
    newobj->vtab = vtab;  
    return newobj;
}


void classarg_init_object(etch_objclass* arg)
{
    memset(arg, 0, sizeof(etch_objclass));
    arg->obj_type = ETCHTYPEB_ETCHOBJECT;
    arg->class_id = CLASSID_OBJECT;
}


/**
 * classarg_init_nativearray()
 * initialize class parameters for a wrapped primitive
 */
void classarg_init_primitive(etch_objclass* arg, short class_id)
{
    memset(arg, 0, sizeof(etch_objclass));
    arg->obj_type = ETCHTYPEB_PRIMITIVE;
    arg->class_id = class_id;
}


/**
 * classarg_init_nativearray()
 * initialize class parameters for native array 
 */
void classarg_init_nativearray(etch_objclass* arg, short class_id, int dim, short conttype, short contclass )
{
    memset(arg, 0, sizeof(etch_objclass));
    arg->obj_type = ETCHTYPEB_NATIVEARRAY;
    arg->class_id = class_id;
    arg->numdims  = dim;
    arg->content_obj_type = conttype;
    arg->content_class_id = contclass;
}


/**
 * classarg_init_customtype_scalar()
 * initialize class parameters for a custom type with an inheritance hierarchy.
 * real code would not allocate an inheritance list as we do here, this would  
 * be handled in the object vtable constructor, and the list subsequently 
 * accessed via the object's cached vtable.  
 */
void classarg_init_customtype_scalar(etch_objclass* arg, short class_id, int numsupers,
   short superclass_1, short superclass_2)
{
    memset(arg, 0, sizeof(etch_objclass));
    arg->obj_type = THISTEST_OBJTYPE;
    arg->class_id = class_id;

    arg->inherits_from = etch_malloc(sizeof(etchparentinfo) * (numsupers + 1), ETCHTYPEB_BYTES);
    arg->inherits_from[0].list_size  = numsupers + 1;
    arg->inherits_from[0].list_count = numsupers;

    if (numsupers > 0) 
    { arg->inherits_from[1].obj_type = THISTEST_OBJTYPE; 
      arg->inherits_from[1].class_id = superclass_1; 
    }

    if (numsupers > 1) 
    { arg->inherits_from[2].obj_type = THISTEST_OBJTYPE; 
      arg->inherits_from[2].class_id = superclass_2; 
    }
}


void classarg_cleanup(etch_objclass* target, etch_objclass* source) 
{
    if (target->inherits_from) etch_free(target->inherits_from);
    if (source->inherits_from) etch_free(source->inherits_from);
    target->inherits_from = source->inherits_from = NULL;
}


/**
 * test_is_assignable_1()
 * test assignability of various classes 
 */
void test_is_assignable_1(void)
{
    etch_objclass target, source;
    int result = 0;

    /* int32obj = int16obj */
    classarg_init_primitive(&target, CLASSID_PRIMITIVE_INT32);
    classarg_init_primitive(&source, CLASSID_PRIMITIVE_INT16);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, FALSE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);

    /* short[]obj = shortobj */
    classarg_init_nativearray(&target, CLASSID_ARRAY_INT16, 1, ETCHTYPEB_PRIMITIVE, CLASSID_PRIMITIVE_INT16);
    classarg_init_primitive(&source, CLASSID_PRIMITIVE_INT16);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, FALSE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);

    /* etchobj = longobj */
    classarg_init_object(&target);
    classarg_init_primitive(&source, CLASSID_PRIMITIVE_INT64);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);

    /* obj[]obj = shortobj */
    classarg_init_nativearray(&target, CLASSID_ARRAY_OBJECT, 1, ETCHTYPEB_ETCHOBJECT, CLASSID_OBJECT);
    classarg_init_primitive(&source, CLASSID_PRIMITIVE_INT16);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, FALSE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);

    /* obj[]obj = short[]obj */
    classarg_init_nativearray(&target, CLASSID_ARRAY_OBJECT, 1, ETCHTYPEB_ETCHOBJECT, CLASSID_OBJECT);
    classarg_init_nativearray(&source, CLASSID_ARRAY_INT16,  1, ETCHTYPEB_PRIMITIVE, CLASSID_PRIMITIVE_INT16);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);

    /* short[]obj = short[]obj */
    classarg_init_nativearray(&target, CLASSID_ARRAY_INT16, 1, ETCHTYPEB_PRIMITIVE, CLASSID_PRIMITIVE_INT16);
    classarg_init_nativearray(&source, CLASSID_ARRAY_INT16, 1, ETCHTYPEB_PRIMITIVE, CLASSID_PRIMITIVE_INT16);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, TRUE);

    /* custom1obj = custom0obj */
    classarg_init_customtype_scalar(&target, CLASSID_CLASS_A, 0, 0, 0);
    classarg_init_customtype_scalar(&source, CLASSID_CLASS_B, 1, CLASSID_CLASS_A, 0);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);
    classarg_cleanup(&source, &target); 

    /* custom2obj = custom0obj */
    classarg_init_customtype_scalar(&target, CLASSID_CLASS_A, 0, 0, 0);
    classarg_init_customtype_scalar(&source, CLASSID_CLASS_C, 2, CLASSID_CLASS_B, CLASSID_CLASS_A);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);
    classarg_cleanup(&source, &target); 

    /* custom2obj = custom1obj */
    classarg_init_customtype_scalar(&target, CLASSID_CLASS_B, 1, CLASSID_CLASS_A, 0);
    classarg_init_customtype_scalar(&source, CLASSID_CLASS_C, 2, CLASSID_CLASS_B, CLASSID_CLASS_A);
    result = etchobj_is_assignable_from(&target, &source); 
    CU_ASSERT_EQUAL(result, TRUE);
    result = etchobj_is_assignable_from(&source, &target); 
    CU_ASSERT_EQUAL(result, FALSE);
    classarg_cleanup(&source, &target); 

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */   
}


/**
 * test_assign_long_to_object
 */
void test_assign_long_to_object(void)
{
    char* objval = etch_malloc(128, ETCHTYPEB_BYTES);
    etch_object* class_object = new_etch_object(CLASSID_OBJECT, objval);
    etch_int64*  class_long   = new_int64(0);
    objmask* resultobj = NULL;
    int result = etchobj_is_assignable_fromobj((objmask*) class_object, (objmask*) class_long);
    CU_ASSERT_EQUAL_FATAL(result, TRUE);

    /* an assignment to object causes the source object to be 
     * wrapped by the target object */

    resultobj = etchobj_assign_to((objmask*) class_object, (objmask*) class_long);
    CU_ASSERT_PTR_EQUAL(resultobj, class_object);

    class_long->destroy(class_long);
    class_object->destroy(class_object);

    /* we destroy class_long allocated above, since the assignment caused
     * it to be wrapped by, but of course not consumed by, class_object.
     * that is, class_object now contains a reference to class_long, but
     * does not own the reference (destructor will not attempt to free it).
     * we do not destroy the character vector objval allocated above, since  
     * the assignment to class_object caused any content already owned by it,    
     * in this case objval, to be destroyed as part of the assignment.
     */

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_invalid_array_assignment_1
 * ensure assignment fails when dimensions do not match
 */
void test_invalid_array_assignment_1(void)
{
    const int numdimbyte = 1, dim0byte = 4;
    const int numdimobj  = 2, dim0obj  = 4, dim1obj  = 2;
    objmask* resultobj = NULL;

    etch_nativearray* array_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimbyte, dim0byte, 0, 0);  

    etch_nativearray* array_of_object = new_nativearray 
        (CLASSID_ARRAY_OBJECT, sizeof(void*), numdimobj, dim0obj, dim1obj, 0); 

    int result = etchobj_is_assignable_fromobj((objmask*) array_of_object, (objmask*) array_of_byte);
    CU_ASSERT_EQUAL_FATAL(result, FALSE);

    resultobj = etchobj_assign_to((objmask*) array_of_object, (objmask*) array_of_byte);
    CU_ASSERT_PTR_EQUAL(resultobj, NULL);

    array_of_byte->destroy(array_of_byte);
    array_of_object->destroy(array_of_object);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_invalid_array_assignment_2
 * ensure assignment fails when types do not match
 */
void test_invalid_array_assignment_2(void)
{
    const int numdim = 1, dim0 = 4;
    objmask* resultobj = NULL;

    etch_nativearray* array_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdim, dim0, 0, 0);  

    etch_nativearray* array_of_bool = new_nativearray 
        (CLASSID_ARRAY_BOOL, sizeof(byte), numdim, dim0, 0, 0);  

    int result  = etchobj_is_assignable_fromobj((objmask*) array_of_bool, (objmask*) array_of_byte);
    CU_ASSERT_EQUAL(result, FALSE);
    result      = etchobj_is_assignable_fromobj((objmask*) array_of_byte, (objmask*) array_of_bool);
    CU_ASSERT_EQUAL(result, FALSE);

    resultobj = etchobj_assign_to((objmask*) array_of_bool, (objmask*) array_of_byte);
    CU_ASSERT_EQUAL(result, NULL);

    array_of_byte->destroy(array_of_byte);
    array_of_bool->destroy(array_of_bool);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_assign_arrayofbyte_to_arrayofobject
 */
void test_assign_arrayofbyte_to_arrayofobject(void)
{
    const int numdimensions = 2, dim0count = 4, dim1count = 2;
    objmask* resultobj = NULL;

    etch_nativearray* array_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0);  

    etch_nativearray* array_of_object = new_nativearray 
        (CLASSID_ARRAY_OBJECT, sizeof(void*), numdimensions, dim0count, dim1count, 0); 

    /* an assignment of one array to the other replaces all array attributes 
     * and content in the target object. the target object will not own the 
     * array content of course */

    int result = etchobj_is_assignable_fromobj((objmask*) array_of_object, (objmask*) array_of_byte);
    CU_ASSERT_EQUAL_FATAL(result, TRUE);

    resultobj = etchobj_assign_to((objmask*) array_of_object, (objmask*) array_of_byte);
    CU_ASSERT_PTR_EQUAL(resultobj, array_of_object);

    array_of_byte->destroy(array_of_byte);
    array_of_object->destroy(array_of_object);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_assign_verify_arraybyte_to_arrayobj
 * assign array of byte to array of object and verify content is identical
 */
void test_assign_verify_arraybyte_to_arrayobj(void)
{
    int   i = 0, j = 0, result = 0;
    char  x[2][4] = { {'a','b','c','d'}, {'e','f','g','h'}, }, thisx = 0;
    const int numdimensions = 2, dim0count = 4, dim1count = 2;
    objmask* resultobj = NULL;

    etch_nativearray* array_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0);   

    etch_nativearray* array_of_object = new_nativearray 
        (CLASSID_ARRAY_OBJECT, sizeof(void*), numdimensions, dim0count, dim1count, 0); 

    for(i = 0; i < dim1count; i++)    /* initialize array of byte */       
    {
        for(j = 0; j < dim0count; j++)      
        {                                  
            result = array_of_byte->put2(array_of_byte, &x[i][j], i, j); 
            CU_ASSERT_EQUAL(result, 0); 
        }
    }

    resultobj = etchobj_assign_to((objmask*) array_of_object, (objmask*) array_of_byte);
    CU_ASSERT_PTR_EQUAL_FATAL(resultobj, array_of_object);

    for(i = 0; i < dim1count; i++)         /* verify arrays are now the same */
    {
        for(j = 0; j < dim0count; j++)      
        {                                   
            result = array_of_object->get2(array_of_object, &thisx, i, j); 
            CU_ASSERT_EQUAL(result, 0); 
            CU_ASSERT_EQUAL(x[i][j], thisx); 
        }
    }

    array_of_byte->destroy(array_of_byte);
    array_of_object->destroy(array_of_object);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_assign_arrayofbyte_to_arrayofbyte
 */
void test_assign_arrayofbyte_to_arrayofbyte(void)
{
    const int numdimensions = 2, dim0count = 4, dim1count = 2;
    objmask* resultobj = NULL;

    etch_nativearray* array1_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0);  

    etch_nativearray* array2_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0);  

    int result = etchobj_is_assignable_fromobj((objmask*) array1_of_byte, (objmask*) array2_of_byte);
    CU_ASSERT_EQUAL_FATAL(result, TRUE);
    result     = etchobj_is_assignable_fromobj((objmask*) array2_of_byte, (objmask*) array1_of_byte);
    CU_ASSERT_EQUAL_FATAL(result, TRUE);

    resultobj = etchobj_assign_to((objmask*) array1_of_byte, (objmask*) array2_of_byte);
    CU_ASSERT_PTR_EQUAL(resultobj, array1_of_byte);

    resultobj = etchobj_assign_to((objmask*) array2_of_byte, (objmask*) array1_of_byte);
    CU_ASSERT_PTR_EQUAL(resultobj, array2_of_byte);

    array1_of_byte->destroy(array1_of_byte);
    array2_of_byte->destroy(array2_of_byte);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */  
}


/**
 * test_assign_verify_arraybyte_to_arraybyte
 * assign array of byte to array of byte and verify content is identical
 */
void test_assign_verify_arraybyte_to_arraybyte(void)
{
    int   i = 0, j = 0, result = 0;
    char  x[2][4] = { {'a','b','c','d'}, {'e','f','g','h'}, }, thisx = 0;
    char  y[2][4] = { {'s','t','u','v'}, {'w','x','y','z'}, };
    const int numdimensions = 2, dim0count = 4, dim1count = 2;
    objmask* resultobj = NULL;

    etch_nativearray* array1_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0);   

    etch_nativearray* array2_of_byte = new_nativearray 
        (CLASSID_ARRAY_BYTE, sizeof(byte), numdimensions, dim0count, dim1count, 0); 

    for(i = 0; i < dim1count; i++)    /* initialize both arrays */       
    {
        for(j = 0; j < dim0count; j++)      
        {                                  
            result = array1_of_byte->put2(array1_of_byte, &x[i][j], i, j); 
            CU_ASSERT_EQUAL(result, 0); 

            result = array2_of_byte->put2(array2_of_byte, &y[i][j], i, j); 
            CU_ASSERT_EQUAL(result, 0); 
        }
    }

    resultobj = etchobj_assign_to((objmask*) array1_of_byte, (objmask*) array2_of_byte);
    CU_ASSERT_PTR_EQUAL_FATAL(resultobj, array1_of_byte);

    for(i = 0; i < dim1count; i++)   /* verify assignment */
    {
        for(j = 0; j < dim0count; j++)      
        {                                   
            result = array1_of_byte->get2(array1_of_byte, &thisx, i, j); 
            CU_ASSERT_EQUAL(result, 0); 
            CU_ASSERT_EQUAL(y[i][j], thisx); 
        }
    }

    array1_of_byte->destroy(array1_of_byte);
    array2_of_byte->destroy(array2_of_byte);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);   
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  
}


/**
 * test_assign_derived_2
 * test that inheritance type 2-derived object can be assigned to its parent
 */
void test_assign_derived_2(void)
{
    class_a* class_parent = NULL;
    class_b* class_child  = NULL;
    objmask* resultobj = NULL;
    int result = 0;

    class_parent = new_class_a(L"it works!");
    class_child  = new_class_b(class_parent, 128);
 
    CU_ASSERT_PTR_NOT_NULL_FATAL(class_child->parent);
    CU_ASSERT_PTR_NULL(class_parent->parent);

    result = etchobj_is_assignable_fromobj((objmask*) class_child, (objmask*) class_parent);
    CU_ASSERT_EQUAL(result, FALSE);

    result = etchobj_is_assignable_fromobj((objmask*) class_parent, (objmask*) class_child);
    CU_ASSERT_EQUAL(result, TRUE);

    /* these objects use type 2 inheritance, in which inherited objects chain to
     * instantiated parent objects. for such objects, when we assign child class
     * to parent class, the result of the assignment couldl be a different memory
     * reference than the requested target. however in this case, the object we 
     * are assigning to is in fact the same object as is in the source object's
     * inheritance chain (since we passed it to the object's constructor).
     * is not known at this writing whether this scenario can exist in etch c; 
     * that is, if we will ever use this inheritance model in conjunction with
     * the assignment of child to parent.  
     */
    resultobj = etchobj_assign_to((objmask*) class_parent, (objmask*) class_child);
    CU_ASSERT_PTR_NOT_NULL_FATAL(resultobj);
    CU_ASSERT_EQUAL(resultobj->class_id, class_parent->class_id); 

    /* an object destructor will recursively destroy() its superclass instances */
    class_child->destroy(class_child);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */   
}


/**
 * test_assign_derived_1a
 * test that inheritance type 2-derived object can be assigned to its parent
 */
void test_assign_derived_2a(void)
{
    class_a* class_parent = NULL;
    class_b* class_child1 = NULL;
    class_c* class_child2 = NULL;
    objmask* resultobj = NULL;
    int result = 0;

    class_parent = new_class_a(L"it works!");
    class_child1 = new_class_b(class_parent, 128);
    class_child2 = new_class_c(class_child1);
 
    CU_ASSERT_PTR_NOT_NULL_FATAL(class_child2->parent);
    CU_ASSERT_PTR_NOT_NULL_FATAL(class_child1->parent);
    CU_ASSERT_PTR_NULL(class_parent->parent);

    result = etchobj_is_assignable_fromobj((objmask*) class_child2, (objmask*) class_parent);
    CU_ASSERT_EQUAL(result, FALSE);

    result = etchobj_is_assignable_fromobj((objmask*) class_parent, (objmask*) class_child2);
    CU_ASSERT_EQUAL(result, TRUE);

    /* see comments at test_assign_derived_2() - they apply again here */
    resultobj = etchobj_assign_to((objmask*) class_parent, (objmask*) class_child2);
    CU_ASSERT_PTR_NOT_NULL_FATAL(resultobj);
    CU_ASSERT_EQUAL(resultobj->class_id, class_parent->class_id); 

    /* an object destructor will recursively destroy() its superclass instances */ 
    class_child2->destroy(class_child2);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */   
}


/**
 * test_assign_derived_1
 * test that inheritance type 1-derived object can be assigned to its parent
 */
void test_assign_derived_1(void)
{
    class_x* class_parent = NULL;
    class_y* class_child  = NULL;
    objmask* resultobj = NULL;
    const int PARENT_CLASSX_DATA = 1, CHILD_CLASSX_DATA = 1000, CHILD_CLASSY_DATA = 1001;
    int result = 0;

    class_parent = new_class_x(PARENT_CLASSX_DATA);
    class_child  = new_class_y(CHILD_CLASSX_DATA, CHILD_CLASSY_DATA);
 
    CU_ASSERT_PTR_NOT_NULL_FATAL(class_parent);
    CU_ASSERT_PTR_NOT_NULL_FATAL(class_child);

    result = etchobj_is_assignable_fromobj((objmask*) class_child, (objmask*) class_parent);
    CU_ASSERT_EQUAL(result, FALSE);

    result = etchobj_is_assignable_fromobj((objmask*) class_parent, (objmask*) class_child);
    CU_ASSERT_EQUAL(result, TRUE);

    /* these objects use type 1 inheritance, in which inherited objects contain
     * their parent's instance data;. there is no physical chaining of objects.
     * rather inheritance is identified from the object vtable's inheritance list. 
     */
    resultobj = etchobj_assign_to((objmask*) class_parent, (objmask*) class_child);

    CU_ASSERT_PTR_NOT_NULL_FATAL(resultobj);
    CU_ASSERT_EQUAL(resultobj->class_id, class_parent->class_id); 
    /* verify that parent's instance data was replaced with that of the child */
    CU_ASSERT_EQUAL(((class_x*)resultobj)->class_x_instance_data, class_child->class_x_instance_data); 

    class_child->destroy(class_child);
    class_parent->destroy(class_parent);

    g_bytes_allocated = etch_showmem(0, IS_DEBUG_CONSOLE);  /* verify all memory freed */
    CU_ASSERT_EQUAL(g_bytes_allocated, 0);  
    memtable_clear();  /* start fresh for next test */   
}


/**
 * main   
 */
int _tmain(int argc, _TCHAR* argv[])
{
    char c=0;
    CU_pSuite ps = NULL;
    g_is_automated_test = argc > 1 && 0 != wcscmp(argv[1], L"-a");
    if (CUE_SUCCESS != CU_initialize_registry()) return 0;
    CU_set_output_filename("../test_etchobject");
    ps = CU_add_suite("etchobject test suite", init_suite, clean_suite);
    etch_watch_id = 0; 

    CU_add_test(ps, "test assignability",    test_is_assignable_1); 
    CU_add_test(ps, "assign long to object", test_assign_long_to_object); 
    CU_add_test(ps, "attempt invalid array assignment 1", test_invalid_array_assignment_1); 
    CU_add_test(ps, "attempt invalid array assignment 2", test_invalid_array_assignment_2); 
    CU_add_test(ps, "assign byte[][] to object[][]", test_assign_arrayofbyte_to_arrayofobject); 
    CU_add_test(ps, "assign byte[][] to object[][] and verify", test_assign_verify_arraybyte_to_arrayobj);
    CU_add_test(ps, "assign byte[][] to byte[][]", test_assign_arrayofbyte_to_arrayofbyte); 
    CU_add_test(ps, "assign byte[][] to byte[][] and verify", test_assign_verify_arraybyte_to_arraybyte); 
    CU_add_test(ps, "assign child to parent (type 2)", test_assign_derived_2); 
    CU_add_test(ps, "assign 2x child to parent (type 2)", test_assign_derived_2a); 
    CU_add_test(ps, "assign child to parent (type 1)", test_assign_derived_1); 

    if (g_is_automated_test)    
        CU_automated_run_tests();    
    else
    {   CU_basic_set_mode(CU_BRM_VERBOSE);
        CU_basic_run_tests();
    }

    if (!g_is_automated_test) { printf("any key ..."); while(!c) c = _getch(); wprintf(L"\n"); }     
    CU_cleanup_registry();
    return CU_get_error(); 
}

