#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libswresample/swresample.h>
#include <kos32sys.h>

#include "winlib/winlib.h"
#include "sound.h"
#include "fplay.h"


astream_t astream;

extern uint8_t *decoder_buffer;
int resampler_size;
volatile int sound_level_0;
volatile int sound_level_1;

volatile enum player_state player_state;
volatile enum player_state decoder_state;
volatile enum player_state sound_state;

static SNDBUF hBuff;

int sample_rate;

static uint32_t samples_written = 0;

int init_audio(vst_t* vst)
{
    int    err;
    int    version =-1;
    char  *errstr;

    if((err = InitSound(&version)) !=0 )
    {
        errstr = "Sound service not installed\n\r";
        goto exit_whith_error;
    };

    if( (SOUND_VERSION>(version&0xFFFF)) ||
        (SOUND_VERSION<(version >> 16)))
    {
        errstr = "Sound service version mismatch\n\r";
        goto exit_whith_error;
    }

    create_thread(audio_thread, vst, 32768);

    return 1;

exit_whith_error:

    printf(errstr);
    return 0;
};

void set_audio_volume(int left, int right)
{
    SetVolume(hBuff, left, right);
};

static uint64_t samples_lost;
static double  audio_delta;
static double  last_time_stamp;


double get_master_clock(void)
{
    double tstamp;

    GetTimeStamp(hBuff, &tstamp);
    return tstamp - audio_delta;
};

int decode_audio(AVCodecContext  *ctx, queue_t *qa)
{
    static struct SwrContext *swr_ctx;
    static int64_t src_layout;
    static int src_freq;
    static int src_channels;
    static enum AVSampleFormat src_fmt = -1;
    static AVFrame *aFrame;

    AVPacket   pkt;
    AVPacket    pkt_tmp;
    int64_t dec_channel_layout;
    int len, len2;
    int got_frame;
    int data_size;

    if( astream.count > 192000*2)
        return -1;

    if( get_packet(qa, &pkt) == 0 )
        return 0;

    if (!aFrame)
    {
        if (!(aFrame = av_frame_alloc()))
            return -1;
    } else
        avcodec_get_frame_defaults(aFrame);

    pkt_tmp = pkt;

    while(pkt_tmp.size > 0)
    {
        data_size = 192000;

        got_frame = 0;
        len = avcodec_decode_audio4(ctx, aFrame, &got_frame, &pkt_tmp);

        if(len >= 0 && got_frame)
        {
            char *samples;
            int ch, plane_size;
            int planar    = av_sample_fmt_is_planar(ctx->sample_fmt);
            int data_size = av_samples_get_buffer_size(&plane_size, ctx->channels,
                                                   aFrame->nb_samples,
                                                   ctx->sample_fmt, 1);
            pkt_tmp.data += len;
            pkt_tmp.size -= len;

            dec_channel_layout =
                (aFrame->channel_layout && aFrame->channels == av_get_channel_layout_nb_channels(aFrame->channel_layout)) ?
                aFrame->channel_layout : av_get_default_channel_layout(aFrame->channels);

            if (aFrame->format          != src_fmt     ||
                dec_channel_layout      != src_layout  ||
                aFrame->sample_rate     != src_freq    ||
                !swr_ctx)
            {
                swr_free(&swr_ctx);
                swr_ctx = swr_alloc_set_opts(NULL, AV_CH_LAYOUT_STEREO, AV_SAMPLE_FMT_S16,
                                             aFrame->sample_rate, dec_channel_layout,aFrame->format,
                                             aFrame->sample_rate, 0, NULL);
                if (!swr_ctx || swr_init(swr_ctx) < 0)
                {
                    printf("Cannot create sample rate converter for conversion of %d Hz %s %d channels to %d Hz %s %d channels!\n",
                        aFrame->sample_rate,   av_get_sample_fmt_name(aFrame->format), (int)aFrame->channels,
                        aFrame->sample_rate, av_get_sample_fmt_name(AV_SAMPLE_FMT_S16), 2);
                    break;
                }

                src_layout   = dec_channel_layout;
                src_channels = aFrame->channels;
                src_freq     = aFrame->sample_rate;
                src_fmt      = aFrame->format;
            };

            if (swr_ctx)
            {
                const uint8_t **in = (const uint8_t **)aFrame->extended_data;
                uint8_t *out[] = {decoder_buffer};
                int out_count = 192000 * 3 / 2 / av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);
                len2 = swr_convert(swr_ctx, out, out_count, in, aFrame->nb_samples);
                if (len2 < 0) {
                    printf("swr_convert() failed\n");
                    break;
                }
                if (len2 == out_count) {
                    printf("warning: audio buffer is probably too small\n");
                    swr_init(swr_ctx);
                }
                data_size = len2 * 2 * av_get_bytes_per_sample(AV_SAMPLE_FMT_S16);

                mutex_lock(&astream.lock);

                samples = astream.buffer+astream.count;

                memcpy(samples, decoder_buffer, data_size);

                astream.count += data_size;
                mutex_unlock(&astream.lock);
            };
       }
       else pkt_tmp.size = 0;
    }
    av_free_packet(&pkt);
    return 1;
};


