/*
 * 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.
 *
 */

/* 2 OCT 2002 - rocko - verified reentrant
 * 2 OCT 2002 - rocko - verified timestamp clean
 *
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <math.h>
#include <errno.h>
#include "mas/mas_dpi.h"
#include "source_internal.h"

#define TOL 0.1

char* repeat_mode[] = { "none", "one", "all", "" };

static void stop_and_reset( struct source_state* state );
static int32 next_valid_track( struct source_state* state );
static int32 prev_valid_track( struct source_state* state );
static int32 skip_to_valid_track( struct source_state* state, int forward );
static int32 pollit( struct source_state* state );
static int32 cue_track( struct source_state* state, struct track_info* tr );
static int32 get_file_length( FILE* file );
static void set_clkid( struct source_state* state );
static int32 fill_out_track_info( struct source_state* state, struct track_info* ti );

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

int32
mas_dev_init_instance( int32 device_instance, void* predicate )
{
    struct source_state*  state;

    /* 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;
    
    masd_get_port_by_name( device_instance, "source",
			   &state->source );
    masd_get_port_by_name( device_instance, "reaction",
			   &state->reaction );

    state->pl = new_plist();
    state->preferred_clkid = -1;
    
    return sourcex_init_instance( state );
}

int32
mas_dev_configure_port( int32 device_instance, void* predicate )
{
    struct source_state*  state; 
    int32 portnum = *(int32*)predicate;
   
    MASD_GET_STATE(device_instance, state);

    if ( portnum == state->source )
        state->source_configured = TRUE;
    if ( portnum == state->sink )
    {
        masc_log_message(MAS_VERBLVL_ERROR, "source: ERROR: transform mode not yet implemented");
        return mas_error(MERR_INVALID);
    }

    return sourcex_configure_port( state, portnum );
}

int32
mas_dev_disconnect_port( int32 device_instance, void* predicate )
{
    struct source_state*  state;
    int32 portnum = *(int32*)predicate;

    MASD_GET_STATE(device_instance, state);

    if ( portnum == state->source )
        state->source_configured = FALSE;
    if ( portnum == state->sink )
        state->sink_configured = FALSE;

    state->strst = STOP_STATE;
    
    return sourcex_disconnect_port( state, portnum );
}

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

    sourcex_exit_instance( state );
    delete_plist( state->pl );
    masc_rtfree( state );

    return err;
}

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

int32
mas_dev_show_state( int32 device_instance, void* predicate )
{
    struct source_state*  state;

    MASD_GET_STATE(device_instance, state);

    return sourcex_show_state( state );
}

int32
mas_source_poll( int32 device_instance, void* predicate )
{
    struct source_state* state;
    struct mas_data* data;
    int played_this_call = FALSE;
    int32 err;
    
    MASD_GET_STATE(device_instance, state);

    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    /* Check to see if we have to change the clock id. */
    if ( state->set_clkid )
    {
        state->set_clkid = FALSE;
        set_clkid( state );
    }
    
    /* stop the periodic action if we're not supposed to be playing
       (paused or stopped) */
    if ( state->strst != PLAY_STATE )
    {
        /* Stop trying to play */
        state->polling_scheduled = FALSE;
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
        return 0;
    }

    /* this while loop ensures that we always output something during
       this action, so we don't get behind on our streamed output. */
    while ( !played_this_call )
    {
        data = 0;
        err = sourcex_get_data( state, state->ti, state->seq, &data );
        if ( data )
        {
            if ( state->mark )
            {
                data->header.mark = TRUE;
                state->mark = FALSE;
            }
            masd_post_data( state->source, data );
            played_this_call = TRUE;
            state->seq++;
        }
        
        /* We skip to the next track or stop if we're at the end of
         * the file, or we fell through from above without outputting
         * anything */
        if ( err == MAS_STREAM_END || !played_this_call )
        {
            /* we're done, queue up next track */
            err = next_valid_track( state );
            if ( err < 0 )
            {
                state->polling_scheduled = FALSE;
                masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
                return 0;
            }
            /* otherwise, loop again so we can output something this
               action */
        }
    }

    /* If we're on a new track and it requires different timing
       than the old one, stop this event and queue up the
       differently-timed action */
    if ( state->newformat )
    {
        state->newformat = FALSE;
        state->polling_scheduled = FALSE;
        pollit( state );
        masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_strike_event", NULL, 0 );
        return 0;
    }
        

    return 0;
}

