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

/**
 ** brutalization of the oss.c file.
 **
 **/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <math.h>
#include "mas/mas_dpi.h"
#include "anx_internal.h"
#include "oss_profile.h"

static int _pow2[] = { 1, 2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048,
		       4096, 8192, 16384, 32768, 65536, 131072, -1 };

/*************************************************************************
 * LOCAL PROTOTYPES
 *************************************************************************/
static int32 get_device_caps( struct anx_state* state );
static int32 oss_set_fragment_stuff( struct anx_state* state, int target_time_ms, int target_fragsize );
static int32 oss_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int oss_ch_num);

/* BEGIN PLATFORM DEPENDENT ACTIONS ***********************************/

int32
pdanx_init_library( void )
{
    return 0;
}

int32
pdanx_exit_library( void )
{
    return 0;
}

int32
pdanx_exit_instance( struct anx_state* state, void* predicate )
{
    return 0;
}

int32
pdanx_init_instance( struct anx_state* state, void* predicate )
{
    int32                    err;
    
    /* open audio device */
    state->pdstate.fd = (int)NULL; /* open_audio_device_fd( state, "/dev/dsp" ); */

    /* open the mixer */
    state->pdstate.mfd = (int)NULL;
     
    
    /* reset dsp */

    /* get device capabilities */
    err = get_device_caps( state );
    if ( err < 0 )
        return err;

    return 0;
}

int32
pdanx_configure_port( struct anx_state* state, int32 portnum, struct mas_data_characteristic* dc )
{
    int32                    err;
    int                      oss_format = -1;
    int                      oss_stereo = -1;
    int                      oss_speed  = -1;
    int                      target_fragsize;
    int                      no_ioctls = FALSE;
    struct audio_buf_info    info;
    int                      target_time_ms;

    /* With OSS, both the record and playback formats are the same.
     * Keep them in sync, unless one is already active. */
    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;
        }
        
    }

    /* pick up the format settings */
    switch ( state->play_format )
    {
    case MAS_ULAW_FMT:
        oss_format = AFMT_MU_LAW;
        break;
    case MAS_ALAW_FMT:
        oss_format = AFMT_A_LAW;
        break;
    case MAS_LINEAR_FMT:
        if ( state->play_resolution == 16 )
        {
#ifdef MAS_LITTLE_ENDIAN
            oss_format = AFMT_S16_LE;
#else
            oss_format = AFMT_S16_BE;
#endif
        }
        else if ( state->play_resolution == 8 )
            oss_format = AFMT_S8;
        else
            return mas_error(MERR_INVALID);
        break;
    case MAS_ULINEAR_FMT:
        if ( state->play_resolution == 16 )
        {
#ifdef MAS_LITTLE_ENDIAN
            oss_format = AFMT_U16_LE;
#else
            oss_format = AFMT_U16_BE;
#endif
        }
        else if ( state->play_resolution == 8 )
            oss_format = AFMT_U8;
        else
            return mas_error(MERR_INVALID);
        break;
    default:
        return mas_error(MERR_INVALID);
    }

    oss_stereo = state->play_channels - 1;
    oss_speed = state->play_sample_rate;
    
    if ( portnum == state->audio_sink && state->source_active )
        no_ioctls = TRUE;
    else if ( portnum == state->audio_source && state->sink_active )
        no_ioctls = TRUE;

    /* this is the null device -- don't do ioctl's */
    no_ioctls = TRUE;
    
    /** configure the sink ********************************************/
    if ( portnum == state->audio_sink )
    {
        /* make the buffer */
        err = anx_make_buffer( &state->play_buffer, PLAY_BUFFER_SIZE, state->buftime_mt * state->play_bpstc );
        if ( err < 0 )
        {
            masc_exiting_log_level();
            return err;
        }
        
        masc_log_message( MAS_VERBLVL_DEBUG, "filling %.1fms buffer", 1000.0 * (float) state->play_buffer->fill_line / (oss_speed * state->play_bpstc ) );
        
    }

    /** configure the source ******************************************/
    if ( portnum == state->audio_source )
    {
/*         ioctl(state->pdstate.fd, SNDCTL_DSP_GETISPACE, &info); */
/*         state->pdstate.fragsize = info.fragsize; */
/*         state->pdstate.fragments = info.fragstotal; */
/*         masc_log_message( MAS_VERBLVL_DEBUG, "Recording: allocated %d fragments of %d bytes each.", state->pdstate.fragments, state->pdstate.fragsize); */
        
    }   
    return 0;
}

