// Copyright (c) 2010 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 "chrome/gpu/gpu_view_win.h"

#include "chrome/common/gpu_messages.h"
#include "chrome/gpu/gpu_backing_store_win.h"
#include "chrome/gpu/gpu_thread.h"

namespace {

void DrawBackground(const RECT& dirty_rect, CPaintDC* dc) {
  HBRUSH white_brush = reinterpret_cast<HBRUSH>(GetStockObject(WHITE_BRUSH));
  dc->FillRect(&dirty_rect, white_brush);
}

void DrawResizeCorner(const RECT& dirty_rect, HDC dc) {
  // TODO(brettw): implement this.
}

}  // namespace

GpuViewWin::GpuViewWin(GpuThread* gpu_thread,
                       HWND parent,
                       int32 routing_id)
    : gpu_thread_(gpu_thread),
      routing_id_(routing_id),
      parent_(parent) {
  gpu_thread_->AddRoute(routing_id_, this);
  Create(parent_);
  SetWindowText(L"GPU window");
  ShowWindow(SW_SHOW);
}

GpuViewWin::~GpuViewWin() {
  gpu_thread_->RemoveRoute(routing_id_);
  // TODO(brettw) may want to delete any dangling backing stores, or perhaps
  // assert if one still exists.
}

void GpuViewWin::OnMessageReceived(const IPC::Message& msg) {
  IPC_BEGIN_MESSAGE_MAP(GpuViewWin, msg)
    IPC_MESSAGE_HANDLER(GpuMsg_NewBackingStore, OnNewBackingStore)
  IPC_END_MESSAGE_MAP_EX()
}

void GpuViewWin::OnChannelConnected(int32 peer_pid) {
}

void GpuViewWin::OnChannelError() {
  // TODO(brettw) do we need to delete ourselves now?
}

void GpuViewWin::DidScrollBackingStoreRect(int dx, int dy,
                                           const gfx::Rect& rect) {
  // We need to pass in SW_INVALIDATE to ScrollWindowEx.  The documentation on
  // MSDN states that it only applies to the HRGN argument, which is wrong.
  // Not passing in this flag does not invalidate the region which was scrolled
  // from, thus causing painting issues.
  RECT clip_rect = rect.ToRECT();
  ScrollWindowEx(dx, dy, NULL, &clip_rect, NULL, NULL, SW_INVALIDATE);
}

void GpuViewWin::OnNewBackingStore(int32 routing_id, const gfx::Size& size) {
  backing_store_.reset(
      new GpuBackingStoreWin(this, gpu_thread_, routing_id, size));
  MoveWindow(0, 0, size.width(), size.height(), TRUE);
}

void GpuViewWin::OnPaint(HDC unused_dc) {
  // Grab the region to paint before creation of paint_dc since it clears the
  // damage region.
  ScopedGDIObject<HRGN> damage_region(CreateRectRgn(0, 0, 0, 0));
  GetUpdateRgn(damage_region, FALSE);

  CPaintDC paint_dc(m_hWnd);

  gfx::Rect damaged_rect(paint_dc.m_ps.rcPaint);
  if (damaged_rect.IsEmpty())
    return;

  if (backing_store_.get()) {
    gfx::Rect bitmap_rect(gfx::Point(), backing_store_->size());

    // Blit only the damaged regions from the backing store.
    DWORD data_size = GetRegionData(damage_region, 0, NULL);
    // TODO(brettw) why is the "+1" necessary here? When I remove it, the
    // page paints black, but according to the documentation, its not needed.
    scoped_array<char> region_data_buf(new char[data_size + 1]);
    RGNDATA* region_data = reinterpret_cast<RGNDATA*>(region_data_buf.get());
    GetRegionData(damage_region, data_size, region_data);

    RECT* region_rects = reinterpret_cast<RECT*>(region_data->Buffer);
    for (DWORD i = 0; i < region_data->rdh.nCount; ++i) {
      gfx::Rect paint_rect = bitmap_rect.Intersect(gfx::Rect(region_rects[i]));
      if (!paint_rect.IsEmpty()) {
        DrawResizeCorner(paint_rect.ToRECT(), backing_store_->hdc());
        BitBlt(paint_dc.m_hDC,
               paint_rect.x(),
               paint_rect.y(),
               paint_rect.width(),
               paint_rect.height(),
               backing_store_->hdc(),
               paint_rect.x(),
               paint_rect.y(),
               SRCCOPY);
      }
    }

    // Fill the remaining portion of the damaged_rect with the background
    if (damaged_rect.right() > bitmap_rect.right()) {
      RECT r;
      r.left = std::max(bitmap_rect.right(), damaged_rect.x());
      r.right = damaged_rect.right();
      r.top = damaged_rect.y();
      r.bottom = std::min(bitmap_rect.bottom(), damaged_rect.bottom());
      DrawBackground(r, &paint_dc);
    }
    if (damaged_rect.bottom() > bitmap_rect.bottom()) {
      RECT r;
      r.left = damaged_rect.x();
      r.right = damaged_rect.right();
      r.top = std::max(bitmap_rect.bottom(), damaged_rect.y());
      r.bottom = damaged_rect.bottom();
      DrawBackground(r, &paint_dc);
    }
  } else {
    DrawBackground(paint_dc.m_ps.rcPaint, &paint_dc);
  }
}