/* $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_message.c -- test etch_message object
 */

#include "apr_time.h" /* some apr must be included first */

#include <tchar.h>
#include <stdio.h>
#include <conio.h>

#include "cunit.h"
#include "basic.h"
#include "automated.h"

#include "etch_connection.h"
#include "etch_global.h"
#include "etch_encoding.h"
#include "etchthread.h"
#include "etchlog.h"
#include "etch_message.h"
#include "etchexcp.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";


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;
}


/* - - - - - - - - - - - - - - - - - - - - - - 
 * local declarations 
 * - - - - - - - - - - - - - - - - - - - - - -
 */   

/* - - - - - - - - - - - - - - - - - - - - - - 
 * local setup and teardown
 * - - - - - - - - - - - - - - - - - - - - - -
 */

int this_setup()
{
    etch_apr_mempool = g_apr_mempool;
    return 0;
}

int this_teardown()
{    
    return 0;
}


/* - - - - - - - - - - - - - - - - - - - - - - 
 * individual setup and teardown
 * - - - - - - - - - - - - - - - - - - - - - -
 */

int this_test_setup()
{
    int result = -1;

    do
    {
      result = 0;
    } while(0);

    return result;
}

int this_test_teardown()
{    

    return 0;
}

int  g_bytes_allocated, g_which_exception_test, g_is_automated_test;
wchar_t* local_excp_text = L"global text";

#if(0)
#define OBJTYPE_FAKETDI_IMPL ETCHTYPEB_INSTANCEDATA
#define CLASSID_FAKETDI_IMPL 0xf0
#define OBJTYPE_FAKETDO_IMPL ETCHTYPEB_INSTANCEDATA
#define CLASSID_FAKETDO_IMPL   0xf1
#define OBJTYPE_FAKETDI_VTABLE 0x88
#define OBJTYPE_FAKETDO_VTABLE 0x89
#define CLASSID_FAKETDI 0xf6
#define CLASSID_FAKETDO 0xf7
#endif

#define OBJTYPE_FAKEVF_IMPL ETCHTYPEB_INSTANCEDATA
#define CLASSID_FAKEVF_IMPL    0xf2
#define OBJTYPE_FAKEVF_VTABLE  0x90
#define OBJTYPE_FAKEVF_IMPL ETCHTYPEB_INSTANCEDATA
#define OBJTYPE_FAKEVF ETCHTYPEB_VALUEFACTORY
#define CLASSID_FAKEVF  0xf5

#define EXCPTEST_UNCHECKED_STATICTEXT 1
#define EXCPTEST_CHECKED_COPYTEXT     2
#define EXCPTEST_CHECKED_STATICTEXT   3


typedef struct testitems
{
    etch_type*  mt1;
    etch_type*  mt2;
    etch_type*  rmt;
    etch_field* mf1;
    etch_field* mf2;
    etch_field* mf3;
    etch_field* mf4;

    etch_string* s1;
    etch_string* s2;
    etch_int32*  n1;
    etch_int32*  n2;
    etch_int64*  l1;
    etch_int64*  l2;

    etch_field *mf_bool_d0_1;   
    etch_field *mf_bool_d0_2;   
    etch_field *mf_bool_d1_1;   
    etch_field *mf_bool_d1_2;  

    etch_field *mf_int32_d0_1;  
    etch_field *mf_int32_d0_2;  
    etch_field *mf_int32_d1_1;  
    etch_field *mf_int32_d1_2;  

    etch_field *mf_str_d0_1;   
    etch_field *mf_str_d0_2;   
    etch_field *mf_str_d1_1;  
    etch_field *mf_str_d1_2;  

    etch_field *mf_message_id;
    etch_field *mf_in_reply_to; 

    etch_hashtable* items;

} testitems;

enum objtyp ETCHTYPE_VTABLE_FAKETDI = 0xfa;
enum objtyp ETCHTYPE_VTABLE_FAKETDO = 0xfb; 
enum objtyp ETCHTYPE_VTABLE_FAKEVF  = 0xfc;


/* = = = = = = = = = = = = = = =
 * VALUE FACTORY IMPLEMENTATION
 * = = = = = = = = = = = = = = =
 */

/**
 * fake_vf_impl
 * instance data for a value factory implementation
 */
typedef struct fake_vf_impl
{
    unsigned int    hashkey;    
    unsigned short  obj_type;   
    unsigned short  class_id;   
    struct objmask* 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_field* mf_message_id;
    etch_field* mf_in_reply_to;

} fake_vf_impl;


/**
 * destroy_fake_vf_impl
 * destructor for fake_vf_impl
 */
int destroy_fake_vf_impl(fake_vf_impl* impl)
{
    int result = verify_object((objmask*)impl, OBJTYPE_FAKEVF_IMPL, CLASSID_FAKEVF_IMPL, 0);
    if (result == -1) return -1; /* object passed was not expected object */

    CU_ASSERT_PTR_NOT_NULL_FATAL(impl->mf_message_id);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(impl->mf_in_reply_to); 

    impl->mf_message_id ->destroy(impl->mf_message_id);
    impl->mf_in_reply_to->destroy(impl->mf_in_reply_to);

    etch_free(impl); 
    return 0;
}


/**
 * new_fake_vf_impl()
 * constructor for TDI implementation instance data
 */
