/*
 * $Id$
 *
 * Copyright (C) 2012 Andrew Mortensen
 *
 * This file is part of the sca module for sip-router, a free SIP server.
 *
 * The sca module is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version
 *
 * The sca module is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 *
 */
#include "sca_common.h"

#include "../../lib/kcore/strcommon.h"

#include "sca.h"
#include "sca_appearance.h"
#include "sca_hash.h"
#include "sca_notify.h"
#include "sca_util.h"

const str SCA_APPEARANCE_INDEX_STR = STR_STATIC_INIT( "appearance-index" );
const str SCA_APPEARANCE_STATE_STR = STR_STATIC_INIT( "appearance-state" );
const str SCA_APPEARANCE_URI_STR = STR_STATIC_INIT( "appearance-uri" );

const str SCA_APPEARANCE_STATE_STR_IDLE = STR_STATIC_INIT( "idle" );
const str SCA_APPEARANCE_STATE_STR_SEIZED = STR_STATIC_INIT( "seized" );
const str SCA_APPEARANCE_STATE_STR_PROGRESSING = STR_STATIC_INIT("progressing");
const str SCA_APPEARANCE_STATE_STR_ALERTING = STR_STATIC_INIT( "alerting" );
const str SCA_APPEARANCE_STATE_STR_ACTIVE = STR_STATIC_INIT( "active" );
const str SCA_APPEARANCE_STATE_STR_HELD = STR_STATIC_INIT( "held" );
const str SCA_APPEARANCE_STATE_STR_HELD_PRIVATE = STR_STATIC_INIT("held-private");
const str SCA_APPEARANCE_STATE_STR_UNKNOWN = STR_STATIC_INIT( "unknown" );


/* STR_ACTIVE is repeated, once for ACTIVE_PENDING, once for ACTIVE */
const str	*state_names[] = {
			&SCA_APPEARANCE_STATE_STR_IDLE,
			&SCA_APPEARANCE_STATE_STR_SEIZED,
			&SCA_APPEARANCE_STATE_STR_PROGRESSING,
			&SCA_APPEARANCE_STATE_STR_ALERTING,
			&SCA_APPEARANCE_STATE_STR_ACTIVE,
			&SCA_APPEARANCE_STATE_STR_ACTIVE,
			&SCA_APPEARANCE_STATE_STR_HELD,
			&SCA_APPEARANCE_STATE_STR_HELD_PRIVATE,
		};
#define SCA_APPEARANCE_STATE_NAME_COUNT \
	( sizeof( state_names ) / sizeof( state_names[ 0 ] ))

    void
sca_appearance_state_to_str( int state, str *state_str )
{
    assert( state_str != NULL );

    if ( state >= SCA_APPEARANCE_STATE_NAME_COUNT || state < 0 ) {
	state_str->len = SCA_APPEARANCE_STATE_STR_UNKNOWN.len;
	state_str->s = SCA_APPEARANCE_STATE_STR_UNKNOWN.s;

	return;
    }

    state_str->len = state_names[ state ]->len;
    state_str->s = state_names[ state ]->s;
}

    int
sca_appearance_state_from_str( str *state_str )
{
    int			state;

    assert( state_str != NULL );

    for ( state = 0; state < SCA_APPEARANCE_STATE_NAME_COUNT; state++ ) {
	if ( SCA_STR_EQ( state_str, state_names[ state ] )) {
	    break;
	}
    }
    if ( state >= SCA_APPEARANCE_STATE_NAME_COUNT ) {
	state = SCA_APPEARANCE_STATE_UNKNOWN;
    }

    return( state );
}

    sca_appearance *
sca_appearance_create( int appearance_index, str *owner_uri )
{
    sca_appearance	*new_appearance = NULL;

    /*
     * we use multiple shm_malloc calls here because uri, owner,
     * dialog and callee are mutable. could also shm_malloc a big
     * block and divide it among the strs....
     */

    new_appearance = (sca_appearance *)shm_malloc( sizeof( sca_appearance ));
    if ( new_appearance == NULL ) {
	LM_ERR( "Failed to shm_malloc new sca_appearance for %.*s, index %d",
		STR_FMT( owner_uri ), appearance_index );
	goto error;
    }
    memset( new_appearance, 0, sizeof( sca_appearance ));

    new_appearance->owner.s = (char *)shm_malloc( owner_uri->len );
    if ( new_appearance->owner.s == NULL ) {
	LM_ERR( "Failed to shm_malloc space for owner %.*s, index %d",
		STR_FMT( owner_uri ), appearance_index );
	goto error;
    }
    SCA_STR_COPY( &new_appearance->owner, owner_uri );

    new_appearance->index = appearance_index;
    new_appearance->times.ctime = time( NULL );
    sca_appearance_update_state_unsafe( new_appearance,
				SCA_APPEARANCE_STATE_IDLE );
    new_appearance->next = NULL;

    return( new_appearance );

error:
    if ( new_appearance != NULL ) {
	if ( !SCA_STR_EMPTY( &new_appearance->owner )) {
	    shm_free( new_appearance->owner.s );
	}

	shm_free( new_appearance );
    }

    return( NULL );
}

    void
