// Copyright 2013 The Flutter 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 "flutter/display_list/dl_vertices.h"

#include "flutter/display_list/utils/dl_bounds_accumulator.h"
#include "flutter/fml/logging.h"

namespace flutter {

using Flags = DlVertices::Builder::Flags;

static void DlVerticesDeleter(void* p) {
  // Some of our target environments would prefer a sized delete,
  // but other target environments do not have that operator.
  // Use an unsized delete until we get better agreement in the
  // environments.
  // See https://github.com/flutter/flutter/issues/100327
  ::operator delete(p);
}

static size_t bytes_needed(int vertex_count, Flags flags, int index_count) {
  int needed = sizeof(DlVertices);
  // We always have vertices
  needed += vertex_count * sizeof(SkPoint);
  if (flags.has_texture_coordinates) {
    needed += vertex_count * sizeof(SkPoint);
  }
  if (flags.has_colors) {
    needed += vertex_count * sizeof(DlColor);
  }
  if (index_count > 0) {
    needed += index_count * sizeof(uint16_t);
  }
  return needed;
}

std::shared_ptr<DlVertices> DlVertices::Make(
    DlVertexMode mode,
    int vertex_count,
    const SkPoint vertices[],
    const SkPoint texture_coordinates[],
    const DlColor colors[],
    int index_count,
    const uint16_t indices[]) {
  if (!vertices || vertex_count <= 0) {
    vertex_count = 0;
    texture_coordinates = nullptr;
    colors = nullptr;
  }
  if (!indices || index_count <= 0) {
    index_count = 0;
    indices = nullptr;
  }

  Flags flags;
  FML_DCHECK(!flags.has_texture_coordinates);
  FML_DCHECK(!flags.has_colors);
  if (texture_coordinates) {
    flags |= Builder::kHasTextureCoordinates;
  }
  if (colors) {
    flags |= Builder::kHasColors;
  }
  Builder builder(mode, vertex_count, flags, index_count);

  builder.store_vertices(vertices);
  if (texture_coordinates) {
    builder.store_texture_coordinates(texture_coordinates);
  }
  if (colors) {
    builder.store_colors(colors);
  }
  if (indices) {
    builder.store_indices(indices);
  }

  return builder.build();
}

size_t DlVertices::size() const {
  return bytes_needed(vertex_count_,
                      {{texture_coordinates_offset_ > 0, colors_offset_ > 0}},
                      index_count_);
}

static SkRect compute_bounds(const SkPoint* points, int count) {
  RectBoundsAccumulator accumulator;
  for (int i = 0; i < count; i++) {
    accumulator.accumulate(points[i]);
  }
  return accumulator.bounds();
}

DlVertices::DlVertices(DlVertexMode mode,
                       int unchecked_vertex_count,
                       const SkPoint* vertices,
                       const SkPoint* texture_coordinates,
                       const DlColor* colors,
                       int unchecked_index_count,
                       const uint16_t* indices,
                       const SkRect* bounds)
    : mode_(mode),
      vertex_count_(std::max(unchecked_vertex_count, 0)),
      index_count_(indices ? std::max(unchecked_index_count, 0) : 0) {
  bounds_ = bounds ? *bounds : compute_bounds(vertices, vertex_count_);

  char* pod = reinterpret_cast<char*>(this);
  size_t offset = sizeof(DlVertices);

  auto advance = [pod, &offset](auto* src, int count) {
    if (src != nullptr && count > 0) {
      size_t bytes = count * sizeof(*src);
      memcpy(pod + offset, src, bytes);
      size_t ret = offset;
      offset += bytes;
      return ret;
    } else {
      return static_cast<size_t>(0);
    }
  };

  vertices_offset_ = advance(vertices, vertex_count_);
  texture_coordinates_offset_ = advance(texture_coordinates, vertex_count_);
  colors_offset_ = advance(colors, vertex_count_);
  indices_offset_ = advance(indices, index_count_);
  FML_DCHECK(offset == bytes_needed(vertex_count_,
                                    {{!!texture_coordinates, !!colors}},
                                    index_count_));
}

DlVertices::DlVertices(const DlVertices* other)
    : DlVertices(other->mode_,
                 other->vertex_count_,
                 other->vertices(),
                 other->texture_coordinates(),
                 other->colors(),
                 other->index_count_,
                 other->indices(),
                 &other->bounds_) {}

DlVertices::DlVertices(DlVertexMode mode,
                       int unchecked_vertex_count,
                       Flags flags,
                       int unchecked_index_count)
    : mode_(mode),
      vertex_count_(std::max(unchecked_vertex_count, 0)),
      index_count_(std::max(unchecked_index_count, 0)) {
  char* pod = reinterpret_cast<char*>(this);
  size_t offset = sizeof(DlVertices);

  auto advance = [pod, &offset](size_t size, int count) {
    if (count > 0) {
      size_t bytes = count * size;
      memset(pod + offset, 0, bytes);
      size_t ret = offset;
      offset += bytes;
      return ret;
    } else {
      return static_cast<size_t>(0);
    }
  };

  vertices_offset_ = advance(sizeof(SkPoint), vertex_count_);
  texture_coordinates_offset_ = advance(
      sizeof(SkPoint), flags.has_texture_coordinates ? vertex_count_ : 0);
  colors_offset_ =
      advance(sizeof(DlColor), flags.has_colors ? vertex_count_ : 0);
  indices_offset_ = advance(sizeof(uint16_t), index_count_);
  FML_DCHECK(offset == bytes_needed(vertex_count_, flags, index_count_));
  FML_DCHECK((vertex_count_ != 0) == (vertices() != nullptr));
  FML_DCHECK((vertex_count_ != 0 && flags.has_texture_coordinates) ==
             (texture_coordinates() != nullptr));
  FML_DCHECK((vertex_count_ != 0 && flags.has_colors) == (colors() != nullptr));
  FML_DCHECK((index_count_ != 0) == (indices() != nullptr));
}

bool DlVertices::operator==(DlVertices const& other) const {
  auto lists_equal = [](auto* a, auto* b, int count) {
    if (a == nullptr || b == nullptr) {
      return a == b;
    }
    for (int i = 0; i < count; i++) {
      if (a[i] != b[i]) {
        return false;
      }
    }
    return true;
  };
  return                                                               //
      mode_ == other.mode_ &&                                          //
      vertex_count_ == other.vertex_count_ &&                          //
      lists_equal(vertices(), other.vertices(), vertex_count_) &&      //
      lists_equal(texture_coordinates(), other.texture_coordinates(),  //
                  vertex_count_) &&                                    //
      lists_equal(colors(), other.colors(), vertex_count_) &&          //
      index_count_ == other.index_count_ &&                            //
      lists_equal(indices(), other.indices(), index_count_);
}

DlVertices::Builder::Builder(DlVertexMode mode,
                             int vertex_count,
                             Flags flags,
                             int index_count)
    : needs_vertices_(true),
      needs_texture_coords_(flags.has_texture_coordinates),
      needs_colors_(flags.has_colors),
      needs_indices_(index_count > 0) {
  vertex_count = std::max(vertex_count, 0);
  index_count = std::max(index_count, 0);
  void* storage =
      ::operator new(bytes_needed(vertex_count, flags, index_count));
  vertices_.reset(new (storage)
                      DlVertices(mode, vertex_count, flags, index_count),
                  DlVerticesDeleter);
}

static void store_points(char* dst, int offset, const float* src, int count) {
  SkPoint* points = reinterpret_cast<SkPoint*>(dst + offset);
  for (int i = 0; i < count; i++) {
    points[i] = SkPoint::Make(src[i * 2], src[i * 2 + 1]);
  }
}

void DlVertices::Builder::store_vertices(const SkPoint vertices[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_vertices_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  size_t bytes = vertices_->vertex_count_ * sizeof(vertices[0]);
  memcpy(pod + vertices_->vertices_offset_, vertices, bytes);
  needs_vertices_ = false;
}

void DlVertices::Builder::store_vertices(const float vertices[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_vertices_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  store_points(pod, vertices_->vertices_offset_, vertices,
               vertices_->vertex_count_);
  needs_vertices_ = false;
}

void DlVertices::Builder::store_texture_coordinates(const SkPoint coords[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_texture_coords_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  size_t bytes = vertices_->vertex_count_ * sizeof(coords[0]);
  memcpy(pod + vertices_->texture_coordinates_offset_, coords, bytes);
  needs_texture_coords_ = false;
}

void DlVertices::Builder::store_texture_coordinates(const float coords[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_texture_coords_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  store_points(pod, vertices_->texture_coordinates_offset_, coords,
               vertices_->vertex_count_);
  needs_texture_coords_ = false;
}

void DlVertices::Builder::store_colors(const DlColor colors[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_colors_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  size_t bytes = vertices_->vertex_count_ * sizeof(colors[0]);
  memcpy(pod + vertices_->colors_offset_, colors, bytes);
  needs_colors_ = false;
}

void DlVertices::Builder::store_indices(const uint16_t indices[]) {
  FML_CHECK(is_valid());
  FML_CHECK(needs_indices_);
  char* pod = reinterpret_cast<char*>(vertices_.get());
  size_t bytes = vertices_->index_count_ * sizeof(indices[0]);
  memcpy(pod + vertices_->indices_offset_, indices, bytes);
  needs_indices_ = false;
}

std::shared_ptr<DlVertices> DlVertices::Builder::build() {
  FML_CHECK(is_valid());
  if (vertices_->vertex_count() <= 0) {
    // We set this to true in the constructor to make sure that they
    // call store_vertices() only once, but if there are no vertices
    // then we will not object to them never having stored any vertices
    needs_vertices_ = false;
  }
  FML_CHECK(!needs_vertices_);
  FML_CHECK(!needs_texture_coords_);
  FML_CHECK(!needs_colors_);
  FML_CHECK(!needs_indices_);

  vertices_->bounds_ =
      compute_bounds(vertices_->vertices(), vertices_->vertex_count_);

  return std::move(vertices_);
}

}  // namespace flutter