int32
mas_source_play( int32 device_instance, void* predicate )
{
    struct source_state* state;
    int32 err;
    
    MASD_GET_STATE(device_instance, state);

    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    masc_log_message( MAS_VERBLVL_DEBUG, "source: play.");

    /* check to see if a file has been set.  If not, do the
       playlist.  If there's nothing in the playlist, error. */
    if ( !state->ti && state->strst == STOP_STATE )
    {
        masc_log_message( MAS_VERBLVL_DEBUG, "source: cueing next valid track in playlist");
        err = next_valid_track( state );
        if ( err < 0 ) return err;
    }

    state->strst = PLAY_STATE;

    state->newformat = FALSE;
    state->mark = TRUE; /* be sure to mark start of new "talkspurt" */

    sourcex_play( state );
    
    /* schedule the periodic poll action */
    pollit( state );
    return 0;
}

int32
mas_source_stop( int32 device_instance, void* predicate )
{
    struct source_state* state;
    
    MASD_GET_STATE(device_instance, state);
    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    state->strst = STOP_STATE; /* this'll cause the poll action to
                                * bail */

    masc_log_message( MAS_VERBLVL_DEBUG, "source: stop.");
    sourcex_stop( state );

    /* don't blow away the format parameters, since we might re-start
       the file */
    
    return 0;
}

int32
mas_source_pause( int32 device_instance, void* predicate )
{
    struct source_state* state;
    
    MASD_GET_STATE(device_instance, state);
    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    state->strst = PAUSE_STATE; /* this'll cause the poll action to
                                 * bail */

    masc_log_message( MAS_VERBLVL_DEBUG, "source: pause.");
    sourcex_pause( state );
    
    /* don't blow away the format parameters, since we'll probably resume. */
    
    return 0;
}

int32
mas_source_next_track( int32 device_instance, void* predicate )
{
    struct source_state* state;
    int32 err;
        
    MASD_GET_STATE(device_instance, state);
    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    /* Play the next track in the playlist. */
    err = next_valid_track( state );
    if ( err < 0 ) return err;

    state->strst = PLAY_STATE;

    sourcex_next_track( state );
    
    /* schedule the periodic poll action */
    pollit( state );
    return 0;
}

int32
mas_source_prev_track( int32 device_instance, void* predicate )
{
    struct source_state* state;
    int32 err;
    
    MASD_GET_STATE(device_instance, state);
    if ( !state->source_configured ) return mas_error( MERR_INVALID );

    /* Play the previous track in the playlist. */
    err = prev_valid_track( state );
    if ( err < 0 ) return err;

    state->strst = PLAY_STATE;

    sourcex_prev_track( state );
    
    /* schedule the periodic poll action */
    pollit( state );
    return 0;
}

int32
mas_get( int32 device_instance, void* predicate )
{
    struct source_state*  state;
    int32 err;
    int32 retport;
    char* key;
    struct mas_package arg;
    struct mas_package r_package;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "playlist", "ctrack", "trklen", "repeat", "mc_clkid", "" };
    int i, n=0;
    struct track_info* ti;
    int16 trk;
    
    MASD_GET_STATE(device_instance, state);

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

    /* construct our response */
    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: /*playlist*/
        masc_pushk_int16( &r_package, "pos", state->pl->c );
        for ( ti = state->pl->head->next; ti; ti = ti->next )
            masc_push_string( &r_package, ti->filename );
        break;
    case 2: /*ctrack*/
        if ( state->ti )
        {
            masc_pushk_string( &r_package, "ctrack", state->ti->filename );
            masc_pushk_int16( &r_package, "pos", state->pl->c );
        }
        else
        {
            masc_pushk_string( &r_package, "ctrack", "");
            masc_pushk_int16( &r_package, "pos", 0 );
        }
        break;
    case 3: /*trklen*/
        if ( arg.contents == 0 )
        {
            masc_pushk_int32( &r_package, "err", mas_error(MERR_INVALID) );
            break;
        }

        masc_pull_int16( &arg, &trk );
        ti = get_track( state->pl, trk );
        masc_pushk_float( &r_package, "trklen", ti->length_sec );
        break;
    case 4: /*repeat*/
        masc_pushk_string( &r_package, "mode", repeat_mode[state->pl->repmode] );
        if ( state->pl->repmode == REPEAT_1 )
            masc_pushk_int16( &r_package, "pos", state->pl->c );
        break;
    case 5: /*mc_clkid*/
        masc_pushk_int32( &r_package, "clkid", state->use_clkid );
        break;
    default:
        break;
    }

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

    return err;
}