sca_appearance_free( sca_appearance *appearance )
{
    if ( appearance != NULL ) {
	if ( appearance->owner.s != NULL ) {
	    shm_free( appearance->owner.s );
	}
	if ( appearance->uri.s != NULL ) {
	    shm_free( appearance->uri.s );
	}
	if ( appearance->dialog.id.s != NULL ) {
	    shm_free( appearance->dialog.id.s );
	}

        if ( appearance->prev_owner.s != NULL ) {
            shm_free( appearance->prev_owner.s );
        }
        if ( appearance->prev_callee.s != NULL ) {
            shm_free( appearance->prev_callee.s );
        }
        if ( appearance->prev_dialog.id.s != NULL ) {
            shm_free( appearance->prev_dialog.id.s );
        }

	shm_free( appearance );
    }
}

/*
 * assumes slot for app_entries is locked.
 *
 * appearance-index values are 1-indexed.
 * return values: 
 *	 -1:	error
 *	>=1:	index reserved for claimant
 */
    static int
sca_appearance_list_next_available_index_unsafe( sca_appearance_list *app_list )
{
    sca_appearance	*app_cur;
    int			idx = 1;

    assert( app_list != NULL );

    for ( app_cur = app_list->appearances; app_cur != NULL;
			app_cur = app_cur->next, idx++ ) {
	if ( idx < app_cur->index ) {
	    break;
	}
    }

    return( idx );
}

    static sca_appearance_list *
sca_appearance_list_create( sca_mod *scam, str *aor )
{
    sca_appearance_list	*app_list;
    int			len;

    len = sizeof( sca_appearance_list ) + aor->len;
    app_list = (sca_appearance_list *)shm_malloc( len );
    if ( app_list == NULL ) {
	LM_ERR( "Failed to shm_malloc sca_appearance_list for %.*s",
		STR_FMT( aor ));
	return( NULL );
    }
    memset( app_list, 0, sizeof( sca_appearance_list ));

    len = sizeof( sca_appearance_list );
    app_list->aor.s = (char *)app_list + len;
    SCA_STR_COPY( &app_list->aor, aor );

    return( app_list );
}

    sca_appearance_list *
sca_appearance_list_for_line( sca_mod *scam, str *aor )
{
    //sca_appearance_list

    return( NULL );
}

    void
sca_appearance_list_insert_appearance( sca_appearance_list *app_list,
	sca_appearance *app )
{
    sca_appearance	**cur;

    assert( app_list != NULL );
    assert( app != NULL );

    app->appearance_list = app_list;

    for ( cur = &app_list->appearances; *cur != NULL; cur = &(*cur)->next ) {
	if ( app->index < (*cur)->index ) {
	    break;
	}
    }

    app->next = *cur;
    *cur = app;
}

    sca_appearance *
sca_appearance_list_unlink_index( sca_appearance_list *app_list, int idx )
{
    sca_appearance	*app = NULL;
    sca_appearance	**cur;

    assert( app_list != NULL );
    assert( idx > 0 );

    for ( cur = &app_list->appearances; *cur != NULL; cur = &(*cur)->next ) {
	if ((*cur)->index == idx ) {
	    app = *cur;
	    app->appearance_list = NULL;

	    *cur = (*cur)->next;

	    break;
	}
    }

    if ( app == NULL ) {
	LM_ERR( "Tried to remove inactive %.*s appearance at index %d",
		STR_FMT( &app_list->aor ), idx );
    }

    return( app );
}

    int
sca_appearance_list_unlink_appearance( sca_appearance_list *app_list,
	sca_appearance **app )
{
    sca_appearance	**cur;
    int			rc = 0;

    assert( app_list != NULL );
    assert( app != NULL && *app != NULL );

    for ( cur = &app_list->appearances; *cur != NULL; cur = &(*cur)->next ) {
	if ( *cur == *app ) {
	    *cur = (*cur)->next;

	    (*app)->appearance_list = NULL;
	    (*app)->next = NULL;

	    rc = 1;

	    break;
	}
    }

    return( rc );
}

    int
sca_appearance_list_aor_cmp( str *aor, void *cmp_value )
{
    sca_appearance_list	*app_list = (sca_appearance_list *)cmp_value;
    int			cmp;

    if (( cmp = aor->len - app_list->aor.len ) != 0 ) {
	return( cmp );
    }

    return( memcmp( aor->s, app_list->aor.s, aor->len ));
}

    void
sca_appearance_list_print( void *value )
{
    sca_appearance_list *app_list = (sca_appearance_list *)value;
    sca_appearance	*app;
    str			state_str = STR_NULL;

    LM_INFO( "Appearance state for AoR %.*s:", STR_FMT( &app_list->aor ));

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	sca_appearance_state_to_str( app->state, &state_str );
	LM_INFO( "index: %d, state: %.*s, uri: %.*s, owner: %.*s, "
		"callee: %.*s, dialog: %.*s;%.*s;%.*s",
		app->index, STR_FMT( &state_str ),
		STR_FMT( &app->uri ), STR_FMT( &app->owner ),
		STR_FMT( &app->callee ), STR_FMT( &app->dialog.call_id ),
		STR_FMT( &app->dialog.from_tag ),
		STR_FMT( &app->dialog.to_tag ));
    }
}

    void
sca_appearance_list_free( void *value )
{
    sca_appearance_list	*app_list = (sca_appearance_list *)value;
    sca_appearance	*app, *app_tmp;

    LM_DBG( "Freeing appearance list for AoR %.*s", STR_FMT( &app_list->aor ));

    for ( app = app_list->appearances; app != NULL; app = app_tmp ) {
	app_tmp = app->next;

	shm_free( app );
    }

    shm_free( app_list );
}

    int
