/*
 * 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"
#include "playlist.h"
#include "mp1a.h"
#include "mp1a_profile.h"

/* parameters needed for mpeg header parsing */
char *mode_names[5] = { "stereo", "j-stereo", "dual-ch", "single-ch" , "multi-ch"
};
char *layer_names[3] = { "I", "II", "III" };
char *version_names[2] = { "MPEG-2 LSF", "MPEG-1" };

/* 1: MPEG-1, 0: MPEG-2 LSF, 1995-07-11 shn */
double s_freq[2][4] = {{22.05, 24, 16, 0}, {44.1, 48, 32, 0}};

/* 1: MPEG-1, 0: MPEG-2 LSF, 1995-07-11 shn */
int     bitrate[2][3][15] = {{
          {0,32,48,56,64,80,96,112,128,144,160,176,192,224,256},
          {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160},
          {0,8,16,24,32,40,48,56,64,80,96,112,128,144,160}},
   
          {{0,32,64,96,128,160,192,224,256,288,320,352,384,416,448},
          {0,32,48,56,64,80,96,112,128,160,192,224,256,320,384},
          {0,32,40,48,56,64,80,96,112,128,160,192,224,256,320}
          }};

static int32 find_sync( FILE* file );
static int32 calculate_frame_length( struct mpeg_header* header );
static int32 interpret_header( struct mpeg_header* header, FILE* file, int syncpos );
static void  compute_period( struct source_state *state, struct track_info* ti );

int32
sourcex_init_instance( struct source_state* state )
{
    /******
     ****** HACK
     ******
     ******/
    {
        struct mas_data_characteristic* dc;
        char   str[128];
        /* form "A" data characteristic */
        dc = MAS_NEW( dc );
        masc_setup_dc( dc, 7 );
        masc_append_dc_key_value( dc, "format", "MPEG Audio" );
        sprintf(str, "%d", 1);
        masc_append_dc_key_value( dc, "version", str );
        sprintf(str, "%d", 3);
        masc_append_dc_key_value( dc, "layer", str );
        sprintf(str, "%d", 0);
        masc_append_dc_key_value( dc, "bit rate", str );
        sprintf(str, "%d", 0);
        masc_append_dc_key_value( dc, "sampling rate", str );
        sprintf(str, "%d", 2);
        masc_append_dc_key_value( dc, "channels", str );
        sprintf(str, "%d", 90000);
        masc_append_dc_key_value( dc, "mt rate", str );
        /* set the source to the dc */
        masd_set_data_characteristic( state->source, dc );
    }
    

    return 0;
}

int32
sourcex_configure_port( struct source_state* state, int32 portnum )
{
    return 0;
}

int32
sourcex_disconnect_port( struct source_state* state, int32 portnum )
{
    return 0;
}

int32
sourcex_exit_instance( struct source_state* state )
{
    return 0;
}

int32
sourcex_show_state( struct source_state* state )
{
    return 0;
}

int32
sourcex_stop( struct source_state* state )
{
    if ( state->ti )
        if ( state->ti->file )
            fseek( state->ti->file, 0, SEEK_SET ); /* rewind the file */

    return 0;
}

int32
sourcex_play( struct source_state* state )
{
    return 0;
}

int32
sourcex_pause( struct source_state* state )
{
    return 0;
}

int32
sourcex_next_track( struct source_state* state )
{
    return 0;
}

int32
sourcex_prev_track( struct source_state* state )
{
    return 0;
}

int32
sourcex_cue_track( struct source_state* state, struct track_info* ti )
{
    return 0;
}

/* Return (in the argument list) the next data segment from the
   source. */
int32
sourcex_get_data( struct source_state* state, struct track_info* ti, uint32 seq, struct mas_data** data_ptr )
{
    struct mas_data* data;
    struct mpeg_track_info* mti = ti->fdti;
    int32 frame_length, syncpos;
    double sec;

    /* check to make sure we're not at the end of the file. */
    if ( feof(ti->file) )
        return MAS_STREAM_END;

    /* find the next sync */
    syncpos = find_sync(ti->file);
    if ( syncpos < 0 )
        return MAS_STREAM_END;

    /* interpret the MPEG header and read the frame into the data
       segment */
    frame_length = interpret_header( &mti->header, ti->file, syncpos );
    fseek( ti->file, syncpos, SEEK_SET );
    data = MAS_NEW( data );
    masc_setup_data( data, frame_length );
    fread(data->segment, frame_length, 1, ti->file);
    data->length = frame_length;

    /* Compute media timestamp as a function of the sequence number. */
    data->header.media_timestamp = seq * mti->spf * mti->clockratio;

    /* NTP calculation is an approximation */
    sec = (double)mti->spf * (double)seq / ( (double)mti->channels * (double)mti->srate );
    data->header.ntp_seconds = (uint32)floor(sec);
    data->header.ntp_fraction = (uint32)(4295000000.0 * (sec - (double)data->header.ntp_seconds));
    /* set sequence number */
    data->header.sequence = seq;

    *data_ptr = data;
    
    return 0;
}

