/*
 * 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 <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <math.h>
#include "mas/mas_dpi.h"
#include "anx_internal.h"

/*************************************************************************
 * ACTIONS
 *************************************************************************/

char *res_state_name[] = { "inactive", "inactive pending", "active pending", "active", "" };

static int32 change_res_state( struct anx_state *state, enum res_state res_state );
static int32 change_res_timeout( struct anx_state *state, int32 res_state_to_s );
static void register_activity( struct anx_state *state );

/* standard actions ****************************************************/
int32 mas_dev_init_instance( int32 , void* );
int32 mas_dev_show_state( int32 , void* );
int32 mas_dev_configure_port( int32, void* );

/* device specific actions *********************************************/
int32 mas_anx_playback_start           ( int32 , void* );
int32 mas_anx_playback_stop            ( int32 , void* );
int32 mas_anx_playback_pause           ( int32 , void* );
int32 mas_anx_playback_poll            ( int32 , void* );
int32 mas_anx_record_start             ( int32 , void* );
int32 mas_anx_record_stop              ( int32 , void* );
int32 mas_anx_record_pause             ( int32 , void* );
int32 mas_anx_record_poll              ( int32 , void* );

int32
mas_dev_init_library( void )
{
    return pdanx_init_library();
}

int32
mas_dev_exit_library( void )
{
    return pdanx_exit_library();
}

int32
mas_dev_init_instance( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32              err;
    
    /* Allocate state holder and cast it so we can work on it */
    state       = MAS_NEW(state);
    if ( state == 0 )
	return mas_error(MERR_MEMORY);

    masd_set_state(device_instance, state); /* set device state */
    state->device_instance = device_instance;
    
    /* retrieve static ports */
    masd_get_port_by_name( device_instance, "audio_sink",
			   &state->audio_sink );
    masd_get_port_by_name( device_instance, "audio_source",
			   &state->audio_source );
    masd_get_port_by_name( device_instance, "reaction",
			   &state->reaction );

    masc_entering_log_level( "anx: mas_dev_init_instance" );

    /* allocate some temporary space for the sample counter */
    /* this will get blown away by the mas_set( mc_count_addx ) call */
    state->scnt = MAS_NEW( state->scnt );

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_init_instance( state, predicate );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    /*** END PLATFORM DEPENDENT ****/

    change_res_timeout( state, DEFAULT_RES_STATE_ITO_S );
    change_res_state( state, DEFAULT_RES_STATE );
    
    masc_exiting_log_level();

    return 0;
}

int32
mas_dev_exit_instance( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    
    MASD_GET_STATE(device_instance, state);

    /* TODO */

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_exit_instance( state, predicate );
    if ( err < 0 )
    {
        masc_exiting_log_level();
        return err;
    }
    /*** END PLATFORM DEPENDENT ****/

    masc_log_message(MAS_VERBLVL_INFO, "anx: [info] there were %d gaps in the audio output since initialization.", state->gaps);
    
    masc_exiting_log_level();

    return 0;
}

int32
mas_dev_terminate( int32 device_instance, void* predicate )
{
    /* NO OP */
    return 0;    
}

