// 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/resources.h"

#include <algorithm>

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

namespace scenic {
namespace {

template <class T>
constexpr const T& clamp(const T& v, const T& lo, const T& hi) {
  return (v < lo) ? lo : (hi < v) ? hi : v;
}

}  // namespace

Resource::Resource(Session* session)
    : session_(session), id_(session->AllocResourceId()) {}

Resource::Resource(Resource&& moved)
    : session_(moved.session_), id_(moved.id_) {
  auto& moved_session = *const_cast<Session**>(&moved.session_);
  auto& moved_id = *const_cast<uint32_t*>(&moved.id_);
  moved_session = nullptr;
  moved_id = 0;
}

Resource::~Resource() {
  // If this resource was moved, it is not responsible for releasing the ID.
  if (session_)
    session_->ReleaseResource(id_);
}

void Resource::Export(zx::eventpair export_token) {
  session_->Enqueue(NewExportResourceCmd(id(), std::move(export_token)));
}

void Resource::ExportAsRequest(zx::eventpair* out_import_token) {
  session_->Enqueue(NewExportResourceCmdAsRequest(id(), out_import_token));
}

void Resource::SetEventMask(uint32_t event_mask) {
  session_->Enqueue(NewSetEventMaskCmd(id(), event_mask));
}

void Resource::SetLabel(const std::string& label) {
  session_->Enqueue(NewSetLabelCmd(id(), label));
}

Shape::Shape(Session* session) : Resource(session) {}

Shape::Shape(Shape&& moved) : Resource(std::move(moved)) {}

Shape::~Shape() = default;

Circle::Circle(Session* session, float radius) : Shape(session) {
  session->Enqueue(NewCreateCircleCmd(id(), radius));
}

Circle::Circle(Circle&& moved) : Shape(std::move(moved)) {}

Circle::~Circle() = default;

Rectangle::Rectangle(Session* session, float width, float height)
    : Shape(session) {
  session->Enqueue(NewCreateRectangleCmd(id(), width, height));
}

Rectangle::Rectangle(Rectangle&& moved) : Shape(std::move(moved)) {}

Rectangle::~Rectangle() = default;

RoundedRectangle::RoundedRectangle(Session* session, float width, float height,
                                   float top_left_radius,
                                   float top_right_radius,
                                   float bottom_right_radius,
                                   float bottom_left_radius)
    : Shape(session) {
  session->Enqueue(NewCreateRoundedRectangleCmd(
      id(), width, height, top_left_radius, top_right_radius,
      bottom_right_radius, bottom_left_radius));
}

RoundedRectangle::RoundedRectangle(RoundedRectangle&& moved)
    : Shape(std::move(moved)) {}

RoundedRectangle::~RoundedRectangle() = default;

Image::Image(const Memory& memory, off_t memory_offset,
             fuchsia::images::ImageInfo info)
    : Image(memory.session(), memory.id(), memory_offset, std::move(info)) {}

Image::Image(Session* session, uint32_t memory_id, off_t memory_offset,
             fuchsia::images::ImageInfo info)
    : Resource(session), memory_offset_(memory_offset), info_(info) {
  session->Enqueue(
      NewCreateImageCmd(id(), memory_id, memory_offset_, std::move(info)));
}

Image::Image(Image&& moved)
    : Resource(std::move(moved)),
      memory_offset_(moved.memory_offset_),
      info_(moved.info_) {}

Image::~Image() = default;

size_t Image::ComputeSize(const fuchsia::images::ImageInfo& image_info) {
  return images::ImageSize(image_info);
}

Buffer::Buffer(const Memory& memory, off_t memory_offset, size_t num_bytes)
    : Buffer(memory.session(), memory.id(), memory_offset, num_bytes) {}

Buffer::Buffer(Session* session, uint32_t memory_id, off_t memory_offset,
               size_t num_bytes)
    : Resource(session) {
  session->Enqueue(
      NewCreateBufferCmd(id(), memory_id, memory_offset, num_bytes));
}

Buffer::Buffer(Buffer&& moved) : Resource(std::move(moved)) {}

Buffer::~Buffer() = default;

Memory::Memory(Session* session, zx::vmo vmo, uint64_t allocation_size,
               fuchsia::images::MemoryType memory_type)
    : Resource(session), memory_type_(memory_type) {
  session->Enqueue(
      NewCreateMemoryCmd(id(), std::move(vmo), allocation_size, memory_type));
}

Memory::Memory(Memory&& moved)
    : Resource(std::move(moved)), memory_type_(moved.memory_type_) {}

Memory::~Memory() = default;

Mesh::Mesh(Session* session) : Shape(session) {
  session->Enqueue(NewCreateMeshCmd(id()));
}

Mesh::Mesh(Mesh&& moved) : Shape(std::move(moved)) {}

Mesh::~Mesh() = default;

void Mesh::BindBuffers(const Buffer& index_buffer,
                       fuchsia::ui::gfx::MeshIndexFormat index_format,
                       uint64_t index_offset, uint32_t index_count,
                       const Buffer& vertex_buffer,
                       fuchsia::ui::gfx::MeshVertexFormat vertex_format,
                       uint64_t vertex_offset, uint32_t vertex_count,
                       const float bounding_box_min[3],
                       const float bounding_box_max[3]) {
  ZX_DEBUG_ASSERT(session() == index_buffer.session() &&
                  session() == vertex_buffer.session());
  session()->Enqueue(NewBindMeshBuffersCmd(
      id(), index_buffer.id(), index_format, index_offset, index_count,
      vertex_buffer.id(), std::move(vertex_format), vertex_offset, vertex_count,
      bounding_box_min, bounding_box_max));
}

Material::Material(Session* session) : Resource(session) {
  session->Enqueue(NewCreateMaterialCmd(id()));
}

Material::Material(Material&& moved) : Resource(std::move(moved)) {}

Material::~Material() = default;

void Material::SetTexture(uint32_t image_id) {
  session()->Enqueue(NewSetTextureCmd(id(), image_id));
}

void Material::SetColor(uint8_t red, uint8_t green, uint8_t blue,
                        uint8_t alpha) {
  session()->Enqueue(NewSetColorCmd(id(), red, green, blue, alpha));
}

Node::Node(Session* session) : Resource(session) {}

Node::Node(Node&& moved) : Resource(std::move(moved)) {}

Node::~Node() = default;

void Node::SetTranslation(const float translation[3]) {
  session()->Enqueue(NewSetTranslationCmd(id(), translation));
}

void Node::SetTranslation(uint32_t variable_id) {
  session()->Enqueue(NewSetTranslationCmd(id(), variable_id));
}

void Node::SetScale(const float scale[3]) {
  session()->Enqueue(NewSetScaleCmd(id(), scale));
}

void Node::SetScale(uint32_t variable_id) {
  session()->Enqueue(NewSetScaleCmd(id(), variable_id));
}

void Node::SetRotation(const float quaternion[4]) {
  session()->Enqueue(NewSetRotationCmd(id(), quaternion));
}

void Node::SetRotation(uint32_t variable_id) {
  session()->Enqueue(NewSetRotationCmd(id(), variable_id));
}

void Node::SetAnchor(const float anchor[3]) {
  session()->Enqueue(NewSetAnchorCmd(id(), anchor));
}

void Node::SetAnchor(uint32_t variable_id) {
  session()->Enqueue(NewSetAnchorCmd(id(), variable_id));
}

void Node::SendSizeChangeHint(float width_change_factor,
                              float height_change_factor) {
  session()->Enqueue(NewSendSizeChangeHintCmdHACK(id(), width_change_factor,
                                                  height_change_factor));
}

void Node::SetTag(uint32_t tag_value) {
  session()->Enqueue(NewSetTagCmd(id(), tag_value));
}

void Node::SetHitTestBehavior(
    fuchsia::ui::gfx::HitTestBehavior hit_test_behavior) {
  session()->Enqueue(NewSetHitTestBehaviorCmd(id(), hit_test_behavior));
}

void Node::Detach() { session()->Enqueue(NewDetachCmd(id())); }

ShapeNode::ShapeNode(Session* session) : Node(session) {
  session->Enqueue(NewCreateShapeNodeCmd(id()));
}

ShapeNode::ShapeNode(ShapeNode&& moved) : Node(std::move(moved)) {}

ShapeNode::~ShapeNode() = default;

void ShapeNode::SetShape(uint32_t shape_id) {
  session()->Enqueue(NewSetShapeCmd(id(), shape_id));
}

void ShapeNode::SetMaterial(uint32_t material_id) {
  session()->Enqueue(NewSetMaterialCmd(id(), material_id));
}

ContainerNode::ContainerNode(Session* session) : Node(session) {}

ContainerNode::ContainerNode(ContainerNode&& moved) : Node(std::move(moved)) {}

ContainerNode::~ContainerNode() = default;

void ContainerNode::AddChild(uint32_t child_node_id) {
  session()->Enqueue(NewAddChildCmd(id(), child_node_id));
}

void ContainerNode::AddPart(uint32_t part_node_id) {
  session()->Enqueue(NewAddPartCmd(id(), part_node_id));
}

void ContainerNode::DetachChildren() {
  session()->Enqueue(NewDetachChildrenCmd(id()));
}

EntityNode::EntityNode(Session* session) : ContainerNode(session) {
  session->Enqueue(NewCreateEntityNodeCmd(id()));
}

void EntityNode::Attach(const ViewHolder& view_holder) {
  session()->Enqueue(NewAddChildCmd(id(), view_holder.id()));
}

void EntityNode::Snapshot(fuchsia::ui::gfx::SnapshotCallbackHACKPtr callback) {
  session()->Enqueue(NewTakeSnapshotCmdHACK(id(), std::move(callback)));
}

EntityNode::~EntityNode() = default;

void EntityNode::SetClip(uint32_t clip_id, bool clip_to_self) {
  session()->Enqueue(NewSetClipCmd(id(), clip_id, clip_to_self));
}

ImportNode::ImportNode(Session* session) : ContainerNode(session) {}

ImportNode::ImportNode(ImportNode&& moved) : ContainerNode(std::move(moved)) {}

ImportNode::~ImportNode() {
  ZX_DEBUG_ASSERT_MSG(is_bound_, "Import was never bound.");
}

void ImportNode::Bind(zx::eventpair import_token) {
  ZX_DEBUG_ASSERT(!is_bound_);
  session()->Enqueue(NewImportResourceCmd(
      id(), fuchsia::ui::gfx::ImportSpec::NODE, std::move(import_token)));
  is_bound_ = true;
}

void ImportNode::BindAsRequest(zx::eventpair* out_export_token) {
  ZX_DEBUG_ASSERT(!is_bound_);
  session()->Enqueue(NewImportResourceCmdAsRequest(
      id(), fuchsia::ui::gfx::ImportSpec::NODE, out_export_token));
  is_bound_ = true;
}

ViewHolder::ViewHolder(Session* session, zx::eventpair token,
                       const std::string& debug_name)
    : Resource(session) {
  session->Enqueue(NewCreateViewHolderCmd(id(), std::move(token), debug_name));
}

ViewHolder::~ViewHolder() = default;

void ViewHolder::SetViewProperties(const float bounding_box_min[3],
                                   const float bounding_box_max[3],
                                   const float inset_from_min[3],
                                   const float inset_from_max[3]) {
  session()->Enqueue(NewSetViewPropertiesCmd(id(), bounding_box_min,
                                             bounding_box_max, inset_from_min,
                                             inset_from_max));
}

void ViewHolder::SetViewProperties(
    const fuchsia::ui::gfx::ViewProperties& props) {
  session()->Enqueue(NewSetViewPropertiesCmd(id(), props));
}

View::View(Session* session, zx::eventpair token, const std::string& debug_name)
    : Resource(session) {
  session->Enqueue(NewCreateViewCmd(id(), std::move(token), debug_name));
}

View::~View() = default;

void View::AddChild(const Node& child) const {
  ZX_DEBUG_ASSERT(session() == child.session());
  session()->Enqueue(NewAddChildCmd(id(), child.id()));
}

void View::DetachChild(const Node& child) const {
  ZX_DEBUG_ASSERT(session() == child.session());
  session()->Enqueue(NewDetachCmd(child.id()));
}

ClipNode::ClipNode(Session* session) : ContainerNode(session) {
  session->Enqueue(NewCreateClipNodeCmd(id()));
}

ClipNode::ClipNode(ClipNode&& moved) : ContainerNode(std::move(moved)) {}

ClipNode::~ClipNode() = default;

OpacityNode::OpacityNode(Session* session) : ContainerNode(session) {
  session->Enqueue(NewCreateOpacityNodeCmd(id()));
}

OpacityNode::OpacityNode(OpacityNode&& moved)
    : ContainerNode(std::move(moved)) {}

OpacityNode::~OpacityNode() = default;

void OpacityNode::SetOpacity(float opacity) {
  opacity = clamp(opacity, 0.f, 1.f);
  session()->Enqueue(NewSetOpacityCmd(id(), opacity));
}

Variable::Variable(Session* session, fuchsia::ui::gfx::Value initial_value)
    : Resource(session) {
  session->Enqueue(NewCreateVariableCmd(id(), std::move(initial_value)));
}

Variable::Variable(Variable&& moved) : Resource(std::move(moved)) {}

Variable::~Variable() = default;

Scene::Scene(Session* session) : ContainerNode(session) {
  session->Enqueue(NewCreateSceneCmd(id()));
}

Scene::Scene(Scene&& moved) : ContainerNode(std::move(moved)) {}

Scene::~Scene() = default;

void Scene::AddLight(uint32_t light_id) {
  session()->Enqueue(NewAddLightCmd(id(), light_id));
}

void Scene::DetachLights() { session()->Enqueue(NewDetachLightsCmd(id())); }

void CameraBase::SetTransform(const float eye_position[3],
                              const float eye_look_at[3],
                              const float eye_up[3]) {
  session()->Enqueue(
      NewSetCameraTransformCmd(id(), eye_position, eye_look_at, eye_up));
}

void CameraBase::SetPoseBuffer(const Buffer& buffer, uint32_t num_entries,
                               int64_t base_time, uint64_t time_interval) {
  session()->Enqueue(NewSetCameraPoseBufferCmd(id(), buffer.id(), num_entries,
                                               base_time, time_interval));
}

Camera::Camera(const Scene& scene) : Camera(scene.session(), scene.id()) {}

Camera::Camera(Session* session, uint32_t scene_id) : CameraBase(session) {
  session->Enqueue(NewCreateCameraCmd(id(), scene_id));
}

Camera::Camera(Camera&& moved) : CameraBase(std::move(moved)) {}

Camera::~Camera() = default;

void Camera::SetProjection(const float fovy) {
  session()->Enqueue(NewSetCameraProjectionCmd(id(), fovy));
}

StereoCamera::StereoCamera(const Scene& scene)
    : StereoCamera(scene.session(), scene.id()) {}

StereoCamera::StereoCamera(Session* session, uint32_t scene_id)
    : CameraBase(session) {
  session->Enqueue(NewCreateStereoCameraCmd(id(), scene_id));
}

StereoCamera::StereoCamera(StereoCamera&& moved)
    : CameraBase(std::move(moved)) {}

StereoCamera::~StereoCamera() = default;

void StereoCamera::SetStereoProjection(const float left_projection[16],
                                       const float right_projection[16]) {
  session()->Enqueue(
      NewSetStereoCameraProjectionCmd(id(), left_projection, right_projection));
}

Renderer::Renderer(Session* session) : Resource(session) {
  session->Enqueue(NewCreateRendererCmd(id()));
}

Renderer::Renderer(Renderer&& moved) : Resource(std::move(moved)) {}

Renderer::~Renderer() = default;

void Renderer::SetCamera(uint32_t camera_id) {
  session()->Enqueue(NewSetCameraCmd(id(), camera_id));
}

void Renderer::SetParam(fuchsia::ui::gfx::RendererParam param) {
  session()->Enqueue(NewSetRendererParamCmd(id(), std::move(param)));
}

void Renderer::SetShadowTechnique(fuchsia::ui::gfx::ShadowTechnique technique) {
  auto param = fuchsia::ui::gfx::RendererParam();
  param.set_shadow_technique(technique);
  SetParam(std::move(param));
}

void Renderer::SetDisableClipping(bool disable_clipping) {
  session()->Enqueue(NewSetDisableClippingCmd(id(), disable_clipping));
}

Layer::Layer(Session* session) : Resource(session) {
  session->Enqueue(NewCreateLayerCmd(id()));
}

Layer::Layer(Layer&& moved) : Resource(std::move(moved)) {}

Layer::~Layer() = default;

void Layer::SetRenderer(uint32_t renderer_id) {
  session()->Enqueue(NewSetRendererCmd(id(), renderer_id));
}

void Layer::SetSize(const float size[2]) {
  session()->Enqueue(NewSetSizeCmd(id(), size));
}

LayerStack::LayerStack(Session* session) : Resource(session) {
  session->Enqueue(NewCreateLayerStackCmd(id()));
}

LayerStack::LayerStack(LayerStack&& moved) : Resource(std::move(moved)) {}

LayerStack::~LayerStack() = default;

void LayerStack::AddLayer(uint32_t layer_id) {
  session()->Enqueue(NewAddLayerCmd(id(), layer_id));
}

void LayerStack::RemoveLayer(uint32_t layer_id) {
  session()->Enqueue(NewRemoveLayerCmd(id(), layer_id));
}

void LayerStack::RemoveAllLayers() {
  session()->Enqueue(NewRemoveAllLayersCmd(id()));
}

DisplayCompositor::DisplayCompositor(Session* session) : Resource(session) {
  session->Enqueue(NewCreateDisplayCompositorCmd(id()));
}

DisplayCompositor::DisplayCompositor(DisplayCompositor&& moved)
    : Resource(std::move(moved)) {}

DisplayCompositor::~DisplayCompositor() = default;

void DisplayCompositor::SetLayerStack(uint32_t layer_stack_id) {
  session()->Enqueue(NewSetLayerStackCmd(id(), layer_stack_id));
}

Compositor::Compositor(Session* session) : Resource(session) {
  session->Enqueue(NewCreateCompositorCmd(id()));
}

Compositor::Compositor(Compositor&& moved) : Resource(std::move(moved)) {}

Compositor::~Compositor() = default;

void Compositor::SetLayerStack(uint32_t layer_stack_id) {
  session()->Enqueue(NewSetLayerStackCmd(id(), layer_stack_id));
}

Light::Light(Session* session) : Resource(session) {}

Light::Light(Light&& moved) : Resource(std::move(moved)) {}

Light::~Light() = default;

void Light::SetColor(const float rgb[3]) {
  session()->Enqueue(NewSetLightColorCmd(id(), rgb));
}

void Light::SetColor(uint32_t variable_id) {
  session()->Enqueue(NewSetLightColorCmd(id(), variable_id));
}

void Light::Detach() { session()->Enqueue(NewDetachLightCmd(id())); }

AmbientLight::AmbientLight(Session* session) : Light(session) {
  session->Enqueue(NewCreateAmbientLightCmd(id()));
}

AmbientLight::AmbientLight(AmbientLight&& moved) : Light(std::move(moved)) {}

AmbientLight::~AmbientLight() = default;

DirectionalLight::DirectionalLight(Session* session) : Light(session) {
  session->Enqueue(NewCreateDirectionalLightCmd(id()));
}

DirectionalLight::DirectionalLight(DirectionalLight&& moved)
    : Light(std::move(moved)) {}

DirectionalLight::~DirectionalLight() = default;

void DirectionalLight::SetDirection(const float direction[3]) {
  session()->Enqueue(NewSetLightDirectionCmd(id(), direction));
}

void DirectionalLight::SetDirection(uint32_t variable_id) {
  session()->Enqueue(NewSetLightDirectionCmd(id(), variable_id));
}

}  // namespace scenic