sca_appearance_register( sca_mod *scam, str *aor )
{
    sca_appearance_list	*app_list;
    int			rc = -1;

    assert( scam != NULL );
    assert( aor != NULL );

    if ( sca_uri_is_shared_appearance( scam, aor )) {
	/* we've already registered */
	rc = 0;
	goto done;
    }

    app_list = sca_appearance_list_create( scam, aor );
    if ( app_list == NULL ) {
	goto done;
    }

    if ( sca_hash_table_kv_insert( scam->appearances, aor, app_list,
		    sca_appearance_list_aor_cmp,
		    sca_appearance_list_print,
		    sca_appearance_list_free ) < 0 ) {
	LM_ERR( "sca_appearance_register: failed to insert appearance list "
		"for %.*s", STR_FMT( aor ));
	goto done;
    }

    rc = 1;

done:
    return( rc );
}

    int
sca_appearance_unregister( sca_mod *scam, str *aor )
{
    int			rc = 0;

    assert( scam != NULL );
    assert( aor != NULL );

    if ( sca_uri_is_shared_appearance( scam, aor )) {
	if (( rc = sca_hash_table_kv_delete( scam->appearances, aor )) == 0 ) {
	    rc = 1;
	    LM_INFO( "unregistered SCA AoR %.*s", STR_FMT( aor ));
	}
    }

    return( rc );
}

    sca_appearance *
sca_appearance_seize_index_unsafe( sca_mod *scam, str *aor, str *owner_uri,
	int app_idx, int slot_idx, int *seize_error )
{
    sca_appearance_list	*app_list;
    sca_appearance	*app = NULL;
    sca_hash_slot	*slot;
    int			error = SCA_APPEARANCE_ERR_UNKNOWN;

    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );
    if ( app_list == NULL ) {
	LM_ERR( "sca_appearance_seize_index_unsafe: no appearance list for "
		"%.*s", STR_FMT( aor ));
	goto done;
    }

    if ( app_idx <= 0 ) {
        app_idx = sca_appearance_list_next_available_index_unsafe( app_list );
    }

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	if ( app->index >= app_idx ) {
	    break;
	}
    }
    if ( app != NULL && app->index == app_idx ) {
	/* attempt to seize in-use appearance-index */
	error = SCA_APPEARANCE_ERR_INDEX_UNAVAILABLE;
	app = NULL;
	goto done;
    }

    app = sca_appearance_create( app_idx, owner_uri );
    if ( app == NULL ) {
        LM_ERR( "Failed to create new appearance for %.*s at index %d",
                STR_FMT( owner_uri ), app_idx );
	error = SCA_APPEARANCE_ERR_MALLOC;
        goto done;
    }
    sca_appearance_update_state_unsafe( app, SCA_APPEARANCE_STATE_SEIZED );

    sca_appearance_list_insert_appearance( app_list, app );

    error = SCA_APPEARANCE_OK;

done:
    if ( seize_error ) {
	*seize_error = error;
    }

    return( app );
}

    int
sca_appearance_seize_index( sca_mod *scam, str *aor, int idx, str *owner_uri )
{
    sca_appearance	*app;
    int			slot_idx;
    int			app_idx = -1;
    int			error = SCA_APPEARANCE_OK;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    sca_hash_table_lock_index( scam->appearances, slot_idx );

    app = sca_appearance_seize_index_unsafe( scam, aor, owner_uri,
						idx, slot_idx, &error );
    if ( app != NULL ) {
	app_idx = app->index;
    }

    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    if ( error == SCA_APPEARANCE_ERR_INDEX_UNAVAILABLE ) {
	app_idx = SCA_APPEARANCE_INDEX_UNAVAILABLE;
    }

    return( app_idx );
}

    sca_appearance *
sca_appearance_seize_next_available_unsafe( sca_mod *scam, str *aor,
	str *owner_uri, int slot_idx )
{
    sca_appearance_list	*app_list;
    sca_appearance	*app = NULL;
    sca_hash_slot	*slot;
    int			idx = -1;

    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );
    if ( app_list == NULL ) {
	app_list = sca_appearance_list_create( scam, aor );
	if ( app_list == NULL ) {
	    goto done;
	}

	if ( sca_hash_table_slot_kv_insert_unsafe( slot, app_list,
			sca_appearance_list_aor_cmp,
			sca_appearance_list_print,
			sca_appearance_list_free ) < 0 ) {
	    LM_ERR( "Failed to insert appearance list for %.*s",
			STR_FMT( aor ));
	    goto done;
	}
    }

    /* XXX this grows without bound. add modparam to set a hard limit */
    idx = sca_appearance_list_next_available_index_unsafe( app_list );
    /* XXX check idx > any configured max appearance index */

    app = sca_appearance_create( idx, owner_uri );
    if ( app == NULL ) {
	LM_ERR( "Failed to create new appearance for %.*s at index %d",
		STR_FMT( owner_uri ), idx );
	goto done;
    }
    sca_appearance_update_state_unsafe( app, SCA_APPEARANCE_STATE_SEIZED );

    sca_appearance_list_insert_appearance( app_list, app );

done:
    return( app );
}

    int
sca_appearance_seize_next_available_index( sca_mod *scam, str *aor,
	str *owner_uri )
{
    sca_appearance	*app;
    int			slot_idx;
    int			idx = -1;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    sca_hash_table_lock_index( scam->appearances, slot_idx );

    app = sca_appearance_seize_next_available_unsafe( scam, aor,
					owner_uri, slot_idx );
    if ( app != NULL ) {
	idx = app->index;
    }

    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    return( idx );
}

    void
