/*
 *  Copyright (c) 2012 The WebRTC project authors. All Rights Reserved.
 *
 *  Use of this source code is governed by a BSD-style license
 *  that can be found in the LICENSE file in the root of the source
 *  tree. An additional intellectual property rights grant can be found
 *  in the file PATENTS.  All contributing project authors may
 *  be found in the AUTHORS file in the root of the source tree.
 */

#include "video_engine/vie_file_player.h"

#include "modules/utility/interface/file_player.h"
#include "system_wrappers/interface/critical_section_wrapper.h"
#include "system_wrappers/interface/event_wrapper.h"
#include "system_wrappers/interface/thread_wrapper.h"
#include "system_wrappers/interface/tick_util.h"
#include "system_wrappers/interface/trace.h"
#include "video_engine/include/vie_file.h"
#include "video_engine/vie_input_manager.h"
#include "voice_engine/main/interface/voe_base.h"
#include "voice_engine/main/interface/voe_file.h"
#include "voice_engine/main/interface/voe_video_sync.h"

namespace webrtc {

const int kThreadWaitTimeMs = 100;

ViEFilePlayer* ViEFilePlayer::CreateViEFilePlayer(
    int file_id,
    int engine_id,
    const char* file_nameUTF8,
    const bool loop,
    const FileFormats file_format,
    ViEInputManager& input_manager,
    VoiceEngine* voe_ptr) {
  ViEFilePlayer* self = new ViEFilePlayer(file_id, engine_id, input_manager);
  if (!self || self->Init(file_nameUTF8, loop, file_format, voe_ptr) != 0) {
    delete self;
    self = NULL;
  }
  return self;
}

ViEFilePlayer::ViEFilePlayer(int Id,
                             int engine_id,
                             ViEInputManager& input_manager)
    : ViEFrameProviderBase(Id, engine_id),
      play_back_started_(false),
      input_manager_(input_manager),
      feedback_cs_(NULL),
      audio_cs_(NULL),
      file_player_(NULL),
      audio_stream_(false),
      video_clients_(0),
      audio_clients_(0),
      local_audio_channel_(-1),
      observer_(NULL),
      voe_file_interface_(NULL),
      voe_video_sync_(NULL),
      decode_thread_(NULL),
      decode_event_(NULL),
      decoded_audio_length_(0) {
  memset(file_name_, 0, FileWrapper::kMaxFileNameSize);
  memset(decoded_audio_, 0, kMaxDecodedAudioLength);
}

ViEFilePlayer::~ViEFilePlayer() {
  // StopPlay deletes decode_thread_.
  StopPlay();
  delete decode_event_;
  delete audio_cs_;
  delete feedback_cs_;
}

int ViEFilePlayer::Init(const char* file_nameUTF8,
                        const bool loop,
                        const FileFormats file_format,
                        VoiceEngine* voice_engine) {
  feedback_cs_ = CriticalSectionWrapper::CreateCriticalSection();
  if (!feedback_cs_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to allocate critsect");
    return -1;
  }

  audio_cs_ = CriticalSectionWrapper::CreateCriticalSection();
  if (!audio_cs_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to allocate critsect");
    return -1;
  }

  decode_event_ = EventWrapper::Create();
  if (!decode_event_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to allocate event");
    return -1;
  }
  if (strlen(file_nameUTF8) > FileWrapper::kMaxFileNameSize) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() Too long filename");
    return -1;
  }
  strncpy(file_name_, file_nameUTF8, strlen(file_nameUTF8) + 1);

