// Copyright (c) 2012 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/browser/prerender/prerender_tab_helper.h"

#include "base/metrics/histogram.h"
#include "base/string_number_conversions.h"
#include "base/time.h"
#include "chrome/browser/prerender/prerender_histograms.h"
#include "chrome/browser/prerender/prerender_manager.h"
#include "chrome/browser/prerender/prerender_manager_factory.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/tab_contents/core_tab_helper.h"
#include "chrome/browser/ui/tab_contents/tab_contents.h"
#include "content/public/browser/render_view_host.h"
#include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_view.h"
#include "skia/ext/platform_canvas.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/gfx/rect.h"

using content::WebContents;

namespace prerender {

// Helper class to compute pixel-based stats on the paint progress
// between when a prerendered page is swapped in and when the onload event
// fires.
class PrerenderTabHelper::PixelStats {
 public:
  explicit PixelStats(PrerenderTabHelper* tab_helper) :
      weak_factory_(this),
      tab_helper_(tab_helper) {
  }

  // Reasons why we need to fetch bitmaps: either a prerender was swapped in,
  // or a prerendered page has finished loading.
  enum BitmapType {
      BITMAP_SWAP_IN,
      BITMAP_ON_LOAD
  };

  void GetBitmap(BitmapType bitmap_type, WebContents* web_contents) {
    if (bitmap_type == BITMAP_SWAP_IN) {
      bitmap_.reset();
      bitmap_web_contents_ = web_contents;
    }

    if (bitmap_type == BITMAP_ON_LOAD && bitmap_web_contents_ != web_contents)
      return;

    if (!web_contents || !web_contents->GetView() ||
        !web_contents->GetRenderViewHost()) {
      return;
    }

    skia::PlatformCanvas* temp_canvas = new skia::PlatformCanvas;
    web_contents->GetRenderViewHost()->CopyFromBackingStore(
        gfx::Rect(),
        gfx::Size(),
        base::Bind(&PrerenderTabHelper::PixelStats::HandleBitmapResult,
                   weak_factory_.GetWeakPtr(),
                   bitmap_type,
                   web_contents,
                   base::Owned(temp_canvas)),
        temp_canvas);
  }

 private:
  void HandleBitmapResult(BitmapType bitmap_type,
                          WebContents* web_contents,
                          skia::PlatformCanvas* temp_canvas,
                          bool succeeded) {
    scoped_ptr<SkBitmap> bitmap;
    if (succeeded) {
      const SkBitmap& canvas_bitmap =
          skia::GetTopDevice(*temp_canvas)->accessBitmap(false);
      bitmap.reset(new SkBitmap());
      canvas_bitmap.copyTo(bitmap.get(), SkBitmap::kARGB_8888_Config);
    }

    if (bitmap_web_contents_ != web_contents)
      return;

    if (bitmap_type == BITMAP_SWAP_IN)
      bitmap_.swap(bitmap);

    if (bitmap_type == BITMAP_ON_LOAD) {
      PrerenderManager* prerender_manager =
          tab_helper_->MaybeGetPrerenderManager();
      if (prerender_manager) {
        prerender_manager->histograms()->RecordFractionPixelsFinalAtSwapin(
            CompareBitmaps(bitmap_.get(), bitmap.get()));
      }
      bitmap_.reset();
      bitmap_web_contents_ = NULL;
    }
  }

  // Helper comparing two bitmaps of identical size.
  // Returns a value < 0.0 if there is an error, and otherwise, a double in
  // [0, 1] indicating the fraction of pixels that are the same.
  double CompareBitmaps(SkBitmap* bitmap1, SkBitmap* bitmap2) {
    if (!bitmap1 || !bitmap2) {
      return -2.0;
    }
    if (bitmap1->width() != bitmap2->width() ||
        bitmap1->height() != bitmap2->height()) {
      return -1.0;
    }
    int pixels = bitmap1->width() * bitmap1->height();
    int same_pixels = 0;
    for (int y = 0; y < bitmap1->height(); ++y) {
      for (int x = 0; x < bitmap1->width(); ++x) {
        if (bitmap1->getColor(x, y) == bitmap2->getColor(x, y))
          same_pixels++;
      }
    }
    return static_cast<double>(same_pixels) / static_cast<double>(pixels);
  }

  // Bitmap of what the last swapped in prerendered tab looked like at swapin,
  // and the WebContents that it was swapped into.
  scoped_ptr<SkBitmap> bitmap_;
  WebContents* bitmap_web_contents_;

  base::WeakPtrFactory<PixelStats> weak_factory_;

