// Copyright 2017 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 "base/at_exit.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "base/stl_util.h"
#include "components/exo/wayland/clients/client_base.h"
#include "components/exo/wayland/clients/client_helper.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkSurface.h"
#include "third_party/skia/include/gpu/GrContext.h"

namespace exo {
namespace wayland {
namespace clients {
namespace {

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

}  // namespace

class VulkanClient : ClientBase {
 public:
  VulkanClient() {}

  void Run(const ClientBase::InitParams& params);

 private:
  friend class ScopedVulkanRenderFrame;

  DISALLOW_COPY_AND_ASSIGN(VulkanClient);
};

// ScopedVulkanRenderFrame class helps setting up all the state needed to begin
// a new frame using vulkan, it creates a command buffer and starts a render
// pass. When destroyed it takes care of submitting to the queue and to wait for
// the work to be done.
class ScopedVulkanRenderFrame {
 public:
  ScopedVulkanRenderFrame(VulkanClient* client,
                          VkFramebuffer framebuffer,
                          SkColor clear_color)
      : client_(client) {
    static const VkCommandBufferBeginInfo vk_command_buffer_begin_info{
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
        .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
    };

    VkCommandBufferAllocateInfo command_buffer_allocate_info{
        .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
        .commandPool = client->vk_command_pool_->get(),
        .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
        .commandBufferCount = 1,
    };

    VkResult result = vkAllocateCommandBuffers(client_->vk_device_->get(),
                                               &command_buffer_allocate_info,
                                               &command_buffer_);
    CHECK_EQ(VK_SUCCESS, result) << "Failed to create a Vulkan command buffer.";

    result =
        vkBeginCommandBuffer(command_buffer_, &vk_command_buffer_begin_info);
    CHECK_EQ(VK_SUCCESS, result);

    SkColor4f sk_color = SkColor4f::FromColor(clear_color);
    VkClearValue clear_value = {
        .color =
            {
                .float32 =
                    {
                        sk_color.fR, sk_color.fG, sk_color.fB, sk_color.fA,
                    },
            },
    };
    VkRenderPassBeginInfo render_pass_begin_info{
        .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
        .renderPass = client_->vk_render_pass_->get(),
        .framebuffer = framebuffer,
        .renderArea =
            (VkRect2D){
                .offset = {0, 0},
                .extent = {client_->surface_size_.width(),
                           client_->surface_size_.height()},
            },
        .clearValueCount = 1,
        .pClearValues = &clear_value,
    };
    vkCmdBeginRenderPass(command_buffer_, &render_pass_begin_info,
                         VK_SUBPASS_CONTENTS_INLINE);
  }
  ~ScopedVulkanRenderFrame() {
    vkCmdEndRenderPass(command_buffer_);

    VkResult result = vkEndCommandBuffer(command_buffer_);
    CHECK_EQ(VK_SUCCESS, result);
    VkSubmitInfo submit_info{.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
                             .commandBufferCount = 1,
                             .pCommandBuffers = &command_buffer_};
    result = vkQueueSubmit(client_->vk_queue_, 1, &submit_info, VK_NULL_HANDLE);
    CHECK_EQ(VK_SUCCESS, result);

    result = vkQueueWaitIdle(client_->vk_queue_);

    vkFreeCommandBuffers(client_->vk_device_->get(),
                         client_->vk_command_pool_->get(), 1, &command_buffer_);
  }

 private:
  VulkanClient* const client_;
  VkCommandBuffer command_buffer_ = VK_NULL_HANDLE;

  DISALLOW_COPY_AND_ASSIGN(ScopedVulkanRenderFrame);
};

void VulkanClient::Run(const ClientBase::InitParams& params) {
  if (!ClientBase::Init(params))
    return;

  bool callback_pending = false;
  std::unique_ptr<wl_callback> frame_callback;
  wl_callback_listener frame_listener = {FrameCallback};

  size_t frame_count = 0;
  do {
    if (callback_pending)
      continue;

    Buffer* buffer = DequeueBuffer();
    if (!buffer)
      continue;

    {
      static const SkColor kColors[] = {SK_ColorRED, SK_ColorBLACK,
                                        SK_ColorGREEN};

      ScopedVulkanRenderFrame vulkan_frame(
          this, buffer->vk_framebuffer->get(),
          kColors[++frame_count % base::size(kColors)]);

      // This is where the drawing code would go.
      // This client is not drawing anything. Just clearing the fb.
    }
    ++frame_count;

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

    frame_callback.reset(wl_surface_frame(surface_.get()));
    wl_callback_add_listener(frame_callback.get(), &frame_listener,
                             &callback_pending);
    wl_surface_commit(surface_.get());
    wl_display_flush(display_.get());
  } while (wl_display_dispatch(display_.get()) != -1);
}

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

int main(int argc, char* argv[]) {
  base::AtExitManager exit_manager;
  base::CommandLine::Init(argc, argv);
  base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();

  exo::wayland::clients::ClientBase::InitParams params;
  if (!params.FromCommandLine(*command_line))
    return 1;

  base::MessageLoopForUI message_loop;
  exo::wayland::clients::VulkanClient client;
  client.Run(params);
  return 1;
}