  file_player_ = FilePlayer::CreateFilePlayer(ViEId(engine_id_, id_),
                                              file_format);
  if (!file_player_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to create file player");
    return -1;
  }
  if (file_player_->RegisterModuleFileCallback(this) == -1) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to "
                 "RegisterModuleFileCallback");
    file_player_ = NULL;
    return -1;
  }
  decode_thread_ = ThreadWrapper::CreateThread(FilePlayDecodeThreadFunction,
                                               this, kHighestPriority,
                                               "ViEFilePlayThread");
  if (!decode_thread_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StartPlay() failed to start decode thread.");
    file_player_ = NULL;
    return -1;
  }

  // Always try to open with Audio since we don't know on what channels the
  // audio should be played on.
  WebRtc_Word32 error = file_player_->StartPlayingVideoFile(file_name_, loop,
                                                            false);
  if (error) {
    // Failed to open the file with audio, try without.
    error = file_player_->StartPlayingVideoFile(file_name_, loop, true);
    audio_stream_ = false;
    if (error) {
      WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                   "ViEFilePlayer::StartPlay() failed to Start play video "
                   "file");
      return -1;
    }

  } else {
    audio_stream_ = true;
  }

  if (audio_stream_) {
    if (voice_engine) {
      // A VoiceEngine has been provided and we want to play audio on local
      // a channel.
      voe_file_interface_ = VoEFile::GetInterface(voice_engine);
      if (!voe_file_interface_) {
        WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                     "ViEFilePlayer::StartPlay() failed to get VEFile "
                     "interface");
        return -1;
      }
      voe_video_sync_ = VoEVideoSync::GetInterface(voice_engine);
      if (!voe_video_sync_) {
        WEBRTC_TRACE(kTraceError, kTraceVideo,
                     ViEId(engine_id_, id_),
                     "ViEFilePlayer::StartPlay() failed to get "
                     "VoEVideoSync interface");
        return -1;
      }
    }
  }

  // Read audio /(or just video) every 10ms.
  decode_event_->StartTimer(true, 10);
  return 0;
}

int ViEFilePlayer::FrameCallbackChanged() {
  // Starts the decode thread when someone cares.
  if (ViEFrameProviderBase::NumberOfRegisteredFrameCallbacks() >
      video_clients_) {
    if (!play_back_started_) {
      play_back_started_ = true;
      unsigned int thread_id;
      if (decode_thread_->Start(thread_id)) {
        WEBRTC_TRACE(kTraceStateInfo, kTraceVideo, ViEId(engine_id_, id_),
                     "ViEFilePlayer::FrameCallbackChanged() Started file decode"
                     " thread %u", thread_id);
      } else {
        WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                     "ViEFilePlayer::FrameCallbackChanged() Failed to start "
                     "file decode thread.");
      }
    } else if (!file_player_->IsPlayingFile()) {
      if (file_player_->StartPlayingVideoFile(file_name_, false,
                                              !audio_stream_) != 0) {
        WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                     "ViEFilePlayer::FrameCallbackChanged(), Failed to restart "
                     "the file player.");
      }
    }
  }
  video_clients_ = ViEFrameProviderBase::NumberOfRegisteredFrameCallbacks();
  return 0;
}

bool ViEFilePlayer::FilePlayDecodeThreadFunction(void* obj) {
  return static_cast<ViEFilePlayer*>(obj)->FilePlayDecodeProcess();
}

bool ViEFilePlayer::FilePlayDecodeProcess() {
  if (decode_event_->Wait(kThreadWaitTimeMs) == kEventSignaled) {
    if (audio_stream_ && audio_clients_ == 0) {
      // There is audio but no one cares, read the audio here.
      Read(NULL, 0);
    }
    if (file_player_->TimeUntilNextVideoFrame() < 10) {
      // Less than 10ms to next videoframe.
      if (file_player_->GetVideoFromFile(decoded_video_) != 0) {
      }
    }
    if (decoded_video_.Length() > 0) {
      if (local_audio_channel_ != -1 && voe_video_sync_) {
        // We are playing audio locally.
        int audio_delay = 0;
        if (voe_video_sync_->GetPlayoutBufferSize(audio_delay) == 0) {
          decoded_video_.SetRenderTime(decoded_video_.RenderTimeMs() +
                                       audio_delay);
        }
      }
      DeliverFrame(decoded_video_);
      decoded_video_.SetLength(0);
    }
  }
  return true;
}