fake_vf_impl* new_fake_vf_impl(testitems* items)
{
    fake_vf_impl* data = etch_malloc(sizeof(fake_vf_impl), ETCHTYPEB_INSTANCEDATA);
    memset(data, 0, sizeof(fake_vf_impl));
    data->obj_type = OBJTYPE_FAKEVF_IMPL;
    data->class_id = CLASSID_FAKEVF_IMPL;

    data->mf_message_id  = items->mf_message_id->clone(items->mf_message_id);
    data->mf_in_reply_to = items->mf_in_reply_to->clone(items->mf_in_reply_to);
 
    data->destroy = destroy_fake_vf_impl;
    return data;
}


/*  
 * value factory virtual function overrides
 */

/**
 * fvf_get_message_id() -- valuefactory.get_message_id() implementation.
 * @return non-disposable reference to id object, or null if not found.
 */
etch_int64* fvf_get_message_id (etch_value_factory* vf, etch_message* msg)
{
    etch_int64* id = NULL;
    fake_vf_impl* data = (fake_vf_impl*) vf->impl;

    id = (etch_int64*) message_get(msg, data->mf_message_id);

    /* return (etch_int64*) verifyx((objmask*) id, 0, 
       CLASSID_PRIMITIVE_INT64, EXCPTYPE_INTERNALERR); */

    return id;
}


/**
 * fvf_set_message_id() -- valuefactory.set_message_id() implementation.
 */
int fvf_set_message_id (etch_value_factory* vf, etch_message* msg, etch_int64* id)
{
    fake_vf_impl* data  = (fake_vf_impl*) vf->impl;
    etch_field* keycopy = data->mf_message_id->clone(data->mf_message_id);

    const int result = message_put(msg, keycopy, (objmask*) id);  
    return result;
}


/**
 * fvf_get_in_reply_to() -- valuefactory.get_in_reply_to() implementation.
 */
etch_int64* fvf_get_in_reply_to (etch_value_factory* vf, etch_message* msg)
{
    etch_int64* id = NULL;
    fake_vf_impl* data = (fake_vf_impl*) vf->impl;

    id = (etch_int64*) message_get(msg, data->mf_in_reply_to);

    return (etch_int64*) verifyx((objmask*) id, 0, CLASSID_PRIMITIVE_INT64, EXCPTYPE_INTERNALERR);
}


/**
 * fvf_set_in_reply_to() -- valuefactory.set_message_id() implementation.
 */
int fvf_set_in_reply_to (etch_value_factory* vf, etch_message* msg, etch_int64* id)
{
    fake_vf_impl* data  = (fake_vf_impl*) vf->impl;
    etch_field* keycopy = clone_field(data->mf_in_reply_to);

    /* FYI this copy of the key is put to etch_message* sent message, and gets   
     * freed in msg.destroy(), message owns memory other than vf and type 
     */
    return message_put(msg, keycopy, (objmask*) id);  
}

/*  
 * value factory constructors/destructors
 */


/**
 * fakevf_close() ala java test
 */
void fakevf_close(etch_value_factory* vf)  
{
    vf->destroy(vf);  
}


#define CLASSID_FAKE_VALUEFACTORY 200

/**
 * new_fake_value_factory()
 * constructor for value factory implementation  
 */
etch_value_factory* new_fake_value_factory(testitems* items)  
{
    etch_value_factory* fakevf = NULL;
    i_value_factory* vtab = NULL;
    const int VTABSIZE = sizeof(i_value_factory);
    const unsigned short CLASS_ID = CLASSID_FAKE_VALUEFACTORY;
    int result = 0;
   
    fakevf = new_value_factory(0);
   
    vtab = cache_find(get_vtable_cachehkey(CLASS_ID), 0);

    if(!vtab)  
    {    
        vtab = new_vtable(fakevf->vtab, VTABSIZE, CLASS_ID);

        /* override four i_value_factory methods */
        vtab->get_message_id  = fvf_get_message_id;
        vtab->set_message_id  = fvf_set_message_id;
        vtab->get_in_reply_to = fvf_get_in_reply_to;
        vtab->set_in_reply_to = fvf_set_in_reply_to;      

        vtab->vtab = fakevf->vtab;  /* chain vtabs */
        cache_insert(vtab->hashkey, vtab, 0);  
    } 

    CU_ASSERT_EQUAL_FATAL(vtab->class_id, CLASS_ID);

    fakevf->vtab = vtab;  /* set override vtab */

    fakevf->impl = (objmask*) new_fake_vf_impl(items); /* create vf instance data */

    switch(g_which_exception_test)
    {   case EXCPTEST_UNCHECKED_STATICTEXT: 
             etch_throw((objmask*) fakevf, EXCPTYPE_NULLPTR, NULL, 0);  
             break;   
        case EXCPTEST_CHECKED_COPYTEXT:   
             etch_throw((objmask*)fakevf, EXCPTYPE_CHECKED_BOGUS, L"copied text", ETCHEXCP_COPYTEXT | ETCHEXCP_FREETEXT);  
             break; 
        case EXCPTEST_CHECKED_STATICTEXT:   
             etch_throw((objmask*)fakevf, EXCPTYPE_CHECKED_BOGUS, local_excp_text, ETCHEXCP_STATICTEXT);  
             break;       
    }

    return fakevf;
}


#if(0)

/* = = = = = = = = = =    
 * TDI IMPLEMENTATION
 * = = = = = = = = = =     
 */

/**
 * fake_tdi_impl
 * instance data for a TDI implementation
 */
typedef struct fake_tdi_impl
{
    unsigned int    hashkey;    
    unsigned short  obj_type;   
    unsigned short  class_id;   
    struct i_value_factory* 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_type*    tdi_type;
    etch_message* xmessage;
    etch_iterator iterator;  
    etch_value_factory* vf;
    testitems*    testdata;
    byte started, done, ended, is_owned_message;

} fake_tdi_impl;



