/*
 * 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 <unistd.h>
#include <string.h>
#include "mas/mas.h"
#include "mas/mas_core.h"

#define SEGLEN 4608
#define NUM    32
#define DEFAULT_RATE 44100
#define DEFAULT_RES  16
#define DEFAULT_CHANNELS 2

typedef struct
{
    int channels;
    int rate;
    int new_channels;
    int new_rate;

    char* codec;

    struct mas_data_characteristic* codec_dc;
    struct mas_data_characteristic* cur_dc;

    mas_port_t lastsrc;
} ctcb_t;

static int connect_stuff( mas_device_t srcdev, mas_port_t src, mas_device_t snkdev, mas_port_t snk, struct mas_data_characteristic* dc );
static int connect_to_new_device( ctcb_t* cb, mas_device_t srcdev, mas_port_t src, char* ndevname, char* ndevsnkname, mas_device_t* ndev, mas_port_t* ndevsnk );
static int to_codec_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src );
static int to_codec_up( ctcb_t* cb, mas_device_t srcdev, mas_port_t src );
static int to_srate_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src );
static int to_channelconv_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src );
static mas_channel_t wire_main_flow(ctcb_t* cb);


int
connect_stuff( mas_device_t srcdev, mas_port_t src, mas_device_t snkdev, mas_port_t snk, struct mas_data_characteristic* dc )
{
    mas_port_t tp;
    int32 err;
    
    if ( src == 0 && snk == 0 )
    {
        if ( dc == 0 )
            return mas_asm_connect_devices( srcdev, snkdev, "source", "sink" );
        else
            return mas_asm_connect_devices_dc( srcdev, snkdev, "source", "sink", dc );
    }
    else
    {
        if ( srcdev && snk && !src )
        {
            err = mas_asm_get_port_by_name( srcdev, "source", &tp );
            if ( err < 0 )
            {
                masc_logerror( err, "couldn't get source" );
                return err;
            }
            
            if ( dc )
                return mas_asm_connect_source_sink( tp, snk, dc );
            else
                return mas_asm_connect_ports( tp, snk );
        }
        else if ( snkdev && src && !snk )
        {
            err = mas_asm_get_port_by_name( snkdev, "sink", &tp );
            if ( err < 0 )
            {
                masc_logerror( err, "couldn't get sink" );
                return err;
            }
            
            if ( dc )
                return mas_asm_connect_source_sink( src, tp, dc );
            else
                return mas_asm_connect_ports( src, tp );
        }
        else if ( src && snk )
        {
            if ( dc )
                return mas_asm_connect_source_sink( src, snk, dc );
            else
                return mas_asm_connect_ports( src, snk );
        }
    }

    return mas_error(MERR_INVALID);
}

int
connect_to_new_device( ctcb_t* cb, mas_device_t srcdev, mas_port_t src, char* ndevname, char* ndevsnkname, mas_device_t* ndev, mas_port_t* ndevsnk )
{
    int32 err;
    mas_port_t tp = 0;

    masc_log_message(MAS_VERBLVL_INFO, "connecting to %s", ndevname );
    err = mas_asm_instantiate_device( ndevname, 0, 0, ndev );
    if ( err < 0 )
    {
        masc_logerror( err, "couldn't instantiate %s", ndevname );
        return err;
    }

    if ( ndevsnkname != 0 )
    {
        err = mas_asm_get_port_by_name( *ndev, ndevsnkname, &tp );
        if ( err < 0 )
        {
            masc_logerror( err, "Couldn't retrieve port '%s' from device '%s'", ndevsnkname, ndevname );
            return err;
        }
    }
       
    err = connect_stuff( srcdev, src, *ndev, tp, cb->cur_dc );
    if ( err < 0 )
    {
        masc_logerror( err, "couldn't connect to %s", ndevname);
        return err;
    }

    return 0;
}

int
to_codec_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src )
{
    mas_device_t codec;
    mas_port_t snk;
    int32 err;

    err = connect_to_new_device( cb, srcdev, src, cb->codec, 0, &codec, &snk );
    if ( err < 0 ) return err;

    masc_strike_dc( cb->cur_dc );
    masc_setup_dc( cb->cur_dc, cb->codec_dc->numkeys );
    masc_copy_dc( cb->cur_dc, cb->codec_dc );
    
    return to_codec_up( cb, codec, 0 );
}


int
to_codec_up( ctcb_t* cb, mas_device_t srcdev, mas_port_t src )
{
    mas_device_t codec;
    mas_port_t snk;
    int32 err;
    
    err = connect_to_new_device( cb, srcdev, src, cb->codec, 0, &codec, &snk );
    if ( err < 0 ) return err;

    mas_asm_get_port_by_name( codec, "source", &cb->lastsrc );
    
    return 0;
}

int
to_srate_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src )
{
    mas_device_t srate;
    mas_port_t snk;
    char ts[MAX_DATACHAR_VALUE_LEN];
    int32 err;

    err = connect_to_new_device( cb, srcdev, src, "srate", "sink", &srate, &snk );
    if ( err < 0 ) return err;

    if ( cb->cur_dc == 0 )
    {
        mas_asm_get_dc( src, &cb->cur_dc );
    }

    snprintf( ts, MAX_DATACHAR_VALUE_LEN, "%d", cb->new_rate );
    masc_change_dc_value( cb->cur_dc, "sampling rate", ts );

    err = mas_asm_get_port_by_name( srate, "source", &src );

    return to_codec_down( cb, srate, src );
}

int
to_channelconv_down( ctcb_t* cb, mas_device_t srcdev, mas_port_t src )
{
    mas_device_t cc;
    mas_port_t snk;
    char ts[MAX_DATACHAR_VALUE_LEN];
    int32 err;
    
    err = connect_to_new_device( cb, srcdev, src, "channelconv", 0, &cc, &snk );
    if ( err < 0 ) return err;

    if ( cb->cur_dc == 0 )
    {
        mas_asm_get_dc( src, &cb->cur_dc );
    }

    snprintf( ts, MAX_DATACHAR_VALUE_LEN, "%d", cb->new_channels );
    masc_change_dc_value( cb->cur_dc, "channels", ts );

    if ( cb->rate != cb->new_rate )
        return to_srate_down( cb, cc, 0 );
    else
        return to_codec_down( cb, cc, 0 );

    return 0;
}

int
to_endian( ctcb_t* cb, mas_device_t srcdev, mas_port_t src )
{
    mas_device_t endian;
    mas_port_t snk, endsrc;
    int32 err;
    
    err = connect_to_new_device( cb, srcdev, src, "endian", "sink", &endian, &snk );
    if ( err < 0 ) return err;

    if ( cb->cur_dc == 0 )
    {
        mas_asm_get_dc( src, &cb->cur_dc );
    }

    masc_change_dc_value( cb->cur_dc, "endian", "host" );
    mas_asm_get_port_by_name( endian, "source", &endsrc );
    
    if ( cb->new_channels != cb->channels )
        err = to_channelconv_down( cb, endian, endsrc );
    else if ( cb->new_rate != cb->rate )
        err = to_srate_down( cb, endian, endsrc );
    else
        err = to_codec_down( cb, endian, endsrc );

    return 0;
}

mas_channel_t
wire_main_flow(ctcb_t* cb)
{
    mas_channel_t dchnl;
    mas_port_t  rsrc, rsnk;
    int32 err = 0;

    masc_log_message( MAS_VERBLVL_INFO, "");
    masc_entering_log_level( "Setting up main flow");
    
    err = mas_make_data_channel( "mascodectest", &dchnl, &rsrc, &rsnk );
    if ( err < 0 )
    {
        masc_logerror( err, "couldn't create data channel" );
        return 0;
    }

    err = to_endian( cb, 0, rsrc );
    if ( err < 0 )
    {
        masc_logerror( err, "Couldn't set up main flow.");
        return 0;
    }
    
    err = mas_asm_connect_ports( cb->lastsrc, rsnk );
    if ( err < 0 )
    {
        masc_logerror( err, "Couldn't set up main flow.");
        return 0;
    }
    

    return dchnl;
}

struct mas_data_characteristic*
parse_dc_args( int argc, char* argv[] )
{
    int i;
    char ts[256];
    char* cp;
    struct mas_data_characteristic* dc;

    dc = MAS_NEW( dc );
    masc_setup_dc( dc, argc - 2 );

    for (i=2; i<argc; i++)
    {
        strncpy( ts, argv[i], 256 );
        cp = strchr( ts, '=' );
        masc_trim_string( ts ); /* key */
        masc_trim_string( cp+1 ); /* value */
        
        if ( cp == 0 )
        {
            fprintf(stderr, "Error in data characteristic pair: %s", argv[i]);
            fprintf(stderr, "Format should be \"key=value\".");
            return 0;
        }
        *cp = 0; /* terminate key */

        masc_append_dc_key_value( dc, ts, cp+1 );
    }

    return dc;
}

