/*
 *  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 "webrtc/video_engine/vie_renderer.h"

#include "webrtc/common_video/libyuv/include/webrtc_libyuv.h"
#include "webrtc/modules/video_render/include/video_render.h"
#include "webrtc/modules/video_render/include/video_render_defines.h"
#include "webrtc/video_engine/vie_render_manager.h"

namespace webrtc {

ViERenderer* ViERenderer::CreateViERenderer(const int32_t render_id,
                                            const int32_t engine_id,
                                            VideoRender& render_module,
                                            ViERenderManager& render_manager,
                                            const uint32_t z_order,
                                            const float left,
                                            const float top,
                                            const float right,
                                            const float bottom) {
  ViERenderer* self = new ViERenderer(render_id, engine_id, render_module,
                                      render_manager);
  if (!self || self->Init(z_order, left, top, right, bottom) != 0) {
    delete self;
    self = NULL;
  }
  return self;
}

ViERenderer::~ViERenderer(void) {
  if (render_callback_)
    render_module_.DeleteIncomingRenderStream(render_id_);

  delete incoming_external_callback_;
}

int32_t ViERenderer::StartRender() {
  return render_module_.StartRender(render_id_);
}
int32_t ViERenderer::StopRender() {
  return render_module_.StopRender(render_id_);
}

int ViERenderer::SetExpectedRenderDelay(int render_delay) {
  return render_module_.SetExpectedRenderDelay(render_id_, render_delay);
}

int32_t ViERenderer::ConfigureRenderer(const unsigned int z_order,
                                       const float left,
                                       const float top,
                                       const float right,
                                       const float bottom) {
  return render_module_.ConfigureRenderer(render_id_, z_order, left, top, right,
                                          bottom);
}

VideoRender& ViERenderer::RenderModule() {
  return render_module_;
}

int32_t ViERenderer::SetTimeoutImage(const I420VideoFrame& timeout_image,
                                     const int32_t timeout_value) {
  return render_module_.SetTimeoutImage(render_id_, timeout_image,
                                        timeout_value);
}

int32_t  ViERenderer::SetRenderStartImage(
    const I420VideoFrame& start_image) {
  return render_module_.SetStartImage(render_id_, start_image);
}

int32_t ViERenderer::SetExternalRenderer(
    const int32_t render_id,
    RawVideoType video_input_format,
    ExternalRenderer* external_renderer) {
  if (!incoming_external_callback_)
    return -1;

  incoming_external_callback_->SetViEExternalRenderer(external_renderer,
                                                      video_input_format);
  return render_module_.AddExternalRenderCallback(render_id,
                                                  incoming_external_callback_);
}

int32_t ViERenderer::SetVideoRenderCallback(int32_t render_id,
                                            VideoRenderCallback* callback) {
  return render_module_.AddExternalRenderCallback(render_id, callback);
}

ViERenderer::ViERenderer(const int32_t render_id,
                         const int32_t engine_id,
                         VideoRender& render_module,
                         ViERenderManager& render_manager)
    : render_id_(render_id),
      render_module_(render_module),
      render_manager_(render_manager),
      render_callback_(NULL),
      incoming_external_callback_(new ViEExternalRendererImpl()) {
}

int32_t ViERenderer::Init(const uint32_t z_order,
                          const float left,
                          const float top,
                          const float right,
                          const float bottom) {
  render_callback_ =
      static_cast<VideoRenderCallback*>(render_module_.AddIncomingRenderStream(
          render_id_, z_order, left, top, right, bottom));
  if (!render_callback_) {
    // Logging done.
    return -1;
  }
  return 0;
}

void ViERenderer::DeliverFrame(int id,
                               I420VideoFrame* video_frame,
                               const std::vector<uint32_t>& csrcs) {
  render_callback_->RenderFrame(render_id_, *video_frame);
}

void ViERenderer::DelayChanged(int id, int frame_delay) {}

int ViERenderer::GetPreferedFrameSettings(int* width,
                                          int* height,
                                          int* frame_rate) {
    return -1;
}

void ViERenderer::ProviderDestroyed(int id) {
  // Remove the render stream since the provider is destroyed.
  render_manager_.RemoveRenderStream(render_id_);
}

ViEExternalRendererImpl::ViEExternalRendererImpl()
    : external_renderer_(NULL),
      external_renderer_format_(kVideoUnknown),
      external_renderer_width_(0),
      external_renderer_height_(0) {
}

int ViEExternalRendererImpl::SetViEExternalRenderer(
    ExternalRenderer* external_renderer,
    RawVideoType video_input_format) {
  external_renderer_ = external_renderer;
  external_renderer_format_ = video_input_format;
  return 0;
}

int32_t ViEExternalRendererImpl::RenderFrame(
    const uint32_t stream_id,
    const I420VideoFrame& video_frame) {
  if (external_renderer_format_ != kVideoI420)
    return ConvertAndRenderFrame(stream_id, video_frame);

  // Fast path for I420 without frame copy.
  NotifyFrameSizeChange(stream_id, video_frame);
  if (video_frame.native_handle() == NULL ||
      external_renderer_->IsTextureSupported()) {
    external_renderer_->DeliverI420Frame(video_frame);
  } else {
    // TODO(wuchengli): readback the pixels and deliver the frame.
  }
  return 0;
}

int32_t ViEExternalRendererImpl::ConvertAndRenderFrame(
    uint32_t stream_id,
    const I420VideoFrame& video_frame) {
  if (video_frame.native_handle() != NULL) {
    NotifyFrameSizeChange(stream_id, video_frame);

    if (external_renderer_->IsTextureSupported()) {
      external_renderer_->DeliverFrame(NULL,
                                       0,
                                       video_frame.timestamp(),
                                       video_frame.ntp_time_ms(),
                                       video_frame.render_time_ms(),
                                       video_frame.native_handle());
    } else {
      // TODO(wuchengli): readback the pixels and deliver the frame.
    }
    return 0;
  }

  // Convert to requested format.
  VideoType type =
      RawVideoTypeToCommonVideoVideoType(external_renderer_format_);
  size_t buffer_size = CalcBufferSize(type, video_frame.width(),
                                      video_frame.height());
  if (buffer_size == 0) {
    // Unsupported video format.
    assert(false);
    return -1;
  }
  converted_frame_.resize(buffer_size);
  uint8_t* out_frame = &converted_frame_[0];

  switch (external_renderer_format_) {
    case kVideoYV12:
    case kVideoYUY2:
    case kVideoUYVY:
    case kVideoARGB:
    case kVideoRGB24:
    case kVideoRGB565:
    case kVideoARGB4444:
    case kVideoARGB1555:
      if (ConvertFromI420(video_frame, type, 0, out_frame) < 0)
        return -1;
      break;
    case kVideoIYUV:
      // no conversion available
      break;
    default:
      assert(false);
      out_frame = NULL;
      break;
  }

  NotifyFrameSizeChange(stream_id, video_frame);

  if (out_frame) {
    external_renderer_->DeliverFrame(out_frame,
                                     converted_frame_.size(),
                                     video_frame.timestamp(),
                                     video_frame.ntp_time_ms(),
                                     video_frame.render_time_ms(),
                                     NULL);
  }
  return 0;
}

void ViEExternalRendererImpl::NotifyFrameSizeChange(
    const uint32_t stream_id,
    const I420VideoFrame& video_frame) {
  if (external_renderer_width_ != video_frame.width() ||
      external_renderer_height_ != video_frame.height()) {
    external_renderer_width_ = video_frame.width();
    external_renderer_height_ = video_frame.height();
    external_renderer_->FrameSizeChange(
        external_renderer_width_, external_renderer_height_, stream_id);
  }
}

}  // namespace webrtc
