// Copyright 2017 The Fuchsia 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 "lib/ui/scenic/cpp/session.h"

#include <stdio.h>
#include <zircon/assert.h>

#include "lib/ui/scenic/cpp/commands.h"

namespace scenic {

constexpr size_t kCommandsPerMessage =
    (ZX_CHANNEL_MAX_MSG_BYTES - sizeof(fidl_message_header_t) -
     sizeof(fidl_vector_t)) /
    sizeof(fuchsia::ui::scenic::Command);

SessionPtrAndListenerRequest CreateScenicSessionPtrAndListenerRequest(
    fuchsia::ui::scenic::Scenic* scenic) {
  fuchsia::ui::scenic::SessionPtr session;
  fidl::InterfaceHandle<fuchsia::ui::scenic::SessionListener> listener_handle;
  auto listener_request = listener_handle.NewRequest();

  scenic->CreateSession(session.NewRequest(), listener_handle.Bind());

  return {std::move(session), std::move(listener_request)};
}

Session::Session(fuchsia::ui::scenic::SessionPtr session,
                 fidl::InterfaceRequest<fuchsia::ui::scenic::SessionListener>
                     session_listener)
    : session_(std::move(session)), session_listener_binding_(this) {
  ZX_DEBUG_ASSERT(session_);
  if (session_listener.is_valid())
    session_listener_binding_.Bind(std::move(session_listener));
}

Session::Session(fuchsia::ui::scenic::Scenic* scenic)
    : session_listener_binding_(this) {
  ZX_DEBUG_ASSERT(scenic);
  scenic->CreateSession(session_.NewRequest(),
                        session_listener_binding_.NewBinding());
}

Session::Session(SessionPtrAndListenerRequest session_and_listener)
    : Session(std::move(session_and_listener.first),
              std::move(session_and_listener.second)) {}

Session::~Session() {
  ZX_DEBUG_ASSERT_MSG(resource_count_ == 0,
                      "Some resources outlived the session: %u",
                      resource_count_);
}

uint32_t Session::AllocResourceId() {
  uint32_t resource_id = next_resource_id_++;
  ZX_DEBUG_ASSERT(resource_id);
  resource_count_++;
  return resource_id;
}

void Session::ReleaseResource(uint32_t resource_id) {
  resource_count_--;
  Enqueue(NewReleaseResourceCmd(resource_id));
}

void Session::Enqueue(fuchsia::ui::gfx::Command command) {
  Enqueue(NewCommand(std::move(command)));
}

void Session::Enqueue(fuchsia::ui::input::Command command) {
  Enqueue(NewCommand(std::move(command)));
}

void Session::Enqueue(fuchsia::ui::scenic::Command command) {
  commands_.push_back(std::move(command));
  if (commands_->size() >= kCommandsPerMessage ||
      command.Which() == fuchsia::ui::scenic::Command::Tag::kInput) {
    Flush();
  }
}

void Session::EnqueueAcquireFence(zx::event fence) {
  ZX_DEBUG_ASSERT(fence);
  acquire_fences_.push_back(std::move(fence));
}

void Session::EnqueueReleaseFence(zx::event fence) {
  ZX_DEBUG_ASSERT(fence);
  release_fences_.push_back(std::move(fence));
}

void Session::Flush() {
  ZX_DEBUG_ASSERT(session_);
  if (!commands_->empty()) {
    ZX_DEBUG_ASSERT(static_cast<bool>(commands_));
    session_->Enqueue(std::move(commands_));

    // After being moved, |commands_| is in a "valid but unspecified state";
    // see http://en.cppreference.com/w/cpp/utility/move.  Calling reset() makes
    // it safe to continue using.
    commands_.reset();
  }
}

void Session::Present(uint64_t presentation_time, PresentCallback callback) {
  ZX_DEBUG_ASSERT(session_);
  Flush();

  if (acquire_fences_.is_null())
    acquire_fences_.resize(0u);
  if (release_fences_.is_null())
    release_fences_.resize(0u);
  session_->Present(presentation_time, std::move(acquire_fences_),
                    std::move(release_fences_), std::move(callback));
}

void Session::HitTest(uint32_t node_id, const float ray_origin[3],
                      const float ray_direction[3], HitTestCallback callback) {
  ZX_DEBUG_ASSERT(session_);
  fuchsia::ui::gfx::vec3 ray_origin_vec;
  ray_origin_vec.x = ray_origin[0];
  ray_origin_vec.y = ray_origin[1];
  ray_origin_vec.z = ray_origin[2];

  fuchsia::ui::gfx::vec3 ray_direction_vec;
  ray_direction_vec.x = ray_direction[0];
  ray_direction_vec.y = ray_direction[1];
  ray_direction_vec.z = ray_direction[2];

  session_->HitTest(node_id, std::move(ray_origin_vec),
                    std::move(ray_direction_vec), std::move(callback));
}

void Session::HitTestDeviceRay(
    const float ray_origin[3], const float ray_direction[3],
    fuchsia::ui::scenic::Session::HitTestDeviceRayCallback callback) {
  ZX_DEBUG_ASSERT(session_);
  fuchsia::ui::gfx::vec3 ray_origin_vec;
  ray_origin_vec.x = ray_origin[0];
  ray_origin_vec.y = ray_origin[1];
  ray_origin_vec.z = ray_origin[2];

  fuchsia::ui::gfx::vec3 ray_direction_vec;
  ray_direction_vec.x = ray_direction[0];
  ray_direction_vec.y = ray_direction[1];
  ray_direction_vec.z = ray_direction[2];

  session_->HitTestDeviceRay(std::move(ray_origin_vec),
                             std::move(ray_direction_vec), std::move(callback));
}

void Session::Unbind() {
  ZX_DEBUG_ASSERT(session_);
  ZX_DEBUG_ASSERT(!session_handle_);
  session_handle_ = session_.Unbind();
  session_ = nullptr;
}

void Session::Rebind() {
  ZX_DEBUG_ASSERT(!session_);
  ZX_DEBUG_ASSERT(session_handle_);
  session_ = fuchsia::ui::scenic::SessionPtr(session_handle_.Bind());
  session_handle_ = nullptr;
}

void Session::OnScenicError(fidl::StringPtr error) {
  // TODO(SCN-903): replace fprintf with SDK-approved logging mechanism.  Also
  // remove "#include <stdio.h>".
  fprintf(stderr, "Session error: %s", error->c_str());
}

void Session::OnScenicEvent(
    fidl::VectorPtr<fuchsia::ui::scenic::Event> events) {
  if (event_handler_)
    event_handler_(std::move(events));
}

void Session::SetDebugName(const std::string& debug_name) {
  session_->SetDebugName(debug_name);
}

}  // namespace scenic