/* Complete the track info fields.  If needed, set the fdti pointer to
 * a format-dependent struct. */
int32
sourcex_fill_out_track_info( struct source_state* state, struct track_info* ti )
{
    struct mpeg_track_info*   mti;
    struct mpeg_header*       h;
    int max_gr;
    int32 syncpos;
    int32 err;

    if ( ti == 0 )
        return mas_error(MERR_NULLPTR);

    /* allocate memory for the format-dependent structure */
    mti = MAS_NEW( mti );
    ti->fdti = mti;
    h = &(mti->header);
    
    /* we've got the length in bytes */
    /* find the first sync & interpret the header fields */
    syncpos = find_sync(ti->file);
    if ( syncpos < 0 )
        return mas_error( MERR_IO );

    err = interpret_header( h, ti->file, syncpos );
    if ( err < 0 )
        return err;
    
    /* set the file pointer to the sync, so we can find it again next time */
    fseek( ti->file, syncpos, SEEK_SET );

    /** calculate additional parameters needed for the period and dc */
    if ( h->version_id == 0 ) max_gr = 1; /* MPEG2 */
    else max_gr = 2;

    if ( h->channels_id == 3 ) mti->channels = 1;
    else mti->channels = 2;

    if ( h->layer == 1 ) mti->spf = 384;
    if ( h->layer == 2 || h->layer == 3 ) mti->spf = 1152;
    if ( h->layer == 3 && h->version_id == 0 ) mti->spf = 576;

    /* save the sampling rate */
    mti->srate = (int32)(s_freq[h->version_id][h->srate_id] * 1000);

    /* compute the correct period to use for this track */
    compute_period( state, ti );
    
    /* compute MPEG 90kHz clock ratio */
    mti->clockratio = 90000.0 / mti->srate;
    
    /* compute estimated track time */
    ti->length_sec = (double)mti->spf/(double)s_freq[h->version_id][h->srate_id];
    ti->length_sec *= (double)ti->length/( (double)h->frame_length * 1000.0 );
    
    return 0;
}

/* Is the format of the two specified tracks different? */
int
sourcex_format_diff( struct source_state* state, struct track_info* ti, struct track_info* prev_ti )
{
    struct mpeg_track_info* mti;
    struct mpeg_track_info* prev_mti;

    if ( ti == 0 || prev_ti == 0 )
        return mas_error(MERR_NULLPTR);

    mti = ti->fdti;
    prev_mti = prev_ti->fdti;
    
    if ( mti->srate != prev_mti->srate )
        return TRUE;

    if ( mti->channels != prev_mti->channels )
        return TRUE;

    if ( ti->period != prev_ti->period )
        return TRUE;

    return FALSE;
}

/* Allocate and return the data characteristic for this track. */
struct mas_data_characteristic*
sourcex_get_track_dc( struct source_state* state, struct track_info* ti )
{
    struct mas_data_characteristic* dc;
    struct mpeg_track_info* mti;
    struct mpeg_header* h;
    char str[128];
    
    if ( ti == 0 )
        return 0;

    mti = ti->fdti;
    h = &(mti->header);
    
    dc = MAS_NEW( dc );
    masc_setup_dc( dc, 7 );
    masc_append_dc_key_value( dc, "format", "MPEG Audio" );

    sprintf(str, "%d", 2 - h->version_id);
    masc_append_dc_key_value( dc, "version", str );

    sprintf(str, "%d", h->layer);
    masc_append_dc_key_value( dc, "layer", str );

    sprintf(str, "%d", 1000*bitrate[h->version_id][h->layer-1][h->bitrate_id]);
    masc_append_dc_key_value( dc, "bit rate", str );

    sprintf(str, "%d", (int)(1000*s_freq[h->version_id][h->srate_id]));
    masc_append_dc_key_value( dc, "sampling rate", str );

    sprintf(str, "%d", mti->channels);
    masc_append_dc_key_value( dc, "channels", str );

    sprintf(str, "%d", 90000);
    masc_append_dc_key_value( dc, "mt rate", str );

    return dc;
}

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

