/*
 * 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.
 *
 */
/*
 * Copyright (c) 2000, 2001 by Shiman Associates Inc. and Sun
 * Microsystems, 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.
 *
 * Except as contained in this notice, the names of the authors or
 * copyright holders shall not be used in advertising or otherwise to
 * promote the sale, use or other dealings in this Software without
 * prior written authorization from the authors or copyright holders,
 * as applicable.
 *
 * All trademarks and registered trademarks mentioned herein are the
 * property of their respective owners. No right, title or interest in
 * or to any trademark, service mark, logo or trade name of the
 * authors or copyright holders or their licensors is granted.
 *
 */

/*
 * $Id: mas_datalog_device.c,v 1.3 2003/10/07 14:12:06 silvio Exp $
 */

/* 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 "mas/mas_dpi.h"
#include "profile.h"

#define HALF_UINT32_MAX 2147483647U

enum datalog_mode
{
    QUIET_MODE = 0,
    HEADER_MODE = 1,
    HEX_MODE = 2,
    HEADER_HEX_MODE = 3,
    RAW_MODE = 4
};

struct datalog_state
{
    int32 reaction;
    int32 sink;
    int32 source;
    double sum;
    int32 count;
    
    int source_configured;
    enum datalog_mode dm;
    uint32 base_ts;

    /* for testing */
    int bpstc;
    int valid_bpstc;
    uint32 expected_mt;
    uint32 expected_seq;
    int32 prev_packet_length;
    
    char* filename;
    FILE* fp;
};

static char* _mode[] = { "quiet", "header", "hex", "header hex", "raw", "" };

/**** local prototypes ****************************************************/

static void show_hex(FILE *fp, char *buffer, int len);
static void log_mode( struct datalog_state* state );
static int setup_test_packets( struct datalog_state *state, struct mas_data_characteristic *dc );
static int test_packet_timestamp( struct datalog_state *state, struct mas_data *data );

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


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

/* device specific actions *********************************************/
int32 mas_datalog_go( int32 , void* );


int32
mas_dev_init_instance( int32 device_instance, void* predicate )
{
    struct datalog_state*  state;
    int32*                  dataflow_port_dependency;
    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 */
    
    masd_get_port_by_name( device_instance, "sink", &state->sink );
    masd_get_port_by_name( device_instance, "source", &state->source );
    masd_get_port_by_name( device_instance, "reaction", &state->reaction );

    state->dm = HEADER_HEX_MODE;
    log_mode( state );
    
    masc_get_short_usec_ts( &state->base_ts );
    
    /* schedule our dataflow dependency on sink */
    dataflow_port_dependency = masc_rtalloc( sizeof (int32) );
    *dataflow_port_dependency = state->sink;
    err = masd_reaction_queue_action(state->reaction, device_instance, 
				     "mas_datalog_go", 0, 0, 0, 0, 0,
				     MAS_PRIORITY_DATAFLOW, 1, 1, 
				     dataflow_port_dependency);
    if ( err < 0 ) return err;

    return 0;
}

int32
mas_dev_configure_port( int32 device_instance, void* predicate )
{
    struct datalog_state*  state;
    struct mas_data_characteristic* dc;
    int32 portnum = *(int32*)predicate;
    int32 err;

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

    err = masd_get_data_characteristic( portnum, &dc );
    if ( err < 0 ) return err;

    if ( portnum == state->sink )
    {
        masc_entering_log_level( "datalog: mas_dev_configure_port, configuring sink");
        masc_print_dc( dc );

        if ( setup_test_packets( state, dc ) )
        {
            masc_log_message( MAS_VERBLVL_INFO, "datalog: I understand the dc.  Enabling packet timestamp testing.");
            masc_log_message( MAS_VERBLVL_INFO, "datalog: Format has %d bytes per sample times channels.", state->bpstc );
        }
        else
        {
            masc_log_message( MAS_VERBLVL_INFO, "datalog: I don't understand the dc. Disabling packet timestamp testing.");
        }

        masc_exiting_log_level();
    }
    else if ( portnum == state->source )
    {
        masc_entering_log_level( "datalog: mas_dev_configure_port, configuring source");
        masc_log_message( MAS_VERBLVL_DEBUG, "datalog: operating in inline 'thru' mode.");
        masc_exiting_log_level();
        state->source_configured = TRUE;
    }
    
    return 0;
}

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

    MASD_GET_STATE( device_instance, state );
    
    if ( portnum == state->source )
        state->source_configured = FALSE;
    
    return 0;
}

int32
mas_dev_exit_instance( int32 device_instance, void* predicate )
{
    struct datalog_state*  state;

    MASD_GET_STATE( device_instance, state );

    /* close files */
    if ( state->fp )
        fclose( state->fp );

    /* free allocated memory */
    if ( state->filename )
        masc_rtfree( state->filename );
    
    masc_rtfree( state );
    
    return 0;
}

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

int32
mas_dev_show_state( int32 device_instance, void* predicate )
{
    return mas_error(MERR_NOTIMP);
}

