// 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 "impeller/entity/contents/text_contents.h"

#include <cstring>
#include <optional>
#include <utility>

#include "impeller/core/buffer_view.h"
#include "impeller/core/formats.h"
#include "impeller/core/sampler_descriptor.h"
#include "impeller/entity/entity.h"
#include "impeller/geometry/color.h"
#include "impeller/geometry/point.h"
#include "impeller/renderer/render_pass.h"
#include "impeller/typographer/glyph_atlas.h"

namespace impeller {
Point SizeToPoint(Size size) {
  return Point(size.width, size.height);
}

using VS = GlyphAtlasPipeline::VertexShader;
using FS = GlyphAtlasPipeline::FragmentShader;

TextContents::TextContents() = default;

TextContents::~TextContents() = default;

void TextContents::SetTextFrame(const std::shared_ptr<TextFrame>& frame) {
  frame_ = frame;
}

void TextContents::SetColor(Color color) {
  color_ = color;
}

Color TextContents::GetColor() const {
  return color_.WithAlpha(color_.alpha * inherited_opacity_);
}

void TextContents::SetInheritedOpacity(Scalar opacity) {
  inherited_opacity_ = opacity;
}

void TextContents::SetOffset(Vector2 offset) {
  offset_ = offset;
}

void TextContents::SetForceTextColor(bool value) {
  force_text_color_ = value;
}

std::optional<Rect> TextContents::GetCoverage(const Entity& entity) const {
  return frame_->GetBounds().TransformBounds(entity.GetTransform());
}

void TextContents::SetTextProperties(
    Color color,
    const std::optional<StrokeParameters>& stroke) {
  if (frame_->HasColor()) {
    // Alpha is always applied when rendering, remove it here so
    // we do not double-apply the alpha.
    properties_.color = color.WithAlpha(1.0);
  }
  properties_.stroke = stroke;
}

namespace {
Scalar AttractToOne(Scalar x) {
  // Epsilon was decided by looking at the floating point inaccuracies in
  // the ScaledK test.
  const Scalar epsilon = 0.005f;
  if (std::abs(x - 1.f) < epsilon) {
    return 1.f;
  }
  if (std::abs(x + 1.f) < epsilon) {
    return -1.f;
  }
  return x;
}

}  // namespace

void TextContents::ComputeVertexData(
    VS::PerVertexData* vtx_contents,
    const std::shared_ptr<TextFrame>& frame,
    Scalar scale,
    const Matrix& entity_transform,
    Vector2 offset,
    std::optional<GlyphProperties> glyph_properties,
    const std::shared_ptr<GlyphAtlas>& atlas) {
  // Common vertex information for all glyphs.
  // All glyphs are given the same vertex information in the form of a
  // unit-sized quad. The size of the glyph is specified in per instance data
  // and the vertex shader uses this to size the glyph correctly. The
  // interpolated vertex information is also used in the fragment shader to
  // sample from the glyph atlas.

  constexpr std::array<Point, 4> unit_points = {Point{0, 0}, Point{1, 0},
                                                Point{0, 1}, Point{1, 1}};

  ISize atlas_size = atlas->GetTexture()->GetSize();
  bool is_translation_scale = entity_transform.IsTranslationScaleOnly();
  Matrix basis_transform = entity_transform.Basis();

  VS::PerVertexData vtx;
  size_t i = 0u;
  size_t bounds_offset = 0u;
  Rational rounded_scale = frame->GetScale();
  Scalar inverted_rounded_scale = static_cast<Scalar>(rounded_scale.Invert());
  Matrix unscaled_basis =
      basis_transform *
      Matrix::MakeScale({inverted_rounded_scale, inverted_rounded_scale, 1});

  // In typical scales < 48x these values should be -1 or 1. We round to
  // those to avoid inaccuracies.
  unscaled_basis.m[0] = AttractToOne(unscaled_basis.m[0]);
  unscaled_basis.m[5] = AttractToOne(unscaled_basis.m[5]);

  for (const TextRun& run : frame->GetRuns()) {
    const Font& font = run.GetFont();
    const Matrix transform = frame->GetOffsetTransform();
    FontGlyphAtlas* font_atlas = nullptr;

    // Adjust glyph position based on the subpixel rounding
    // used by the font.
    Point subpixel_adjustment(0.5, 0.5);
    switch (font.GetAxisAlignment()) {
      case AxisAlignment::kNone:
        break;
      case AxisAlignment::kX:
        subpixel_adjustment.x = 0.125;
        break;
      case AxisAlignment::kY:
        subpixel_adjustment.y = 0.125;
        break;
      case AxisAlignment::kAll:
        subpixel_adjustment.x = 0.125;
        subpixel_adjustment.y = 0.125;
        break;
    }

    Point screen_offset = (entity_transform * Point(0, 0));
    for (const TextRun::GlyphPosition& glyph_position :
         run.GetGlyphPositions()) {
      const FrameBounds& frame_bounds = frame->GetFrameBounds(bounds_offset);
      bounds_offset++;
      auto atlas_glyph_bounds = frame_bounds.atlas_bounds;
      auto glyph_bounds = frame_bounds.glyph_bounds;

      // If frame_bounds.is_placeholder is true, this is the first frame
      // the glyph has been rendered and so its atlas position was not
      // known when the glyph was recorded. Perform a slow lookup into the
      // glyph atlas hash table.
      if (frame_bounds.is_placeholder) {
        if (!font_atlas) {
          font_atlas =
              atlas->GetOrCreateFontGlyphAtlas(ScaledFont{font, rounded_scale});
        }

        if (!font_atlas) {
          VALIDATION_LOG << "Could not find font in the atlas.";
          continue;
        }
        SubpixelPosition subpixel = TextFrame::ComputeSubpixelPosition(
            glyph_position, font.GetAxisAlignment(), transform);

        std::optional<FrameBounds> maybe_atlas_glyph_bounds =
            font_atlas->FindGlyphBounds(SubpixelGlyph{
                glyph_position.glyph,  //
                subpixel,              //
                glyph_properties       //
            });
        if (!maybe_atlas_glyph_bounds.has_value()) {
          VALIDATION_LOG << "Could not find glyph position in the atlas.";
          continue;
        }
        atlas_glyph_bounds = maybe_atlas_glyph_bounds.value().atlas_bounds;
      }

      Rect scaled_bounds = glyph_bounds.Scale(inverted_rounded_scale);
      // For each glyph, we compute two rectangles. One for the vertex
      // positions and one for the texture coordinates (UVs). The atlas
      // glyph bounds are used to compute UVs in cases where the
      // destination and source sizes may differ due to clamping the sizes
      // of large glyphs.
      Point uv_origin = atlas_glyph_bounds.GetLeftTop() / atlas_size;
      Point uv_size = SizeToPoint(atlas_glyph_bounds.GetSize()) / atlas_size;

      Point unrounded_glyph_position =
          // This is for RTL text.
          unscaled_basis * glyph_bounds.GetLeftTop() +
          (basis_transform * glyph_position.position);

      Point screen_glyph_position =
          (screen_offset + unrounded_glyph_position + subpixel_adjustment)
              .Floor();
      for (const Point& point : unit_points) {
        Point position;
        if (is_translation_scale) {
          position = (screen_glyph_position +
                      (unscaled_basis * point * glyph_bounds.GetSize()))
                         .Round();
        } else {
          position = entity_transform *
                     (glyph_position.position + scaled_bounds.GetLeftTop() +
                      point * scaled_bounds.GetSize());
        }
        vtx.uv = uv_origin + (uv_size * point);
        vtx.position = position;
        vtx_contents[i++] = vtx;
      }
    }
  }
}

bool TextContents::Render(const ContentContext& renderer,
                          const Entity& entity,
                          RenderPass& pass) const {
  Color color = GetColor();
  if (color.IsTransparent()) {
    return true;
  }

  GlyphAtlas::Type type = frame_->GetAtlasType();
  const std::shared_ptr<GlyphAtlas>& atlas =
      renderer.GetLazyGlyphAtlas()->CreateOrGetGlyphAtlas(
          *renderer.GetContext(), renderer.GetTransientsDataBuffer(), type);

  if (!atlas || !atlas->IsValid()) {
    VALIDATION_LOG << "Cannot render glyphs without prepared atlas.";
    return false;
  }
  if (!frame_->IsFrameComplete()) {
    VALIDATION_LOG << "Failed to find font glyph bounds.";
    return false;
  }

  // Information shared by all glyph draw calls.
  pass.SetCommandLabel("TextFrame");
  auto opts = OptionsFromPassAndEntity(pass, entity);
  opts.primitive_type = PrimitiveType::kTriangle;
  pass.SetPipeline(renderer.GetGlyphAtlasPipeline(opts));

  // Common vertex uniforms for all glyphs.
  VS::FrameInfo frame_info;
  frame_info.mvp =
      Entity::GetShaderTransform(entity.GetShaderClipDepth(), pass, Matrix());
  bool is_translation_scale = entity.GetTransform().IsTranslationScaleOnly();
  Matrix entity_transform = entity.GetTransform();

  VS::BindFrameInfo(
      pass, renderer.GetTransientsDataBuffer().EmplaceUniform(frame_info));

  FS::FragInfo frag_info;
  frag_info.use_text_color = force_text_color_ ? 1.0 : 0.0;
  frag_info.text_color = ToVector(color.Premultiply());
  frag_info.is_color_glyph = type == GlyphAtlas::Type::kColorBitmap;

  FS::BindFragInfo(
      pass, renderer.GetTransientsDataBuffer().EmplaceUniform(frag_info));

  SamplerDescriptor sampler_desc;
  if (is_translation_scale) {
    sampler_desc.min_filter = MinMagFilter::kNearest;
    sampler_desc.mag_filter = MinMagFilter::kNearest;
  } else {
    // Currently, we only propagate the scale of the transform to the atlas
    // renderer, so if the transform has more than just a translation, we turn
    // on linear sampling to prevent crunchiness caused by the pixel grid not
    // being perfectly aligned.
    // The downside is that this slightly over-blurs rotated/skewed text.
    sampler_desc.min_filter = MinMagFilter::kLinear;
    sampler_desc.mag_filter = MinMagFilter::kLinear;
  }

  // No mipmaps for glyph atlas (glyphs are generated at exact scales).
  sampler_desc.mip_filter = MipFilter::kBase;

  FS::BindGlyphAtlasSampler(
      pass,                 // command
      atlas->GetTexture(),  // texture
      renderer.GetContext()->GetSamplerLibrary()->GetSampler(
          sampler_desc)  // sampler
  );

  HostBuffer& data_host_buffer = renderer.GetTransientsDataBuffer();
  HostBuffer& indexes_host_buffer = renderer.GetTransientsIndexesBuffer();
  size_t glyph_count = 0;
  for (const auto& run : frame_->GetRuns()) {
    glyph_count += run.GetGlyphPositions().size();
  }
  size_t vertex_count = glyph_count * 4;
  size_t index_count = glyph_count * 6;

  BufferView buffer_view = data_host_buffer.Emplace(
      vertex_count * sizeof(VS::PerVertexData), alignof(VS::PerVertexData),
      [&](uint8_t* data) {
        VS::PerVertexData* vtx_contents =
            reinterpret_cast<VS::PerVertexData*>(data);
        ComputeVertexData(/*vtx_contents=*/vtx_contents,
                          /*frame=*/frame_,
                          /*scale=*/scale_,
                          /*entity_transform=*/entity_transform,
                          /*offset=*/offset_,
                          /*glyph_properties=*/GetGlyphProperties(),
                          /*atlas=*/atlas);
      });
  BufferView index_buffer_view = indexes_host_buffer.Emplace(
      index_count * sizeof(uint16_t), alignof(uint16_t), [&](uint8_t* data) {
        uint16_t* indices = reinterpret_cast<uint16_t*>(data);
        size_t j = 0;
        for (auto i = 0u; i < glyph_count; i++) {
          size_t base = i * 4;
          indices[j++] = base + 0;
          indices[j++] = base + 1;
          indices[j++] = base + 2;
          indices[j++] = base + 1;
          indices[j++] = base + 2;
          indices[j++] = base + 3;
        }
      });

  pass.SetVertexBuffer(std::move(buffer_view));
  pass.SetIndexBuffer(index_buffer_view, IndexType::k16bit);
  pass.SetElementCount(index_count);

  return pass.Draw().ok();
}

std::optional<GlyphProperties> TextContents::GetGlyphProperties() const {
  return (properties_.stroke || frame_->HasColor())
             ? std::optional<GlyphProperties>(properties_)
             : std::nullopt;
}

}  // namespace impeller
