/*
 *  Copyright (c) 2013 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 "webrtc/modules/video_render/ios/video_render_ios_gles20.h"
#include "webrtc/system_wrappers/interface/critical_section_wrapper.h"
#include "webrtc/system_wrappers/interface/event_wrapper.h"
#include "webrtc/system_wrappers/interface/thread_wrapper.h"

using namespace webrtc;

VideoRenderIosGles20::VideoRenderIosGles20(VideoRenderIosView* view,
                                           bool full_screen,
                                           int render_id)
    : gles_crit_sec_(CriticalSectionWrapper::CreateCriticalSection()),
      screen_update_event_(0),
      screen_update_thread_(0),
      view_(view),
      window_rect_(),
      window_width_(0),
      window_height_(0),
      is_full_screen_(full_screen),
      agl_channels_(),
      z_order_to_channel_(),
      gles_context_([view context]),
      is_rendering_(true),
      id_(render_id) {
  screen_update_thread_ = ThreadWrapper::CreateThread(
      ScreenUpdateThreadProc, this, kRealtimePriority);
  screen_update_event_ = EventWrapper::Create();
  GetWindowRect(window_rect_);
}

VideoRenderIosGles20::~VideoRenderIosGles20() {
  // Signal event to exit thread, then delete it
  ThreadWrapper* thread_wrapper = screen_update_thread_;
  screen_update_thread_ = NULL;

  if (thread_wrapper) {
    thread_wrapper->SetNotAlive();
    screen_update_event_->Set();
    screen_update_event_->StopTimer();

    if (thread_wrapper->Stop()) {
      delete thread_wrapper;
    }
    delete screen_update_event_;
    screen_update_event_ = NULL;
    is_rendering_ = FALSE;
  }

  // Delete all channels
  std::map<int, VideoRenderIosChannel*>::iterator it = agl_channels_.begin();
  while (it != agl_channels_.end()) {
    delete it->second;
    agl_channels_.erase(it);
    it = agl_channels_.begin();
  }
  agl_channels_.clear();

  // Clean the zOrder map
  std::multimap<int, int>::iterator z_it = z_order_to_channel_.begin();
  while (z_it != z_order_to_channel_.end()) {
    z_order_to_channel_.erase(z_it);
    z_it = z_order_to_channel_.begin();
  }
  z_order_to_channel_.clear();
}

int VideoRenderIosGles20::Init() {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  if (!screen_update_thread_) {
    return -1;
  }

  if (!view_) {
    view_ = [[VideoRenderIosView alloc] init];
  }

  if (![view_ createContext]) {
    return -1;
  }

  unsigned int thread_id;
  screen_update_thread_->Start(thread_id);

  // Start the event triggering the render process
  unsigned int monitor_freq = 60;
  screen_update_event_->StartTimer(true, 1000 / monitor_freq);

  window_width_ = window_rect_.right - window_rect_.left;
  window_height_ = window_rect_.bottom - window_rect_.top;

  return 0;
}

VideoRenderIosChannel* VideoRenderIosGles20::CreateEaglChannel(int channel,
                                                               int z_order,
                                                               float left,
                                                               float top,
                                                               float right,
                                                               float bottom) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  if (HasChannel(channel)) {
    return NULL;
  }

  VideoRenderIosChannel* new_eagl_channel = new VideoRenderIosChannel(view_);

  if (new_eagl_channel->SetStreamSettings(z_order, left, top, right, bottom) ==
      -1) {
    return NULL;
  }

  agl_channels_[channel] = new_eagl_channel;
  z_order_to_channel_.insert(std::pair<int, int>(z_order, channel));

  return new_eagl_channel;
}

int VideoRenderIosGles20::DeleteEaglChannel(int channel) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  std::map<int, VideoRenderIosChannel*>::iterator it;
  it = agl_channels_.find(channel);
  if (it != agl_channels_.end()) {
    delete it->second;
    agl_channels_.erase(it);
  } else {
    return -1;
  }

  std::multimap<int, int>::iterator z_it = z_order_to_channel_.begin();
  while (z_it != z_order_to_channel_.end()) {
    if (z_it->second == channel) {
      z_order_to_channel_.erase(z_it);
      break;
    }
    z_it++;
  }

  return 0;
}

bool VideoRenderIosGles20::HasChannel(int channel) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  std::map<int, VideoRenderIosChannel*>::iterator it =
      agl_channels_.find(channel);

  if (it != agl_channels_.end()) {
    return true;
  }

  return false;
}

// Rendering process
bool VideoRenderIosGles20::ScreenUpdateThreadProc(void* obj) {
  return static_cast<VideoRenderIosGles20*>(obj)->ScreenUpdateProcess();
}

bool VideoRenderIosGles20::ScreenUpdateProcess() {
  screen_update_event_->Wait(100);

  CriticalSectionScoped cs(gles_crit_sec_.get());

  if (!is_rendering_) {
    return false;
  }

  if (!screen_update_thread_) {
    return false;
  }

  if (GetWindowRect(window_rect_) == -1) {
    return true;
  }

  if (window_width_ != (window_rect_.right - window_rect_.left) ||
      window_height_ != (window_rect_.bottom - window_rect_.top)) {
    window_width_ = window_rect_.right - window_rect_.left;
    window_height_ = window_rect_.bottom - window_rect_.top;
  }

  // Check if there are any updated buffers
  bool updated = false;

  std::map<int, VideoRenderIosChannel*>::iterator it = agl_channels_.begin();
  while (it != agl_channels_.end()) {
    VideoRenderIosChannel* agl_channel = it->second;

    updated = agl_channel->IsUpdated();
    if (updated) {
      break;
    }
    it++;
  }

  if (updated) {
    // At least one buffer has been updated, we need to repaint the texture
    // Loop through all channels starting highest zOrder ending with lowest.
    for (std::multimap<int, int>::reverse_iterator r_it =
             z_order_to_channel_.rbegin();
         r_it != z_order_to_channel_.rend();
         r_it++) {
      int channel_id = r_it->second;
      std::map<int, VideoRenderIosChannel*>::iterator it =
          agl_channels_.find(channel_id);

      VideoRenderIosChannel* agl_channel = it->second;

      agl_channel->RenderOffScreenBuffer();
    }

    [view_ presentFramebuffer];
  }

  return true;
}

int VideoRenderIosGles20::GetWindowRect(Rect& rect) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  if (!view_) {
    return -1;
  }

  CGRect bounds = [view_ bounds];
  rect.top = bounds.origin.y;
  rect.left = bounds.origin.x;
  rect.bottom = bounds.size.height + bounds.origin.y;
  rect.right = bounds.size.width + bounds.origin.x;

  return 0;
}

int VideoRenderIosGles20::ChangeWindow(void* new_window) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  view_ = (VideoRenderIosView*)new_window;

  return 0;
}

int VideoRenderIosGles20::ChangeUniqueID(int unique_id) {
  CriticalSectionScoped cs(gles_crit_sec_.get());

  id_ = unique_id;

  return 0;
}

int VideoRenderIosGles20::StartRender() {
  is_rendering_ = true;
  return 0;
}

int VideoRenderIosGles20::StopRender() {
  is_rendering_ = false;
  return 0;
}

int VideoRenderIosGles20::GetScreenResolution(uint& screen_width,
                                              uint& screen_height) {
  screen_width = [view_ bounds].size.width;
  screen_height = [view_ bounds].size.height;
  return 0;
}

int VideoRenderIosGles20::SetStreamCropping(const uint stream_id,
                                            const float left,
                                            const float top,
                                            const float right,
                                            const float bottom) {
  // Check if there are any updated buffers
  // bool updated = false;
  uint counter = 0;

  std::map<int, VideoRenderIosChannel*>::iterator it = agl_channels_.begin();
  while (it != agl_channels_.end()) {
    if (counter == stream_id) {
      VideoRenderIosChannel* agl_channel = it->second;
      agl_channel->SetStreamSettings(0, left, top, right, bottom);
    }
    counter++;
    it++;
  }

  return 0;
}
