/*
#  $Id$
# -----------------------------------------------------------------------------
#  The part of 'VideoNEXT MediaClient SDK'
# -----------------------------------------------------------------------------
#  Author: Petrov Maxim, 08/20/2010
#  Edited by:
#  QA by:
#  Copyright: videoNEXT LLC
# -----------------------------------------------------------------------------
*/
#include "impl/FileRecorder.h"
#include <stdio.h>
#include <string.h>
#include "vn_player_stream_handler.h"
#include <assert.h>

using namespace videonext::media;

#define VN_PLAYER_MAGIC 0x3C3D3E3F

static void vn_player_finalize(vn_player_context_t *ctx)
{
   delete ctx->producer; ctx->producer = 0;
   delete ctx->handler;  ctx->handler  = 0;

   if (ctx->delete_after_play)
   {
       ctx->player_mutex.unlock();
       delete ctx;
   }
   else
   {
       ctx->player_mutex.unlock();
   }
}

#ifdef WIN32
static DWORD WINAPI vn_player_do_open(LPVOID arg)
#else
static void* vn_player_do_open(void* arg)
#endif
{
   vn_player_context_t *ctx = (vn_player_context_t *)arg;

   assert(ctx->magic == VN_PLAYER_MAGIC);
   
   ctx->producer->open();

   ctx->player_mutex.lock();

   if (ctx->did_resume)
   {
       // Finalize should be done after resume
       ctx->need_finalize = true;
       ctx->player_mutex.unlock();
   }
   else
   {
       vn_player_finalize(ctx);
   }

   return 0;
}

#ifdef WIN32
static DWORD WINAPI vn_player_do_resume(LPVOID arg)
#else
static void* vn_player_do_resume(void* arg)
#endif
{
   vn_player_context_t *ctx = (vn_player_context_t *)arg;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   ctx->player_mutex.lock();
   ctx->did_resume = true;
   ctx->player_mutex.signal();
   ctx->player_mutex.unlock();

   ctx->producer->play();

   ctx->player_mutex.lock();
   
   ctx->is_running = false;
   ctx->did_resume = false;

   if (ctx->need_finalize)
       vn_player_finalize(ctx);
   else
       ctx->player_mutex.unlock();

   return 0;
}

const char *vn_player_state_to_str(int state)
{
    static const char *states[] = {"IDLE",
                                   "PLAY_FROM_SERVER",
                                   "PLAY_FROM_BUFFER",
                                   "STOPPED",
                                   "OPENING",
                                   "BUFFERING",
                                   "OPENED",
                                   "UNKNOWN"};

    if (state > 6)
        return states[7];
    else
        return states[state];
}

vn_player_context_t *vn_player_create(vn_player_config_t *config)
{
   struct vn_player_context_t *ctx = new vn_player_context_t;

   memset(&ctx->callbacks, 0, sizeof(struct vn_player_callbacks_t));

   ctx->config             = *config;
   ctx->handler            = 0;
   ctx->producer           = 0;
   ctx->delete_after_play  = false;
   ctx->did_resume         = false;
   ctx->is_running         = false;
   ctx->need_finalize      = false;
   ctx->magic              = VN_PLAYER_MAGIC;
   ctx->audio_muted        = 0;
   ctx->buffer_len         = 0;

   return ctx;
}

void vn_player_set_callbacks(vn_player_context_t *ctx, const vn_player_callbacks_t *callbacks)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   ctx->callbacks = *callbacks;
}

bool vn_player_open(vn_player_context_t *ctx, const char* url, float speed)
{
   if (ctx == 0) 
       return false;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer == 0)
   {
      VN_Media_Stream_Handler *handler = NULL;
      MediaStreamProducer *producer = NULL;
      try {
         handler = ctx->handler = new VN_Media_Stream_Handler(ctx);
      } catch(std::bad_alloc) {
         return false;
      }

      try {
         producer = ctx->producer = new MediaStreamProducer(handler,
                                                 url, 
                                                 ctx->config.cache_size * 1024 * 1024, 
                                                 0,
                                                 (ctx->config.rtp_transport == RTP_TRANSPORT_TCP ? true : false),
                                                 ctx->buffer_len ? ctx->buffer_len : ctx->config.buffer_len,
                                                 speed == 0.f ? 1.f : speed);
      } catch(std::bad_alloc) {
         delete ctx->handler; ctx->handler = NULL;
         return false;
      }

      if (ctx->config.decoder_type)
      {
         producer->enableDecoding(ctx->config.decoder_type, (PIXEL_FORMAT)ctx->config.pixel_format);
      }
      producer->muteAudio(ctx->audio_muted);

      // Create and detach thread
      do {
#ifdef WIN32
      HANDLE hndl = CreateThread(NULL, 0, &vn_player_do_open, ctx, 0, NULL);
      if(hndl == NULL)
          break;
      CloseHandle(hndl);
      return true;
#else
      pthread_t thr;
      if(pthread_create(&thr, 0, vn_player_do_open, ctx))
          break;
      pthread_detach(thr);
      return true;
#endif
      } while(0);
      delete ctx->producer; ctx->producer = NULL;
      delete ctx->handler;  ctx->handler  = NULL;
      return false;
   }

   return true;
}