int32
mas_dev_configure_port( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    struct mas_data_characteristic* dc;
    int32 err;
    uint8 format;
    uint32 srate;
    uint8 resolution;
    uint8 channels;
    uint8 endian;
    int bpstc; /* bytes per sample times channels */
    int32 portnum;
    struct mas_data_characteristic* odc;

    err = masd_get_state(device_instance, (void**)&state);

    portnum = *(int32*)predicate;

    /* assembler has configured the dc */
    err = masd_get_data_characteristic( portnum, &dc );
    if ( err < 0 ) return err;
    
    masc_entering_log_level( "anx: mas_dev_configure_port" );

    /* proceed independently of portnum, check further down. */

    /* grab the format parameters */
    err = masc_scan_audio_basic_dc( dc, &format, &srate, &resolution, &channels, &endian );
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "anx: [error] interpreting dc for %s port!", (portnum == state->audio_sink)?"sink":"source");
        masc_exiting_log_level();
        return mas_error(MERR_INVALID);
    }
    if ( endian != MAS_HOST_ENDIAN_FMT )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "anx: [error] endian format is not HOST endian!");
        masc_exiting_log_level();
        return mas_error(MERR_INVALID);
    }

    /* compute bytes per sample times channels */
    bpstc = 2;
    if ( format == MAS_ULAW_FMT || format == MAS_ALAW_FMT )
        bpstc = 1;
    else if ( resolution == 8 )
        bpstc = 1;
    bpstc *= channels;

    /* set the expected (nominal) rate of the sample clock */
    if ( state->scnt )
        state->scnt->expected_rate = 1E+6 / (double)state->play_sample_rate;
    
    /* set format parameters */
    if ( portnum == state->audio_sink )
    {
        state->played_bytes = 0;
        state->sink_active = TRUE;
        
        state->play_bpstc = bpstc;
        state->play_format = format;
        state->play_sample_rate = srate;
        state->play_channels = channels;
        state->play_resolution = resolution;

        /* mas_set "clock_addx" may override this with a more accurate
           sample clock. */
        state->play_clkid = masd_mc_match_rate( state->play_sample_rate );
        if ( state->play_clkid < 0 )
            state->play_clkid = MAS_MC_SYSCLK_US;

        state->buftime_ms = DEFAULT_BUFTIME_MS;
        state->buftime_mt = state->buftime_ms * state->play_sample_rate / 1000;
    }
    else
    {
        /* configure the source */
        state->rec_bytes = 0;
        state->source_active = TRUE;
        
        state->rec_bpstc = bpstc;
        state->rec_format = format;
        state->rec_sample_rate = srate;
        state->rec_channels = channels;
        state->rec_resolution = resolution;
        state->rec_clkid = masd_mc_match_rate( state->rec_sample_rate );
        if ( state->rec_clkid < 0 )
        {
            /* preserve parentheses or you'll overflow */
            state->rec_period = (uint32) 1000000 * ( MAS_ANX_SEGLEN/( (float)state->rec_bpstc * (float)state->rec_sample_rate) );
            state->rec_clkid = MAS_MC_SYSCLK_US;
        }
        else
        {
            state->rec_period = MAS_ANX_SEGLEN / state->rec_bpstc;
            /* clkid from above */
        }
    }

    /* if the device is not capable of using differing input and
       output formats, keep them in sync */
    if ( state->cap_io_diff_fmt == FALSE )
    {
        if ( portnum == state->audio_sink )
        {
            if ( !state->source_active )
            {
                state->rec_bpstc = state->play_bpstc;
                state->rec_format = state->play_format;
                state->rec_sample_rate = state->play_sample_rate;
                state->rec_channels = state->play_channels;
                state->rec_resolution = state->play_resolution;
            }
        }
        else
        {
            if ( !state->sink_active )
            {
                state->play_bpstc = state->rec_bpstc;
                state->play_format = state->rec_format;
                state->play_sample_rate = state->rec_sample_rate;
                state->play_channels = state->rec_channels;
                state->play_resolution = state->rec_resolution;
            }
        }
    }
    
    register_activity( state );

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_configure_port( state, portnum, dc );
    /*** END PLATFORM DEPENDENT ****/

    if ( err < 0 )
        return err;

    /* If the device is not capable of using differing input and
       output formats, copy the dc to make the assembler enforce
       it. */
    if ( ! state->cap_io_diff_fmt )
    {
        /* make a copy of the dc for the other port if it isn't
         * active. */
        if ( ! state->sink_active || ! state->source_active )
        {
            odc = MAS_NEW( odc );
            masc_setup_dc( odc, dc->numkeys );
            masc_copy_dc( odc, dc );

            if ( !state->sink_active )
                masd_set_data_characteristic( state->audio_sink, odc );
            else
                masd_set_data_characteristic( state->audio_source, odc );
        }
    }
    
    /* by default, start playing now if the sink has been
     * configured */
    if ( portnum == state->audio_sink )
    {
        masd_reaction_queue_action_simple(state->reaction, device_instance, "mas_anx_playback_start", 0, 0);
    }

    /* Display device state */
    if ( portnum == state->audio_sink )
        mas_dev_show_state(device_instance, 0);
    
    masc_exiting_log_level();
    
    return 0;
}

