/* $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. 
 */ 

/*
 * etch_message.c
 * message object
 */

#include "etch_message.h"
#include "etch_global.h"
#include "etchlog.h"
char* ETCHMESS = "MESS";

etch_message* new_message_init(etch_type*, const int);


/**
 * new_message()  
 * constructor for etch_message
 * the value factory is a reference, the type is a copy to be passed through
 * to the underlying struct, which will then own the type memory.
 */
etch_message* new_message (etch_type* type, const int size, etch_value_factory* vf) 
{
    etch_message* newmsg = NULL;
    if (!type || !vf) return NULL;

    newmsg = new_message_init(type, size);
    if (newmsg == NULL) return NULL;

    newmsg->vf = vf; 
    etchlog(ETCHMESS, ETCHLOG_XDEBUG, "creating message %x\n", newmsg);
    return newmsg;
}


/**
 * new_message_init() 
 * common initialization on etch_message construction.
 */
etch_message* new_message_init(etch_type* type, const int initialsize)
{
    etch_message* newmsg = NULL;
    if (NULL == type) return NULL;

    newmsg = (etch_message*) new_object(sizeof(etch_message), 
        ETCHTYPEB_MESSAGE, CLASSID_ETCHMESSAGE);
    
    newmsg->destroy = destroy_message;
    newmsg->sv = new_structvalue(type, initialsize);

    return newmsg;
}


/**
 * destroy_message()
 * destructor for etch_message
 */ 
int destroy_message (etch_message* msg) 
{
    int result = 0;
    if (msg->refcount > 0 && --msg->refcount > 0) return -1;  

    if (!is_etchobj_static_content(msg))
    {    etchlog(ETCHMESS, ETCHLOG_XDEBUG, "destroying message %x\n", msg);
         result = destroy_structvalue(msg->sv);
    }

    destroy_objectex((objmask*)msg);
    return result;    
}


/**
 * message_remove()
 * removes an element from the message struct, returning the element.
 * if the element is found, its key is destroyed, and the object is returned.
 * caller owns returned object.
 */ 
objmask* message_remove(etch_message* msg, etch_field* key)
{
    return structvalue_remove(msg->sv, key);
}


/**
 * message_put()
 * insert key/value pair
 *
 * @param key an etch_field whose destructor will be invoked when the struct is
 * destroyed. 
 *
 * @param value a *disposable* object which is the value of the key/val pair.
 * if passed as null, specified key is removed from the message, per java binding.
 * otherwise this object's destructor will be invoked when its struct is destroyed.
 *
 * @return 0 or -1. 
 * on failure, destructors are invoked here on both key and value.
 *
 * remarks: to simplify caller logic under normal circumstances, caller
 * relinquishes ownership of parameters (other than this of course), regardless
 * of outcome. should caller wish to retain ownership on failure, the parameter
 * object could be marked such that the destructor invoked here becomes benign.
 */ 
int message_put (etch_message* msg, etch_field* key, objmask* value) 
{ 
    #if(0)
    if (NULL == key || NULL == msg->sv) 
        etch_throw((objmask*)msg, EXCPTYPE_ILLEGALARG, NULL, 0);
    else
    if (0 > structvalue_put(msg->sv, key, value))     
        etch_throw((objmask*)msg, EXCPTYPE_IO, NULL, 0);
    return msg;
    #endif

    /* on success, struct owns key and value */
    if (0 == structvalue_put(msg->sv, key, value))
        return 0;

    ETCHOBJ_DESTROY(value);
    destroy_field(key); /* note we are often passed protected vf keys */
    return -1; 
}


/**
 * message_putc()
 * insert key/value pair and clear value reference
 *
 * @param key an etch_field whose destructor will be invoked when the struct is
 * destroyed. 
 *
 * @param value an indirect reference to a *disposable* object which is the value 
 * of the key/val pair.
 * caller's reference is nulled out once this object is relinquished; thus caller
 * can safely test for null and then destroy the object.
 *
 * @return 0 or -1. 
 * on failure, destructors are invoked here on both key and value.
 *
 * remarks: to simplify caller logic under normal circumstances, caller
 * relinquishes ownership of parameters (other than this of course), regardless
 * of outcome. should caller wish to retain ownership on failure, the parameter
 * object could be marked such that the destructor invoked here becomes benign.
 */ 
int message_putc (etch_message* msg, etch_field* key, void** valref) 
{ 
    objmask* value = NULL;

    if (valref)
    {   value = (objmask*) *valref;
        *valref = NULL;  /* clear caller's reference as we now own value */
    }
    
    if (value) /* on success, struct owns key and value */
        if (0 == structvalue_put (msg->sv, key, value))
            return 0;

    /* a struct put was not successful so destroy parameter objects */
    ETCHOBJ_DESTROY(value);
    destroy_field(key);  /* note we are often passed protected vf keys */
    return -1; 
}


/**
 * message_get()
 * fetch value for key.
 * @return a non-disposable reference to the value, not a copy, or null.
 */