  PrerenderTabHelper* tab_helper_;
};

PrerenderTabHelper::PrerenderTabHelper(TabContents* tab)
    : content::WebContentsObserver(tab->web_contents()),
      tab_(tab) {
}

PrerenderTabHelper::~PrerenderTabHelper() {
}

void PrerenderTabHelper::ProvisionalChangeToMainFrameUrl(
    const GURL& url,
    const GURL& opener_url,
    content::RenderViewHost* render_view_host) {
  url_ = url;
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (!prerender_manager)
    return;
  if (prerender_manager->IsWebContentsPrerendering(web_contents()))
    return;
  prerender_manager->MarkWebContentsAsNotPrerendered(web_contents());
}

void PrerenderTabHelper::DidCommitProvisionalLoadForFrame(
    int64 frame_id,
    bool is_main_frame,
    const GURL& validated_url,
    content::PageTransition transition_type,
    content::RenderViewHost* render_view_host) {
  if (!is_main_frame)
    return;
  url_ = validated_url;
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (!prerender_manager)
    return;
  if (prerender_manager->IsWebContentsPrerendering(web_contents()))
    return;
  prerender_manager->RecordNavigation(validated_url);
}

void PrerenderTabHelper::DidStopLoading() {
  // Compute the PPLT metric and report it in a histogram, if needed.
  // We include pages that are still prerendering and have just finished
  // loading -- PrerenderManager will sort this out and handle it correctly
  // (putting those times into a separate histogram).
  if (!pplt_load_start_.is_null()) {
    double fraction_elapsed_at_swapin = -1.0;
    base::TimeTicks now = base::TimeTicks::Now();
    if (!actual_load_start_.is_null()) {
      double plt = (now - actual_load_start_).InMillisecondsF();
      if (plt > 0.0) {
        fraction_elapsed_at_swapin = 1.0 -
            (now - pplt_load_start_).InMillisecondsF() / plt;
      } else {
        fraction_elapsed_at_swapin = 1.0;
      }
      DCHECK_GE(fraction_elapsed_at_swapin, 0.0);
      DCHECK_LE(fraction_elapsed_at_swapin, 1.0);
    }
    PrerenderManager::RecordPerceivedPageLoadTime(
        now - pplt_load_start_, fraction_elapsed_at_swapin, web_contents(),
        url_);
    if (IsPrerendered() && pixel_stats_.get())
      pixel_stats_->GetBitmap(PixelStats::BITMAP_ON_LOAD, web_contents());
  }

  // Reset the PPLT metric.
  pplt_load_start_ = base::TimeTicks();
  actual_load_start_ = base::TimeTicks();
}

void PrerenderTabHelper::DidStartProvisionalLoadForFrame(
      int64 frame_id,
      bool is_main_frame,
      const GURL& validated_url,
      bool is_error_page,
      content::RenderViewHost* render_view_host) {
  if (is_main_frame) {
    // Record the beginning of a new PPLT navigation.
    pplt_load_start_ = base::TimeTicks::Now();
    actual_load_start_ = base::TimeTicks();
  }
}

PrerenderManager* PrerenderTabHelper::MaybeGetPrerenderManager() const {
  return PrerenderManagerFactory::GetForProfile(
      Profile::FromBrowserContext(web_contents()->GetBrowserContext()));
}

bool PrerenderTabHelper::IsPrerendering() {
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (!prerender_manager)
    return false;
  return prerender_manager->IsWebContentsPrerendering(web_contents());
}

bool PrerenderTabHelper::IsPrerendered() {
  PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
  if (!prerender_manager)
    return false;
  return prerender_manager->IsWebContentsPrerendered(web_contents());
}

void PrerenderTabHelper::PrerenderSwappedIn() {
  // Ensure we are not prerendering any more.
  DCHECK(!IsPrerendering());
  if (pplt_load_start_.is_null()) {
    // If we have already finished loading, report a 0 PPLT.
    PrerenderManager::RecordPerceivedPageLoadTime(base::TimeDelta(), 1.0,
                                                  web_contents(), url_);
    PrerenderManager* prerender_manager = MaybeGetPrerenderManager();
    if (prerender_manager)
      prerender_manager->histograms()->RecordFractionPixelsFinalAtSwapin(1.0);
  } else {
    // If we have not finished loading yet, record the actual load start, and
    // rebase the start time to now.
    actual_load_start_ = pplt_load_start_;
    pplt_load_start_ = base::TimeTicks::Now();
    if (pixel_stats_.get())
      pixel_stats_->GetBitmap(PixelStats::BITMAP_SWAP_IN, web_contents());
  }
}

}  // namespace prerender