int32
mas_datalog_go( int32 device_instance, void* predicate )
{
    struct datalog_state* state;
    struct mas_data*       data;
    struct mas_ntpval      ntptime;
    double                 doubletime;
    uint32                 ts;
    double                 rate = 0.0;
    int32 err;
    int32 wrote;

    MASD_GET_STATE(device_instance, state);
    
    masd_get_data( state->sink, &data );
    masc_get_short_usec_ts( &ts );
    if ( state->count == 0 )
    {
        state->base_ts = ts;
    }
    else
    {
        rate = 1E+6 * state->sum / (ts - state->base_ts);
    }

    /* log packet headers */
    if ( state->dm == HEADER_MODE || state->dm == HEADER_HEX_MODE )
    {
        /* convert the NTP time */
        ntptime.secs = data->header.ntp_seconds; 
        ntptime.frac = data->header.ntp_fraction;
        masc_ntp_to_double(&ntptime, &doubletime);

        /* logging to a file */
        if ( state->fp )
        {
            fprintf( state->fp, "\n");
            fprintf( state->fp, "datalog: received at % 6.3f ms   average data rate %0.3fkbytes/s\n", (ts - state->base_ts)/1000.0, rate);
            fprintf( state->fp, "\n");
            fprintf( state->fp, "M typ   sequence      media             NTP    length     alloc\n");
            fprintf( state->fp, "%c %03d %10u %10u % 15.4f % 9d % 9d\n", (data->header.mark)?'y':'n', data->header.type, data->header.sequence, data->header.media_timestamp, doubletime, data->length, data->allocated_length );
            fprintf( state->fp, "\n");
        }
        else
        {
            /* nope, logging to the MAS log */
            masc_log_message(0, "");
            masc_log_message(0, "datalog: received at % 6.3f ms   average data rate %0.3fkbytes/s", (ts - state->base_ts)/1000.0, rate);
            masc_log_message(0, "");
            masc_log_message(0, "M typ   sequence      media             NTP    length     alloc");
            masc_log_message(0, "%c %03d %10u %10u % 15.4f % 9d % 9d", (data->header.mark)?'y':'n', data->header.type, data->header.sequence, data->header.media_timestamp, doubletime, data->length, data->allocated_length );
            masc_log_message(0, "");

            if( state->valid_bpstc )
                test_packet_timestamp( state, data );
        }
    }

    /* log hexdumps of data segments */
    if ( state->dm == HEX_MODE || state->dm == HEADER_HEX_MODE )
    {
        /* to a file? */
        if ( state->fp )
            show_hex(state->fp, data->segment, data->length);
        else
            show_hex(NULL, data->segment, data->length);
    }

    /* log raw data segment data */
    if ( state->dm == RAW_MODE )
    {
        /* this only works if there's a file set! */
        if ( ! state->fp )
        {
            masc_log_message( MAS_VERBLVL_ERROR, "datalog: No file set.  Logging raw packets to the MAS log file disabled.");
        }
        else
        {
            wrote = 0;
            while ( wrote < data->length )
            {
                err = fwrite( data->segment+wrote, 1, data->length-wrote, state->fp );
                wrote += err;
            }
        }
    }
    
    state->count++;
    state->sum += data->length;
    
    /* Pass on the data to the source, if operating inline.
       Otherwise, destroy it. */
    if ( state->source_configured )
        masd_post_data( state->source, data );
    else
    {
        masc_strike_data( data );
        masc_rtfree( data );
    }

    return 0;
}

int32
mas_get( int32 device_instance, void* predicate )
{
    struct datalog_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", "mode", "filename", "" };
    int i, n=0;
    
    masd_get_state(device_instance, (void**)&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: /*mode*/
        masc_pushk_string( &r_package, "mode", _mode[state->dm] );
        break;
    case 2: /*filename*/
        if ( state->filename )
            masc_pushk_string( &r_package, "filename", state->filename );
        else
            masc_pushk_string( &r_package, "filename", "" );
        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 datalog_state*  state;
    int32 err;
    char* key;
    struct mas_package arg;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "mode", "filename", "" };
    int i, n=0;

    masd_get_state(device_instance, (void**)&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: /*mode*/
    {
        char *tstr;
        int mcnt=0, j;

        /* count the defined modes */
        while ( *_mode[mcnt] != 0 ) mcnt++;
        
        masc_pull_string( &arg, &tstr, FALSE );
        j = masc_get_string_index(tstr, _mode, mcnt);

        if ( j >= 0 && j < mcnt)
        {
            state->dm = j;
            log_mode( state );
        }
        else
        {
            masc_log_message(0, "datalog: mas_set(mode) invalid mode %s", tstr );
            return mas_error(MERR_INVALID);
        }
    }
    break;
    case 1: /*filename*/
        if ( state->fp != NULL )
        {
            masc_log_message(0, "datalog: mas_set(filename) closing prior open file %s", state->filename );
            if ( state->filename )
                masc_rtfree( state->filename );
            fclose( state->fp );
        }
        masc_pull_string( &arg, &state->filename, TRUE );
        state->fp = fopen( state->filename, "w" );
        if ( state->fp == NULL )
        {
            masc_log_message(0, "datalog: mas_set(filename) invalid file %s", state->filename );
            masc_rtfree( state->filename );
            return mas_error(MERR_FILE_CANNOT_OPEN);
        }
        
        break;
    default:
        break;
    }

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

    return err;
}

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

