/*
 * Copyright (c) 2001-2003 Shiman Associates Inc. All Rights Reserved.
 * 
 * Permission is hereby granted, free of charge, to any person
 * obtaining a copy of this software and associated documentation
 * files (the "Software"), to deal in the Software without
 * restriction, including without limitation the rights to use, copy,
 * modify, merge, publish, distribute, sublicense, and/or sell copies
 * of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 *
 */

#include <stdio.h>
#include <string.h>
#include "mas/mas_dpi.h"

#define MASD_DYNPORT_PREFIX "dyn "
#define MASD_DYNPORT_PREFIX_LEN 4 /* must be set with DYN_PREFIX! */
#define MASD_DYNPORT_LIMIT_MULTIPLIER 2 /* the queue is at most twice
                                         * as full as it should be --
                                         * this can save malloc/free
                                         * calls. */

/* temporary error-handling stuff */
#define MAS_SMEM mas_error(MERR_MEMORY);
#define MAS_SPORT mas_error(MERR_INVALID);
#define MAS_SNULL mas_error(MERR_NULLPTR);
#define MAS_SNOPORTS mas_error(MERR_INVALID);
#define MAS_SCLEANUPFAIL mas_error(MERR_MEMORY);

/* LOCAL PROTOTYPES ****************************************************/

static int32 request_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction );
static int32 release_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int destroy);
static struct masd_dynport* new_dynport( void );
static int32 delete_dynport_head( struct masd_dynport_pool* dp_pool );
static int32 append_dynport(struct masd_dynport_pool* dp_pool, struct masd_dynport* node);
static int32 count_dynports( struct masd_dynport_pool* dp_pool );
static struct masd_dynport* alloc_and_name_dp( struct masd_dynport_pool* dp_pool );
static int32 schedule_destroy_port( int32 di, int32 reaction, int32 portnum );

/* TODO: better error handling! */


/* Clears the dynamic port pool, and requests "num" dynamic ports.  */
int32
masd_init_dynport_pool( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int32 num )
{
    int32 err;
    
    masc_entering_log_level( "masd_init_dynport_pool" );
    
    if ( dp_pool == 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] dp_pool is null");
        masc_exiting_log_level( );
        return MAS_SNULL;
    }
    
    memset( dp_pool, 0, sizeof *dp_pool );

    err = masd_set_dynport_pool_size( dp_pool, device_instance, reaction, num );

    masc_exiting_log_level( );
    return err;
}

/* Requests "num" dynamic ports.  If "num" is greater than
 * dp_pool->ports, additional ports are created.  If "num" is less
 * than dp_pool->ports, some of the ports are destroyed.  */
int32
masd_set_dynport_pool_size( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int32 num ) 
{
    int32 err;
    int   add_ports = 0;
    
    masc_entering_log_level( "masd_set_dynport_pool_size" );
    
    if ( dp_pool == 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] dp_pool is null"); 
        masc_exiting_log_level( );
        return MAS_SNULL;
    }
    
    /* We need at least "num" ports... */
    dp_pool->ports = num;
    while ( count_dynports( dp_pool ) < dp_pool->ports )
    {
        add_ports++;
	err = request_dynport( dp_pool, device_instance, reaction );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] Failed requesting dynamic port");
            masc_exiting_log_level( );
            return err;
        }
    }

    /* but no more than MAS_DYNPORT_LIMIT_MULTIPLIER times that. */
    while ( count_dynports( dp_pool ) > dp_pool->ports * MASD_DYNPORT_LIMIT_MULTIPLIER )
    {
        add_ports--;
        err = release_dynport( dp_pool, device_instance, reaction, TRUE );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] Failed releasing dynamic port");
            masc_exiting_log_level( );
            return err;
        }
    }

    if ( add_ports > 0 )
        masc_log_message( MAS_VERBLVL_DEBUG, "Added %d ports to reach pool size %d.", add_ports, num );
    if ( add_ports < 0 )
        masc_log_message( MAS_VERBLVL_DEBUG, "Removed %d ports over pool's limit %d.", add_ports, dp_pool->ports * MASD_DYNPORT_LIMIT_MULTIPLIER );

    masc_exiting_log_level();
    return 0;
}