/**
 * destroy_fake_tdi_impl
 * memory cleanup handler for fake_tdi_impl
 */
int destroy_fake_tdi_impl(fake_tdi_impl* impl)
{
    etch_destructor destroy = NULL;
    int result = verify_object((objmask*)impl, OBJTYPE_FAKETDI_IMPL, CLASSID_FAKETDI_IMPL, 0);
    if (result == -1) return -1; /* object passed was not expected object */

    /* type is a reference, it does not belong to the tdi. message is created  
     * in the tdi, but ownership is assumed to transfer to the caller when  
     * message_read returns. if this is not the case then is_owned_message 
     * must have been set elsewhere.
     */
    if (impl->is_owned_message) 
    {   /* not the default case, see comment above */
        CU_ASSERT_PTR_NOT_NULL_FATAL(impl->xmessage);  
        impl->xmessage->destroy(impl->xmessage);  
    }
 
    etch_free(impl); 
    return 0;
}


/**
 * new_fake_tdi_impl()
 * constructor for TDI implementation instance data
 */
fake_tdi_impl* new_fake_tdi_impl(etch_value_factory* vf, etch_type* static_type, testitems* testdata)
{
    fake_tdi_impl* data = (fake_tdi_impl*) 
        new_object(sizeof(fake_tdi_impl), ETCHTYPEB_INSTANCEDATA, CLASSID_FAKETDI_IMPL);

    /* value factory and type are references, memory not owned by tdi */
    data->tdi_type = static_type;     
    data->vf = vf;  
    data->testdata = testdata;

    data->destroy = destroy_fake_tdi_impl;        
    return data;
}


/**
 * faketdi_start_message() overrides tdi_start_message()
 */
etch_message* faketdi_start_message(tagged_data_input* tdi) 
{
    int result = 0;
    fake_tdi_impl* tdidata = NULL;
    etch_message* newmessage = NULL;
    etch_value_factory* vf = NULL;
    const int READONLY = TRUE, DEFSIZE = 0, DEFDELTA = 0;
    
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi->impl);

    tdidata = (fake_tdi_impl*) tdi->impl; /* validate instance data */
    result  = verify_object((objmask*)tdidata, OBJTYPE_FAKETDI_IMPL, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdidata->vf);

    result = tdidata->destroy(NULL); /* ensure we can call into instance destructor */
    CU_ASSERT_EQUAL(result,-1);

    CU_ASSERT_EQUAL(tdidata->started,FALSE);
    CU_ASSERT_EQUAL(tdidata->done,FALSE);
    CU_ASSERT_EQUAL(tdidata->ended,FALSE);

    tdidata->started = TRUE;

    /* create a message (struct) to receive the elements read. a message owns all  
     * its memory except the value factory and type (note that the vf might need 
     * to be refcounted for that reason, to ensure that if the service shuts down 
     * while a message is in the pipeline, a vf is not destroyed prior to a message
     * which references it). the type is passed through to the underlying struct. 
     * a struct owns all its memory except type, so it is assigned a reference to 
     * the tdi's type, which is an object global to the service. ownership of the 
     * struct, and by extension the messsage, memory, is assumed to be passed to 
     * the caller when this function returns. if instead it was desired that the 
     * tdi own the message and hence the struct, an indicator of such and an 
     * associated message dtor call, would need to be coded in the tdi and tdi 
     * destructor implementation.
     */
    newmessage = new_message (tdidata->tdi_type, 0, tdidata->vf);  

    tdidata->xmessage = newmessage;
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdidata->xmessage);
    
    set_iterator(&tdidata->iterator, /* establish iterator on the input data */
        tdidata->testdata->items, &tdidata->testdata->items->iterable);

    /* while an empty dataset is valid in the general case, 
     * this test assumes that input is non-empty */
    result = tdidata->iterator.has_next(&tdidata->iterator); 
    CU_ASSERT_EQUAL(result,TRUE);

    return newmessage;
}

 
/**
 * faketdi_read_struct_element() overrides tdi_read_struct_element()
 */
int faketdi_read_struct_element(tagged_data_input* tdi, etch_struct_element* out_se)   
{
    int result = 0;
    fake_tdi_impl* data = NULL;
    etch_iterator* iterator = NULL;
    etch_destructor destroy = NULL;    
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi->impl);
    CU_ASSERT_PTR_NOT_NULL_FATAL(out_se);

    data = (fake_tdi_impl*) tdi->impl; /* validate instance data */
    result = verify_object((objmask*)data, OBJTYPE_FAKETDI_IMPL, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);
  
    CU_ASSERT_EQUAL(data->started,TRUE);
    CU_ASSERT_EQUAL(data->done,FALSE);
    CU_ASSERT_EQUAL(data->ended,FALSE);
    iterator = &data->iterator;

	if (iterator->has_next(iterator)) 
	{   
        CU_ASSERT_EQUAL_FATAL(result,0);
        CU_ASSERT_PTR_NOT_NULL_FATAL(iterator->current_key);
        CU_ASSERT_PTR_NOT_NULL_FATAL(iterator->current_value);

    	out_se->key   = (etch_field*) iterator->current_key;
        out_se->value = (objmask*)    iterator->current_value;

        iterator->next(iterator);
		return TRUE;
	}
	
	data->done = TRUE;
	return FALSE;
}


/**
 * faketdi_end_message() overrides tdi_end_struct()
 */