static void
show_hex(FILE* fp, char *buffer, int len)
{
#define STRINGLEN 512
    int i, j, k;
    char line[STRINGLEN];
    char t[STRINGLEN];

    for (i=0; i<len; i+=16)
    {
	sprintf(line, "%04X: ", i);
	for (j=i; (j<(i+16))&&(j<len); j++)
	{
	    sprintf(t, "%02X ", (unsigned char) *(buffer++));
            strncat(line, t, STRINGLEN - strlen(line) - strlen(t) - 1);
	}
	buffer-=j-i;
        strncat(line, " ", STRINGLEN - strlen(line) - 2);
	for (k=j; k<(i+16); k++) strncat(line, "   ", STRINGLEN - strlen(line) - 4);
	for (j=i; (j<(i+16))&&(j<len); j++)
	{
	    if ((*buffer)>31 && (*buffer)!=37 && (*buffer)<127)
            {
		sprintf(t, "%c", *(buffer++));
                strncat(line, t, STRINGLEN - strlen(line) - strlen(t) - 1);
            }
            else
	    {
		strncat(line, ".", STRINGLEN - strlen(line) - 2);
		buffer++;
	    }
	}

        if ( fp )
        {
            fprintf( fp, line );
            fprintf( fp, "\n" );
        }
        else
        {
            masc_log_message(0, line);
        }
    }

    if ( fp )
        fprintf( fp, "\n");
    else
        masc_log_message(0, "");
}

void
log_mode( struct datalog_state* state )
{
    switch (state->dm)
    {
    case QUIET_MODE:
        masc_log_message( 0, "datalog: Quiet mode; not logging." );
        break;
    case HEADER_MODE:
        masc_log_message( 0, "datalog: Logging data segment headers." );
        break;
    case HEX_MODE:
        masc_log_message( 0, "datalog: Logging hexdumps of data segments." );
        break;
    case HEADER_HEX_MODE:
        masc_log_message( 0, "datalog: Logging headers and hexdumps of data segments." );
        break;
    case RAW_MODE:
        masc_log_message( 0, "datalog: Logging raw data segments only." );
        break;
    default:
        break;
    }
}

/* Returns TRUE if we "get" the dc.  Otherwise false. */
int
setup_test_packets( struct datalog_state *state, struct mas_data_characteristic *dc )
{
    uint8 format, resolution, channels, endian;
    uint32 srate;
    int32 err;

    state->valid_bpstc = FALSE;
    
    /* only get "audio basic" stuff for now */
    err = masc_scan_audio_basic_dc( dc, &format, &srate, &resolution, &channels, &endian );
    if ( err < 0 )
        return FALSE;

    state->bpstc = masc_get_audio_basic_bpstc( resolution, channels );
    if ( state->bpstc < 0 )
        return FALSE;

    state->valid_bpstc = TRUE;
    return TRUE;
}

int
test_packet_timestamp( struct datalog_state *state, struct mas_data *data )
{
    /* Test the sequence number */
    if ( data->header.sequence != state->expected_seq )
    {
        masc_log_message( 0, "datalog: sequence number %u does not match expected sequence %u.", data->header.sequence, state->expected_seq);
    }

    /* Test the media timestamp, but only if we have a valid
       bytes-per-sample-times-channels */
    if ( ! data->header.mark && state->valid_bpstc )
    {
        if ( data->header.media_timestamp != state->expected_mt )
        {
            masc_log_message( 0, "datalog: media timestamp %u does not match expected media timestamp %u.", data->header.media_timestamp, state->expected_mt );
            masc_log_message( 0, "datalog:  the last packet was %d bytes long, requiring a media timestamp increase of %u.", state->prev_packet_length, state->prev_packet_length/state->bpstc );
            if ( state->expected_mt - data->header.media_timestamp < HALF_UINT32_MAX )
            {
                masc_log_message( 0, "datalog:  but this packet's media timestamp increase of %u is smaller than expected.", state->expected_mt - data->header.media_timestamp );
            }
            else
            {
                masc_log_message( 0, "datalog:  but this packet's media timestamp increase of %u is bigger than expected.", data->header.media_timestamp - state->expected_mt );
            }
        }

        /* Also, check to make sure we have an integer # of samples in
         * the packet! */

        if ( data->length / state->bpstc != (uint32) ceil( (float)data->length / (float) state->bpstc ) )
        {
            masc_log_message( 0, "datalog: there are not an integer number of samples in the packet!");
            masc_log_message( 0, "datalog:  data characteristic indicated %d bytes per sample times channels. ", state->bpstc);
            masc_log_message( 0, "datalog:  with %d bytes in it, this packet has %0.2f samples. ", data->length, (float)data->length / (float)state->bpstc);
        }
    }

    state->expected_seq = data->header.sequence + 1;
    state->expected_mt = data->header.media_timestamp + data->length / state->bpstc;
    state->prev_packet_length = data->length;

    masc_log_message(0, "");
    
    return 0;
}