int32
masd_get_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int32* retval_portnum)
{
    int32 err = 0;
    
    masc_entering_log_level( "masd_get_dynport" );
    
    if ( dp_pool == NULL || dp_pool->head == NULL )
    {
        if ( dp_pool == NULL )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] dp_pool is null");
        }
        else
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] no dynamic ports in pool");
        }
        masc_exiting_log_level( );
        return MAS_SNULL;
    }
    

    *retval_portnum = -1;
    
    while ( dp_pool->head && *retval_portnum == -1)
    {
        /* pull the port off the head of the list */
        err = masd_get_port_by_name( device_instance, dp_pool->head->name, retval_portnum );
        /* if there was an error grabbing this port, delete it from
           the dynamic pool and try again. */
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] Couldn't retrieve dynamic port named %s; discarding it and trying another.", dp_pool->head->name );
            *retval_portnum = -1;
        }

        err = delete_dynport_head(dp_pool);
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "[error] failed to delete dynamic port"); 
            masc_exiting_log_level( );
            return err;         /* this is an exception */
        }
        
    }

    /* replenish the dynport pool */
    err = masd_set_dynport_pool_size( dp_pool, device_instance, reaction, dp_pool->ports );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] failed to set dynamic port pool size"); 
        masc_exiting_log_level( );
        return err;
    }

    /* If we still don't have a port num, then there wasn't a valid
     * dynamic port in the pool! */
    if ( *retval_portnum == -1 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] there are no valid dynamic ports in the pool"); 
        masc_exiting_log_level( );
        return MAS_SNOPORTS;
    }

    masc_exiting_log_level( );
    return 0;
}

int32
masd_cleanup_dynport_pool( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction )
{
    int32 err = 0;
    int errs = 0;

    masc_entering_log_level( "masd_cleanup_dynport_pool" );
    
    if ( dp_pool == 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] dp_pool is null");
        masc_exiting_log_level( );
        return MAS_SNULL;
    }
    
    while ( dp_pool->head )
    {
        /* if an error occurs, don't bail right away. */
        err = release_dynport( dp_pool, device_instance, reaction, FALSE );
        if ( err < 0 )
        {
            masc_log_message( MAS_VERBLVL_WARNING, "[warning] couldn't release a dynamic port");
            errs++;
        }
    }
    
    memset( dp_pool, 0, sizeof *dp_pool );

    masc_exiting_log_level( );
        
    /* one or more errors occurred during cleanup */
    if ( errs ) return MAS_SCLEANUPFAIL;
    
    return 0;
}

int32
masd_recycle_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int32 portnum )
{
    int32 err;
    struct masd_dynport *dp;
    void *predicate;
    
    masc_entering_log_level( "masd_recycle_dynport" );
    
    if ( dp_pool == 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "[error] dp_pool is null");
        masc_exiting_log_level( );
        return MAS_SNULL;
    }

    /* Only recycle the port if we have less than
       MASD_DYNPORT_LIMIT_MULTIPLIER times the requested number of
       ports in the pool.  If we have too many ports, just junk it.*/
    if ( count_dynports( dp_pool ) > dp_pool->ports * MASD_DYNPORT_LIMIT_MULTIPLIER )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "Pool is full, not recycling port %d", portnum );
        goto killport;
    }
    
    /* clear the port's type and cmatrix -- if either fails, just
     * screw it.  delete the port.*/
    err = masd_set_port_type( portnum, 0 );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "Couldn't clear port type, not recycling port %d", portnum );
        goto killport;
    }

    err = masd_set_port_cmatrix( portnum, NULL );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "Couldn't clear port characteristic matrix, not recycling port %d", portnum );
        goto killport;
    }

    /* create a new dynamic port, put it in the pool, and name it. */
    dp = alloc_and_name_dp( dp_pool );
    if ( dp == NULL )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "Couldn't add port to pool, not recycling port %d", portnum );
        goto killport;
    }

    /* set the actual port's name to the new dynport name */
    err = masd_set_port_name( portnum, dp->name );
    if ( err < 0 )
    {
        /* If this fails, we gotta remove the dynamic port entry too.
         * We know it's the tail. */
        dp->prev->next = NULL;
        masc_rtfree( dp );
        masc_log_message( MAS_VERBLVL_ERROR, "[error] failed to set name of port %d", portnum);
        goto killport;
    }

    /* Schedule the mas_asm_cleanup_port action.  This removes
       connections, events, and the dc. */
    predicate = masc_rtalloc(sizeof portnum);
    if ( predicate == 0 )
    {
        masc_exiting_log_level();
        return MAS_SMEM;
    }

    memcpy(predicate, &portnum, sizeof portnum);
    
    masc_log_message( MAS_VERBLVL_DEBUG, "Recycling port %d.", portnum );
    err = masd_reaction_queue_action_simple( reaction, MAS_ASM_INSTANCE,  "mas_asm_cleanup_port", predicate, sizeof (int32));
    masc_exiting_log_level( );
    return err;
    /* stop here */
    
 killport:
    err = schedule_destroy_port( device_instance, reaction, portnum );
    masc_exiting_log_level( );
    return err;
}


/* LOCAL FUNCTIONS **************************************************/