int32
mas_set( int32 device_instance, void* predicate )
{
    struct source_state*  state;
    int32 err;
    char* key;
    struct mas_package arg;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "playlist", "ctrack", "repeat", "mc_clkid", "" };
    int i, n=0;
    struct track_info* ti;

    MASD_GET_STATE(device_instance, state);

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

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

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 0: /*playlist*/
        if ( state->ti )
        {
            /* SPECIAL CASE: Playlist is pulled out from underneath
             * the currently playing track.  We have to copy the track
             * info.
             *
             * ti is the entry in the track list - we ALSO need
             * to null out ti's pointers, so they don't get freed in
             * clear_plist() below. BUT, we need to reconnect the
             * linked list! */
            ti = state->ti;
            state->ti = MAS_NEW( state->ti );
            if ( ti->next )
                ti->next->prev = ti->prev;
            if ( ti->prev )
                ti->prev->next = ti->next;
            ti->prev = ti->next = 0;
            memcpy( state->ti, ti, sizeof *ti );
            memset( ti, 0, sizeof *ti );
        }
        
        clear_plist( state->pl );
        masc_pullk_int16( &arg, "pos", &state->pl->c );
        masc_log_message( MAS_VERBLVL_DEBUG, "source: mas_set(playlist) %d files.", arg.members - 1 );
        for ( n=0; n < (arg.members - 1); n++ )
        {
            ti = MAS_NEW( ti );
            masc_pull_string( &arg, &ti->filename, TRUE );
            
            err = fill_out_track_info( state, ti );
            if ( err < 0 )
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "source: mas_set(playlist) track %d: '%s' is invalid", n, ti->filename );
                ti->invalid = TRUE;
            }
            
            append_track( state->pl, ti );
        }
        break;
    case 1: /*ctrack*/
        masc_pullk_int16( &arg, "pos", &state->pl->c );
        ti = set_track( state->pl, state->pl->c );
        if ( ti == 0 ) return mas_error( MERR_INVALID );
        err = cue_track( state, ti );
        if ( err < 0 ) return err;
        masc_log_message( MAS_VERBLVL_DEBUG, "source: mas_set(ctrack) %d.", state->pl->c );
        break;
    case 2: /*repeat*/
    {
        char* repmode_str;
        int16 ctrack = -1;
        int repmode_cnt = 0;
        int repmode = 0;
        
        masc_pullk_string( &arg, "mode", &repmode_str, FALSE );
        if ( masc_test_key( &arg, "pos" ) == 0 )
            masc_pullk_int16( &arg, "pos", &ctrack );

        while ( *repeat_mode[repmode_cnt] != 0 ) repmode_cnt++;
        repmode = masc_get_string_index(repmode_str, repeat_mode, repmode_cnt);

        /* If we're going to repeat this one track */
        if ( repmode == REPEAT_1 )
        {
            /* error if we didn't say which track */
            if ( ctrack == -1 )
                return mas_error(MERR_INVALID);

            /* Switch to the right track if we said one other than the
               currently playing one. */
            if ( ctrack != state->pl->c )
            {
                state->pl->c = ctrack;
                ti = set_track( state->pl, state->pl->c );
                if ( ti == 0 ) return mas_error( MERR_INVALID );
                err = cue_track( state, ti );
                if ( err < 0 ) return err;
            }
        }

        state->pl->repmode = repmode;
        masc_log_message( MAS_VERBLVL_DEBUG, "source: mas_set(repmode) %s.", repmode_str );
        break;
    }
    case 3: /*mc_clkid*/
        masc_pull_int32( &arg, &state->use_clkid );
        state->preferred_clkid = state->use_clkid;
        state->set_clkid = TRUE;
        if ( state->ti )
            state->ti->period_clkid = state->use_clkid;
        masc_log_message(MAS_VERBLVL_DEBUG, "source: set mc_clkid to %d", state->use_clkid );
        break;
    default:
        break;
    }

    /* cleanup after our mess */
    err = masd_set_post( key, &arg );
    
    return err;
}

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

int32
cue_track( struct source_state* state, struct track_info* ti )
{
    struct track_info* prev_ti; /*previous track info*/
    struct mas_data_characteristic* dc;
    int32 err;

    prev_ti = state->ti;

    if ( state->ti )
    {
        /* close a prior file */
        if ( state->ti->file )
        {
            fclose( state->ti->file );
            state->ti->file = 0;
        }
    }
    
    /* check to see if we have to fill out the track info now */
    if ( ti->period == 0 )
    {
        if ( ti->filename == 0 )
            return mas_error(MERR_INVALID);

        err = fill_out_track_info( state, ti );
        if ( err < 0 )
            return err;
    }
    
    /* change to the new track */
    state->ti = ti;
    
    /* If the track is file-mode, open it for reading, unless it's
     * open already. */
    if ( ti->filename && !ti->file )
    {
        ti->file = fopen( ti->filename, "r" );
        if ( ti->file == 0 )
        {
            masc_rtfree( ti->filename );
            ti->filename = 0;
            return mas_error(MERR_FILE_CANNOT_OPEN) | mas_make_serror(errno);
        }
    }

    /* test to see if we have to reconfigure the output */
    if ( sourcex_format_diff( state, ti, prev_ti ) )
    {
        state->strst = STOP_STATE; /* playback is stopped */
        state->newformat = TRUE;

        /* override the new clock setting if the old sampling rate is
           the same - this lets someone else tell us what clock to use. */
        if ( state->use_clkid )
        {
            ti->period_clkid = state->use_clkid;
        }

        dc = sourcex_get_track_dc( state, ti );
        if ( ! dc )
            return mas_error(MERR_INVALID);
        
        /* set the source to the dc */

        /**** FIX THIS! */
        /* masd_set_data_characteristic( state->source, dc ); */
    }

    /* mark the first packet as the start of a new sequence */
    state->mark = TRUE;

    masc_log_message( MAS_VERBLVL_DEBUG, "source: cue track '%s'", ti->filename );
    err = sourcex_cue_track( state, ti );
    if ( err < 0 )
        return err;
    
    return state->newformat;
}

