// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/compositor/reflector_impl.h"

#include "base/bind.h"
#include "base/location.h"
#include "content/browser/compositor/browser_compositor_output_surface.h"
#include "content/common/gpu/client/gl_helper.h"
#include "ui/compositor/layer.h"

namespace content {

ReflectorImpl::ReflectorImpl(
    ui::Compositor* mirrored_compositor,
    ui::Layer* mirroring_layer,
    IDMap<BrowserCompositorOutputSurface>* output_surface_map,
    int surface_id)
    : texture_id_(0),
      texture_size_(mirrored_compositor->size()),
      output_surface_map_(output_surface_map),
      mirrored_compositor_(mirrored_compositor),
      mirroring_compositor_(mirroring_layer->GetCompositor()),
      mirroring_layer_(mirroring_layer),
      impl_message_loop_(ui::Compositor::GetCompositorMessageLoop()),
      main_message_loop_(base::MessageLoopProxy::current()),
      surface_id_(surface_id) {
  impl_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::InitOnImplThread, this));
}

void ReflectorImpl::InitOnImplThread() {
  // Ignore if the reflector was shutdown before
  // initialized, or it's already initialized.
  if (!output_surface_map_ || gl_helper_.get())
    return;

  BrowserCompositorOutputSurface* source_surface =
      output_surface_map_->Lookup(surface_id_);
  // Skip if the source surface isn't ready yet. This will be
  // initiailze when the source surface becomes ready.
  if (!source_surface)
    return;

  AttachToOutputSurfaceOnImplThread(source_surface);
  // The shared texture doesn't have the data, so invokes full redraw
  // now.
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::FullRedrawContentOnMainThread,
                 scoped_refptr<ReflectorImpl>(this)));
}

void ReflectorImpl::OnSourceSurfaceReady(int surface_id) {
  DCHECK_EQ(surface_id_, surface_id);
  InitOnImplThread();
}

void ReflectorImpl::Shutdown() {
  mirroring_compositor_ = NULL;
  mirroring_layer_ = NULL;
  {
    base::AutoLock lock(texture_lock_);
    texture_id_ = 0;
  }
  shared_texture_ = NULL;
  impl_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::ShutdownOnImplThread, this));
}

void ReflectorImpl::ShutdownOnImplThread() {
  BrowserCompositorOutputSurface* output_surface =
      output_surface_map_->Lookup(surface_id_);
  if (output_surface)
    output_surface->SetReflector(NULL);
  output_surface_map_ = NULL;
  gl_helper_.reset();
  // The instance must be deleted on main thread.
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::DeleteOnMainThread,
                 scoped_refptr<ReflectorImpl>(this)));
}

void ReflectorImpl::ReattachToOutputSurfaceFromMainThread(
    BrowserCompositorOutputSurface* output_surface) {
  impl_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::AttachToOutputSurfaceOnImplThread,
                 this, output_surface));
}

void ReflectorImpl::OnMirroringCompositorResized() {
  mirroring_compositor_->ScheduleFullRedraw();
}

void ReflectorImpl::OnLostResources() {
  mirroring_layer_->SetShowPaintedContent();
}

void ReflectorImpl::OnReshape(gfx::Size size) {
  if (texture_size_ == size)
    return;
  texture_size_ = size;
  {
    base::AutoLock lock(texture_lock_);
    if (texture_id_) {
      gl_helper_->ResizeTexture(texture_id_, size);
      gl_helper_->Flush();
    }
  }
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::UpdateTextureSizeOnMainThread,
                 this->AsWeakPtr(),
                 texture_size_));
}

void ReflectorImpl::OnSwapBuffers() {
  {
    base::AutoLock lock(texture_lock_);
    if (texture_id_) {
      gl_helper_->CopyTextureFullImage(texture_id_, texture_size_);
      gl_helper_->Flush();
    }
  }
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::FullRedrawOnMainThread,
                 this->AsWeakPtr(),
                 texture_size_));
}

void ReflectorImpl::OnPostSubBuffer(gfx::Rect rect) {
  {
    base::AutoLock lock(texture_lock_);
    if (texture_id_) {
      gl_helper_->CopyTextureSubImage(texture_id_, rect);
      gl_helper_->Flush();
    }
  }
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::UpdateSubBufferOnMainThread,
                 this->AsWeakPtr(),
                 texture_size_,
                 rect));
}

void ReflectorImpl::CreateSharedTextureOnMainThread(gfx::Size size) {
  {
    base::AutoLock lock(texture_lock_);
    texture_id_ =
        ImageTransportFactory::GetInstance()->GetGLHelper()->CreateTexture();
    shared_texture_ =
        ImageTransportFactory::GetInstance()->CreateOwnedTexture(
            size, 1.0f, texture_id_);
  }
  mirroring_layer_->SetExternalTexture(shared_texture_.get());
  FullRedrawOnMainThread(size);
}

ReflectorImpl::~ReflectorImpl() {
  // Make sure the reflector is deleted on main thread.
  DCHECK_EQ(main_message_loop_.get(),
            base::MessageLoopProxy::current().get());
}

void ReflectorImpl::AttachToOutputSurfaceOnImplThread(
    BrowserCompositorOutputSurface* output_surface) {
  output_surface->context_provider()->BindToCurrentThread();
  gl_helper_.reset(
      new GLHelper(output_surface->context_provider()->ContextGL(),
                   output_surface->context_provider()->ContextSupport()));
  output_surface->SetReflector(this);
  main_message_loop_->PostTask(
      FROM_HERE,
      base::Bind(&ReflectorImpl::CreateSharedTextureOnMainThread,
                 this->AsWeakPtr(),
                 texture_size_));
}

void ReflectorImpl::UpdateTextureSizeOnMainThread(gfx::Size size) {
  if (!mirroring_layer_)
    return;
  mirroring_layer_->SetBounds(gfx::Rect(size));
}

void ReflectorImpl::FullRedrawOnMainThread(gfx::Size size) {
  if (!mirroring_compositor_)
    return;
  UpdateTextureSizeOnMainThread(size);
  mirroring_compositor_->ScheduleFullRedraw();
}

void ReflectorImpl::UpdateSubBufferOnMainThread(gfx::Size size,
                                                gfx::Rect rect) {
  if (!mirroring_compositor_)
    return;
  UpdateTextureSizeOnMainThread(size);
  // Flip the coordinates to compositor's one.
  int y = size.height() - rect.y() - rect.height();
  gfx::Rect new_rect(rect.x(), y, rect.width(), rect.height());
  mirroring_layer_->SchedulePaint(new_rect);
}

void ReflectorImpl::FullRedrawContentOnMainThread() {
  mirrored_compositor_->ScheduleFullRedraw();
}

}  // namespace content