int32
mas_dev_disconnect_port( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    struct mas_data_characteristic *dc, *odc;
    int32 portnum = *(int32*)predicate;
    int32 err;
    
    MASD_GET_STATE( device_instance, state );

    if ( portnum == state->audio_sink )
    {
        state->played_bytes = 0;
        state->sink_active = FALSE;
        
        state->play_bpstc = 0;
        state->play_format = 0;
        state->play_sample_rate = 0;
        state->play_channels = 0;
        state->play_resolution = 0;
        state->play_state = STOP_STATE;
        masd_reaction_queue_action_simple( state->reaction, device_instance, "mas_anx_playback_stop", 0, 0 );
    }
    else if (portnum == state->audio_source )
    {
        state->rec_bytes = 0;
        state->source_active = FALSE;
    
        state->rec_bpstc = 0;
        state->rec_format = 0;
        state->rec_sample_rate = 0;
        state->rec_channels = 0;
        state->rec_resolution = 0;
        state->rec_state = STOP_STATE;
        masd_reaction_queue_action_simple( state->reaction, device_instance, "mas_anx_record_stop", 0, 0 );
    }

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_disconnect_port( state, portnum );
    /*** END PLATFORM DEPENDENT ****/

    if ( err < 0 )
        return err;

    /* handle manually setting port dc's for devices that can't
       support differing and record formats.*/
    if ( ! state->cap_io_diff_fmt )
    {
        if ( portnum == state->audio_sink )
        {
            /* re-set the dc of this port to the currently configured one */
            err = masd_get_data_characteristic( state->audio_source, &dc );
            if ( err >= 0 )
            {
                odc = MAS_NEW( odc );
                masc_setup_dc( odc, dc->numkeys );
                masc_copy_dc( odc, dc );
                masd_set_data_characteristic( state->audio_sink, odc );
            }
        }
        else if ( portnum == state->audio_source )
        {
            /* re-set the dc of this port to the currently configured one. */
            err = masd_get_data_characteristic( state->audio_sink, &dc );
        
            if ( err >= 0 )
            {
                odc = MAS_NEW( odc );
                masc_setup_dc( odc, dc->numkeys );
                masc_copy_dc( odc, dc );
                masd_set_data_characteristic( state->audio_source, odc );
            }
        }
    }
    
    return err;
}

int32 
mas_anx_playback_start( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32              err;
    int32*             dataflow_port_dependency;

    masd_get_state(device_instance, (void**)&state);
    state->play_state = PLAY_STATE;
    state->rebuffer = TRUE;
    state->valid_refmark = FALSE;
    
    masc_log_message(MAS_VERBLVL_DEBUG, "anx: playback started.");

    register_activity( state );
    
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_playback_start( state );
    if ( err < 0 ) return err;
    /*** END PLATFORM DEPENDENT ****/

    /* schedule our dataflow dependency on data_sink */
    dataflow_port_dependency = masc_rtalloc( sizeof (int32) );
    *dataflow_port_dependency = state->audio_sink;
    err = masd_reaction_queue_action(state->reaction, device_instance, 
				     "mas_anx_playback_poll", 0, 0, 0, 0, 0,
				     MAS_PRIORITY_DATAFLOW, 1, 1, 
				     dataflow_port_dependency);   
    return 0;
}

int32 
mas_anx_playback_stop( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    
    masd_get_state(device_instance, (void**)&state);
    state->play_state = STOP_STATE;

    masc_log_message(MAS_VERBLVL_DEBUG, "anx: playback stopped.");
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_playback_stop( state );
    if ( err < 0 ) return err;
    /*** END PLATFORM DEPENDENT ****/

    return 0;
}

int32 
mas_anx_playback_pause( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    
    masd_get_state(device_instance, (void**)&state);
    state->play_state = PAUSE_STATE;

    masc_log_message(MAS_VERBLVL_DEBUG, "anx: playback paused.");
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_playback_pause( state );
    if ( err < 0 ) return err;
    /*** END PLATFORM DEPENDENT ****/

    return 0;
}