etch_message* faketdi_end_message(tagged_data_input* tdi, etch_message* msg) 
{
    int result = 0;
    fake_tdi_impl* data = NULL;
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi->impl);

    data = (fake_tdi_impl*) tdi->impl; /* validate instance data */
    result = verify_object((objmask*)data, OBJTYPE_FAKETDI_IMPL, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);
    CU_ASSERT_EQUAL_FATAL(data->xmessage, msg); 

    CU_ASSERT_EQUAL(data->started,TRUE);
    CU_ASSERT_EQUAL(data->done,TRUE);
    CU_ASSERT_EQUAL(data->ended,FALSE);
    			
	data->ended = TRUE;
    return msg;
}


/**
 * faketdi_close() ala java test
 * also tested here is that we can call base methods on a derived object. we invoke 
 * the TDI destructor here, which is an overide of the etchobject destructor.
 * the TDI destructor walks the vtable chain to its parent, and invokes the parent
 * destructor to destroy etchobject content such as any exception, and finally
 * the object itself.
 */
void faketdi_close(tagged_data_input* tdi)  
{
    tdi->destroy(tdi);  
}


/**
 * new_fake_tdi()
 * constructor for TDI implementation  
 */
tagged_data_input* new_fake_tdi(etch_type* static_type, etch_value_factory* vfobj, testitems* data)  
{
    tagged_data_input* faketdi = NULL;
    i_tagged_data_input* vtab  = NULL;
    const unsigned short CLASS_ID = CLASSID_FAKETDI;
    CU_ASSERT_EQUAL_FATAL(is_good_type(static_type),TRUE);
   
    faketdi = new_tagged_data_input();

    vtab = cache_find(get_vtable_cachehkey(CLASS_ID), 0);

    if(!vtab)  
    {    
        vtab = new_vtable(faketdi->vtab, sizeof(i_tagged_data_input), CLASS_ID);

        /* override three i_tagged_data_input methods */
        vtab->start_message       = faketdi_start_message;  
        vtab->end_message         = faketdi_end_message;    
        vtab->read_struct_element = faketdi_read_struct_element;

        vtab->vtab = faketdi->vtab;      /* chain parent vtab to override vtab */
        cache_insert(vtab->hashkey, vtab, FALSE);
    } 

    CU_ASSERT_EQUAL_FATAL(vtab->class_id, CLASS_ID);

    faketdi->vtab = vtab;  /* set override vtab */

    faketdi->impl = (objmask*) 
        new_fake_tdi_impl(vfobj, static_type, data); /* create TDI instance data */

    switch(g_which_exception_test)
    {   case EXCPTEST_UNCHECKED_STATICTEXT: 
             etch_throw((objmask*) faketdi, EXCPTYPE_NULLPTR, NULL, 0);  
             break;   
        case EXCPTEST_CHECKED_COPYTEXT:   
             etch_throw((objmask*) faketdi, EXCPTYPE_CHECKED_BOGUS, L"copied text", ETCHEXCP_COPYTEXT | ETCHEXCP_FREETEXT);  
             break; 
        case EXCPTEST_CHECKED_STATICTEXT:   
             etch_throw((objmask*) faketdi, EXCPTYPE_CHECKED_BOGUS, local_excp_text, ETCHEXCP_STATICTEXT);  
             break;       
    }

    return faketdi;
}


/* = = = = = = = = = = 
 * TDO IMPLEMENTATION
 * = = = = = = = = = = 
 */

/**
 * fake_tdo_impl
 * instance data for a TDO implementation
 */
typedef struct fake_tdo_impl
{
    unsigned int    hashkey;    
    unsigned short  obj_type;   
    unsigned short  class_id;   
    struct objmask* 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;

    byte started, ended, closed;
    etch_message* xmessage;  /* reference */
    etch_hashtable* fakeout; /* fake output sink */  

} fake_tdo_impl;


/**
 * destroy_fake_tdo_impl
 * memory cleanup handler for fake_tdo_impl
 * we do not destroy the message and underlying struct, this is a reference. 
 */
int destroy_fake_tdo_impl(fake_tdo_impl* impl)
{
    etch_destructor destroy = NULL;
    int result = verify_object((objmask*)impl, OBJTYPE_FAKETDO_IMPL, CLASSID_FAKETDO_IMPL, NULL);
    if (result == -1) return -1; /* object passed was not expected object */

    /* destroy the fake output sink, in this case a map. we do not want the map to
     * destroy its content because the message owns that content, and we did not
     * in this case give the output sink copies of that content. 
    */
    impl->fakeout->destroy(impl->fakeout); 

    etch_free(impl);    
    return 0;
}


/**
 * new_fake_tdo_impl()
 * constructor for TDO implementation instance data
 */
fake_tdo_impl* new_fake_tdo_impl(etch_message* msg)
{
    fake_tdo_impl* data = (fake_tdo_impl*) 
        new_object(sizeof(fake_tdo_impl), ETCHTYPEB_INSTANCEDATA, CLASSID_FAKETDO_IMPL);

    data->xmessage = msg; /* reference, not owned */

    data->destroy = destroy_fake_tdo_impl;    
    return data;
}


/**
 * faketdo_start_message() overrides tdo_start_message()
 */
etch_message* faketdo_start_message(tagged_data_output* tdo, etch_message* msg)  
{
    int result = 0;
    fake_tdo_impl* data = NULL;
    etch_destructor destructor = NULL;
  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo->impl);

    data = (fake_tdo_impl*) tdo->impl; /* validate instance data */
    result = verify_object((objmask*)data, OBJTYPE_FAKETDO_IMPL, 0, (void**) &destructor);
    CU_ASSERT_EQUAL_FATAL(result,0);
    
    result = data->destroy(NULL); /* ensure we can call instance destructor */
    CU_ASSERT_EQUAL(result,-1);

    CU_ASSERT_EQUAL(data->started,FALSE);
    CU_ASSERT_EQUAL(data->ended,FALSE);

    CU_ASSERT_EQUAL_FATAL(msg, data->xmessage);
    CU_ASSERT_PTR_NOT_NULL_FATAL(data->fakeout); 

    data->started = TRUE;    
    return msg;
}