int32
pdanx_disconnect_port( struct anx_state* state, int32 portnum )
{
    return 0;
}

int32 
pdanx_playback_start( struct anx_state* state )
{
 return 0;
}

int32 
pdanx_playback_stop( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_playback_pause( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_playback_poll( struct anx_state* state, struct mas_data* data )
{
    struct oss_state*        pdst = &state->pdstate;

    int32                    err;
    audio_buf_info           buf_info;
    int device_buffer_used = 0;
    int report_xrun = 0;

        if ( !state->play_buffer->filling )
        {
            /* check the buffer space used in the device */
            if ( ioctl(pdst->fd, SNDCTL_DSP_GETOSPACE, &buf_info) == 0 )
                device_buffer_used = (buf_info.fragstotal * buf_info.fragsize) - buf_info.bytes;
            else device_buffer_used = 0;

            /* if the device buffer has underrun, start filling it */
            if ( device_buffer_used == 0 )
                report_xrun = TRUE;
        }

        if ( state->rebuffer )
        {
            state->play_buffer->filling = TRUE;
            state->rebuffer = FALSE;
        }

        /** we've got to fill the buffer */
        if ( state->play_buffer->filling )
        {
            err = anx_buffer_append( state->play_buffer, data->segment, data->length );
            masc_log_message(MAS_VERBLVL_DEBUG, "anx: filling buffer - %%%d percent full", 100 * state->play_buffer->pos/state->play_buffer->fill_line );
            if ( err < 0) return err;
        }

        if (! state->play_buffer->filling ) /* catches changes from above */
        {
            /* Write anything in the buffer, all at once. */
            if (state->play_buffer->pos > 0)
            {
                /* set reference mark */
                state->mcref = state->mcnow;
                state->mtref = data->header.media_timestamp - ( state->play_buffer->pos - data->length ) / state->play_bpstc;
                state->valid_refmark = TRUE;
                
                /* write the buffer */
               /*  if (( error = write (pdst->fd, state->play_buffer->contents, state->play_buffer->pos)) != state->play_buffer->pos )		 */
/*                     return mas_error(MERR_IO); */
        
                /* enable playback */
                
                state->played_bytes += state->play_buffer->pos;
                anx_reset_buffer( state->play_buffer );
            }
            else 
            {
                /* nothing in buffer - write this data's data */
/*                 if (( error = write (pdst->fd, data->segment, */
/*                                      data->length)) != */
/*                     data->length ) */
/*                     return mas_error(MERR_IO); */
                state->played_bytes += data->length;        
            }
        }
    

    /* we ran out of buffer */
    if ( report_xrun ) return mas_error(MERR_XRUN);

    return 0;
}


int32 
pdanx_record_start( struct anx_state* state )
{


#if 0 /* triggering code not working, for now */
    /* trigger */
    if ( !state->rec_bytes )
    {
        /* preserve play trigger, set record low */
        state->trigger &= ~PCM_ENABLE_INPUT;
        ioctl( state->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );

        /* set trigger high */
        state->trigger |= PCM_ENABLE_INPUT;
        ioctl( state->fd, SNDCTL_DSP_SETTRIGGER, &state->trigger );
    }
#endif

    /* not bothering with recording yet... */
#if 0
         
    /* If we were recording before, stopped, and have now restarted,
       there's gonna be a ton of junk in the buffer.  read it all and
       trash it! */
    if ( state->rec_bytes )
    {
        audio_buf_info recinfo;
        void* trash;

        ioctl(pdst->fd, SNDCTL_DSP_GETISPACE, &recinfo);

        if ( recinfo.fragsize*recinfo.fragments )
        {
            trash = masc_rtalloc(recinfo.fragsize*recinfo.fragments);
            if ( read (pdst->fd, trash, recinfo.fragsize*recinfo.fragments) != recinfo.fragsize*recinfo.fragments )
            {
                masc_rtfree( trash ); /* junk it */
                return mas_error(MERR_IO) | mas_make_serror( errno );
            }
            masc_rtfree( trash ); /* junk it */
        }
        else
        {
            /* On some devies, the ioctl above will return bogus
             * information until the overruns are cleared.  So, don't
             * believe it! Read the entire buffer and scrap it.  */
            trash = masc_rtalloc(pdst->fragsize*pdst->fragments);
            if ( read (pdst->fd, trash, pdst->fragsize*pdst->fragments) != pdst->fragsize*pdst->fragments )
            {
                masc_rtfree( trash ); /* junk it */
                return mas_error(MERR_IO) | mas_make_serror( errno );
            }
            masc_rtfree( trash ); /* junk it */
        }
        
    }
#endif
    return 0;
}

int32 
pdanx_record_stop( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_record_pause( struct anx_state* state )
{
    return 0;
}

int32 
pdanx_record_poll( struct anx_state* state, struct mas_data** data_ptr )
{




    *data_ptr = 0;
#if 0
    /* can we read anything?  avoids blocking - could just use
     * non-blocking I/O, dummy. */
    if ( ioctl(pdst->fd, SNDCTL_DSP_GETISPACE, &recinfo) <  0 )
        return mas_error(MERR_IO);

    /* Either there's data waiting to be read or we haven't started
       the recording process yet.  In the latter case, force a read. */
    if ( (recinfo.fragsize*recinfo.fragments >= MAS_ANX_SEGLEN) ||
         state->rec_state == START_STATE)
    {
        masc_make_mas_data( &data, MAS_ANX_SEGLEN );
        
        if ( read (pdst->fd, data->segment, MAS_ANX_SEGLEN) != MAS_ANX_SEGLEN )
        {
            masc_destroy_mas_data( data );
            return mas_error(MERR_IO) | mas_make_serror( errno );
        }

        *data_ptr = data;
    }
#endif
    return 0;
}

int32
pdanx_show_state( struct anx_state* state )
{
    u_int32                  holder;
    audio_buf_info           playinfo;
    
    masc_log_message(0, "*-- platform dependent anx state -------------------------------\n");

    ioctl(state->pdstate.fd, SNDCTL_DSP_GETBLKSIZE, &holder);
    masc_log_message(0, "fragment size: %d", holder);

    ioctl(state->pdstate.fd, SNDCTL_DSP_GETOSPACE, &playinfo);

    masc_log_message(0, "    fragments: %d", playinfo.fragments);
    masc_log_message(0, "   fragstotal: %d", playinfo.fragstotal);
    masc_log_message(0, "     fragsize: %d", playinfo.fragsize);
    masc_log_message(0, "        bytes: %d", playinfo.bytes);

    masc_log_message(0, "for recording");
    ioctl(state->pdstate.fd, SNDCTL_DSP_GETISPACE, &playinfo);

    masc_log_message(0, "    fragments: %d", playinfo.fragments);
    masc_log_message(0, "   fragstotal: %d", playinfo.fragstotal);
    masc_log_message(0, "     fragsize: %d", playinfo.fragsize);
    masc_log_message(0, "        bytes: %d", playinfo.bytes);

    return 0;
}


int32
pdanx_get( struct anx_state* state, char* key, struct mas_package* arg, struct mas_package** r_package_ptr )
{

    struct mas_package* r_package;
    /* list of nuggets.  preserve the terminator */
    static char* nuggets[] = 
        { "list", "gain_db", "gain_linear", "channels", "recsource",
          "outremain", "playclock", "" };
    int remain=0;
    int i, n=0;
    struct count_info cinfo;
    
    masc_make_package( &r_package, 0 );
    
    /* count the defined nuggets */
    while ( *nuggets[n] != 0 ) n++;

    i = masc_get_string_index(key, nuggets, n);

    switch(i)
    {
    case 5: /*outremain*/
/*         if ( ioctl( pdst->fd, SNDCTL_DSP_GETODELAY, &remain ) < 0 ) */
/*             masc_pushk_int32( r_package, "error", mas_error( MERR_IO ) ); */
/*         else */
        masc_pushk_int32( r_package, "outremain", remain );
        break;
    case 6: /*playclock*/
/*         if ( ioctl( pdst->fd, SNDCTL_DSP_GETOPTR, &cinfo ) < 0 ) */
/*             masc_pushk_int32( r_package, "error", mas_error( MERR_IO ) ); */
/*         else */
/*         { */
        masc_pushk_int32( r_package, "ticks", cinfo.bytes );
/*         } */
        
        break;
    default:
        masc_destroy_package( r_package );
        return mas_error(MERR_INVALID);
        break;
    }

    *r_package_ptr = r_package;
    
    return 0;
}

int32
pdanx_set( struct anx_state* state, char* key, struct mas_package* arg )
{
    /* everything taken care of in platform independent code */
    return 0;
}

int32
pdanx_set_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 holder;
    int left, right;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */

    /* normalize the possible 10dB gain */
    left = (dbvol_to_linear( mch->left )*10)/11;
    right = (dbvol_to_linear( mch->right )*10)/11;

    /* and clamp */
    if ( left < 0 ) left = 0;
    if ( left > 100 ) left = 100;
    if ( right < 0 ) right = 0;
    if ( right > 100 ) right = 100;

    /* ioctl expects 0x0000RRLL - (RR) is the right channel, (LL) is
       the left.  Each ranges 0-100 */
    holder = (left & 0xFF) + ( ( right & 0xFF ) << 8 );
    
/*     if (ioctl(state->pdstate.mfd, MIXER_WRITE(state->pdstate.oss_mch_map[ch_id]), &holder) < 0) */
/*         return mas_error(MERR_IO); */

    return 0;
}

int32
pdanx_get_mixer_volume(struct anx_state* state, int ch_id )
{
    int32 holder=0;
    int left, right;
    struct mixer_channel* mch = &(state->mch[ch_id]); /* convenience */

/*     if (ioctl(state->pdstate.mfd, MIXER_READ(state->pdstate.oss_mch_map[ch_id]), &holder) < 0) */
/*         return mas_error(MERR_IO); */

    /* The ioctl sets holder to 0x0000RRLL. (RR) is the right channel,
       (LL) is the left.  Each ranges 0-100 */
    left = holder & 0xFF;
    right = (holder >> 8) & 0xFF;
    
    left = linear_to_dbvol( (left * 11)/10 );
    right = linear_to_dbvol( (right * 11)/10 );

    /* and clamp */
    if ( left > 60 ) left = 60;
    if ( right > 60 ) right = 60;

    mch->left = left;
    mch->right = right;

    return 0;
}

int32
pdanx_set_recording_source(struct anx_state* state, int ch_id )
{


    /* ioctl expects 0x0000RRLL - (RR) is the right channel, (LL) is
       the left.  Each ranges 0-100 */
/*     holder = 1 << state->pdstate.oss_mch_map[ch_id]; */
    
/*     if (ioctl(state->pdstate.mfd, SOUND_MIXER_WRITE_RECSRC, &holder) < 0) */
/*         return mas_error(MERR_IO); */

    return 0;
}

int32
pdanx_get_recording_source(struct anx_state* state )
{




/*     if (ioctl(state->pdstate.mfd, SOUND_MIXER_READ_RECSRC, &holder) < 0) */
/* 	return mas_error(MERR_IO); */

/*     for (j=0; *(mch[j].name) != 0; j++) */
/*     { */
/*         if ( holder & ( 1 << state->pdstate.oss_mch_map[j] ) ) */
/*             mch[j].recsrc = TRUE; */
/*         else */
/*             mch[j].recsrc = FALSE; */
/*     } */

    return 0;
}

/*
 * The sample counter counts the number of N-channel samples that have
 * been successfully played by the interface. It will not increment if
 * nothing is playing, and it will not increment during buffer
 * underruns.  Its value is held in scnt->val in the master clock
 * value structure scnt.  If scnt->err is set, then the value of
 * scnt->val is invalid.  scnt->err must be cleared.
 *
 * If the current count is less than the prior count, then the counter
 * has wrapped; the scnt->err flag is set and the result is invalidated.
 *
 * This function returns zero (0) if an error occurs, otherwise it
 * returns the sample counter's value.  This function must NOT return
 * a valid count when an error has occurred.  For the OSS drivers, we
 * check for playback underruns immediately after retrieving the
 * sample counter.
 *
 * The counter only counts at the sampling frequency, and is
 * independent of the number of channels or resolution of the samples:
 * e.g. for 44.1 kHz, 16-bit, stereo, the counter will read 44100
 * after one second, 88200 after 2 seconds, and so on.
 * 
 */

uint32
pdanx_get_sample_count( struct anx_state* state, struct mas_mc_clkval* scnt )
{
    if ( scnt == 0 ) return 0;
    scnt->err = TRUE;
    scnt->valid = FALSE;
    return 0;
    
}

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

int32
get_device_caps( struct anx_state* state )
{
    struct oss_state*  pdst = &state->pdstate;

    /* get the device capabilities */

    /* full duplex? */
    state->is_full_duplex = 0;
    
    /* Can we do single-sample stuff? */
    state->is_sample_accurate = 1;
    
    /* or, is there a big buffer on the card? */
    pdst->is_fragment_accurate = 0;
    if ( state->is_sample_accurate )
        masc_log_message( MAS_VERBLVL_DEBUG, "device is sample accurate");

    /* could we MMAP the DMA buffer?  (not used) */
    pdst->is_mmap_able = 0;
        
    return 0;
}
int32
oss_set_fragment_stuff( struct anx_state* state, int target_time_ms, int target_fragsize )
{
    struct oss_state* pdst = &state->pdstate; 
    int oss_fragmess;
    int fragsize_pow2;
    int bail, fail;
    
    /*** COMPUTE FRAGMENT SIZE AND NUMBER OF FRAGMENTS ***************/

    /* compute log_2(target_fragsize) */
    fragsize_pow2 = 0;
    while (( _pow2[fragsize_pow2] != -1 ) && ( _pow2[fragsize_pow2] < target_fragsize ) )
        fragsize_pow2++;

    /* fragment size of 2^(i-1) BYTES */
    /* this shoots under the target fragment size in milliseconds */
    fragsize_pow2--;

    bail = FALSE;
    fail = FALSE;
    /* divine how small a fragment size we can get */
    while ( ! bail )
    {
        pdst->fragsize = _pow2[fragsize_pow2];
        pdst->fragments = target_time_ms * state->play_sample_rate * state->play_bpstc * 0.001 / pdst->fragsize;
            
        /** to set fragment size and number of fragments, form a
            32-bit integer as follows 0xMMMMSSSS, where MMMM is
            the number of fragments (buffer size) to keep in the
            kernel driver, and SSSS is used to set the size of
            each fragment as 2^(SSSS) bytes -- this is a horrible
            kluge, but at least I'm not responsible for THIS
            one. */
            
        oss_fragmess = fragsize_pow2 | ( pdst->fragments << 16 );  
            
        if ( ioctl(pdst->fd, SNDCTL_DSP_SETFRAGMENT, &oss_fragmess) < 0)
        {
            fragsize_pow2++;
            if ( _pow2[fragsize_pow2] >= ( state->play_sample_rate * state->play_bpstc * MAX_FRAGSIZE_MS * 0.001 ) )
            {
                bail = TRUE;
                fail = TRUE;
            }
        }
        else
        {
            bail = TRUE;
        }
            
    }

    if ( fail )
        masc_log_message( MAS_VERBLVL_DEBUG, "Can't set the fragment size.  I tried lots of sizes.  Sorry.  Using defaults.");
    
    return 0;
}

/* sets up a new mixer channel.  returns -1 on error, or the ID of the
   new channel if successful. */
int32
oss_add_mix_channel(struct anx_state* state, char* name, int32 portnum, int oss_ch_num)
{
    int32 i;

    /* use platform-independent mixer channel add */
    i = add_mix_channel(state->mch, name, portnum, ALLOCATED_MIX_CH );
    if ( i < 0 ) return i;
    
    state->pdstate.oss_mch_map[i] = oss_ch_num;
    
    return i;
}