int32 
mas_anx_playback_poll( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    struct mas_data*         data;
    int32                    err;
    uint32 save_mt, save_bytes, discarded_bytes;

    masd_get_state(device_instance, (void**)&state);

    /* stop playing, if we need to */
    if ( state->play_state != PLAY_STATE )
    {
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
        return 0;
    }
    
    /* get data & check for zero len */
    err = masd_get_data( state->audio_sink, &data );
    if (data->length == 0) return mas_error(MERR_INVALID);

    /* handle the resource state */
    state->res_state_usectr++;
    if ( state->res_state == INACTIVE )
    {
        masc_strike_data( data );
        masc_rtfree( data );
        return 0;
    }

    register_activity( state );
    
    /* get current value of the synthesized playblack clock */
    err = masd_mc_val( state->play_clkid, &state->mcnow );
    if ( err < 0 ) return mas_error(MERR_INVALID);

    /* rebuffer on marked packets */
    if ( data->header.mark )
    {
        state->valid_refmark = FALSE;
        state->rebuffer = TRUE;
    }
    
    /* Only attempt to drop old packets if we have a valid reference
       timestamp.  If platform-dependent code never sets this, we'll
       never drop any packets.  */
    if ( state->valid_refmark )
    {
        state->mtnow = state->mtref + (state->mcnow - state->mcref);

#if 0
        masc_log_message(MAS_VERBLVL_DEBUG, "anx: mcnow %u, this packet: %u", state->mcnow, data->header.media_timestamp );
#endif
        
        /* Drop this packet if it is too old. */
        if ( ( data->header.media_timestamp - state->mtnow >= HALF_UINT32_MAX ) && !data->header.mark )
        {
            /* But, try to save part of the data in the packet... */
            save_mt = (data->header.media_timestamp + data->length / state->play_bpstc) - state->mtnow;

            masc_log_message(MAS_VERBLVL_DEBUG, "anx: data lagging by %u samples", state->mtnow - data->header.media_timestamp );
            
            if ( save_mt < HALF_UINT32_MAX )
            {
                save_bytes = save_mt * state->play_bpstc;
                discarded_bytes = data->length - save_bytes;

                /* move the save-able data to the head of the segment,
                   and adjust the timestamp & length */
                memmove( data->segment, data->segment + discarded_bytes, save_bytes );
                data->header.media_timestamp += discarded_bytes / state->play_bpstc;
                data->length = save_bytes;
                
                masc_log_message(MAS_VERBLVL_DEBUG, "anx: dropped %u samples from old packet %u", discarded_bytes/state->play_bpstc, data->header.sequence);
            }
            else
            {
                /* Otherwise, the segment is all too old, and we have
                 * to drop the whole thing.*/
                masc_log_message(MAS_VERBLVL_DEBUG, "anx: dropped all of old packet %u", data->header.sequence);
                masc_strike_data( data );
                masc_rtfree( data );
                return 0;
            }

            state->dropped++;
        }

    }
    
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_playback_poll( state, data );
    /*** END PLATFORM DEPENDENT ****/

    /* set error on sample count clock if there was an underrun */
    if ( err < 0 )
    {
        state->scnt->veto = TRUE;
        state->gapping = TRUE;
    }
    else
    {
        if ( state->gapping )
            state->gaps++;
        state->gapping = FALSE;
    }
    
    
    
    /* first, get rid of the data... we don't need it anymore */
    masc_strike_data( data );
    masc_rtfree( data );

    /* then, report the error */
    if ( err < 0 ) return err;

    return 0;
}

int32 
mas_anx_record_start( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32              err;

    masd_get_state(device_instance, (void**)&state);

    state->rec_state = START_STATE;

    masc_log_message(MAS_VERBLVL_DEBUG, "anx: record started.");

    register_activity( state );

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_record_start( state );
    /*** END PLATFORM DEPENDENT ****/
    
    /* schedule our periodic poll action */
    state->rec_clkid = state->play_clkid; /* the same clock for now */
    if ( state->rec_clkid == 0 )
    {
        /* preserve parentheses or you'll overflow */
        state->rec_period = (uint32) 1000000 * ( MAS_ANX_SEGLEN/( (float)state->rec_bpstc * (float)state->rec_sample_rate) );
    }
    else
    {
        state->rec_period = MAS_ANX_SEGLEN / state->rec_bpstc;
    }
    err = masd_reaction_queue_periodic(state->reaction, device_instance, "mas_anx_record_poll", 0, 0, MAS_PRIORITY_ASAP, state->rec_period, state->rec_clkid);

    if ( err < 0 ) return err;
    
    return 0;
}