int32
get_file_length( FILE* file )
{
    long pos;
    long length;

    pos = ftell( file );

    if ( fseek( file, 0, SEEK_END ) < 0 )
        return mas_error(MERR_IO);
    
    length = ftell( file );
    if ( length < 0 )
        return mas_error(MERR_IO);
    
    if ( fseek( file, pos, SEEK_SET ) < 0 )
        return mas_error(MERR_IO);

    return length;
}

int32
fill_out_track_info( struct source_state* state, struct track_info* ti )
{
    int32 err;
    
    /* Do we have to open the file? */
    if ( ti->file == 0 )
    {
        ti->file = fopen( ti->filename, "r" );
        if ( ti->file == 0 )
            return mas_error(MERR_FILE_CANNOT_OPEN);
    }

    ti->length = get_file_length( ti->file );
    
    err = sourcex_fill_out_track_info( state, ti );
    fclose( ti->file );
    ti->file = 0;
    
    if ( err < 0 ) return err;

    return 0;
}

int32
skip_to_valid_track( struct source_state* state, int forward )
{
    struct track_info* ti = 0;
    int reached_end = FALSE;
    int32 err;

    while ( ( ti == 0 ) && !reached_end )
    {
        /* Skip either forward or backward in list */
        if ( forward )
        {
            ti = advance_track( state->pl );
        }
        else
        {
            ti = back_track( state->pl );
        }

        /* Was there a "next" track? */
        if ( ti == 0 )
        {
            /* No, we're done with the playlist. */
            masc_log_message( MAS_VERBLVL_DEBUG, "source: finished playlist.");
            reached_end = TRUE;
        }
        else /* Yes, there is! */
        {
            /* But, don't play it if it's invalid.  */
            if ( ti->invalid )
            {
                masc_log_message( MAS_VERBLVL_DEBUG, "source: track contains invalid data: %s", ti->filename );
                ti = 0;
            }
            else /* track is valid */
            {
                /* Try to cue the track. */
                err = cue_track( state, ti );
                if ( err < 0 )
                {
                    masc_log_message( MAS_VERBLVL_DEBUG, "source: couldn't cue track: %s", ti->filename );
                    ti = 0;
                }
                
            } /* end test: track invalid */
        } /* end test: track */
    } /* while: scan through playlist */
    
    /* If ti is still null, then there's no next track. */
    if ( ti == 0 )
    {
        stop_and_reset( state );
        return mas_error( MERR_INVALID );
    }

    return 0;
}

int32
prev_valid_track( struct source_state* state )
{
    return skip_to_valid_track( state, FALSE );
}

int32
next_valid_track( struct source_state* state )
{
    return skip_to_valid_track( state, TRUE );
}

void
stop_and_reset( struct source_state* state )
{
    state->strst = STOP_STATE;
    if ( state->ti )
    {
        if ( state->ti->file )
        {
            fclose(state->ti->file);
            state->ti->file = 0;
        }
    }
    
    state->ti = 0;
    state->pl->c = 0;
}

int32
pollit( struct source_state* state )
{
    if ( state->polling_scheduled )
        return mas_error( MERR_INVALID );

    state->polling_scheduled = TRUE;
    return masd_reaction_queue_periodic( state->reaction, state->device_instance, "mas_source_poll", 0, 0, MAS_PRIORITY_ASAP, state->ti->period, state->ti->period_clkid );
}

void
set_clkid( struct source_state* state )
{
    struct mas_package p;
        
    masc_setup_package( &p, NULL, 0, MASC_PACKAGE_NOFREE );
    masc_push_int32( &p, state->use_clkid );
    masc_finalize_package( &p );

    masd_reaction_queue_action_simple( state->reaction, MAS_SCH_INSTANCE, "mas_sch_set_event_clkid", p.contents, p.size );
    masc_strike_package( &p );
}