/**
 * faketdo_write_struct_element() overrides tdo_write_struct_element()
 */
int faketdo_write_struct_element(tagged_data_output* tdo, etch_field* key, objmask* val)  
{
    int result = 0;
    fake_tdo_impl*  data  = NULL;
    etch_hashtable* fakeout = NULL;
  	etch_hashitem   hashbucket;
    etch_hashitem*  myentry = &hashbucket;
  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo);  /* validate parameters */
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo->impl);
    CU_ASSERT_PTR_NOT_NULL_FATAL(val);

    result = is_good_field(key);
    CU_ASSERT_EQUAL_FATAL(result,TRUE);

    data = (fake_tdo_impl*) tdo->impl; /* validate instance data */
    result = verify_object((objmask*)data, OBJTYPE_FAKETDO_IMPL, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);

    fakeout = data->fakeout;
    CU_ASSERT_PTR_NOT_NULL_FATAL(fakeout);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(fakeout->realtable);  

    CU_ASSERT_EQUAL(data->started,TRUE);
    CU_ASSERT_EQUAL(data->ended, FALSE);
    CU_ASSERT_EQUAL(data->closed,FALSE);

    /* do the write to the fake output */ 
    result = fakeout->vtab->insert(fakeout->realtable, key, HASHSIZE_FIELD, val, 0, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);

    /* verify the write by accessing the item just written */
    result = fakeout->vtab->find(fakeout->realtable, key, HASHSIZE_FIELD, 0, &myentry);
    CU_ASSERT_EQUAL_FATAL(result,0);
    CU_ASSERT_EQUAL_FATAL(myentry->key, (void*)key);
    CU_ASSERT_EQUAL_FATAL(myentry->value, val);
    
	return 0;
}


/**
 * faketdo_end_message() overrides tdo_end_message()
 */
etch_message* faketdo_end_message(tagged_data_output* tdo, etch_message* msg)
{
    int result = 0;
    fake_tdo_impl* data = NULL;
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdo->impl);

    data = (fake_tdo_impl*)  tdo->impl; /* validate instance data */
    result = verify_object((objmask*)data, OBJTYPE_FAKETDO_IMPL, 0, 0);
    CU_ASSERT_EQUAL_FATAL(result,0);
    CU_ASSERT_PTR_NOT_NULL_FATAL(data->xmessage); 
 
    CU_ASSERT_EQUAL(data->started,TRUE);
    CU_ASSERT_EQUAL(data->ended,FALSE);
    CU_ASSERT_EQUAL(data->closed,FALSE);
    CU_ASSERT_EQUAL(data->xmessage, msg);
			
	data->ended = TRUE;
    return msg;
}


/**
 * faketdo_close() ala java test
 */
void faketdo_close(tagged_data_output* tdo)  
{
    tdo->destroy(tdo); 
}


/**
 * new_fake_tdo()
 * constructor for TDO implementation  
 */
tagged_data_output* new_fake_tdo(etch_message* msg)  
{
    tagged_data_output* faketdo = NULL;
    i_tagged_data_output*  vtab = NULL;
    fake_tdo_impl* impl = NULL;
    const unsigned short CLASS_ID = CLASSID_FAKETDO;
    CU_ASSERT_PTR_NOT_NULL_FATAL(msg);

    faketdo = new_tagged_data_output();     

    vtab = cache_find(get_vtable_cachehkey(CLASS_ID), 0);

    if(!vtab)  
    {   
        vtab = new_vtable(faketdo->vtab, sizeof(i_tagged_data_output), CLASS_ID);

        /* override three i_tagged_data_output methods */
        vtab->start_message        = faketdo_start_message;
        vtab->end_message          = faketdo_end_message;
        vtab->write_struct_element = faketdo_write_struct_element;

        vtab->vtab = faketdo->vtab;       /* chain parent vtab */
    
        cache_insert(vtab->hashkey, vtab, FALSE);
    } 

    CU_ASSERT_EQUAL_FATAL(vtab->class_id, CLASS_ID);

    faketdo->vtab = vtab;  /* set override vtab */

    impl = new_fake_tdo_impl(msg);  /* construct TDO instance data */
    CU_ASSERT_PTR_NOT_NULL_FATAL(impl); 
            
    impl->fakeout = new_hashtable(16); /* instantiate fake output target */
    CU_ASSERT_PTR_NOT_NULL_FATAL(impl->fakeout);
    impl->fakeout->content_type = ETCHHASHTABLE_CONTENT_OBJECT;

    faketdo->impl = (objmask*) impl;

    return faketdo; 
}

#endif /* #if(0) */


/* = = = = = = = = = = 
 * TEST DATA  
 * = = = = = = = = = = 
 */

/* 
 * testdata_clear_handler()
 * memory callback on testdata clear
 */
int testdata_clear_handler (etch_field* key, objmask* value)  
{
    return TRUE; /* indicate free handled */
}

/* 
 * new_testitems()
 * instantiate and return types and fields used in tests
 */