int32 
mas_anx_record_stop( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;

    masd_get_state(device_instance, (void**)&state);
    state->rec_state = STOP_STATE;

    masc_log_message(MAS_VERBLVL_DEBUG, "anx: record stopped.");
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_record_stop( state );
    /*** END PLATFORM DEPENDENT ****/

    return 0;
}

int32 
mas_anx_record_pause( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    
    masd_get_state(device_instance, (void**)&state);
    state->rec_state = PAUSE_STATE;

    masc_log_message(MAS_VERBLVL_DEBUG, "anx: record paused.");
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_record_pause( state );
    /*** END PLATFORM DEPENDENT ****/

    return 0;
}

int32 
mas_anx_record_poll( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    struct mas_data*   data;
    double sec;
    int32 err;

    masd_get_state(device_instance, (void**)&state);

    if ( state->rec_state == PAUSE_STATE || state->rec_state == STOP_STATE )
    { 
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
        return 0;
    }

    /* handle the resource state */
    if ( state->res_state == INACTIVE || state->res_state == INACTIVE_PENDING )
        return 0;
    
    /* loop to pull multiple segments if we can */
    do
    {
        /*** PLATFORM DEPENDENT ********/
        err = pdanx_record_poll( state, &data );
        /*** END PLATFORM DEPENDENT ****/
        /* if we pulled data out, set all the header information and
           post it to the source */
        if ( data != 0 && err >= 0 )
        {
            /* mark the first packet of a new stream of data (called a
               "talkspurt" in RFC 1890) */
            if ( state->rec_state == START_STATE )
                data->header.mark = TRUE;

            data->length = MAS_ANX_SEGLEN;
            data->header.media_timestamp = state->rec_mts;
            state->rec_bytes += data->length;
            data->header.sequence = state->rec_seq++;
            state->rec_mts += data->length/state->rec_bpstc;
            /* NTP calculation is an approximation */
            sec = (double)data->header.media_timestamp / state->rec_sample_rate;
            data->header.ntp_seconds = (uint32)floor(sec);
            data->header.ntp_fraction = (uint32)(4295000000.0 * (sec - (double)data->header.ntp_seconds));
            state->res_state_usectr++;
            err = masd_post_data( state->audio_source, data );
            if ( err < 0 )
            {
                masc_log_message( MAS_VERBLVL_ERROR, "anx: could not post data to source");
            }
            
            /* We're now in the "play" state, even if we were in the start
               state before - this has to happen in-loop */
            state->rec_state = PLAY_STATE;
        }
    } while (data != 0 && err >= 0 && !state->no_loop_record);

    return err;
}

int32
mas_dev_show_state( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int                      i;
    struct mixer_channel*    mch;
    
    masd_get_state(device_instance, (void**)&state);
    
    masc_log_message(0, "*-- anx state ---------------------------------------\n");
    /* walk the channel list */
    for (i=0; *(state->mch[i].name) != 0; i++)
    {
        mch = &(state->mch[i]);
        masc_log_message(0,"  ----- mix channel %d '%s' (%s) -----------", i, mch->name, mch->is_stereo?"stereo":"mono");
        masc_log_message(0,"        volume (L,R): %0.1fdB, %0.1fdB", (float)mch->left/10.0, (float)mch->right/10.0);
        masc_log_message(0,"         port number: %d", mch->portnum);
    }

    /*** PLATFORM DEPENDENT ********/
    pdanx_show_state( state );
    /*** END PLATFORM DEPENDENT ****/
    
    return 0;
}

int32
mas_anx_poll_inactivity( int32 device_instance, void *predicate )
{
    struct anx_state*  state;
    struct mas_package pkg;
    struct mas_ntpval ntpv;
    int32 err;

    MASD_GET_STATE( device_instance, state );

    /* adjust the polling period if necessary */
    if ( state->res_state_ito_changed == TRUE )
    {
        masc_setup_package( &pkg, NULL, 0, MASC_PACKAGE_NOFREE );
        masc_pushk_uint32( &pkg, "x", state->res_state_ito_s * 1000000 );
        masc_finalize_package( &pkg );
            
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_set_event_period", pkg.contents,  pkg.size );
        masc_strike_package( &pkg );
        
        state->res_state_ito_changed = FALSE;
    }

    masc_get_systime( &ntpv );
    err = 0;
    
    /* If the state is ACTIVE_PENDING, but we haven't done anything
       since the last timeout, switch to inactive pending. */
    if ( state->res_state_usectr == 0 && ( ntpv.secs - state->res_state_last_s ) >= state->res_state_ito_s )
    {
        masc_log_message( MAS_VERBLVL_INFO, "anx: [info] no activity in the last %d seconds.", state->res_state_ito_s );
        err = change_res_state( state, INACTIVE_PENDING );
    }

    /* Stop polling for inactivity if we're inactive!  With this last,
     * it catches changes from above. */ 
    if ( state->res_state != ACTIVE_PENDING )
    {
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
    }

    /* Else: OK, we're still active */
    state->res_state_usectr = 0;
    state->res_state_last_s = ntpv.secs;
    
    return err;
}