int main(int argc, char* argv[])
{
    int32 err, i, count;
    mas_channel_t dchnl;
    struct mas_data_characteristic* dc;
    struct mas_data stack_data, stack_data2;
    struct mas_data* data = &stack_data;
    struct mas_data* rdata = &stack_data2;
    ctcb_t cb;
    uint32 seq = 0;

    if ( argc < 3 )
    {
        fprintf(stderr, "\nmascodectest - MAS back-to-back CODEC tester\n\n" );
        fprintf(stderr, " Takes audio from standard input, routes it through the requested\n");
        fprintf(stderr, " back-to-back codecs and sends the result out standard output.\n");
        fprintf(stderr, " If necessary, channelconv and srate devices are instantiated to\n");
        fprintf(stderr, " change the input data prior to the codec input.\n\n");
        
        fprintf( stderr, "usage:\n");
        fprintf( stderr, "  mascodectest [codec] [key1=value1] [key2=value2] ... [keyN=valueN]\n\n");
        fprintf( stderr, " [codec] is the name of the codec to test (e.g. codec_g721, codec_ulaw, etc.)\n");
        fprintf( stderr, "     It must be capable of encoding and decoding for this test.\n\n");
        fprintf( stderr, " [keyN=valueN] are the keys and values of the data characteristic of\n");
        fprintf( stderr, "     the coded information, used to connect the source of the coding\n");
        fprintf( stderr, "     codec to the sink of the decoding codec.  Use double-quotes around\n");
        fprintf( stderr, "     the pairs to ensure capture of space characters.\n");
        fprintf( stderr, "\nExample:\n");
        fprintf( stderr, " mascodectest codec_g721 \"format=g721\" \"sampling rate=44100\" \"endian=host\" \"resolution=4\" \"channels=1\" < orig_track.sw > output_track.sw");

        exit( 1 );
    }

    cb.codec = argv[1];
    cb.codec_dc = parse_dc_args( argc, argv );
    if ( cb.codec_dc == 0 )
        exit(1);
    cb.rate = DEFAULT_RATE;
    cb.channels = DEFAULT_CHANNELS;
    i = masc_get_index_of_key( cb.codec_dc, "sampling rate" );
    if ( i < 0 )
        cb.new_rate = DEFAULT_RATE;
    else
        cb.new_rate = atoi( cb.codec_dc->values[i] );

    i = masc_get_index_of_key( cb.codec_dc, "channels" );
    if ( i < 0 )
        cb.new_channels = DEFAULT_CHANNELS;
    else
        cb.new_channels = atoi( cb.codec_dc->values[i] );

    masc_log_verbosity( MAS_VERBLVL_DEBUG );
    masc_init_log_program( "", "Debugging information", 0 );
    masc_log_message( MAS_VERBLVL_INFO, "" );
    
    masc_log_message( MAS_VERBLVL_INFO, "input: defaulting to %d Hz, %d-bit, %d channel, little-endian linear audio.", cb.rate, DEFAULT_RES, cb.channels );

    if ( cb.new_channels != cb.channels )
        masc_log_message(MAS_VERBLVL_INFO, "input: converting to %d channels.", cb.new_channels );
    
    if ( cb.new_rate != cb.rate )
        masc_log_message(MAS_VERBLVL_INFO, "input: converting to %d Hz sampling rate.", cb.new_rate );
    
    masc_log_message( MAS_VERBLVL_INFO, "output: %d Hz, %d-bit, %d channel linear audio.", cb.new_rate, DEFAULT_RES, cb.new_channels );
    masc_log_message( MAS_VERBLVL_INFO, "");
    
    masc_entering_log_level( "codec: specified data characteristic");
    masc_print_dc( cb.codec_dc );
    masc_exiting_log_level();
    masc_log_message( MAS_VERBLVL_INFO, "" );
    
    err = mas_init();
    if (err < 0)
    {
	printf("\nconnection with server failed.\n");
	exit(1);
    }
    
    cb.cur_dc = masc_make_audio_basic_dc( MAS_LINEAR_FMT, cb.rate, DEFAULT_RES, cb.channels, MAS_LITTLE_ENDIAN_FMT );
    
    dchnl = wire_main_flow(&cb);
    if ( dchnl == 0 )
        exit(1);
    
    masc_entering_log_level( "Decoding output" );
    while ( 1 )
    {
        for ( i=0; i<NUM; i++)
        {
            data->segment = masc_rtalloc( SEGLEN );
            data->length = SEGLEN;
            data->allocated_length = SEGLEN;
            data->header.sequence = seq++;
            
            if ( fread( data->segment, data->length, 1, stdin ) != 1 )
            {
                if ( feof( stdin ) )
                {
                    masc_log_message(0, "Done.");
                }
                else
                {
                    masc_log_message(0, "Read error.");
                }

                i--;
                break;
            }
            
            err = mas_send( dchnl, data );
            if ( err < 0 )
            {
                masc_logerror( err, "Couldn't send data to MAS." );
                masc_log_message( MAS_VERBLVL_ERROR, "Exiting.");
                exit(1);
            }
            
        }

        count = i;        
        
        for (i=0; i<count; i++)
        {
            err = mas_recv( dchnl, rdata );
            if ( err < 0 )
            {
                masc_logerror( err, "Couldn't recv data from MAS." );
                masc_log_message( MAS_VERBLVL_ERROR, "Exiting.");
                exit(1);
            }
            
            fwrite( rdata->segment, rdata->length, 1, stdout );
            masc_rtfree( rdata->segment );
        }
        if ( count < NUM )
            break;
    }
    
    sleep(1);
    
    exit(0);
}
