// Copyright 2020 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 "ash/accessibility/point_scan_controller.h"

#include "ash/shell.h"
#include "ash/test/ash_test_base.h"
#include "ash/test/test_window_builder.h"
#include "base/bind.h"
#include "base/command_line.h"
#include "base/macros.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/aura/window.h"
#include "ui/compositor/compositor_switches.h"
#include "ui/gfx/image/image.h"
#include "ui/snapshot/snapshot.h"

namespace ash {

class PointScanControllerTest : public AshTestBase {
 protected:
  PointScanControllerTest() = default;
  ~PointScanControllerTest() override = default;

  // AshTestBase:
  void SetUp() override {
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ::switches::kEnableSwitchAccessPointScanning);
    base::CommandLine::ForCurrentProcess()->AppendSwitch(
        ::switches::kEnablePixelOutputInTests);
    AshTestBase::SetUp();
  }

  void CaptureBeforeImage(const gfx::Rect& bounds) {
    Capture(bounds);
    if (before_bmp_.tryAllocPixels(image_.AsBitmap().info())) {
      image_.AsBitmap().readPixels(before_bmp_.info(), before_bmp_.getPixels(),
                                   before_bmp_.rowBytes(), 0, 0);
    }
  }

  void CaptureAfterImage(const gfx::Rect& bounds) {
    Capture(bounds);
    if (after_bmp_.tryAllocPixels(image_.AsBitmap().info())) {
      image_.AsBitmap().readPixels(after_bmp_.info(), after_bmp_.getPixels(),
                                   after_bmp_.rowBytes(), 0, 0);
    }
  }

  void Capture(const gfx::Rect& bounds) {
    // Occasionally we don't get any pixels the first try.
    // Keep trying until we get the correct size bitmap and the first pixel
    // is transparent.
    while (true) {
      aura::Window* window = Shell::GetPrimaryRootWindow();
      base::RunLoop run_loop;
      ui::GrabWindowSnapshotAndScaleAsync(
          window, bounds, bounds.size(),
          base::BindOnce(
              [](base::RunLoop* run_loop, gfx::Image* image,
                 gfx::Image got_image) {
                run_loop->Quit();
                *image = got_image;
              },
              &run_loop, &image_));
      run_loop.Run();
      SkBitmap bitmap = image_.AsBitmap();
      if (bitmap.width() != bounds.width() ||
          bitmap.height() != bounds.height()) {
        LOG(INFO) << "Bitmap not correct size, trying to capture again";
        continue;
      } else if (255 == SkColorGetA(bitmap.getColor(0, 0))) {
        LOG(INFO) << "Bitmap is transparent, trying to capture again";
        break;
      }
    }
  }

  void ComputeImageStats() {
    diff_count_ = 0;
    row_diff_count_ = 0;
    col_diff_count_ = 0;
    bool row_diff_tracker_[before_bmp_.height()];
    for (int i = 0; i < before_bmp_.height(); ++i) {
      row_diff_tracker_[i] = false;
    }

    for (int x = 0; x < before_bmp_.width(); ++x) {
      bool col_diff = false;
      for (int y = 0; y < before_bmp_.height(); ++y) {
        SkColor before_color = before_bmp_.getColor(x, y);
        SkColor after_color = after_bmp_.getColor(x, y);
        if (before_color != after_color) {
          diff_count_++;
          col_diff = true;
          row_diff_tracker_[y] = true;
        }
      }
      if (col_diff)
        ++col_diff_count_;
    }

    for (int i = 0; i < before_bmp_.height(); ++i) {
      if (row_diff_tracker_[i])
        ++row_diff_count_;
    }
  }

  int diff_count() const { return diff_count_; }
  int row_diff_count() const { return row_diff_count_; }
  int col_diff_count() const { return col_diff_count_; }

 private:
  gfx::Image image_;
  SkBitmap before_bmp_;
  SkBitmap after_bmp_;
  int diff_count_ = 0;
  int row_diff_count_ = 0;
  int col_diff_count_ = 0;

  DISALLOW_COPY_AND_ASSIGN(PointScanControllerTest);
};

TEST_F(PointScanControllerTest, StartScanning) {
  gfx::Rect bounds = Shell::GetPrimaryRootWindow()->bounds();

  // Create a white background window for captured image color smoke test.
  std::unique_ptr<aura::Window> window =
      TestWindowBuilder()
          .SetColorWindowDelegate(SK_ColorWHITE)
          .SetBounds(bounds)
          .Build();

  // For some reason, the white window is not fully drawn the first time we call
  // CaptureBeforeImage.
  CaptureBeforeImage(bounds);
  CaptureBeforeImage(bounds);

  PointScanController controller;
  controller.StartHorizontalRangeScan();

  CaptureAfterImage(bounds);
  ComputeImageStats();

}

}  // namespace ash