testitems* new_testitems()
{
    testitems* items = etch_malloc(sizeof(struct testitems), 0);
    items->mt1 = new_type(L"mt1");
    items->mt2 = new_type(L"mt2");
    items->rmt = new_type(L"rmt");
    items->mf1 = new_field(L"x");
    items->mf2 = new_field(L"y");
    items->mf3 = new_field(L"s1");
    items->mf4 = new_field(L"s2");

    items->s1 = new_string(L"string1", ETCH_ENCODING_UTF16);
    items->s2 = new_string(L"string2", ETCH_ENCODING_UTF16);
    items->n1 = new_int32(1);
    items->n2 = new_int32(2);
    items->l1 = new_int64(12345);
    items->l2 = new_int64(54321);

    items->mf_bool_d0_1  = new_field(L"f1");
    items->mf_bool_d0_2  = new_field(L"f2");
    items->mf_bool_d1_1  = new_field(L"f3");
    items->mf_bool_d1_2  = new_field(L"f4");

    items->mf_int32_d0_1 = new_field(L"f5");
    items->mf_int32_d0_2 = new_field(L"f6");
    items->mf_int32_d1_1 = new_field(L"f7");
    items->mf_int32_d1_2 = new_field(L"f8");

    items->mf_str_d0_1   = new_field(L"f9");
    items->mf_str_d0_2   = new_field(L"fa");
    items->mf_str_d1_1   = new_field(L"fb");
    items->mf_str_d1_2   = new_field(L"fc");

    items->mf_message_id  = new_field(L"_messageId"); 
    items->mf_in_reply_to = new_field(L"_inReplyTo"); 
 
    etchtype_put_validator(items->mt1, clone_field(items->mf_message_id), (objmask*) etchvtor_int64_get(0));
    etchtype_put_validator(items->mt1, clone_field(items->mf1), (objmask*) etchvtor_int32_get(0));
    etchtype_put_validator(items->mt1, clone_field(items->mf2), (objmask*) etchvtor_int32_get(0));
    etchtype_put_validator(items->mt1, clone_field(items->mf3), (objmask*) etchvtor_string_get(0));
    etchtype_put_validator(items->mt1, clone_field(items->mf4), (objmask*) etchvtor_string_get(0));

    etchtype_put_validator(items->rmt, clone_field(items->mf_message_id), (objmask*) etchvtor_int64_get(0));
    etchtype_put_validator(items->rmt, clone_field(items->mf_in_reply_to),(objmask*) etchvtor_int64_get(0));

    #if(0)
    etchtype_put_validator(items->mt1, items->mf_bool_d0_1->clone(items->mf_bool_d0_1), (objmask*) etchvtor_boolean_get(0));
    etchtype_put_validator(items->mt1, items->mf_bool_d0_2->clone(items->mf_bool_d0_2), (objmask*) etchvtor_boolean_get(0));

    etchtype_put_validator(items->mt1, items->mf_bool_d1_1->clone(items->mf_bool_d1_1), (objmask*) etchvtor_boolean_get(1));
    etchtype_put_validator(items->mt1, items->mf_bool_d1_2->clone(items->mf_bool_d1_2), (objmask*) etchvtor_boolean_get(1));

    etchtype_put_validator(items->mt1, items->mf_int32_d0_1->clone(items->mf_int32_d0_1), (objmask*) etchvtor_int32_get(0));
    etchtype_put_validator(items->mt1, items->mf_int32_d0_2->clone(items->mf_int32_d0_2), (objmask*) etchvtor_int32_get(0));

    etchtype_put_validator(items->mt1, items->mf_int32_d1_1->clone(items->mf_int32_d1_1), (objmask*) etchvtor_int32_get(1));
    etchtype_put_validator(items->mt1, items->mf_int32_d1_2->clone(items->mf_int32_d1_2), (objmask*) etchvtor_int32_get(1));

    etchtype_put_validator(items->mt1, items->mf_str_d0_1->clone(items->mf_str_d0_1), (objmask*) etchvtor_string_get(0));
    etchtype_put_validator(items->mt1, items->mf_str_d0_2->clone(items->mf_str_d0_2), (objmask*) etchvtor_string_get(0));

    etchtype_put_validator(items->mt1, items->mf_str_d1_1->clone(items->mf_str_d1_1), (objmask*) etchvtor_string_get(1));
    etchtype_put_validator(items->mt1, items->mf_str_d1_2->clone(items->mf_str_d1_2), (objmask*) etchvtor_string_get(1));
    #endif

    items->items = new_hashtable(0);
    items->items->is_readonly_keys   = FALSE;
    items->items->is_readonly_values = FALSE; 
    items->items->is_tracked_memory  = TRUE;
    items->items->content_type = ETCHHASHTABLE_CONTENT_OBJECT;
    items->items->freehook = testdata_clear_handler;
    return items;
}

/* 
 * destroy_testitems()
 * free memory for test data
 */
void destroy_testitems(testitems* items)
{
    items->mt1->destroy(items->mt1);
    items->mt2->destroy(items->mt2);
    items->rmt->destroy(items->rmt);
    items->mf1->destroy(items->mf1);
    items->mf2->destroy(items->mf2); 
    items->mf3->destroy(items->mf3);
    items->mf4->destroy(items->mf4);

    items->s1->destroy(items->s1); 
    items->s2->destroy(items->s2); 
    items->n1->destroy(items->n1); 
    items->n2->destroy(items->n2); 
    items->l1->destroy(items->l1); 
    items->l2->destroy(items->l2); 

    items->mf_message_id->destroy (items->mf_message_id); 
    items->mf_in_reply_to->destroy(items->mf_in_reply_to);

    items->mf_bool_d0_1->destroy(items->mf_bool_d0_1);   
    items->mf_bool_d0_2->destroy(items->mf_bool_d0_2);   
    items->mf_bool_d1_1->destroy(items->mf_bool_d1_1);  
    items->mf_bool_d1_2->destroy(items->mf_bool_d1_2);   

    items->mf_int32_d0_1->destroy(items->mf_int32_d0_1); 
    items->mf_int32_d0_2->destroy(items->mf_int32_d0_2);  
    items->mf_int32_d1_1->destroy(items->mf_int32_d1_1);  
    items->mf_int32_d1_2->destroy(items->mf_int32_d1_2);  

    items->mf_str_d0_1->destroy(items->mf_str_d0_1);    
    items->mf_str_d0_2->destroy(items->mf_str_d0_2);    
    items->mf_str_d1_1->destroy(items->mf_str_d1_1);    
    items->mf_str_d1_2->destroy(items->mf_str_d1_2);   

    etchvtor_clear_cache(); /* free all cached validators */

    destroy_hashtable(items->items, FALSE, FALSE);

    etch_free(items);   
}