int32
find_sync( FILE* file )
{
    uint8 sync[3];
    int32 top = ftell(file);
    int32 i = top;
    size_t rb = 1;

    fread( sync, 2, 1, file);

    while ( !( (sync[0] == 0xFF) && (( sync[1] & 0xF0) == 0xF0) ) && !feof(file) && rb > 0 )
    {
        rb = fread(&sync[2], 1, 1, file);
        sync[0] = sync[1];
        sync[1] = sync[2];
        i++;
    }
    
    if (feof(file) || rb == 0 ) return -1;
    return i;
}

int32
interpret_header( struct mpeg_header* h, FILE* file, int syncpos )
{
    uint8 header[4];

    if ( fseek( file, syncpos, SEEK_SET ) < 0 )
        return mas_error( MERR_IO );
    if ( !fread( header, 4, 1, file ) )
        return mas_error( MERR_IO );
    
    h->version_id = ( header[1] & 0x08 ) >> 3;
    h->layer = 4 - (( header[1] & 0x06 ) >> 1);
    h->bitrate_id = ( header[2] & 0xF0 ) >> 4;
    h->srate_id = ( header[2] & 0x0C ) >> 2;
    h->padding = ( header[2] & 0x02 ) >> 1;
    h->channels_id = ( header[3] &0xC0 ) >> 6;
    
    calculate_frame_length( h );

    return h->frame_length;
}

int32
calculate_frame_length( struct mpeg_header* h )
{
    if ( h->layer == 1 )
    {
        h->frame_length =
            (int) floor( (12.0
                          * bitrate[h->version_id][h->layer-1][h->bitrate_id]
                          / s_freq[h->version_id][h->srate_id] + h->padding )
                         * 4.0);
    }
    else /* II or III */
    {
        h->frame_length =
            (int) floor(144.0
                        * bitrate[h->version_id][h->layer-1][h->bitrate_id]
                        / s_freq[h->version_id][h->srate_id] + h->padding);
    }
    
    return h->frame_length;
}

void
compute_period( struct source_state *state, struct track_info* ti )
{
    struct mpeg_track_info* mti = ti->fdti;
/*     double rate; */
    int32 srate;
    
    
    if( state->preferred_clkid >= 0 )
    {
        /* we've got a preferred clock. Use it, and calculate the
           period accordingly */
        ti->period_clkid = state->use_clkid;
 
        /****** This would be an alternative to assuming 44100Hz */
        /* the base problem is that we don't know the 'intended' rate */
        /*         masd_mc_rate( state->use_clkid, &rate ); */
        /*         if( (rate>43400) && (rate<44800) ) */
        /*         { */
        /*             srate = 44100; */
        /*         } */
        /*         else */
        /*         { */
        /*             srate = rate; */
        /*         } */
        /*         /\******\/ */

        srate = 44100;
        
        if( (srate % mti->srate)==0 )
        {
            ti->period = mti->spf * (44100/mti->srate);
            masc_log_message( 0, "source: compute_period, will use clock %d period %d",  ti->period_clkid, ti->period );
            return;
        }
        else
        {

            /* srate is not a multiple of needed sample rate.
             * we could get fancy by querying the clock for its rate.
             * for now, fall back to the old method*/
        }
    }
    
    /* Try to retrieve the id of a clock at our sampling rate. */
    /* This clock can be overridden, see cue_track */
    ti->period_clkid = masd_mc_match_rate( mti->srate );
    
    if ( ti->period_clkid < 0 )
    {
        /** clock doesn't exist */
        /* period is calculated as the granules * samples per frame
           normalized to frequency rate and # of channels */
        ti->period_clkid = MAS_MC_SYSCLK_US;
        ti->period = mti->spf * 1000 / s_freq[mti->header.version_id][mti->header.srate_id];
    }
    else
    {
        /* This is preferable if the clock is available */
        ti->period = mti->spf;
    }
    
    masc_log_message( 0, "source: compute_period, will use clock %d period %d",  ti->period_clkid, ti->period );
}