sca_appearance_update_state_unsafe( sca_appearance *app, int state )
{
    assert( app != NULL );

    app->state = state;
    app->times.mtime = time( NULL );
}

    int
sca_appearance_update_owner_unsafe( sca_appearance *app, str *owner )
{
    assert( app != NULL );
    assert( owner != NULL );

    if ( !SCA_STR_EMPTY( &app->owner )) {
	if ( app->prev_owner.s != NULL ) {
	    shm_free( app->prev_owner.s );
	}
	app->prev_owner.s = app->owner.s;
	app->prev_owner.len = app->owner.len;
    }

    app->owner.s = (char *)shm_malloc( owner->len );
    if ( app->owner.s == NULL ) {
	LM_ERR( "sca_appearance_update_owner_unsafe: shm_malloc for new "
		"owner %.*s failed: out of memory", STR_FMT( owner ));
	goto error;
    }
    SCA_STR_COPY( &app->owner, owner );

    return( 1 );

error:
    /* restore owner */
    app->owner.s = app->prev_owner.s;
    app->owner.len = app->prev_owner.len;
    memset( &app->prev_owner, 0, sizeof( str ));

    return( -1 );
}

    int
sca_appearance_update_callee_unsafe( sca_appearance *app, str *callee )
{
    assert( app != NULL );
    assert( callee != NULL );

    if ( !SCA_STR_EMPTY( &app->callee )) {
	if ( app->prev_callee.s != NULL ) {
	    shm_free( app->prev_callee.s );
	}
	app->prev_callee.s = app->callee.s;
	app->prev_callee.len = app->callee.len;
    }

    app->callee.s = (char *)shm_malloc( callee->len );
    if ( app->callee.s == NULL ) {
	LM_ERR( "sca_appearance_update_owner_unsafe: shm_malloc for new "
		"callee %.*s failed: out of memory", STR_FMT( callee ));
	goto error;
    }
    SCA_STR_COPY( &app->callee, callee );

    return( 1 );

error:
    /* restore callee */
    app->callee.s = app->prev_callee.s;
    app->callee.len = app->prev_callee.len;
    memset( &app->prev_callee, 0, sizeof( str ));

    return( -1 );
}
    int
sca_appearance_update_dialog_unsafe( sca_appearance *app, str *call_id,
	str *from_tag, str *to_tag )
{
    int		len;

    assert( app != NULL );
    assert( call_id != NULL );
    assert( from_tag != NULL );

    if ( !SCA_STR_EMPTY( &app->dialog.id )) {
	if ( app->prev_dialog.id.s != NULL ) {
	    shm_free( app->prev_dialog.id.s );
	}
	app->prev_dialog.id.s = app->dialog.id.s;
	app->prev_dialog.id.len = app->dialog.id.len;

	app->prev_dialog.call_id.s = app->dialog.call_id.s;
	app->prev_dialog.call_id.len = app->dialog.call_id.len;

	app->prev_dialog.from_tag.s = app->dialog.from_tag.s;
	app->prev_dialog.from_tag.len = app->dialog.from_tag.len;

	app->prev_dialog.to_tag.s = app->dialog.to_tag.s;
	app->prev_dialog.to_tag.len = app->dialog.to_tag.len;
    }

    len = call_id->len + from_tag->len;
    if ( !SCA_STR_EMPTY( to_tag )) {
	len += to_tag->len;
    }

    app->dialog.id.s = (char *)shm_malloc( len );
    if ( app->dialog.id.s == NULL ) {
	LM_ERR( "sca_appearance_update_dialog_unsafe: shm_malloc new dialog "
		"failed: out of memory" );
	goto error;
    }
    SCA_STR_COPY( &app->dialog.id, call_id );
    SCA_STR_APPEND( &app->dialog.id, from_tag );

    app->dialog.call_id.s = app->dialog.id.s;
    app->dialog.call_id.len = call_id->len;

    app->dialog.from_tag.s = app->dialog.id.s + call_id->len;
    app->dialog.from_tag.len = from_tag->len;

    app->dialog.to_tag.s = app->dialog.id.s + call_id->len + from_tag->len;
    app->dialog.to_tag.len = to_tag->len;

    return( 1 );

error:
    /* restore dialog */
    app->prev_dialog.id.s = app->dialog.id.s;
    app->prev_dialog.id.len = app->dialog.id.len;

    app->prev_dialog.call_id.s = app->dialog.call_id.s;
    app->prev_dialog.call_id.len = app->dialog.call_id.len;

    app->prev_dialog.from_tag.s = app->dialog.from_tag.s;
    app->prev_dialog.from_tag.len = app->dialog.from_tag.len;

    app->prev_dialog.to_tag.s = app->dialog.to_tag.s;
    app->prev_dialog.to_tag.len = app->dialog.to_tag.len;

    memset( &app->prev_dialog, 0, sizeof( sca_dialog ));

    return( -1 );
}

    int
