#include "voice.h"

wavdata_t *input_file;
fs_offset_t input_pos;

voice_state_t voice;

convar_t	*voice_enable;
convar_t	*voice_inputfromfile;
convar_t	*voice_loopback;
convar_t	*voice_scale;

void Voice_RegisterCvars( void )
{
    voice_enable = Cvar_Get( "voice_enable", "1", CVAR_ARCHIVE, "enable voice chat" );
    voice_inputfromfile = Cvar_Get( "voice_inputfromfile", "0", 0, "input voice from voice_input.wav" );
    voice_loopback = Cvar_Get( "voice_loopback", "0", 0, "loopback voice back to the speaker" );
    voice_scale = Cvar_Get( "voice_scale", "1.0", FCVAR_ARCHIVE, "incoming voice volume scale" );
}

static void Voice_Status( int entindex, qboolean bTalking )
{
    clgame.dllFuncs.pfnVoiceStatus( entindex, bTalking );
}

// parameters currently unused
qboolean Voice_Init( const char *pszCodecName, int quality )
{
    int err;

    if ( !voice_enable->value )
        return false;

    Voice_DeInit();

    voice.was_init = true;

    voice.channels = 1;
    voice.width = 2;
    voice.samplerate = SOUND_48k;
    voice.frame_size = voice.channels * ( (float)voice.samplerate / ( 1000.0f / 20.0f ) ) * voice.width;

    voice.encoder = opus_encoder_create( voice.samplerate, voice.channels, OPUS_APPLICATION_VOIP, &err );
    voice.decoder = opus_decoder_create( voice.samplerate, voice.channels, &err );

    return true;
}

void Voice_DeInit( void )
{
    if ( !voice.was_init )
        return;

    Voice_RecordStop();

    opus_encoder_destroy( voice.encoder );
    opus_decoder_destroy( voice.decoder );

    voice.was_init = false;
}

uint Voice_GetCompressedData( byte *out, uint maxsize, uint *frames )
{
    uint ofs, size = 0;

    if ( input_file )
    {
        uint numbytes;
        double time;

        time = Sys_DoubleTime();

        numbytes = ( time - voice.start_time ) * voice.samplerate;
        numbytes = min( numbytes, input_file->size - input_pos );
        numbytes = min( numbytes, sizeof( voice.buffer ) - voice.buffer_pos );

        memcpy( voice.buffer + voice.buffer_pos, input_file->buffer + input_pos, numbytes );
        voice.buffer_pos += numbytes;
        input_pos += numbytes;

        voice.start_time = time;
    }

    for ( ofs = 0; voice.buffer_pos - ofs >= voice.frame_size && ofs <= voice.buffer_pos; ofs += voice.frame_size )
    {
        int bytes;

        bytes = opus_encode( voice.encoder, (const opus_int16*)(voice.buffer + ofs), voice.frame_size / voice.width, out + size, maxsize );
        memmove( voice.buffer, voice.buffer + voice.frame_size, sizeof( voice.buffer ) - voice.frame_size );
        voice.buffer_pos -= voice.frame_size;

        if ( bytes > 0 )
        {
            size += bytes;
            (*frames)++;
        }
    }

    return size;
}

void Voice_Idle( float frametime )
{
    if ( !voice_enable->value )
    {
        Voice_DeInit();
        return;
    }

    if ( voice.talking_ack )
    {
        voice.talking_timeout += frametime;

        if ( voice.talking_timeout > 0.2f )
        {
            voice.talking_ack = false;
            Voice_Status( -2, false );
        }
    }
}

qboolean Voice_IsRecording( void )
{
    return voice.is_recording;
}

void Voice_RecordStop( void )
{
    if ( input_file )
    {
        FS_FreeSound( input_file );
        input_file = NULL;
    }

    voice.buffer_pos = 0;
    memset( voice.buffer, 0, sizeof( voice.buffer ) );

    if ( Voice_IsRecording() )
        Voice_Status( -1, false );

    VoiceCapture_RecordStop();

    voice.is_recording = false;
}

void Voice_RecordStart( void )
{
    Voice_RecordStop();

    if ( voice_inputfromfile->value )
    {
        input_file = FS_LoadSound( "voice_input.wav", NULL, 0 );

        if ( input_file )
        {
            Sound_Process( &input_file, voice.samplerate, voice.width, SOUND_RESAMPLE );
            input_pos = 0;

            voice.start_time = Sys_DoubleTime();
            voice.is_recording = true;
        }
        else
        {
            FS_FreeSound( input_file );
            input_file = NULL;
        }
    }

    if ( !Voice_IsRecording() )
        voice.is_recording = VoiceCapture_RecordStart();

    if ( Voice_IsRecording() )
        Voice_Status( -1, true );
}

void Voice_AddIncomingData( int ent, byte *data, uint size, uint frames )
{
    byte decompressed[MAX_RAW_SAMPLES];
    int samples;

    samples = opus_decode( voice.decoder, (const byte*)data, size, (short *)decompressed, voice.frame_size / voice.width * frames, false );

    if ( samples > 0 )
        Voice_StartChannel( samples, decompressed, ent );
}

void CL_AddVoiceToDatagram( void )
{
    uint size, frames = 0;
    byte data[MAX_RAW_SAMPLES];

    if ( cls.state != ca_active || !Voice_IsRecording() )
        return;

    size = Voice_GetCompressedData( data, sizeof( data ), &frames );

    if ( size > 0 && BF_GetNumBytesLeft( &cls.datagram ) >= size + 32 )
    {
        BF_WriteByte( &cls.datagram, clc_voicedata );
        BF_WriteByte( &cls.datagram, Voice_GetLoopback() );
        BF_WriteByte( &cls.datagram, frames );
        BF_WriteShort( &cls.datagram, size );
        BF_WriteBytes( &cls.datagram, data, size );
    }
}

qboolean Voice_GetLoopback( void )
{
    return voice_loopback->value;
}

void Voice_LocalPlayerTalkingAck( void )
{
    if ( !voice.talking_ack )
    {
        Voice_Status( -2, true );
    }

    voice.talking_ack = true;
    voice.talking_timeout = 0.0f;
}

void Voice_StartChannel( uint samples, byte *data, int entnum )
{
    //SND_ForceInitMouth( entnum );
    Voice_Status( entnum, true );
    //S_RawEntSamples( entnum, samples, voice.samplerate, voice.width, voice.channels, data, 128.0f * voice_scale.value );

    S_StreamRawSamples(samples, voice.samplerate, voice.width, voice.channels, data);
}