static void sync_audio(SNDBUF hbuff, int buffsize)
{
    SND_EVENT   evnt;
    uint32_t    offset;
    double      time_stamp;

#ifdef BLACK_MAGIC_SOUND

    while( player_state != CLOSED)
    {
        GetNotify(&evnt);

        if(evnt.code != 0xFF000001)
        {
            printf("invalid event code %d\n\r", evnt.code);
            continue;
        }

        if(evnt.stream != hbuff)
        {
            printf("invalid stream %x hBuff= %x\n\r",
                    evnt.stream, hbuff);
            continue;
        }

        GetTimeStamp(hbuff, &time_stamp);
        audio_delta = time_stamp - last_time_stamp;

        offset = evnt.offset;

        mutex_lock(&astream.lock);
        {
            if(astream.count < buffsize)
            {
                memset(astream.buffer+astream.count,
                       0, buffsize-astream.count);
                astream.count = buffsize;
            };

            SetBuffer(hbuff, astream.buffer, offset, buffsize);
            samples_written+= buffsize/4;

            astream.count -= buffsize;
            if(astream.count)
                memcpy(astream.buffer, astream.buffer+buffsize, astream.count);
            mutex_unlock(&astream.lock);
        };
        break;
    };
#endif

};

int audio_thread(void *param)
{
    vst_t *vst = param;
    SND_EVENT evnt;

    int       buffsize;
    int      samples;
    int       err;
    char     *errstr;
    int       active;

    if((err = CreateBuffer(vst->snd_format|PCM_RING,0, &hBuff)) != 0)
    {
        errstr = "Cannot create sound buffer\n\r";
        goto exit_whith_error;
    };

    SetVolume(hBuff,-900,-900);

    if((err = GetBufferSize(hBuff, &buffsize)) != 0)
    {
        errstr = "Cannot get buffer size\n\r";
        goto exit_whith_error;
    };

    __sync_or_and_fetch(&threads_running,AUDIO_THREAD);

    resampler_size = buffsize = buffsize/2;

    samples = buffsize/4;

    while( player_state != CLOSED)
    {
        uint32_t  offset;
        double    event_stamp, wait_stamp;
        int       too_late = 0;

        switch(sound_state)
        {
            case PREPARE:

                mutex_lock(&astream.lock);
                    if(astream.count < buffsize*2)
                    {
                        memset(astream.buffer+astream.count,
                               0, buffsize*2-astream.count);
                        astream.count = buffsize*2;
                    };

                    SetBuffer(hBuff, astream.buffer, 0, buffsize*2);
                    astream.count -= buffsize*2;
                    if(astream.count)
                        memcpy(astream.buffer, astream.buffer+buffsize*2, astream.count);
                mutex_unlock(&astream.lock);

                SetTimeBase(hBuff, vst->audio_timer_base);

            case PAUSE_2_PLAY:
                GetTimeStamp(hBuff, &last_time_stamp);

                if((err = PlayBuffer(hBuff, 0)) !=0 )
                {
                    errstr = "Cannot play buffer\n\r";
                    goto exit_whith_error;
                };
                active = 1;
                sync_audio(hBuff, buffsize);
                sound_state = PLAY;

                /* breaktrough */

            case PLAY:
                GetNotify(&evnt);

                if(evnt.code != 0xFF000001)
                {
                    printf("invalid event code %d\n\r", evnt.code);
                    continue;
                }

                if(evnt.stream != hBuff)
                {
                    printf("invalid stream %x hBuff= %x\n\r",
                            evnt.stream, hBuff);
                    continue;
                };

                offset = evnt.offset;

                mutex_lock(&astream.lock);
                if(astream.count < buffsize)
                {
                    memset(astream.buffer+astream.count,
                           0, buffsize-astream.count);
                    astream.count = buffsize;
                };

                SetBuffer(hBuff, astream.buffer, offset, buffsize);

                {
                    double  val = 0;
                    int16_t *src = (int16_t*)astream.buffer;
                    int samples = buffsize/2;
                    int i;

                    for(i = 0, val = 0; i < samples/2; i++, src++)
                        if(val < abs(*src))
                            val= abs(*src); // * *src;

                    sound_level_0 = val; //sqrt(val / (samples/2));

                    for(i = 0, val = 0; i < samples/2; i++, src++)
                        if(val < abs(*src))
                            val= abs(*src); // * *src;

                    sound_level_1 = val; //sqrt(val / (samples/2));

 //                   printf("%d\n", sound_level);
                };

                samples_written+= buffsize/4;

                astream.count -= buffsize;
                if(astream.count)
                    memcpy(astream.buffer, astream.buffer+buffsize, astream.count);
                mutex_unlock(&astream.lock);
                break;

            case PLAY_2_STOP:
                if( active )
                {
                    ResetBuffer(hBuff, SND_RESET_ALL);
                	vst->audio_timer_valid = 0;
                    active = 0;
                }
                sound_state = STOP;
                break;

            case PLAY_2_PAUSE:
                if( active )
                {
                    StopBuffer(hBuff);
                };
                sound_state = PAUSE;

            case PAUSE:
            case STOP:
                delay(1);
        };
    }

    __sync_and_and_fetch(&threads_running,~AUDIO_THREAD);

    StopBuffer(hBuff);
    DestroyBuffer(hBuff);

    return 0;

exit_whith_error:

    printf(errstr);
    return -1;

};