// Copyright 2018 Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "tools/render/rectangle_renderer.h"

#include <array>

#include "absl/algorithm/container.h"

namespace quic_trace {
namespace render {

namespace {

const char* kVertexShader = R"(
in vec2 coord;
in vec4 color_in;
out vec4 color_mid;

void main(void) {
  gl_Position = windowToGl(coord);
  color_mid = color_in;
}
)";

const char* kFragmentShader = R"(
in vec4 color_mid;
out vec4 color_out;

void main(void) {
  color_out = color_mid;
}
)";

const char* kLineFragmentShader = R"(
out vec4 color_out;
void main(void) {
  color_out = vec4(0, 0, 0, 1);
}
)";

}  // namespace

RectangleRenderer::RectangleRenderer(const ProgramState* state)
    : state_(state),
      shader_(kVertexShader, kFragmentShader),
      line_shader_(kVertexShader, kLineFragmentShader) {}

void RectangleRenderer::AddRectangle(Box box, uint32_t rgba) {
  rgba = __builtin_bswap32(rgba);
  // This looks like this:
  //   2  3
  //   0  1
  points_.push_back(Point{box.origin.x, box.origin.y, rgba});               // 0
  points_.push_back(Point{box.origin.x + box.size.x, box.origin.y, rgba});  // 1
  points_.push_back(Point{box.origin.x, box.origin.y + box.size.y, rgba});  // 2
  points_.push_back(
      Point{box.origin.x + box.size.x, box.origin.y + box.size.y, rgba});  // 3
}

void RectangleRenderer::AddRectangleWithBorder(Box box, uint32_t rgba) {
  const size_t point_buffer_offset = points_.size();
  AddRectangle(box, rgba);

  const std::array<uint16_t, 8> lines = {0, 1, 1, 3, 3, 2, 2, 0};
  line_indices_.resize(line_indices_.size() + 8);
  absl::c_transform(lines, line_indices_.end() - 8,
                    [=](uint16_t i) { return i + point_buffer_offset; });
}

void RectangleRenderer::Render() {
  GlVertexBuffer point_buffer;
  glBufferData(GL_ARRAY_BUFFER, points_.size() * sizeof(Point), points_.data(),
               GL_STREAM_DRAW);

  const size_t rectangle_count = points_.size() / 4;
  const size_t point_count = rectangle_count * 6;
  CHECK(point_count < std::numeric_limits<uint16_t>::max());

  // Transform points of each rectangle into a pair of triangles.
  auto indices = std::make_unique<uint16_t[]>(point_count);
  for (int i = 0; i < rectangle_count; i++) {
    indices[6 * i + 0] = 4 * i + 0;
    indices[6 * i + 1] = 4 * i + 1;
    indices[6 * i + 2] = 4 * i + 2;
    indices[6 * i + 3] = 4 * i + 2;
    indices[6 * i + 4] = 4 * i + 3;
    indices[6 * i + 5] = 4 * i + 1;
  }

  glUseProgram(*shader_);
  state_->Bind(shader_);

  {
    GlVertexArray array;
    glBindVertexArray(*array);

    GlElementBuffer index_buffer;
    glBufferData(GL_ELEMENT_ARRAY_BUFFER, point_count * sizeof(indices[0]),
                 indices.get(), GL_STREAM_DRAW);

    GlVertexArrayAttrib coord(shader_, "coord");
    glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, sizeof(Point), 0);
    GlVertexArrayAttrib color(shader_, "color_in");
    glVertexAttribPointer(*color, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Point),
                          (void*)offsetof(Point, rgba));

    glDrawElements(GL_TRIANGLES, point_count, GL_UNSIGNED_SHORT, nullptr);
    points_.clear();
  }

  glUseProgram(*line_shader_);
  state_->Bind(line_shader_);
  {
    GlVertexArray array;
    glBindVertexArray(*array);

    GlElementBuffer index_buffer;
    glBufferData(GL_ELEMENT_ARRAY_BUFFER,
                 line_indices_.size() * sizeof(line_indices_[0]),
                 line_indices_.data(), GL_STREAM_DRAW);

    GlVertexArrayAttrib coord(shader_, "coord");
    glVertexAttribPointer(*coord, 2, GL_FLOAT, GL_FALSE, sizeof(Point), 0);

    glDrawElements(GL_LINES, line_indices_.size(), GL_UNSIGNED_SHORT, nullptr);
    line_indices_.clear();
  }
}

}  // namespace render
}  // namespace quic_trace