int32
request_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction )
{
    int32  err;
    struct masd_dynport* dp;
    char*  predicate;
    
    dp = alloc_and_name_dp( dp_pool );
    if (dp == 0) return MAS_SMEM;
    
    /*
     *  need to pack stuff into action struct here for asm_make_port
     *
     *  doing it manually, now... this'll change, trust me.
     *
     */
    predicate = masc_rtalloc(sizeof(int32) + MASD_DYNPORT_PREFIX_LEN + 6);
    if ( predicate == 0 )
        return MAS_SMEM;
    
    memcpy(predicate, &device_instance, sizeof device_instance);
    strcpy(predicate + sizeof device_instance, dp->name);
    
    err = masd_reaction_queue_action_simple( reaction, MAS_ASM_INSTANCE, "mas_asm_make_port", predicate, (sizeof device_instance) + MASD_DYNPORT_PREFIX_LEN + 6);
    if ( err < 0 )
        return err;
    
    return 0;
}

/* assumes dp_pool is non-null */
struct masd_dynport*
alloc_and_name_dp( struct masd_dynport_pool* dp_pool )
{
    struct masd_dynport* dp;
    int32 err;

    dp = new_dynport();
    if (dp == NULL)
        return NULL;
    
    strcpy(dp->name, MASD_DYNPORT_PREFIX);
    
    /* create a unique string */
    dp_pool->ctr++;
    dp->name[0+MASD_DYNPORT_PREFIX_LEN] =  (0x0000F & dp_pool->ctr)        + 65;
    dp->name[1+MASD_DYNPORT_PREFIX_LEN] = ((0x000F0 & dp_pool->ctr) >> 4)  + 65;
    dp->name[2+MASD_DYNPORT_PREFIX_LEN] = ((0x00F00 & dp_pool->ctr) >> 8)  + 65;
    dp->name[3+MASD_DYNPORT_PREFIX_LEN] = ((0x0F000 & dp_pool->ctr) >> 12) + 65;
    dp->name[4+MASD_DYNPORT_PREFIX_LEN] = ((0xF0000 & dp_pool->ctr) >> 16) + 65;
    dp->name[5+MASD_DYNPORT_PREFIX_LEN] = 0;

    err = append_dynport( dp_pool, dp );
    if ( err < 0 ) return NULL;
    
    return dp;
}

/* Frees the memory allocated for the head of the dynamic port queue
   and queues the assembler action to destroy the port. */
int32
release_dynport( struct masd_dynport_pool* dp_pool, int32 device_instance, int32 reaction, int destroy )
{
    struct masd_dynport *dp;
    int32 err, err2;
    int32 portnum;
    
    if ( dp_pool == NULL || dp_pool->head == NULL )
        return MAS_SNULL;

    dp = dp_pool->head;
    
    /* pry it off the head of the list */
    /* if error, we still pull it out of the list, but we can't ask
       the assembler to destroy it! */
    err2 = masd_get_port_by_name( device_instance, dp->name, &portnum );
    err = delete_dynport_head(dp_pool);
    if ( err < 0 ) return err;
    if ( err2 < 0 )
        return MAS_SPORT;

    if ( destroy )
    {
        err = schedule_destroy_port( device_instance, reaction, portnum );
        if ( err < 0 ) return err;
    }
    
    return 0;
}

int32
schedule_destroy_port( int32 di, int32 reaction, int32 portnum )
{
    void *predicate;
    
    predicate = masc_rtalloc(sizeof portnum);
    if ( predicate == 0 ) return MAS_SMEM;

    memcpy(predicate, &portnum, sizeof portnum);
    
    return masd_reaction_queue_action_simple( reaction, MAS_ASM_INSTANCE,  "mas_asm_destroy_port", predicate, sizeof (int32));
}

/*************************************************************************
 * LOCAL LINKED LIST MANAGEMENT
 *************************************************************************/

struct masd_dynport*
new_dynport( void )
{
    struct masd_dynport* node;

    if ( (node = masc_rtalloc(sizeof (struct masd_dynport))) == 0)
	return 0;

    memset(node, 0, sizeof (struct masd_dynport));

    return node;
}

int32
delete_dynport_head( struct masd_dynport_pool* dp_pool )
{
    struct masd_dynport* dp;
    
    if ( dp_pool == NULL )
        return MAS_SNULL;

    dp = dp_pool->head;
    
    if ( dp == NULL )
        return 0;

    if ( dp->next != NULL )
        dp->next->prev = NULL;

    dp_pool->head = dp->next;
    masc_rtfree(dp);

    return 0;
}

int32
append_dynport(struct masd_dynport_pool* dp_pool, struct masd_dynport* node)
{
    struct masd_dynport* tail;

    if ( dp_pool == NULL )
        return MAS_SNULL;

    tail = dp_pool->head;
    
    if ( node == NULL )
        return MAS_SNULL;

    /* special case: is this the first one? */
    if ( tail == NULL )
    {
        dp_pool->head = node;
        return 0;
    }
    
    while( tail->next != NULL ) tail = tail->next;

    tail->next = node;
    node->prev = tail;

    return 0;
}

int32
count_dynports( struct masd_dynport_pool* dp_pool )
{
    int32 i = 0;
    struct masd_dynport* dp;

    for ( dp = dp_pool->head; dp != NULL; dp = dp->next )
        i++;

    return i;
}