int32
mas_get( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    int32 retport;
    int32 retval = 0;
    char* key;
    struct mas_package arg;
    struct mas_package r_package;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "gain_db", "gain_linear", "channels", "recsource",
          "mc_clocks", "buftime_ms", "mc_clkid", "res_state", "res_state_ito_s", "" };
    int i, j, n=0;
    uint8 channel;
    struct mixer_channel* mch;
    
    masd_get_state(device_instance, (void**)&state);
    mch = state->mch;
    
    /* Use the standard mas_get wrapper. */
    err = masd_get_pre( predicate, &retport, &key, &arg );
    if ( err < 0 ) return err;

    masc_setup_package( &r_package, NULL, 0, MASC_PACKAGE_NOFREE );
    
    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 0: /*list*/
        masc_push_strings( &r_package, nuggets, n );
        break;
    case 1: /*gain_db*/
        if ( arg.contents == NULL )
        {
            retval = mas_error(MERR_INVALID);
            goto done;
        }

        masc_pullk_uint8( &arg, "channel", &channel );
        if ( mch[channel].is_stereo)
        {
            masc_pushk_int16( &r_package, "left", mch[channel].left );
            masc_pushk_int16( &r_package, "right", mch[channel].right );
        }
        else
        {
            masc_pushk_int16( &r_package, "mono", mch[channel].left );
        }
        break;
    case 2: /*gain_linear*/
        if ( arg.contents == NULL )
        {
            retval = mas_error(MERR_INVALID);
            break;
        }

        masc_pullk_uint8( &arg, "channel", &channel );
        if ( mch[channel].is_stereo)
        {
            masc_pushk_int16( &r_package, "left", dbvol_to_linear(mch[channel].left) );
            masc_pushk_int16( &r_package, "right", dbvol_to_linear(mch[channel].right) );
        }
        else
        {
            masc_pushk_int16( &r_package, "mono", dbvol_to_linear(mch[channel].left) );
        }
        break;
    case 3: /*channels*/
        for (j=0; *(mch[j].name) != 0; j++)
            masc_push_string( &r_package, mch[j].name );
        break;
    case 4: /*recsource*/
        for (j=0; *(mch[j].name) != 0; j++)
        {
            if ( mch[j].recsrc )
            {
                masc_pushk_uint8( &r_package, "channel", j );
                break;
            }
        }
        if ( !mch[j].name ) masc_pushk_uint8( &r_package, "channel", 0 );
        break;
    case 5: /*mc_clocks*/
        masc_push_string( &r_package, "sample" );
        break;
    case 6: /*buftime_ms*/
        masc_push_uint32( &r_package, state->buftime_ms );
        break;
    case 7: /*mc_clkid*/
        masc_pushk_int32( &r_package, "mc_clkid", state->play_clkid );
        break;
    case 8: /*res_state*/
    {
        masc_pushk_string( &r_package, "res_state", res_state_name[state->res_state] );
        break;
    }
    case 9: /*res_state_ito_s*/
    {
        masc_pushk_int32( &r_package, "res_state_ito_s", state->res_state_ito_s );
        break;
    }
    default:
        /* no error here... need to check pdanx_get! */
        break;
    }

    /* if that didn't handle it, call the device-specific handler */
    if ( r_package.members == 0 )
    {
        /*** PLATFORM DEPENDENT ********/
        err = pdanx_get( state, key, &arg, &r_package );
        /*** END PLATFORM DEPENDENT ****/

        /* if it's still null, we've got a problem */
        if ( r_package.members == 0 )
        {
            retval = mas_error(MERR_INVALID);
            goto done;
        }
    }

 done:
    /* return an error */
    if ( retval < 0 )
        masc_pushk_int32( &r_package, "err", retval );

    masc_finalize_package( &r_package );

    /* post the response where it belongs and free the data structures
     * we abused */
    masd_get_post( state->reaction, retport, key, &arg, &r_package );

    return retval;
}

