// Copyright 2018 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 "components/exo/wayland/clients/fullscreen_shell.h"

#include "base/command_line.h"
#include "base/logging.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/stringprintf.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkRefCnt.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrBackendSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"
#include "third_party/skia/include/gpu/gl/GrGLAssembleInterface.h"
#include "third_party/skia/include/gpu/gl/GrGLInterface.h"
#include "ui/gfx/geometry/rect.h"
#include "ui/gfx/skia_util.h"
#include "ui/gl/gl_bindings.h"

namespace exo {
namespace wayland {
namespace clients {

namespace {

void FrameCallback(void* data, wl_callback* callback, uint32_t time) {
  bool* frame_callback_pending = static_cast<bool*>(data);
  *frame_callback_pending = false;
}

}  // namespace

////////////////////////////////////////////////////////////////////////////////
// FullscreenClient, public:

FullscreenClient::FullscreenClient() {}

FullscreenClient::~FullscreenClient() {}

bool FullscreenClient::Run(const InitParams& params) {
  wl_callback_listener frame_listener = {FrameCallback};

  // Wait for screen mode to be received
  while (wl_display_dispatch(display_.get()) != -1) {
    if (done_receiving_modes_ && !has_mode_) {
      LOG(ERROR) << "Did not receive screen mode";
      return false;
    } else if (has_mode_) {
      break;
    }
  }

  AllocateBuffers(params);

  do {
    if (frame_callback_pending_)
      continue;

    if (frame_count_ == frames_)
      break;

    Paint(frame_listener);

  } while (wl_display_dispatch(display_.get()) != -1);

  return true;
}

////////////////////////////////////////////////////////////////////////////////
// FullscreenClient: Private

void FullscreenClient::AllocateBuffers(const InitParams& params) {
  for (size_t i = 0; i < params.num_buffers; ++i) {
    auto buffer = CreateBuffer(size_, params.drm_format, params.bo_usage);
    if (!buffer) {
      LOG(ERROR) << "Failed to create buffer";
      return;
    }
    buffers_.push_back(std::move(buffer));
  }
}

void FullscreenClient::Paint(const wl_callback_listener& frame_listener) {
  Buffer* buffer = DequeueBuffer();
  if (!buffer)
    return;

  ++frame_count_;

  int left = point_.x();
  int top = point_.y();
  int right = point_.x() + square_size_.width();
  int bottom = point_.y() + square_size_.height();

  if (right >= size_.width() || left <= 0) {
    dir_x_ *= -1;
  }

  if (top <= 0 || bottom >= size_.height()) {
    dir_y_ *= -1;
  }

  point_.set_x(point_.x() + dir_x_ * step_size_);
  point_.set_y(point_.y() + dir_y_ * step_size_);

  SkCanvas* canvas = buffer->sk_surface->getCanvas();
  canvas->clear(color_);
  const SkRect rect = gfx::RectToSkRect(gfx::Rect{point_, square_size_});
  const SkPaint paint;
  canvas->drawRect(rect, paint);

  if (gr_context_) {
    gr_context_->flush();
    glFinish();
  }

  wl_surface_set_buffer_scale(surface_.get(), scale_);
  wl_surface_set_buffer_transform(surface_.get(), transform_);
  wl_surface_damage(surface_.get(), 0, 0, size_.width(), size_.height());
  wl_surface_attach(surface_.get(), buffer->buffer.get(), 0, 0);

  // Set up the frame callback.
  frame_callback_pending_ = true;
  frame_callback_.reset(wl_surface_frame(surface_.get()));
  wl_callback_add_listener(frame_callback_.get(), &frame_listener,
                           &frame_callback_pending_);

  wl_surface_commit(surface_.get());
  wl_display_flush(display_.get());
}

void FullscreenClient::HandleDown(void* data,
                                  struct wl_touch* wl_touch,
                                  uint32_t serial,
                                  uint32_t time,
                                  struct wl_surface* surface,
                                  int32_t id,
                                  wl_fixed_t x,
                                  wl_fixed_t y) {
  if (color_ == SK_ColorBLUE) {
    color_ = SK_ColorRED;
  } else {
    color_ = SK_ColorBLUE;
  }
}

void FullscreenClient::HandleMode(void* data,
                                  struct wl_output* wl_output,
                                  uint32_t flags,
                                  int32_t width,
                                  int32_t height,
                                  int32_t refresh) {
  if ((WL_OUTPUT_MODE_CURRENT & flags) != WL_OUTPUT_MODE_CURRENT)
    return;

  size_.SetSize(width, height);

  std::unique_ptr<wl_region> opaque_region(static_cast<wl_region*>(
      wl_compositor_create_region(globals_.compositor.get())));

  if (!opaque_region) {
    LOG(ERROR) << "Can't create region";
    return;
  }

  wl_region_add(opaque_region.get(), 0, 0, size_.width(), size_.height());
  wl_surface_set_opaque_region(surface_.get(), opaque_region.get());
  wl_surface_set_input_region(surface_.get(), opaque_region.get());

  has_mode_ = true;
}

void FullscreenClient::HandleDone(void* data, struct wl_output* wl_output) {
  done_receiving_modes_ = true;
}

}  // namespace clients
}  // namespace wayland
}  // namespace exo