void vn_player_play(vn_player_context_t *ctx)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      ctx->producer->play();
}

void vn_player_pause(vn_player_context_t *ctx)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      ctx->producer->pause();
}

bool vn_player_resume(vn_player_context_t *ctx, int direction)
{
   if (ctx == 0) 
       return false;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
   {
      ctx->producer->setPlayDirection(direction);

      ctx->did_resume = false;
      ctx->is_running = true;

      // Create and detach thread
#ifdef WIN32
      HANDLE hndl = CreateThread(NULL, 0, &vn_player_do_resume, ctx, 0, NULL);
      if(hndl == NULL)
          return false;
      CloseHandle(hndl);	
#else
      pthread_t thr;
      if(pthread_create(&thr, 0, vn_player_do_resume, ctx))
          return false;
      pthread_detach(thr);
#endif

      // Wait until "resume" thread actually starts
      while (ctx->is_running && !ctx->did_resume)
          ctx->player_mutex.wait();
   }

   return true;
}

void vn_player_set_step_mode(vn_player_context_t *ctx, unsigned mode)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      ctx->producer->setStepMode(mode==0?false:true);
}

void vn_player_set_playback_speed(vn_player_context_t *ctx, float speed)
{
   if (ctx == 0)
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      ctx->producer->setSpeed(speed);
}

void vn_player_set_jitter_buffer_len(vn_player_context_t *ctx, unsigned value)
{
   if (ctx == 0)
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   ctx->buffer_len = value;

   if (ctx->producer != 0)
      ctx->producer->setJitterBufferLen(value);
}

void vn_player_mute_audio(vn_player_context_t *ctx, int mute)
{
    if (ctx == 0)
        return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   ctx->audio_muted = mute;

   if (ctx->producer != 0)
       ctx->producer->muteAudio(mute);
}

void vn_player_move_to_ts(vn_player_context_t *ctx, time_t time)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      ctx->producer->moveToTimestamp(time);  
}

bool vn_player_start_recording(vn_player_context_t* ctx, const char* file_name, vn_kv_t** metadata)
{
   if (ctx == 0) 
       return false;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
   {
	  std::map<std::string, std::string> meta;
	  if (metadata)
	  {
		  for (int i = 0; metadata[i] != NULL; i++)
		  {
			  if (metadata[i]->key && metadata[i]->value)
			  {
			  	meta[(const char*)metadata[i]->key] = (const char*)metadata[i]->value;
			  }
		  }
	  }
      return ctx->producer->startRecording(file_name, meta);
   }

   return false;
}

bool vn_player_end_recording(vn_player_context_t* ctx)
{
   if (ctx == 0) 
       return false;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer != 0)
      return ctx->producer->endRecording();
	  
   return false;
}

/*
void vn_player_stop(vn_player_context_t *ctx)
{
   if (ctx == 0) return;

   MutexGuard g(ctx->player_mutex);

   if (ctx->producer == 0) return;

   ctx->handler->set_enable_callbacks(false);
   ctx->producer->teardown();
}
*/
void vn_player_destroy(vn_player_context_t *ctx)
{
   if (ctx == 0) 
       return;

   assert(ctx->magic == VN_PLAYER_MAGIC);

   ctx->player_mutex.lock();
   ctx->callback_mutex.lock();  // wait for pending callback finished

   if (ctx->producer == 0) 
   {
      ctx->player_mutex.unlock();
      ctx->callback_mutex.unlock();
      delete ctx;
   }
   else
   {
      ctx->handler->set_enable_callbacks(false);
      ctx->delete_after_play = true;

      /* ensure that places which wait callback_mutex, e.g. in VN_Media_Stream_Handler::newFrame(), will be finished before calling teardown() below
       * to avoid dead-lock while waiting fPlayoutBufferMutex in AudioVideoPlaybackController::setPaused()
       */
      ctx->callback_mutex.unlock();

      ctx->producer->teardown();
      ctx->player_mutex.unlock();
   }
}