int32
mas_set( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    int32 err;
    char* key;
    struct mas_package arg;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "gain_db", "gain_linear", "recsource", "mc_clock_addx", "buftime_ms", "res_state", "res_state_ito_s", "" };
    int i, j, n=0;
    uint8 channel;
    int16 t;
    struct mixer_channel* mch;
   
    masd_get_state(device_instance, (void**)&state);
    mch = state->mch;

    /* Use the standard get_nugget wrapper. */
    err = masd_set_pre( predicate, &key, &arg );
    if ( err < 0 ) return err;

    if ( arg.contents == NULL )
        return mas_error( MERR_INVALID );

    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 0: /*gain_db*/
        masc_pullk_uint8( &arg, "channel", &channel );
        if ( mch[channel].is_stereo)
        {
            masc_pullk_int16( &arg, "left", &mch[channel].left );
            masc_pullk_int16( &arg, "right", &mch[channel].right );
        }
        else
        {
            masc_pullk_int16( &arg, "mono", &mch[channel].left );
        }
        pdanx_set_mixer_volume( state, channel );
        break;
    case 1: /*gain_linear*/
        masc_pullk_uint8( &arg, "channel", &channel );
        if ( mch[channel].is_stereo)
        {
            masc_pullk_int16( &arg, "left", &t );
            mch[channel].left = linear_to_dbvol( t );
            masc_pullk_int16( &arg, "right", &t );
            mch[channel].right = linear_to_dbvol( t );
        }
        else
        {
            masc_pullk_int16( &arg, "mono", &t );
            mch[channel].left = linear_to_dbvol( t );
        }
        pdanx_set_mixer_volume( state, channel );
        break;
    case 2: /*recsource*/
        masc_pullk_uint8( &arg, "channel", &channel );
        for (j=0; *(mch[j].name) != 0; j++)
        {
            mch[j].recsrc = FALSE;
        }
        mch[channel].recsrc = TRUE;
        pdanx_set_recording_source( state, channel );
        break;
    case 3: /*mc_clock_addx*/
    {
        /* there's only one clock.  don't check the clockname arg. */
        uint32 len;
        void* addx;
        
        if ( state->scnt != 0 )
            masc_rtfree( state->scnt );

        /* this is a little strange:

           The payload in this package is taken as the address of a
           struct mas_mc_clkval.  The address is pulled out of the
           payload and used as the address of state->scnt. */

        masc_pullk_payload(&arg, "addx", &addx, &len, TRUE );
        
        /* check to make sure it is really the size of a pointer */
        if ( len != sizeof state->scnt )
            return mas_error( MERR_INVALID );

        state->scnt = *(void**)addx;

        /* set the expected (nominal) rate of the sample clock */
        if ( state->scnt )
            state->scnt->expected_rate = 1E+6 / (double)state->play_sample_rate;

        /* estabish this clock as the playback clock */
        state->play_clkid = state->scnt->id;
        break;
    }
    case 4: /*buftime_ms*/
        masc_pull_uint32( &arg, &state->buftime_ms );
        state->buftime_mt = state->buftime_ms * state->play_sample_rate / 1000;
        state->play_buffer->fill_line = state->buftime_mt * state->play_bpstc;
        break;
    case 5: /*res_state*/
    {
        char *name;
        enum res_state i;
        
        masc_pullk_string( &arg, "res_state", &name, FALSE );
        i = masc_get_string_index( name, res_state_name, 4 );
        change_res_state( state, i );
        
        break;
    }
    case 6: /*res_state_ito_s*/
    {
        int32 tmp;
        
        masc_pullk_int32( &arg, "res_state_ito_s", &tmp );
        if ( tmp > 0 )
            change_res_timeout( state, tmp );
        
        break;
    }
    default:
        break;
    }

    /* call our platform-DEpendent handler */
    /*** PLATFORM DEPENDENT ********/
    err = pdanx_set( state, key, &arg );
    if ( err < 0 ) return err;
    /*** END PLATFORM DEPENDENT ****/
    
    /* cleanup after our mess */
    err = masd_set_post( key, &arg );

    return err;
}