sca_appearance_update_unsafe( sca_appearance *app, int state, str *display,
	str *uri, sca_dialog *dialog, str *owner, str *callee )
{
    int			rc = SCA_APPEARANCE_OK;
    int			len;

    if ( state != SCA_APPEARANCE_STATE_UNKNOWN ) {
	sca_appearance_update_state_unsafe( app, state );
    }

    if ( !SCA_STR_EMPTY( uri )) {
	if ( !SCA_STR_EMPTY( &app->uri )) {
	    /* the uri str's s member is shm_malloc'd separately */
	    shm_free( app->uri.s );
	    memset( &app->uri, 0, sizeof( str ));
	}

	/* +2 for left & right carets surrounding URI */
	len = uri->len + 2;
	if ( !SCA_STR_EMPTY( display )) {
	    /* cheaper to scan string than shm_malloc 2x display? */
	    len += sca_uri_display_escapes_count( display );
	    /* +1 for space between display & uri */
	    len += display->len + 1;
	}
	app->uri.s = (char *)shm_malloc( len );
	if ( app->uri.s == NULL ) {
	    LM_ERR( "shm_malloc %d bytes returned NULL", uri->len );
	    rc = SCA_APPEARANCE_ERR_MALLOC;
	    goto done;
	}

	if ( !SCA_STR_EMPTY( display )) {
	    /* copy escaped display information... */
	    app->uri.len = escape_common( app->uri.s, display->s,
					  display->len );

	    /* ... and add a space between it and the uri */
	    *(app->uri.s + app->uri.len) = ' ';
	    app->uri.len++;
	}

	*(app->uri.s + app->uri.len) = '<';
	app->uri.len++;

	SCA_STR_APPEND( &app->uri, uri );

	*(app->uri.s + app->uri.len) = '>';
	app->uri.len++;
    }

    if ( !SCA_DIALOG_EMPTY( dialog )) {
	if ( !SCA_STR_EQ( &dialog->id, &app->dialog.id )) {
	    if ( app->dialog.id.s != NULL ) {
		shm_free( app->dialog.id.s );
	    }

	    app->dialog.id.s = (char *)shm_malloc( dialog->id.len );
	    if ( app->dialog.id.s == NULL ) {
		LM_ERR( "sca_appearance_update_unsafe: shm_malloc dialog id "
			"failed: out of shared memory" );
		/* XXX this seems bad enough to abort... */
		return( -1 );
	    }
	    SCA_STR_COPY( &app->dialog.id, &dialog->id );

	    app->dialog.call_id.s = app->dialog.id.s;
	    app->dialog.call_id.len = dialog->call_id.len;

	    app->dialog.from_tag.s = app->dialog.id.s + dialog->call_id.len;
	    app->dialog.from_tag.len = dialog->from_tag.len;

	    if ( !SCA_STR_EMPTY( &dialog->to_tag )) {
		app->dialog.to_tag.s = app->dialog.id.s +
					dialog->call_id.len +
					dialog->from_tag.len;
		app->dialog.to_tag.len = dialog->to_tag.len;
	    } else {
		app->dialog.to_tag.s = NULL;
		app->dialog.to_tag.len = 0;
	    }
	}
    }

    /* note these two blocks could be condensed and inlined */
    if ( !SCA_STR_EMPTY( owner )) {
	if ( !SCA_STR_EQ( &app->owner, owner )) {
	    if ( app->owner.s != NULL ) {
		shm_free( app->owner.s );
	    }

	    app->owner.s = (char *)shm_malloc( owner->len );
	    if ( app->owner.s == NULL ) {
		LM_ERR( "sca_appearance_update_unsafe: shm_malloc "
			"appearance owner URI failed: out of shared memory" );
		return( -1 );
	    }
	    SCA_STR_COPY( &app->owner, owner );
	}
    }

    if ( !SCA_STR_EMPTY( callee )) {
	if ( !SCA_STR_EQ( &app->callee, callee )) {
	    if ( app->callee.s != NULL ) {
		shm_free( app->callee.s );
	    }

	    app->callee.s = (char *)shm_malloc( callee->len );
	    if ( app->callee.s == NULL ) {
		LM_ERR( "sca_appearance_update_unsafe: shm_malloc "
			"appearance callee URI failed: out of shared memory" );
		return( -1 );
	    }
	    SCA_STR_COPY( &app->callee, callee );
	}
    }

done:
    return( rc );
}

    int
sca_uri_is_shared_appearance( sca_mod *scam, str *aor )
{
    sca_hash_slot	*slot;
    sca_appearance_list	*app_list;
    int			slot_idx;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    sca_hash_table_lock_index( scam->appearances, slot_idx );
    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );
    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    if ( app_list == NULL ) {
	return( 0 );
    }

    return( 1 );
}

    int
sca_uri_lock_shared_appearance( sca_mod *scam, str *aor )
{
    sca_hash_slot	*slot;
    sca_appearance_list	*app_list;
    int			slot_idx;

    if ( SCA_STR_EMPTY( aor )) {
	return( -1 );
    }

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    sca_hash_table_lock_index( scam->appearances, slot_idx );
    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );

    if ( app_list == NULL ) {
	sca_hash_table_unlock_index( scam->appearances, slot_idx );
	slot_idx = -1;
    }

    return( slot_idx );
}

    int
sca_uri_lock_if_shared_appearance( sca_mod *scam, str *aor, int *slot_idx )
{
    sca_hash_slot	*slot;
    sca_appearance_list	*app_list;

    assert( slot_idx != NULL );

    if ( SCA_STR_EMPTY( aor )) {
	*slot_idx = -1;
	return( 0 );
    }

    *slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, *slot_idx );

    sca_hash_table_lock_index( scam->appearances, *slot_idx );
    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );

    if ( app_list == NULL ) {
	sca_hash_table_unlock_index( scam->appearances, *slot_idx );
	*slot_idx = -1;

	return( 0 );
    }

    return( 1 );
}

    int
