/*
 * Copyright 2017 Google, Inc.
 *
 * 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
 *
 *      http://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 <minikin/Layout.h>

#include "flutter/fml/command_line.h"
#include "flutter/fml/logging.h"
#include "flutter/third_party/txt/tests/txt_test_utils.h"
#include "minikin/LayoutUtils.h"
#include "third_party/benchmark/include/benchmark/benchmark_api.h"
#include "third_party/icu/source/common/unicode/unistr.h"
#include "third_party/skia/include/core/SkBitmap.h"
#include "third_party/skia/include/core/SkCanvas.h"
#include "third_party/skia/include/core/SkColor.h"
#include "txt/font_collection.h"
#include "txt/font_skia.h"
#include "txt/font_style.h"
#include "txt/font_weight.h"
#include "txt/paragraph.h"
#include "txt/paragraph_builder_txt.h"

namespace txt {

static void BM_ParagraphShortLayout(benchmark::State& state) {
  const char* text = "Hello World";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;
  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  builder.Pop();
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
}
BENCHMARK(BM_ParagraphShortLayout);

static void BM_ParagraphLongLayout(benchmark::State& state) {
  const char* text =
      "This is a very long sentence to test if the text will properly wrap "
      "around and go to the next line. Sometimes, short sentence. Longer "
      "sentences are okay too because they are necessary. Very short. "
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
      "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
      "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
      "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
      "occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
      "mollit anim id est laborum. "
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
      "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
      "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
      "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
      "occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
      "mollit anim id est laborum.";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;

  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  builder.Pop();
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
}
BENCHMARK(BM_ParagraphLongLayout);

static void BM_ParagraphJustifyLayout(benchmark::State& state) {
  const char* text =
      "This is a very long sentence to test if the text will properly wrap "
      "around and go to the next line. Sometimes, short sentence. Longer "
      "sentences are okay too because they are necessary. Very short. "
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
      "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
      "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
      "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
      "occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
      "mollit anim id est laborum. "
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod "
      "tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim "
      "veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
      "commodo consequat. Duis aute irure dolor in reprehenderit in voluptate "
      "velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint "
      "occaecat cupidatat non proident, sunt in culpa qui officia deserunt "
      "mollit anim id est laborum.";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;
  paragraph_style.text_align = TextAlign::justify;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;

  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  builder.Pop();
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
}
BENCHMARK(BM_ParagraphJustifyLayout);

static void BM_ParagraphManyStylesLayout(benchmark::State& state) {
  const char* text = "-";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;
  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
  for (int i = 0; i < 1000; ++i) {
    builder.PushStyle(text_style);
    builder.AddText(u16_text);
  }
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
}
BENCHMARK(BM_ParagraphManyStylesLayout);

static void BM_ParagraphTextBigO(benchmark::State& state) {
  std::vector<uint16_t> text;
  for (uint16_t i = 0; i < state.range(0); ++i) {
    text.push_back(i % 5 == 0 ? ' ' : i);
  }
  std::u16string u16_text(text.data(), text.data() + text.size());

  txt::ParagraphStyle paragraph_style;
  paragraph_style.font_family = "Roboto";

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;

  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  builder.Pop();
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
  state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphTextBigO)
    ->RangeMultiplier(4)
    ->Range(1 << 6, 1 << 14)
    ->Complexity(benchmark::oN);

static void BM_ParagraphStylesBigO(benchmark::State& state) {
  const char* text = "vry shrt ";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;

  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  for (int i = 0; i < state.range(0); ++i) {
    builder.PushStyle(text_style);
    builder.AddText(u16_text);
  }
  auto paragraph = BuildParagraph(builder);
  while (state.KeepRunning()) {
    paragraph->SetDirty();
    paragraph->Layout(300);
  }
  state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphStylesBigO)
    ->RangeMultiplier(4)
    ->Range(1 << 3, 1 << 12)
    ->Complexity(benchmark::oN);

static void BM_ParagraphPaintSimple(benchmark::State& state) {
  const char* text = "Hello world! This is a simple sentence to test drawing.";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;
  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  auto paragraph = BuildParagraph(builder);
  paragraph->Layout(300);

  std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
  bitmap->allocN32Pixels(1000, 1000);
  std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
  canvas->clear(SK_ColorWHITE);
  int offset = 0;
  while (state.KeepRunning()) {
    paragraph->Paint(canvas.get(), offset % 700, 10);
    offset++;
  }
}
BENCHMARK(BM_ParagraphPaintSimple);

static void BM_ParagraphPaintLarge(benchmark::State& state) {
  const char* text =
      "Hello world! This is a simple sentence to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.Hello world! This is a simple sentence "
      "to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.Hello world! This is a simple sentence "
      "to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.Hello world! This is a simple sentence "
      "to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.Hello world! This is a simple sentence "
      "to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.Hello world! This is a simple sentence "
      "to test drawing. Hello world! "
      "This is a simple sentence to test drawing. Hello world! This is a "
      "simple sentence to test drawing.";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.color = SK_ColorBLACK;
  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());
  builder.PushStyle(text_style);
  builder.AddText(u16_text);
  auto paragraph = BuildParagraph(builder);
  paragraph->Layout(300);

  std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
  bitmap->allocN32Pixels(1000, 1000);
  std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
  canvas->clear(SK_ColorWHITE);
  int offset = 0;
  while (state.KeepRunning()) {
    paragraph->Paint(canvas.get(), offset % 700, 10);
    offset++;
  }
}
BENCHMARK(BM_ParagraphPaintLarge);

static void BM_ParagraphPaintDecoration(benchmark::State& state) {
  const char* text =
      "Hello world! This is a simple sentence to test drawing. Hello world! "
      "This is a simple sentence to test drawing.";
  auto icu_text = icu::UnicodeString::fromUTF8(text);
  std::u16string u16_text(icu_text.getBuffer(),
                          icu_text.getBuffer() + icu_text.length());

  txt::ParagraphStyle paragraph_style;

  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  text_style.decoration = TextDecoration::kUnderline |
                          TextDecoration::kOverline |
                          TextDecoration::kLineThrough;
  text_style.decoration_style = TextDecorationStyle(kSolid);
  text_style.color = SK_ColorBLACK;

  txt::ParagraphBuilderTxt builder(paragraph_style, GetTestFontCollection());

  builder.PushStyle(text_style);
  builder.AddText(u16_text);

  text_style.decoration_style = TextDecorationStyle(kDotted);
  builder.PushStyle(text_style);
  builder.AddText(u16_text);

  text_style.decoration_style = TextDecorationStyle(kWavy);
  builder.PushStyle(text_style);
  builder.AddText(u16_text);

  auto paragraph = BuildParagraph(builder);
  paragraph->Layout(300);

  std::unique_ptr<SkBitmap> bitmap = std::make_unique<SkBitmap>();
  bitmap->allocN32Pixels(1000, 1000);
  std::unique_ptr<SkCanvas> canvas = std::make_unique<SkCanvas>(*bitmap);
  canvas->clear(SK_ColorWHITE);
  int offset = 0;
  while (state.KeepRunning()) {
    paragraph->Paint(canvas.get(), offset % 700, 10);
    offset++;
  }
}
BENCHMARK(BM_ParagraphPaintDecoration);

// -----------------------------------------------------------------------------
//
// The following benchmarks break down the layout function and attempts to time
// each of the components to more finely attribute latency.
//
// -----------------------------------------------------------------------------

static void BM_ParagraphMinikinDoLayout(benchmark::State& state) {
  std::vector<uint16_t> text;
  for (uint16_t i = 0; i < 16000 * 2; ++i) {
    text.push_back(i % 5 == 0 ? ' ' : i);
  }
  minikin::FontStyle font;
  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  minikin::MinikinPaint paint;

  font = minikin::FontStyle(4, false);
  paint.size = text_style.font_size;
  paint.letterSpacing = text_style.letter_spacing;
  paint.wordSpacing = text_style.word_spacing;

  auto collection =
      GetTestFontCollection()->GetMinikinFontCollectionForFamilies(
          text_style.font_families, "en-US");

  while (state.KeepRunning()) {
    minikin::Layout layout;
    layout.doLayout(text.data(), 0, state.range(0), state.range(0), 0, font,
                    paint, collection);
  }
  state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphMinikinDoLayout)
    ->RangeMultiplier(4)
    ->Range(1 << 7, 1 << 14)
    ->Complexity(benchmark::oN);

static void BM_ParagraphMinikinAddStyleRun(benchmark::State& state) {
  std::vector<uint16_t> text;
  for (uint16_t i = 0; i < 16000 * 2; ++i) {
    text.push_back(i % 5 == 0 ? ' ' : i);
  }
  minikin::FontStyle font;
  txt::TextStyle text_style;
  text_style.font_families = std::vector<std::string>(1, "Roboto");
  minikin::MinikinPaint paint;

  font = minikin::FontStyle(4, false);
  paint.size = text_style.font_size;
  paint.letterSpacing = text_style.letter_spacing;
  paint.wordSpacing = text_style.word_spacing;

  auto font_collection = GetTestFontCollection();

  minikin::LineBreaker breaker;
  breaker.setLocale(icu::Locale(), nullptr);
  breaker.resize(text.size());
  memcpy(breaker.buffer(), text.data(), text.size() * sizeof(text[0]));
  breaker.setText();

  while (state.KeepRunning()) {
    for (int i = 0; i < 20; ++i) {
      breaker.addStyleRun(&paint,
                          font_collection->GetMinikinFontCollectionForFamilies(
                              std::vector<std::string>(1, "Roboto"), "en-US"),
                          font, state.range(0) / 20 * i,
                          state.range(0) / 20 * (i + 1), false);
    }
  }
  state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphMinikinAddStyleRun)
    ->RangeMultiplier(4)
    ->Range(1 << 7, 1 << 14)
    ->Complexity(benchmark::oN);

static void BM_ParagraphSkTextBlobAlloc(benchmark::State& state) {
  SkFont font;
  font.setEdging(SkFont::Edging::kAntiAlias);
  font.setSize(14);
  font.setEmbolden(false);

  while (state.KeepRunning()) {
    SkTextBlobBuilder builder;
    builder.allocRunPos(font, state.range(0));
  }
  state.SetComplexityN(state.range(0));
}
BENCHMARK(BM_ParagraphSkTextBlobAlloc)
    ->RangeMultiplier(4)
    ->Range(1 << 7, 1 << 14)
    ->Complexity(benchmark::oN);

}  // namespace txt