int32 
mas_mc_update( int32 device_instance, void* predicate )
{
    struct anx_state*  state;
    uint32 pre_scnt_us;
    uint32 post_scnt_us;
    uint32 oldval;
    uint32 val;

    MASD_GET_STATE(device_instance, state);

    /* make sure we have a clkval structure */
    if ( state->scnt == 0 )
        return mas_error(MERR_INVALID);

    /*** ignore predicate - there's only one clock */

    /* Collect the sample counter and get a hires wallclock timestamp
     * before and after.*/

    oldval = state->scnt->val;

    masc_get_short_usec_ts( &pre_scnt_us );

    /*** PLATFORM DEPENDENT ********/
    val = pdanx_get_sample_count( state, state->scnt );
    /*** END PLATFORM DEPENDENT ****/

    masc_get_short_usec_ts( &post_scnt_us );

    if ( val == 0 )
        goto fail;

    /* Set the wallclock timestamp to the mean of the pre and post
     * timestamps.   This wraps correctly */
    state->scnt->ts_us = pre_scnt_us + ( (post_scnt_us - pre_scnt_us) >> 1 );

    /* Set error if the counter wrapped. */
    if ( oldval > state->scnt->val )
        goto fail;

    /* Last step: mark the measurement as new. */
    state->scnt->newmeas = TRUE;
    
    return 0;

 fail:
    state->scnt->veto = TRUE;

    return 0;
}

int32
change_res_state( struct anx_state *state, enum res_state res_state )
{
    int32 err;
    struct mas_ntpval ntpv;
    
    /* do nothing if the state is not different */
    if ( res_state == state->res_state )
        return 0;

    if ( res_state < 0 || res_state > 3 )
        return mas_error(MERR_INVALID);
    

    /*** PLATFORM DEPENDENT ********/
    err = pdanx_change_res_state( state, res_state );
    /*** END PLATFORM DEPENDENT ****/
    if ( err < 0 )
    {
        masc_log_message( MAS_VERBLVL_ERROR, "anx: [error] failed changing resource state to '%s'", res_state_name[res_state]);
        if ( res_state != INACTIVE )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "anx: [error] dropping state to '%s'", res_state_name[INACTIVE]);
            change_res_state( state, INACTIVE );
        }
                
        return err;
    }
    
    masc_log_message( MAS_VERBLVL_INFO, "anx: [info] changing resource state to '%s'", res_state_name[res_state] );

    state->res_state_usectr = 0;

    /* Schedule inactivity polling when entering the ACTIVE_PENDING
       state.  Any other state, we don't need it.  */
    if ( ( state->res_state == INACTIVE || state->res_state == INACTIVE_PENDING ) && res_state == ACTIVE_PENDING )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "anx: polling for inactivity every %d seconds", state->res_state_ito_s );
        masd_reaction_queue_periodic(state->reaction, state->device_instance, "mas_anx_poll_inactivity", 0, 0, MAS_PRIORITY_ASAP, state->res_state_ito_s * 1000000, MAS_MC_SYSCLK_US );
        masc_get_systime( &ntpv );
        state->res_state_last_s = ntpv.secs;
    }
    
    /* finally, change the state */
    state->res_state = res_state;
    
    return 0;
}

int32
change_res_timeout( struct anx_state *state, int32 res_state_ito_s )
{
    int32 err;
    
    /* do nothing if the state is not different */
    if ( state->res_state_ito_s == res_state_ito_s )
        return 0;

    /* change the inactivity timeout */
    state->res_state_ito_s = res_state_ito_s;
    state->res_state_ito_changed = TRUE;
    
    masc_log_message( MAS_VERBLVL_INFO, "anx: [info] changing resource inactivity timeout to %d seconds", res_state_ito_s );

    return 0;
}

/* call this function whenever activity occurs */
void
register_activity( struct anx_state *state )
{
    if ( state->res_state == INACTIVE_PENDING )
    {
        change_res_state( state, ACTIVE_PENDING );
    }
}