int ViEFilePlayer::StopPlay() {
  // Only called from destructor.
  if (decode_thread_) {
    decode_thread_->SetNotAlive();
    if (decode_thread_->Stop()) {
      delete decode_thread_;
    } else {
      assert(false && "ViEFilePlayer::StopPlay() Failed to stop decode thread");
      WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                   "ViEFilePlayer::StartPlay() Failed to stop file decode "
                   "thread.");
    }
  }
  decode_thread_ = NULL;
  if (decode_event_) {
    decode_event_->StopTimer();
  }
  StopPlayAudio();

  if (voe_file_interface_) {
    voe_file_interface_->Release();
    voe_file_interface_ = NULL;
  }
  if (voe_video_sync_) {
    voe_video_sync_->Release();
    voe_video_sync_ = NULL;
  }

  if (file_player_) {
    file_player_->StopPlayingFile();
    FilePlayer::DestroyFilePlayer(file_player_);
    file_player_ = NULL;
  }
  return 0;
}

int ViEFilePlayer::StopPlayAudio() {
  // Stop sending audio.

  std::set<int>::iterator it = audio_channels_sending_.begin();
  while (it != audio_channels_sending_.end()) {
    StopSendAudioOnChannel(*it);
    // StopSendAudioOnChannel erases the item from the map.
    it = audio_channels_sending_.begin();
  }

  // Stop local audio playback.
  if (local_audio_channel_ != -1) {
    StopPlayAudioLocally(local_audio_channel_);
  }
  local_audio_channel_ = -1;
  audio_channel_buffers_.clear();
  audio_clients_ = 0;
  return 0;
}

int ViEFilePlayer::Read(void* buf, int len) {
  // Protect from simultaneous reading from multiple channels.
  CriticalSectionScoped lock(*audio_cs_);
  if (NeedsAudioFromFile(buf)) {
    // We will run the VoE in 16KHz.
    if (file_player_->Get10msAudioFromFile(decoded_audio_,
                                           decoded_audio_length_, 16000) != 0) {
      // No data.
      decoded_audio_length_ = 0;
      return 0;
    }
    // 2 bytes per sample.
    decoded_audio_length_ *= 2;
    if (buf) {
      audio_channel_buffers_.push_back(buf);
    }
  } else {
    // No need for new audiobuffer from file, ie the buffer read from file has
    // not been played on this channel.
  }
  if (buf) {
    memcpy(buf, decoded_audio_, decoded_audio_length_);
  }
  return decoded_audio_length_;
}

bool ViEFilePlayer::NeedsAudioFromFile(void* buf) {
  bool needs_new_audio = false;
  if (audio_channel_buffers_.size() == 0) {
    return true;
  }

  // Check if we the buf already have read the current audio.
  for (std::list<void*>::iterator it = audio_channel_buffers_.begin();
       it != audio_channel_buffers_.end(); ++it) {
    if (*it == buf) {
      needs_new_audio = true;
      audio_channel_buffers_.erase(it);
      break;
    }
  }
  return needs_new_audio;
}

void ViEFilePlayer::PlayFileEnded(const WebRtc_Word32 id) {
  WEBRTC_TRACE(kTraceInfo, kTraceVideo, ViEId(engine_id_, id),
               "%s: file_id %d", __FUNCTION__, id_);
  file_player_->StopPlayingFile();

  CriticalSectionScoped lock(*feedback_cs_);
  if (observer_) {
    observer_->PlayFileEnded(id_);
  }
}

bool ViEFilePlayer::IsObserverRegistered() {
  CriticalSectionScoped lock(*feedback_cs_);
  return observer_ != NULL;
}

int ViEFilePlayer::RegisterObserver(ViEFileObserver& observer) {
  CriticalSectionScoped lock(*feedback_cs_);
  if (observer_) {
    return -1;
  }
  observer_ = &observer;
  return 0;
}

int ViEFilePlayer::DeRegisterObserver() {
  CriticalSectionScoped lock(*feedback_cs_);
  observer_ = NULL;
  return 0;
}

int ViEFilePlayer::SendAudioOnChannel(const int audio_channel,
                                      bool mix_microphone,
                                      float volume_scaling) {
  if (!voe_file_interface_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "%s No VEFile interface.", __FUNCTION__);
    return -1;
  }
  if (voe_file_interface_->StartPlayingFileAsMicrophone(audio_channel, this,
                                                       mix_microphone,
                                                       kFileFormatPcm16kHzFile,
                                                       volume_scaling) != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::SendAudioOnChannel() "
                 "VE_StartPlayingFileAsMicrophone failed. audio_channel %d, "
                 " mix_microphone %d, volume_scaling %.2f",
                 audio_channel, mix_microphone, volume_scaling);
    return -1;
  }
  audio_channels_sending_.insert(audio_channel);

  CriticalSectionScoped lock(*audio_cs_);
  audio_clients_++;
  return 0;
}

