/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 03/04/2009
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/

extern "C" {
#include "libavcodec/get_bits.h"
#include "libavutil/internal.h"
#include "libavcodec/golomb.h"
}

extern "C" {
//#define SDL_MAIN_HANDLED
//#define __STDC_CONSTANT_MACROS
#include <SDL2/SDL.h>
#include <SDL2/SDL_main.h>
#include <SDL2/SDL_audio.h>
#include <SDL2/SDL_types.h>
}

#include <sys/time.h>
#include <stdlib.h>

#include "AudioVideoPlaybackController.h"
#include "AudioFrameConsumer.h"
#include "RTSPMediaStreamProducerImpl.h"

#if (defined(__WIN32__) || defined(_WIN32)) && !defined(IMN_PIM) && !defined(__MINGW32__)
// For Windoze, we need to implement our own gettimeofday()
extern int gettimeofday(struct timeval*, int*);
#endif


namespace videonext { namespace media {

static unsigned const sampling_frequency_table[16] = {
  96000, 88200, 64000, 48000,
  44100, 32000, 24000, 22050,
  16000, 12000, 11025, 8000,
  7350, 0, 0, 0
};

const AVSampleFormat outAudioFmt = AV_SAMPLE_FMT_S32; // SDL only accept interleaved audio data.

static std::multimap<uint64_t, uint32_t> audioDevices;
static Mutex lock;
static bool sdlInited = false;

AudioFrameConsumer* AudioFrameConsumer::createNew(AdaptiveUsageEnvironment& env,
                                                  AudioVideoPlaybackController *controller,
                                                  unsigned bufferSize,
                                                  unsigned decoderType,
                                                  StreamInfo *streamInfo)
{
    AudioFrameConsumer* newSink = NULL;

    newSink = new AudioFrameConsumer(env, controller,
                                     bufferSize, decoderType, streamInfo);

    if (newSink == NULL) return NULL;

    return newSink;
}

AudioFrameConsumer::AudioFrameConsumer(AdaptiveUsageEnvironment& env,
                                       AudioVideoPlaybackController *controller,
                                       unsigned bufferSize,
                                       unsigned decoderType,
                                       StreamInfo *streamInfo)