/* 
 * compare_lists()
 * compares specified message content with content of some other map.
 * returns boolean indicating equal or not.
 */
int compare_lists(etch_message* msg, etch_hashtable* otherdata)  
{
    int thiscount = 0, fakecount = 0, result = 0, i = 0, eqcount = 0;
    etch_structvalue* svx = NULL;
    etch_iterator  iterator;
  	etch_hashitem  hashbucket;
    etch_hashitem* sventry = &hashbucket;

    svx = msg->sv;
    CU_ASSERT_PTR_NOT_NULL_FATAL(svx);  
    CU_ASSERT_PTR_NOT_NULL_FATAL(svx->items);

    thiscount = svx->items->vtab->count(svx->items->realtable,0,0); 
    fakecount = otherdata ->vtab->count(otherdata->realtable,0,0); 
    CU_ASSERT_EQUAL(fakecount, thiscount);
    if (fakecount != thiscount) return FALSE;

    set_iterator(&iterator, otherdata, &otherdata->iterable);

    while(iterator.vtab->has_next(&iterator) && result == 0)
    {
        result = svx->items->vtab->findh
            (svx->items->realtable, ((objmask*)iterator.current_key)->hashkey, 
                otherdata, &sventry);
        CU_ASSERT_EQUAL(iterator.current_value, sventry->value);

        iterator.vtab->next(&iterator);
    }

    return TRUE;
}


/* 
 * hashtable_clear_handler()
 * override callback from hashtable during clear()
 */
int hashtable_clear_handler (void* key, void* value)  
{
    /* prior to calling clear() on any hashtable htab, set: 
     *    htab.callback = hashtable_clear_handler; 
     * and the hashtable will call back here for each item in the table,
     * replacing any such handler installed. return FALSE to have the hashtable 
     * proceed as it would with a handler. save off the existing handler and call 
     * it, to proceed as if you had not replaced the handler. return TRUE to  
     * indicate you handled the free and the hashtable should not attempt to free 
     * memory for key and/or value, if in fact it would have done so otherwise. 
     * note that structs always set such a callback on their backing store
     * in order to free key and value allocations.
     */
    return FALSE;
}


/* = = = = = = = = = = 
 * TESTS   
 * = = = = = = = = = = 
 */

/* 
 * run_iterator_test()
 * iterate over test collection, inserting pairs to specified message.
 * iterate over message, veriying that content matches test collection.
 */
int run_iterator_test(etch_message* msg, testitems* data)
{
    etch_iterator* iterator  = NULL;
    int msgcount = 0, testcount = 0, result = 0;

    iterator = new_iterator(data->items, &data->items->iterable);

    CU_ASSERT_PTR_NOT_NULL_FATAL(iterator);
    CU_ASSERT_EQUAL(iterator->ordinal,1);

    while(iterator->has_next(iterator))
    {
        testcount++;

        result = message_put(msg, iterator->current_key, iterator->current_value);

        CU_ASSERT_EQUAL(result,0);
        
        iterator->next(iterator);
    }
    
    set_iterator(iterator, msg->sv->items, &msg->sv->items->iterable);
    CU_ASSERT_EQUAL(iterator->ordinal,1);

    while(iterator->has_next(iterator))
    {
        msgcount++;
        iterator->next(iterator);
    }

    CU_ASSERT_EQUAL(testcount, msgcount);
    iterator->destroy(iterator);
    return msgcount;
}


/* 
 * iterator_test()
 */
void iterator_test(void)
{
    int result = 0;
    testitems* data = NULL;
    etch_hashtable* map = NULL;
    etch_message*   msg = NULL;
    etch_value_factory* vf = NULL; 

    etch_string *sval1 = NULL, *sval2 = NULL;
    etch_int32  *ival1 = NULL, *ival2 = NULL;
    etch_field  *skey1 = NULL, *skey2 = NULL;
    etch_field  *ikey1 = NULL, *ikey2 = NULL;

    data = new_testitems();
    map  = data->items;

    /* a message owns all its memory except the value factory and type, so we'll
     * make copies of all the testdata keys and values to be inserted into it */
    ikey1 = data->mf1->clone(data->mf1); ikey2 = data->mf2->clone(data->mf2);
    skey1 = data->mf3->clone(data->mf3); skey2 = data->mf4->clone(data->mf4);

    sval1 = data->s1->clone(data->s1);  sval2 = data->s2->clone(data->s2);
    ival1 = data->n1->clone(data->n1);  ival2 = data->n2->clone(data->n2);

    /* insert cloned fields and values to the test input collection */
    map->vtab->inserth(map->realtable, ikey1, ival1, map, 0);
    map->vtab->inserth(map->realtable, ikey2, ival2, map, 0);
    map->vtab->inserth(map->realtable, skey1, sval1, map, 0);
    map->vtab->inserth(map->realtable, skey2, sval2, map, 0);
   
    vf = new_fake_value_factory(data);

    CU_ASSERT_PTR_NOT_NULL_FATAL(vf);
    CU_ASSERT_FALSE_FATAL(is_exception(vf));

    /* a message owns its memory except vf and type, so we give it global type */
    msg = new_message (data->mt1, 0, vf);  

    CU_ASSERT_PTR_NOT_NULL_FATAL(msg);
    CU_ASSERT_FALSE_FATAL(is_exception(msg));
    
    result = run_iterator_test(msg, data);
    CU_ASSERT_EQUAL(result,4);  /* 4 items in message */

    msg->destroy(msg);
    vf ->destroy(vf); 
  
    destroy_testitems(data);

    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 */   
}