sca_appearance_state_for_index( sca_mod *scam, str *aor, int idx )
{
    sca_hash_slot	*slot;
    sca_appearance_list	*app_list;
    sca_appearance	*app;
    int			slot_idx;
    int			state = SCA_APPEARANCE_STATE_UNKNOWN;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );
    
    sca_hash_table_lock_index( scam->appearances, slot_idx );

    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );
    if ( app_list == NULL ) {
	LM_DBG( "%.*s has no in-use appearances", STR_FMT( aor ));
	goto done;
    }

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	if ( app->index == idx ) {
	    break;
	}
    }
    if ( app == NULL ) {
	LM_WARN( "%.*s appearance-index %d is not in use",
		STR_FMT( aor ), idx );
	goto done;
    }

    state = app->state;

done:
    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    return( state );
}

    int
sca_appearance_update_index( sca_mod *scam, str *aor, int idx,
	int state, str *display, str *uri, sca_dialog *dialog )
{
    sca_hash_slot	*slot;
    sca_appearance_list	*app_list;
    sca_appearance	*app;
    str			state_str = STR_NULL;
    int			len;
    int			slot_idx;
    int			rc = SCA_APPEARANCE_ERR_UNKNOWN;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    sca_hash_table_lock_index( scam->appearances, slot_idx );

    sca_appearance_state_to_str( state, &state_str );

    app_list = sca_hash_table_slot_kv_find_unsafe( slot, aor );
    if ( app_list == NULL ) {
	LM_WARN( "Cannot update %.*s index %d to state %.*s: %.*s has no "
		 "in-use appearances", STR_FMT( aor ), idx,
		 STR_FMT( &state_str ), STR_FMT( aor ));
	rc = SCA_APPEARANCE_ERR_NOT_IN_USE;
	goto done;
    }

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	if ( app->index == idx ) {
	    break;
	} else if ( idx == 0 ) {
	    if ( SCA_STR_EQ( &dialog->id, &app->dialog.id )) {
		break;
	    }
	}
    }
    if ( app == NULL ) {
	LM_WARN( "Cannot update %.*s index %d to %.*s: index %d not in use",
		 STR_FMT( aor ), idx, STR_FMT( &state_str ), idx );
	rc = SCA_APPEARANCE_ERR_INDEX_INVALID;
	goto done;
    }

    if ( state != SCA_APPEARANCE_STATE_UNKNOWN && app->state != state ) {
	sca_appearance_update_state_unsafe( app, state );
    }

    if ( !SCA_STR_EMPTY( uri )) {
	if ( !SCA_STR_EMPTY( &app->uri )) {
	    /* the uri str's s member is shm_malloc'd separately */
	    shm_free( app->uri.s );
	    memset( &app->uri, 0, sizeof( str ));
	}

	/* +2 for left & right carets surrounding URI */
	len = uri->len + 2;
	if ( !SCA_STR_EMPTY( display )) {
	    /* cheaper to scan string than shm_malloc 2x display? */
	    len += sca_uri_display_escapes_count( display );
	    /* +1 for space between display & uri */
	    len += display->len + 1;
	}
	app->uri.s = (char *)shm_malloc( len );
	if ( app->uri.s == NULL ) {
	    LM_ERR( "Failed to update %.*s index %d uri to %.*s: "
		    "shm_malloc %d bytes returned NULL",
		    STR_FMT( aor ), idx, STR_FMT( uri ), uri->len );
	    rc = SCA_APPEARANCE_ERR_MALLOC;
	    goto done;
	}

	if ( !SCA_STR_EMPTY( display )) {
	    /* copy escaped display information... */
	    app->uri.len = escape_common( app->uri.s, display->s,
					  display->len );

	    /* ... and add a space between it and the uri */
	    *(app->uri.s + app->uri.len) = ' ';
	    app->uri.len++;
	}

	*(app->uri.s + app->uri.len) = '<';
	app->uri.len++;

	SCA_STR_APPEND( &app->uri, uri );

	*(app->uri.s + app->uri.len) = '>';
	app->uri.len++;
    }

    if ( !SCA_DIALOG_EMPTY( dialog )) {
	if ( !SCA_STR_EQ( &dialog->id, &app->dialog.id )) {
	    if ( app->dialog.id.s != NULL ) {
		shm_free( app->dialog.id.s );
	    }

	    app->dialog.id.s = (char *)shm_malloc( dialog->id.len );
	    SCA_STR_COPY( &app->dialog.id, &dialog->id );

	    app->dialog.call_id.s = app->dialog.id.s;
	    app->dialog.call_id.len = dialog->call_id.len;

	    app->dialog.from_tag.s = app->dialog.id.s + dialog->call_id.len;
	    app->dialog.from_tag.len = dialog->from_tag.len;

	    if ( !SCA_STR_EMPTY( &dialog->to_tag )) {
		app->dialog.to_tag.s = app->dialog.id.s +
					dialog->call_id.len +
					dialog->from_tag.len;
		app->dialog.to_tag.len = dialog->to_tag.len;
	    } else {
		app->dialog.to_tag.s = NULL;
		app->dialog.to_tag.len = 0;
	    }
	}
    }

    rc = SCA_APPEARANCE_OK;

done:
    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    return( rc );
}

    int