int ViEFilePlayer::StopSendAudioOnChannel(const int audio_channel) {
  int result = 0;
  if (!voe_file_interface_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StopSendAudioOnChannel() - no VoE interface");
    return -1;
  }
  std::set<int>::iterator it = audio_channels_sending_.find(audio_channel);
  if (it == audio_channels_sending_.end()) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StopSendAudioOnChannel AudioChannel %d not "
                 "sending", audio_channel);
    return -1;
  }
  result = voe_file_interface_->StopPlayingFileAsMicrophone(audio_channel);
  if (result != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "ViEFilePlayer::StopSendAudioOnChannel() "
                 "VE_StopPlayingFileAsMicrophone failed. audio_channel %d",
                 audio_channel);
  }
  audio_channels_sending_.erase(audio_channel);
  CriticalSectionScoped lock(*audio_cs_);
  audio_clients_--;
  assert(audio_clients_ >= 0);
  return 0;
}

int ViEFilePlayer::PlayAudioLocally(const int audio_channel,
                                    float volume_scaling) {
  if (!voe_file_interface_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "%s No VEFile interface.", __FUNCTION__);
    return -1;
  }
  if (voe_file_interface_->StartPlayingFileLocally(audio_channel, this,
                                                   kFileFormatPcm16kHzFile,
                                                   volume_scaling) != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "%s  VE_StartPlayingFileAsMicrophone failed. audio_channel %d,"
                 " mix_microphone %d, volume_scaling %.2f",
                 __FUNCTION__, audio_channel, volume_scaling);
    return -1;
  }

  CriticalSectionScoped lock(*audio_cs_);
  local_audio_channel_ = audio_channel;
  audio_clients_++;
  return 0;
}

int ViEFilePlayer::StopPlayAudioLocally(const int audio_channel) {
  if (!voe_file_interface_) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "%s No VEFile interface.", __FUNCTION__);
    return -1;
  }
  if (voe_file_interface_->StopPlayingFileLocally(audio_channel) != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, ViEId(engine_id_, id_),
                 "%s VE_StopPlayingFileLocally failed. audio_channel %d.",
                 __FUNCTION__, audio_channel);
    return -1;
  }

  CriticalSectionScoped lock(*audio_cs_);
  local_audio_channel_ = -1;
  audio_clients_--;
  return 0;
}

int ViEFilePlayer::GetFileInformation(int engine_id,
                                      const char* file_name,
                                      VideoCodec& video_codec,
                                      CodecInst& audio_codec,
                                      const FileFormats file_format) {
  WEBRTC_TRACE(kTraceInfo, kTraceVideo, engine_id, "%s ", __FUNCTION__);

  FilePlayer* file_player = FilePlayer::CreateFilePlayer(engine_id,
                                                         file_format);
  if (!file_player) {
    return -1;
  }

  bool video_only = false;

  memset(&video_codec, 0, sizeof(video_codec));
  memset(&audio_codec, 0, sizeof(audio_codec));

  if (file_player->StartPlayingVideoFile(file_name, false, false) != 0) {
    video_only = true;
    if (file_player->StartPlayingVideoFile(file_name, false, true) != 0) {
      WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id,
                   "%s Failed to open file.", __FUNCTION__);
      FilePlayer::DestroyFilePlayer(file_player);
      return -1;
    }
  }

  if (!video_only && file_player->AudioCodec(audio_codec) != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id,
                 "%s Failed to get audio codec.", __FUNCTION__);
    FilePlayer::DestroyFilePlayer(file_player);
    return -1;
  }
  if (file_player->video_codec_info(video_codec) != 0) {
    WEBRTC_TRACE(kTraceError, kTraceVideo, engine_id,
                 "%s Failed to get video codec.", __FUNCTION__);
    FilePlayer::DestroyFilePlayer(file_player);
    return -1;
  }
  FilePlayer::DestroyFilePlayer(file_player);
  return 0;
}

}  // namespace webrtc