objmask* message_get (etch_message* msg, etch_field* key)
{
    objmask* retobj = NULL;

    if (msg && key && msg->sv)
        retobj = structvalue_get(msg->sv, key); 

    #if(0)
    if (NULL == msg || NULL == key || NULL == msg->sv) 
        return throw_from(EXCPTYPE_ILLEGALARG, ETCHTYPEB_UNDEFINED, 0, 0); 
    retobj = structvalue_get(msg->sv, key); 
    #endif
     
    return retobj; 
}


/**
 * message_type()
 * return etch type of specified message
 */
etch_type* message_type(etch_message* msg)
{
    return msg && msg->sv? msg->sv->struct_type: NULL;
}


/**
 * message_aname()
 * return name of specified message in 8-bit encoding.
 * @return a reference to the message name, caller does not own it.
 */
char* message_aname (etch_message* msg)
{
    etch_type* msgtype = message_type(msg);
    char*  msgname = msgtype && msgtype->aname? msgtype->aname: etch_emptystra;
    return msgname;
}


/**
 * message_reply()
 * creates a message which is a reply to the current message.
 * the current message's value factory is copied to the new message. 
 * message id of the current message (if any) is copied into the 
 * in-reply-to field of the new message.
 * @param newtype the type of the reply. caller retains ownership.
 * @return a reply message, which will contain exception if error.
 */ 
etch_message* message_reply (etch_message* msg, etch_type* newtype)  
{
    int result = -1;
    etch_int64*   msgid  = NULL;
    etch_message* newmsg = NULL;

    if (NULL == msg) return NULL;

    if (NULL == newtype) /* use message type's result type */
        newtype = etchtype_get_result_type (msg->sv->struct_type);

    if (NULL == newtype) return NULL;

    /* construct message. caller retains ownership of type */
    newmsg = new_message (newtype, 0, msg->vf);  

    msgid  = message_get_id (msg); /* get back a ref to ID or null */

    if (msgid)  
        result = message_set_in_reply_to (newmsg, msgid->clone(msgid));
    
    if (0 != result)
    {
        ETCHOBJ_DESTROY(newmsg);
        newmsg = NULL;
    }     

    return newmsg;
}


/**
 * message_set_id()
 * sets the message-id field of this message.
 * @param id a *disposable* long object wrapping the connection specific 
 * unique identifier of this message, or NULL if the message has not yet
 * been sent. NOTE that the send process overwrites any value which might 
 * otherwise be set here.
 */ 
int message_set_id(etch_message* msg, etch_int64* id)
{
    int  result = 0;
    if (!id) return -1;       

    /* id object ownership is relinquished here even if the call fails */
    result = msg->vf->vtab->set_message_id(msg->vf, msg, id);
    return result;
}


/**
 * message_get_id()
 * @return a non-disposable reference to the connection specific unique  
 * identifier of this message, or null if there was no such identifier. 
 */ 
etch_int64* message_get_id(etch_message* msg)
{
    etch_int64* id = msg->vf->vtab->get_message_id(msg->vf, msg);
    return id;
}


/**
 * message_get_in_reply_to()
 * @return a non-disposable reference to the message-id of the message that 
 * this message is a response to, or null if this is an original message 
 * or if the original message did not have a message-id. 
 * caller does not own the returned object.
 */ 
etch_int64* message_get_in_reply_to(etch_message* msg)
{
    /* vf returns to us a reference to its value */
    etch_int64* id = msg->vf->vtab->get_in_reply_to(msg->vf, msg);
    return id;
}


/**
 * message_set_in_reply_to()
 * sets the in-reply-to field of this message.
 * @param msgid a *disposable* long object wrapping the message-id of the 
 * message that this message is a response to. note that caller must clone 
 * or otherwise supply a disposable object as this parameter.  
 */ 
int message_set_in_reply_to(etch_message* msg, etch_int64* msgid)
{
    int result = 0;
    if (!msgid) return -1;       

    /* msgid ownership is relinquished here even if the call fails */
    result = msg->vf->vtab->set_in_reply_to(msg->vf, msg, msgid);
    return result;
}


/**
 * message_size()
 */ 

int message_size (etch_message* msg) 
{
    return msg? structvalue_count(msg->sv): 0;
}


/* - - - - - - - - - - -
 * etch_unwanted_message
 * - - - - - - - - - - -
 */
  
/**
 * destroy_unwanted_message()
 * etch_unwanted_message destructor
 */
int destroy_unwanted_message(etch_unwanted_message* msg)
{
   if (msg->refcount > 0 && --msg->refcount > 0) return -1;  

    if (!is_etchobj_static_content(msg))
    {   
        if (msg->message)
            msg->message->destroy(msg->message);
    }

   return destroy_objectex((objmask*) msg);
}


/**
 * new_unwanted_message()
 * etch_unwanted_message constructor
 * @param whofrom caller retains
 * @param msg caller relinquishes
 */
etch_unwanted_message* new_unwanted_message(etch_who* whofrom, etch_message* msg)
{
   etch_unwanted_message* newmsg = (etch_unwanted_message*) new_object
     (sizeof(etch_unwanted_message), ETCHTYPEB_EVENT, CLASSID_EVENT_UNWANTMSG);

    newmsg->destroy = destroy_unwanted_message;
    newmsg->clone   = clone_null;

    newmsg->message = msg;
    newmsg->whofrom = whofrom;
    return newmsg;
}