sca_appearance_release_index( sca_mod *scam, str *aor, int idx )
{
    sca_hash_slot	*slot;
    sca_hash_entry	*ent;
    sca_appearance_list *app_list = NULL;
    sca_appearance	*app;
    int			slot_idx;
    int			rc = SCA_APPEARANCE_ERR_UNKNOWN;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    sca_hash_table_lock_index( scam->appearances, slot_idx );

    app_list = NULL;
    for ( ent = slot->entries; ent != NULL; ent = ent->next ) {
	if ( ent->compare( aor, ent->value ) == 0 ) {
	    app_list = (sca_appearance_list *)ent->value;
	    break;
	}
    }
    if ( app_list == NULL ) {
	LM_ERR( "No appearances for %.*s", STR_FMT( aor ));
	rc = SCA_APPEARANCE_ERR_NOT_IN_USE;
	goto done;
    }

    app = sca_appearance_list_unlink_index( app_list, idx );
    if ( app == NULL ) {
	LM_ERR( "Failed to unlink %.*s appearance-index %d: invalid index",
		STR_FMT( aor ), idx );
	rc = SCA_APPEARANCE_ERR_INDEX_INVALID;
	goto done;
    }
    sca_appearance_free( app );

    rc = SCA_APPEARANCE_OK;
    
done:
    sca_hash_table_unlock_index( scam->appearances, slot_idx );

    return( rc );
}

    int
sca_appearance_owner_release_all( str *aor, str *owner )
{
    sca_appearance_list	*app_list = NULL;
    sca_appearance	*app, **cur_app, **tmp_app;
    sca_hash_slot	*slot;
    sca_hash_entry	*ent;
    int			slot_idx = -1;
    int			released = -1;

    slot_idx = sca_uri_lock_shared_appearance( sca, aor );
    slot = sca_hash_table_slot_for_index( sca->appearances, slot_idx );

    for ( ent = slot->entries; ent != NULL; ent = ent->next ) {
	if ( ent->compare( aor, ent->value ) == 0 ) {
	    app_list = (sca_appearance_list *)ent->value;
	    break;
	}
    }

    released = 0;

    if ( app_list == NULL ) {
	LM_DBG( "sca_appearance_owner_release_all: No appearances for %.*s",
		STR_FMT( aor ));
	goto done;
    }

    for ( cur_app = &app_list->appearances; *cur_app != NULL;
		cur_app = tmp_app ) {
	tmp_app = &(*cur_app)->next;

	if ( !SCA_STR_EQ( owner, &(*cur_app)->owner )) {
	    continue;
	}

	app = *cur_app;
	*cur_app = (*cur_app)->next;
	tmp_app = cur_app;

	if ( app ) {
	    sca_appearance_free( app );
	    released++;
	}
    }

done:
    if ( slot_idx >= 0 ) {
	sca_hash_table_unlock_index( sca->appearances, slot_idx );
    }

    return( released );
}

    sca_appearance *
sca_appearance_for_index_unsafe( sca_mod *scam, str *aor, int app_idx,
	int slot_idx )
{
    sca_appearance_list	*app_list;
    sca_appearance	*app = NULL;
    sca_hash_slot	*slot;
    sca_hash_entry	*ent;

    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    app_list = NULL;
    for ( ent = slot->entries; ent != NULL; ent = ent->next ) {
	if ( ent->compare( aor, ent->value ) == 0 ) {
	    app_list = (sca_appearance_list *)ent->value;
	    break;
	}
    }
    if ( app_list == NULL ) {
	LM_ERR( "No appearances for %.*s", STR_FMT( aor ));
	return( NULL );
    }

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	if ( app->index == app_idx ) {
	    break;
	}
    }
    
    return( app );
}

    sca_appearance *
sca_appearance_for_dialog_unsafe( sca_mod *scam, str *aor, sca_dialog *dialog,
	int slot_idx )
{
    sca_appearance_list	*app_list;
    sca_appearance	*app = NULL;
    sca_hash_slot	*slot;
    sca_hash_entry	*ent;

    slot = sca_hash_table_slot_for_index( scam->appearances, slot_idx );

    app_list = NULL;
    for ( ent = slot->entries; ent != NULL; ent = ent->next ) {
	if ( ent->compare( aor, ent->value ) == 0 ) {
	    app_list = (sca_appearance_list *)ent->value;
	    break;
	}
    }
    if ( app_list == NULL ) {
	LM_ERR( "No appearances for %.*s", STR_FMT( aor ));
	return( NULL );
    }

    for ( app = app_list->appearances; app != NULL; app = app->next ) {
	if ( SCA_STR_EQ( &app->dialog.call_id, &dialog->call_id ) &&
		SCA_STR_EQ( &app->dialog.from_tag, &dialog->from_tag )) {
#ifdef notdef
	    if ( !SCA_STR_EMPTY( &app->dialog.to_tag ) &&
		    !SCA_STR_EMPTY( &dialog->to_tag ) &&
		    !SCA_STR_EQ( &app->dialog.to_tag, &dialog->to_tag )) {
		continue;
	    }
#endif /* notdef */
	    break;
	}
    }
    
    return( app );
} 

    sca_appearance *
sca_appearance_for_tags_unsafe( sca_mod *scam, str *aor,
	str *call_id, str *from_tag, str *to_tag, int slot_idx )
{
    sca_dialog		dialog;
    char		dlg_buf[ 1024 ];

    dialog.id.s = dlg_buf;
    if ( sca_dialog_build_from_tags( &dialog, sizeof( dlg_buf ),
		call_id, from_tag, to_tag ) < 0 ) {
	LM_ERR( "sca_appearance_for_tags_unsafe: failed to build dialog "
		"from tags" );
	return( NULL );
    }

    return( sca_appearance_for_dialog_unsafe( scam, aor, &dialog, slot_idx ));
}

    sca_appearance  *
