// 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 <drm_fourcc.h>
#include <fcntl.h>
#include <gbm.h>
#include <sys/mman.h>

#include "base/at_exit.h"
#include "base/command_line.h"
#include "base/message_loop/message_loop.h"
#include "components/exo/wayland/clients/client_base.h"
#include "components/exo/wayland/clients/client_helper.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 YuvClient : public ClientBase {
 public:
  YuvClient() {}

  bool WriteSolidColor(gbm_bo* bo, SkColor color);

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

bool YuvClient::WriteSolidColor(gbm_bo* bo, SkColor color) {
  for (size_t i = 0; i < gbm_bo_get_num_planes(bo); ++i) {
    base::ScopedFD fd(gbm_bo_get_plane_fd(bo, i));
    uint32_t stride = gbm_bo_get_plane_stride(bo, i);
    uint32_t offset = gbm_bo_get_plane_offset(bo, i);
    uint32_t map_size = gbm_bo_get_plane_size(bo, i) + offset;
    void* void_data = mmap(nullptr, map_size, (PROT_READ | PROT_WRITE),
                           MAP_SHARED, fd.get(), 0);
    if (void_data == MAP_FAILED) {
      LOG(ERROR) << "Failed mmap().";
      return false;
    }
    uint8_t* data = static_cast<uint8_t*>(void_data) + offset;
    uint8_t yuv[] = {
        (0.257 * SkColorGetR(color)) + (0.504 * SkColorGetG(color)) +
            (0.098 * SkColorGetB(color)) + 16,
        -(0.148 * SkColorGetR(color)) - (0.291 * SkColorGetG(color)) +
            (0.439 * SkColorGetB(color)) + 128,
        (0.439 * SkColorGetR(color)) - (0.368 * SkColorGetG(color)) -
            (0.071 * SkColorGetB(color)) + 128};
    if (i == 0) {
      for (int y = 0; y < size_.height(); ++y) {
        for (int x = 0; x < size_.width(); ++x) {
          data[stride * y + x] = yuv[0];
        }
      }
    } else {
      for (int y = 0; y < size_.height() / 2; ++y) {
        for (int x = 0; x < size_.width() / 2; ++x) {
          data[stride * y + x * 2] = yuv[1];
          data[stride * y + x * 2 + 1] = yuv[2];
        }
      }
    }
    int ret = munmap(void_data, map_size);
    if (ret) {
      LOG(ERROR) << "Failed munmap().";
      return false;
    }
  }
  return true;
}

void YuvClient::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_number = 0;
  do {
    if (callback_pending)
      continue;
    frame_number++;

    Buffer* buffer = DequeueBuffer();
    if (!buffer) {
      LOG(ERROR) << "Can't find free buffer";
      return;
    }
    const SkColor kColors[] = {SK_ColorBLUE,   SK_ColorGREEN, SK_ColorRED,
                               SK_ColorYELLOW, SK_ColorCYAN,  SK_ColorMAGENTA};
    if (!WriteSolidColor(buffer->bo.get(),
                         kColors[frame_number % buffers_.size()]))
      return;

    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);
    callback_pending = true;
    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;
  params.use_drm = true;
  params.num_buffers = 8;  // Allow up to 8 buffers by default.
  if (!params.FromCommandLine(*command_line))
    return 1;

  // TODO(dcastagna): Support other YUV formats.
  params.drm_format = DRM_FORMAT_NV12;
  params.bo_usage =
      GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING;

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