/* 
 * reply_test
 * test message.reply 
 */
void reply_test(void)
{
    int result = 0;
    etch_message* msg = NULL; 
    etch_message* newmsg = NULL;
    etch_type* replytype = NULL;
    etch_value_factory* vf = NULL;
    etch_int64* id_original = NULL;
    etch_int64* id_replied_to = NULL;
    int64 val_origid = 0, val_replyid = 0;
    testitems* data = new_testitems();

    id_original = data->l1;

    vf = new_fake_value_factory(data);
    
    msg = new_message (data->mt1, 0, vf);  /* message owns neither arg */

    result = message_set_id(msg, id_original->clone(id_original));  
    CU_ASSERT_EQUAL_FATAL(result,0);

    replytype = data->rmt;    /* again, a message does not own a type */ 

    newmsg = message_reply (msg, replytype);

    CU_ASSERT_PTR_NOT_NULL_FATAL(newmsg);
    CU_ASSERT_FALSE_FATAL(is_exception(newmsg));

    result = is_equal_types(data->rmt, newmsg->sv->struct_type);
    CU_ASSERT_TRUE(result);
    CU_ASSERT_EQUAL(vf, newmsg->vf);

    id_replied_to = message_get_in_reply_to(newmsg);

    CU_ASSERT_PTR_NOT_NULL_FATAL(id_replied_to);
    CU_ASSERT_FALSE_FATAL(is_exception(id_replied_to));

    val_origid  = id_original->value;
    val_replyid = id_replied_to->value;   
    CU_ASSERT_EQUAL(val_origid, val_replyid);

    newmsg->destroy(newmsg);
    msg->destroy(msg);  
    vf->destroy(vf);   
    destroy_testitems(data);

    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 */   
}


/* 
 * run_exception_test
 * execute an individual exception test 
 */
void run_exception_test(const int whichtest)
{
    int result = 0;
    tagged_data_input* tdi = NULL;
    etch_message* msg = NULL;
    etchexception* excp = NULL;
    etch_type* static_type = NULL;
    etch_value_factory* vf = NULL;
    testitems* data = new_testitems();

    /* global marker asks components to throw exceptions */
    g_which_exception_test = whichtest; 
    static_type = new_type(L"type1");
    vf = new_fake_value_factory(data);

    #if(0)
    /* create a bogus TDI inheriting from tagged data input */
    tdi = new_fake_tdi(static_type, vf, NULL);
    CU_ASSERT_PTR_NOT_NULL_FATAL(tdi);

    switch(whichtest)
    {   case EXCPTEST_UNCHECKED_STATICTEXT:
        case EXCPTEST_CHECKED_COPYTEXT:
        case EXCPTEST_CHECKED_STATICTEXT:
        {   CU_ASSERT_TRUE_FATAL(is_exception(tdi));
            #if IS_DEBUG_CONSOLE 
            wprintf(L"\ncaught %s exception on tdi\n", tdi->result->exception->excptext);          
            #endif   
        }
    }

    msg = message_read(NULL); /* pass NULL for TDI */
    CU_ASSERT_PTR_NOT_NULL_FATAL(msg);
    CU_ASSERT_TRUE(is_exception(msg));
    excp = get_exception(msg);
    CU_ASSERT_EQUAL(excp->excptype, EXCPTYPE_ILLEGALARG);
    #if IS_DEBUG_CONSOLE 
    wprintf(L"\ncaught %s exception on message\n", msg->result->exception->excptext);          
    #endif   

    /* free TDI */
    faketdi_close(tdi);

    /* free struct, it is just a shell with no content other than the exception */
    msg->destroy(msg);

    #endif

    vf->destroy(vf);

    destroy_testitems(data);
    destroy_type(static_type);
    g_which_exception_test = 0;

    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 */   
}


/**
 * exception_test
 * all exception tests   
 */
void exception_test(void)
{
    run_exception_test(EXCPTEST_UNCHECKED_STATICTEXT);
    run_exception_test(EXCPTEST_CHECKED_COPYTEXT);
    run_exception_test(EXCPTEST_CHECKED_STATICTEXT);
    g_which_exception_test = 0;
}



/**
 * main   
 */
int _tmain(int argc, _TCHAR* argv[])
{    
    char c=0;
    CU_pSuite pSuite = NULL;
    g_is_automated_test = argc > 1 && 0 != wcscmp(argv[1], L"-a");
    if (CUE_SUCCESS != CU_initialize_registry()) return 0;
    pSuite = CU_add_suite("suite_message", init_suite, clean_suite);
    CU_set_output_filename("../test_message");
    etch_watch_id = 0; 

    CU_add_test(pSuite, "test iterator over message", iterator_test);  
    CU_add_test(pSuite, "test msg exceptions", exception_test); 
    CU_add_test(pSuite, "message reply test", reply_test); 

    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(); 
}