sca_appearance_unlink_by_tags( sca_mod *scam, str *aor,
	str *call_id, str *from_tag, str *to_tag )
{
    sca_appearance	*app = NULL, *unl_app;
    int			slot_idx = -1;

    slot_idx = sca_hash_table_index_for_key( scam->appearances, aor );
    sca_hash_table_lock_index( scam->appearances, slot_idx );

    app = sca_appearance_for_tags_unsafe( scam, aor, call_id, from_tag,
					to_tag, slot_idx );
    if ( app == NULL ) {
	LM_ERR( "sca_appearance_unlink_by_tags: no appearances found for %.*s "
		"with dialog %.*s;%.*s;%.*s", STR_FMT( aor ),
		 STR_FMT( call_id ), STR_FMT( from_tag ),  STR_FMT( to_tag ));
	goto done;
    }

    unl_app = sca_appearance_list_unlink_index( app->appearance_list,
						app->index );
    if ( unl_app == NULL || unl_app != app ) {
	LM_ERR( "sca_appearance_unlink_by_tags: failed to unlink %.*s "
		"appearance-index %d", STR_FMT( aor ), app->index );
	app = NULL;
	goto done;
    }

done:
    if ( slot_idx >= 0 ) {
	sca_hash_table_unlock_index( scam->appearances, slot_idx );
    }

    return( app );
}

    void
sca_appearance_purge_stale( unsigned int ticks, void *param )
{
    struct notify_list {
	struct notify_list	*next;
	str			aor;
    };

    sca_mod		*scam = (sca_mod *)param;
    sca_hash_table	*ht;
    sca_hash_entry	*ent;
    sca_appearance_list	*app_list;
    sca_appearance	**cur_app, **tmp_app, *app = NULL;
    struct notify_list	*notify_list = NULL, *tmp_nl;
    int			i;
    int			unlinked;
    time_t		now, ttl;

    LM_INFO( "SCA: purging stale appearances" );

    assert( scam != NULL );
    assert( scam->appearances != NULL );

    now = time( NULL );

    ht = scam->appearances;
    for ( i = 0; i < ht->size; i++ ) {
	sca_hash_table_lock_index( ht, i );

	for ( ent = ht->slots[ i ].entries; ent != NULL; ent = ent->next ) {
	    app_list = (sca_appearance_list *)ent->value;
	    if ( app_list == NULL ) {
		continue;
	    }

	    unlinked = 0;

	    for ( cur_app = &app_list->appearances; *cur_app != NULL;
			cur_app = tmp_app ) {
		tmp_app = &(*cur_app)->next;

		switch ((*cur_app)->state ) {
		case SCA_APPEARANCE_STATE_ACTIVE_PENDING:
		    ttl = SCA_APPEARANCE_STATE_PENDING_TTL;
		    break;

		case SCA_APPEARANCE_STATE_SEIZED:
		    ttl = SCA_APPEARANCE_STATE_SEIZED_TTL;
		    break;

		default:
		    /* XXX for now just skip other appearances */
		    ttl = now + 60;
		    break;
		}
		if (( now - (*cur_app)->times.mtime ) < ttl ) {
		    continue;
		}

		/* unlink stale appearance */
		app = *cur_app;
		*cur_app = (*cur_app)->next;
		tmp_app = cur_app;

		if ( app ) {
		    sca_appearance_free( app );
		}

		if ( unlinked ) {
		    /* we've already added this AoR to the NOTIFY list */
		    continue;
		}
		unlinked++;

		/*
		 * can't notify while slot is locked. make a list of AoRs to
		 * notify after unlocking.
		 */
		tmp_nl = (struct notify_list *)pkg_malloc(
				sizeof( struct notify_list ));
		if ( tmp_nl == NULL ) {
		    LM_ERR( "sca_appearance_purge_stale: failed to pkg_malloc "
			    "notify list entry for %.*s",
			    STR_FMT( &app_list->aor ));
		    continue;
		}

		tmp_nl->aor.s = (char *)pkg_malloc( app_list->aor.len );
		if ( tmp_nl->aor.s == NULL ) {
		    LM_ERR( "sca_appearance_purge_stale: failed to pkg_malloc "
			    "space for copy of %.*s",
			    STR_FMT( &app_list->aor ));
		    pkg_free( tmp_nl );
		    continue;
		}
		SCA_STR_COPY( &tmp_nl->aor, &app_list->aor );

		/* simple insert-at-head. order doesn't matter. */
		tmp_nl->next = notify_list;
		notify_list = tmp_nl;
	    }
	}

	sca_hash_table_unlock_index( ht, i );

	for ( ; notify_list != NULL; notify_list = tmp_nl ) {
	    tmp_nl = notify_list->next;

	    LM_INFO( "sca_appearance_purge_stale: notifying %.*s call-info "
		    "subscribers", STR_FMT( &notify_list->aor ));

	    if ( sca_notify_call_info_subscribers( scam,
			&notify_list->aor ) < 0 ) {
		LM_ERR( "sca_appearance_purge_stale: failed to send "
			"call-info NOTIFY %.*s subscribers",
			STR_FMT( &notify_list->aor ));
		/* fall through, free memory anyway */
	    }

	    if ( notify_list->aor.s ) {
		pkg_free( notify_list->aor.s );
	    }
	    pkg_free( notify_list );
	}
    }
}