   : FrameConsumer(env, controller, bufferSize, decoderType, streamInfo),
     fCodecCtx(0), fSwr(0), fSwrBuf(0), fAudioDevice(0), fMuted(false)
{
}

bool AudioFrameConsumer::initSDLAudio()
{
    MutexGuard mg(lock);

    if (!sdlInited)
    {
        SDL_SetMainReady();
        SDL_SetHint(SDL_HINT_NO_SIGNAL_HANDLERS, "no");
        // SDL_LogSetAllPriority(SDL_LOG_PRIORITY_VERBOSE);
        int ret = SDL_Init(SDL_INIT_AUDIO);
        if (ret != 0)
        {
            fprintf(stderr, "Failed to init audio subsystem (%d): %s\n", ret, SDL_GetError());
            envir().setResultMsg("Failed to init audio");
            envir().setResultErr(EINTERNALERROR);
            return false;
        }
        sdlInited = true;
    }

    uint64_t deviceKey = ((uint64_t)fCodecCtx->sample_rate) | ((uint64_t)fCodecCtx->channels << 32);
    std::multimap<uint64_t, uint32_t>::iterator it = audioDevices.find(deviceKey);
    if (it != audioDevices.end())
    {
        fAudioDevice = it->second;
        audioDevices.erase(it);
        fprintf(stderr, "Reuse audio device: %d\n", fAudioDevice);
    }
    else
    {
        SDL_AudioSpec audioSpec;

        SDL_memset(&audioSpec, 0, sizeof(audioSpec));
        audioSpec.freq = fCodecCtx->sample_rate;
        audioSpec.format = AUDIO_S32SYS;
        audioSpec.channels = fCodecCtx->channels;
        audioSpec.silence = 0;
        audioSpec.samples = 1024; // samples per channel. It equals to frame->nb_samples.
        audioSpec.callback = NULL;
        audioSpec.userdata = NULL;

        fAudioDevice = SDL_OpenAudioDevice(NULL, 0, &audioSpec, NULL, 0);

        if (fAudioDevice == 0)
        {
            fprintf(stderr, "Failed to open audio device: %s\n", SDL_GetError());
            envir().setResultMsg("Failed to open audio device");
            envir().setResultErr(EINTERNALERROR);
            return false;

        }
    }

    SDL_PauseAudioDevice(fAudioDevice, fMuted);

    fprintf(stderr, "SDL inited, audio device: %d\n", fAudioDevice);
    return true;
}

/*virtual*/bool AudioFrameConsumer::init()
{
   if (fDecoderType && fStreamInfo->RTPCodecStr == "MPEG4-GENERIC")
   {
      if (fStreamInfo->configDataSize == 0)
      {
         envir().setResultMsg("Cannot initialize audio decoding facility (No configuration data provided)");
         envir().setResultErr(EINTERNALERROR);
         return false;
      }

      const AVCodec *avcodec = avcodec_find_decoder(AV_CODEC_ID_AAC);

      if (!avcodec)
      {
          envir().setResultMsg("AAC decoder not found");
          envir().setResultErr(EINTERNALERROR);
          return false;
      }

      fCodecCtx = avcodec_alloc_context3(NULL);
      if (!fCodecCtx) {
          envir().setResultMsg("Could't initialize audio codec context");
          envir().setResultErr(EINTERNALERROR);
          return false;
      }

      /* Disable MT behaviour */
      fCodecCtx->thread_count = 1;
      fCodecCtx->thread_type  = 0;

      GetBitContext cfg_bc;
      init_get_bits(&cfg_bc, fStreamInfo->configData, fStreamInfo->configDataSize * 8);

      // profile
      uint8_t audio_object_type = get_bits(&cfg_bc, 5);

      // sampling_frequency_index
      uint8_t sampling_frequency_index = get_bits(&cfg_bc, 4);

      // channel_configuration
      uint8_t channel_configuration = get_bits(&cfg_bc, 4);

      fprintf(stderr, "audio_object_type: %d\n", audio_object_type);
      fprintf(stderr, "sampling_frequency: %d\n", sampling_frequency_table[sampling_frequency_index]);
      fprintf(stderr, "channels: %d\n", channel_configuration);

      fCodecCtx->sample_rate = sampling_frequency_table[sampling_frequency_index];
      fCodecCtx->channels    = channel_configuration;
      if (fCodecCtx->channels == 1)
      {
          fCodecCtx->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_MONO;
      }
      else if (fCodecCtx->channels == 2)
      {
          fCodecCtx->ch_layout = (AVChannelLayout)AV_CHANNEL_LAYOUT_STEREO;
      }
      else
      {
          envir() << "Unsupported channel configuration: " << fCodecCtx->channels;
          avcodec_free_context(&fCodecCtx);
          return false;
      }

      // fill extradata for decoder
      fCodecCtx->extradata_size = fStreamInfo->configDataSize;
      fCodecCtx->extradata = (uint8_t*)av_mallocz(fStreamInfo->configDataSize + 8);
      memcpy(fCodecCtx->extradata, fStreamInfo->configData, fStreamInfo->configDataSize);


      int ret = avcodec_open2(fCodecCtx, avcodec, NULL);
      if (ret < 0) {
          char errbuf[256];
          envir() << "Could't open audio decoder: " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
          avcodec_free_context(&fCodecCtx);
          return false;
      }

      if (!initSDLAudio())
      {
           fprintf(stderr, "Audio initialization failed\n");
           return false;
      }

      // redefine audio params
      fStreamInfo->bitsPerSample = 32;
      fStreamInfo->samplingFreq  = fCodecCtx->sample_rate;
      fStreamInfo->numChannels   = fCodecCtx->channels;
   }
   else
   {
       envir().setResultMsg("Audio codec not supported");
       envir().setResultErr(EINTERNALERROR);
       return false;
   }

   fPlaybackController->mediaStreamHandler()->newStream(*fStreamInfo);

   return true;
}

AudioFrameConsumer::~AudioFrameConsumer()
{
    if (fAudioDevice)
    {
        MutexGuard mg(lock);
        SDL_PauseAudioDevice(fAudioDevice, 1);
        SDL_ClearQueuedAudio(fAudioDevice);

        uint64_t deviceKey = ((uint64_t)fCodecCtx->sample_rate) | ((uint64_t)fCodecCtx->channels << 32);
        audioDevices.insert(std::make_pair(deviceKey, fAudioDevice));
    }
    if (fCodecCtx)
    {
        avcodec_free_context(&fCodecCtx);
    }

    if (fSwr)
    {
        swr_free(&fSwr);
    }

    if (fSwrBuf)
    {
        av_freep(&fSwrBuf[0]);
    }
}

/*virtual*/ void AudioFrameConsumer::addData(unsigned char* data, unsigned dataSize,
                                             const struct timeval &presentationTime,
                                             bool parseOurHeader, const std::string* pObjectsData)
{
    struct timeval tv = {.tv_sec = 0, .tv_usec = 0};

    if (fPlaybackController->isPaused())
        return;

    if (parseOurHeader)
    {
        uint8_t *ptr = findHeader(data, dataSize);

        if (ptr != NULL)
        {
            // parsing additional header:


            if (sscanf((char*)ptr, "VNTime: %ld.%d", &tv.tv_sec, (int*)&tv.tv_usec) != 2)
            {
                // seems frame is buggy.. skip it.
                envir() << "Unexpected extra headers ("<< (char*)ptr <<") Skiping..\n";

                return;
            }
        }
        else if (fPlaybackController->mediaProducer()->serverVersion() == 0) // stream is going from unknown server
        {
            tv = presentationTime;
        }
        else
        {
            // seems frame is buggy.. or was sent not from our server.
            // Anyway skip it.
            envir() << "Frame does not have extra headers. Skiping..\n";
            return;
        }
    }
    else
    {
        tv = presentationTime;
    }

    if (!fDecoderType)
    {
        FrameConsumer::addData(data, dataSize, tv, parseOurHeader);
    }
    else
    {
        // Decode frame

        AVPacket pkt = {};
        av_init_packet(&pkt);

        pkt.data = data;
        pkt.size = dataSize;

        int ret = avcodec_send_packet(fCodecCtx, &pkt);
        if (ret < 0) {
            char errbuf[256];
            envir() << "Error while sending to decode audio frame (size: " << pkt.size << ": " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
            av_packet_unref(&pkt);
            return;
        }
        av_packet_unref(&pkt);

        AVFrame *f = av_frame_alloc();

        ret = avcodec_receive_frame(fCodecCtx, f);
        if (ret < 0)
        {
            char errbuf[256];
            envir() << "Error while decoding audio frame (size: " << pkt.size << ": " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
            av_frame_free(&f);
            return;
        }


        if (!fSwr)
        {
            ret = swr_alloc_set_opts2(&fSwr,
                                      &fCodecCtx->ch_layout,   // out_ch_layout
                                      outAudioFmt,             // out_sample_fmt
                                      f->sample_rate,          // out_sample_rate
                                      &fCodecCtx->ch_layout,   // in_ch_layout
                                      (enum AVSampleFormat)f->format, // in_sample_fmt
                                      f->sample_rate,          // in_sample_rate
                                      0,                       // log_offset
                                      NULL);                   // log_ctx
            if (ret < 0)
            {
                char errbuf[256];
                envir() << "Cannot create audio converter: " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
                av_frame_free(&f);
                return;
            }
            ret = swr_init(fSwr);
            if (ret < 0)
            {
                char errbuf[256];
                envir() << "Cannot init audio converter: " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
                av_frame_free(&f);
                return;
            }

            ret = av_samples_alloc_array_and_samples(&fSwrBuf, NULL, fCodecCtx->channels, f->nb_samples, outAudioFmt, 0);
            if (ret < 0)
            {
                char errbuf[256];
                envir() << "Samples allocation failed: " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
                av_frame_free(&f);
                return;
            }
        }

        ret = swr_convert(fSwr, fSwrBuf, f->nb_samples, (const uint8_t**)f->data, f->nb_samples);
        if (ret < 0)
        {
            char errbuf[256];
            envir() << "Audio convert failed: " << av_make_error_string(errbuf, sizeof(errbuf), ret) << ")\n";
            av_frame_free(&f);
            return;
        }

        FrameConsumer::addData(fSwrBuf[0], f->nb_samples * fCodecCtx->channels * 4, tv, parseOurHeader);

        av_frame_free(&f);
    }

}

/*virtual*/ void AudioFrameConsumer::playFrame(CPMediaFrame &cpMediaFrame, bool show)
{
    CacheBoundaries cacheBoundaries;

    fPlaybackController->cache()->boundaries(&cacheBoundaries.begin, &cacheBoundaries.end);

    if (!show)
    {
        SDL_ClearQueuedAudio(fAudioDevice);
        return;
    }

    if (!fDecoderType)
        return;

    if (!fAudioDevice)
        return;

    if (fPlaybackController->isPaused() || fMuted)
        return;

    int a_time_fraction_us = 1024/*input samples from aac encoder*/ * 1000000 / (fCodecCtx->sample_rate * fCodecCtx->channels);
    int buffered_audio_us = (SDL_GetQueuedAudioSize(fAudioDevice) / 4096) * a_time_fraction_us;

//    fprintf(stderr, "Queue queue size: %db (%dms)\n", SDL_GetQueuedAudioSize(fAudioDevice), buffered_audio_us/1000);

    if (buffered_audio_us >= 256000)
    {
        fprintf(stderr, "Audio queue size is too high: %db (%dms)\n", SDL_GetQueuedAudioSize(fAudioDevice), buffered_audio_us/1000);
        SDL_ClearQueuedAudio(fAudioDevice);
        return;
    }

    if (SDL_QueueAudio(fAudioDevice, cpMediaFrame->data, cpMediaFrame->size) != 0)
    {
        fprintf(stderr, "Queue audio failed: %s\n", SDL_GetError());
    }
}

/*virtual*/ void AudioFrameConsumer::mute(bool mute)
{
    if (fMuted == mute)
        return;

    fMuted = mute;

    if (!fAudioDevice)
        return;

    if (fMuted)
    {
        SDL_PauseAudioDevice(fAudioDevice, 1);
    }
    else
    {
        SDL_ClearQueuedAudio(fAudioDevice);
        SDL_PauseAudioDevice(fAudioDevice, 0);
    }
}

/*virtual*/ void AudioFrameConsumer::setFrameType(CPMediaFrame &cpMediaFrame)
{
   cpMediaFrame->mediaType = MediaFrame::AUDIO;
   cpMediaFrame->type = 0;
}

uint8_t* AudioFrameConsumer::findHeader(uint8_t* data, unsigned dataSize)
{
    uint8_t *ptr = NULL;

    // searchnig for "VNTime" in first 10 bytes
    int max_search_length = dataSize<10?dataSize:10;
    int n = 0;
    do
    {
       ptr = (uint8_t *)memchr(&data[n], 'V', max_search_length-n);

       if (ptr == NULL)
       {
          return NULL;
       }

       if (*(ptr+1) == 'N' && *(ptr+2) == 'T'
            && *(ptr+3) == 'i' && *(ptr+4) == 'm'
            && *(ptr+5) == 'e' && *(ptr+6) == ':'
            && *(ptr+7) == ' '
          )
       {
          break;
       }

       n = (ptr - data) + 1;

       ptr = NULL;

    } while (n < max_search_length);


    return ptr;
}


}}
