#include "main.h"
#include "internal.h"

TEST_CASE("TextLayout") {
  auto nc_ = testing_notcurses();
  if(!nc_){
    return;
  }
  ncplane* ncp_ = notcurses_stdplane(nc_);
  REQUIRE(ncp_);

  const char str[] = "this is going to be broken up";

  SUBCASE("LayoutLeft") {
    auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, str, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(str));
    char* line = ncplane_contents(sp, 0, 0, 2, 20);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "this is going to be broken up"));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutRight") {
    auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_RIGHT, str, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(str));
    char* line = ncplane_contents(sp, 0, 0, 2, 20);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "this is going to be broken up"));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutCenter") {
    auto sp = ncplane_new(nc_, 2, 20, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, str, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(str));
    char* line = ncplane_contents(sp, 0, 0, 2, 20);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "this is going to be broken up"));
    free(line);
    ncplane_destroy(sp);
  }

  // lay out text where a word ends on the boundary
  SUBCASE("LayoutOnBoundary") {
    auto sp = ncplane_new(nc_, 2, 10, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "my nuclear arms";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "my nucleararms"));
    free(line);
    ncplane_destroy(sp);
  }

  // lay out text where a word crosses the boundary
  SUBCASE("LayoutCrossBoundary") {
    auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "my grasping arms";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "mygraspingarms"));
    free(line);
    ncplane_destroy(sp);
  }

  // lay out text where a wide word crosses the boundary
  SUBCASE("LayoutCrossBoundaryWide") {
    if(enforce_utf8()){
      auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
      REQUIRE(sp);
      size_t bytes;
      const char boundstr[] = "a 血的神";
      CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
      CHECK(0 == notcurses_render(nc_));
      CHECK(bytes == strlen(boundstr));
      char* line = ncplane_contents(sp, 0, 0, -1, -1);
      REQUIRE(line);
      CHECK(0 == strcmp(line, "a血的神"));
      free(line);
      ncplane_destroy(sp);
    }
  }

  // a long word (one requiring a split no matter what) ought not force the
  // next line, but instead be printed where it starts
  SUBCASE("LayoutTransPlanar") {
    auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "my thermonuclear arms";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "my thermonucleararms"));
    free(line);
    ncplane_destroy(sp);
  }

  // a long word (one requiring a split no matter what) ought not force the
  // next line, but instead be printed where it starts
  SUBCASE("LayoutTransPlanarWide") {
    if(enforce_utf8()){
      auto sp = ncplane_new(nc_, 2, 8, 0, 0, nullptr);
      REQUIRE(sp);
      size_t bytes;
      const char boundstr[] = "1 我能吞下玻璃";
      CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
      CHECK(0 == notcurses_render(nc_));
      CHECK(bytes == strlen(boundstr));
      char* line = ncplane_contents(sp, 0, 0, -1, -1);
      REQUIRE(line);
      CHECK(0 == strcmp(line, "1 我能吞下玻璃"));
      free(line);
      ncplane_destroy(sp);
    }
  }

  SUBCASE("LayoutLeadingSpaces") {
    auto sp = ncplane_new(nc_, 3, 10, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "  \t\n my thermonuclear arms";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_CENTER, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "my thermonucleararms"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of a single row, and fill it exactly with one word
  SUBCASE("LayoutFills1DPlane") {
    auto sp = ncplane_new(nc_, 1, 14, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "quarkgluonfart";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "quarkgluonfart"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of a single row, and fill it exactly with words
  SUBCASE("LayoutFills1DPlaneWords") {
    auto sp = ncplane_new(nc_, 1, 16, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "quark gluon fart";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "quark gluon fart"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of two rows, and exactly fill the first line
  SUBCASE("LayoutFillsSingleLine") {
    auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "quantum balls";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "quantum balls"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of two rows, and exactly fill both
  SUBCASE("LayoutFillsPlane") {
    auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "quantum balls scratchy no?!";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "quantum ballsscratchy no?!"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of two rows, and exactly fill both, with no spaces
  SUBCASE("LayoutFillsPlaneNoSpaces") {
    auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "0123456789AB";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "0123456789AB"));
    free(line);
    ncplane_destroy(sp);
  }

  // create a plane of two rows, and exactly fill both with wide chars
  SUBCASE("LayoutFillsPlaneWide") {
    if(enforce_utf8()){
      auto sp = ncplane_new(nc_, 2, 6, 0, 0, nullptr);
      REQUIRE(sp);
      size_t bytes;
      const char boundstr[] = "我能吞 下玻璃";
      CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
      CHECK(0 == notcurses_render(nc_));
      CHECK(bytes == strlen(boundstr));
      char* line = ncplane_contents(sp, 0, 0, -1, -1);
      REQUIRE(line);
      CHECK(0 == strcmp(line, "我能吞下玻璃"));
      free(line);
      ncplane_destroy(sp);
    }
  }

  SUBCASE("LayoutLongNoScroll") {
    auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    const char boundstr[] = "quantum balls scratchy no?! truly! arrrrp";
    int res = ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes);
    CHECK(0 > res);
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes < strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "quantum ballsscratchy no?!"));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutLongScroll") {
    auto sp = ncplane_new(nc_, 2, 13, 0, 0, nullptr);
    REQUIRE(sp);
    ncplane_set_scrolling(sp, true);
    size_t bytes;
    const char boundstr[] = "quantum balls scratchy?! true! arrrrp";
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, boundstr, &bytes));
    CHECK(0 == notcurses_render(nc_));
    CHECK(bytes == strlen(boundstr));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "scratchy?!true! arrrrp"));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutLongLines") {
    // straight from the zoo demo, which didn't work at first
    const int READER_COLS = 71; // equal to longest line
    const int READER_ROWS = 4;
    const char text[] =
      "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornare "
      "neque ac ipsum viverra, vestibulum hendrerit leo consequat. Integer "
      "velit, pharetra sed nisl quis, porttitor ornare purus. Cras ac "
      "sollicitudin dolor, eget elementum dolor. Quisque lobortis sagittis.";
    auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    ncplane_home(sp);
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
    CHECK(bytes == strlen(text));
    CHECK(0 == notcurses_render(nc_));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin ornareneque ac ipsum viverra, vestibulum hendrerit leo consequat. Integervelit, pharetra sed nisl quis, porttitor ornare purus. Cras acsollicitudin dolor, eget elementum dolor. Quisque lobortis sagittis."));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutZooText") {
    // straight from the zoo demo, which didn't work at first
    const int READER_COLS = 64;
    const int READER_ROWS = 8;
    const char text[] =
      "Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
      "This NCReader widget facilitates free-form text entry complete with readline-style bindings. " "NCSelector allows a single option to be selected from a list. " "NCMultiselector allows 0..n options to be selected from a list of n items. "
      "NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. "
      "A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.\n\n"
      "Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others.";
    auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
    REQUIRE(sp);
    ncplane_set_scrolling(sp, true);
    size_t bytes;
    ncplane_home(sp);
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
    CHECK(bytes == strlen(text));
    CHECK(0 == notcurses_render(nc_));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "to be selected from a list of n items. NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. A variety of plots are supported, and menus can beplaced along the top and/or bottom of any plane.Widgets can be controlled with the keyboard and/or mouse. Theyare implemented atop ncplanes, and these planes can bemanipulated like all others."));
    free(line);
    ncplane_destroy(sp);
  }

  SUBCASE("LayoutZooTextNoScroll") {
    // straight from the zoo demo, which didn't work at first
    const int READER_COLS = 64;
    const int READER_ROWS = 15;
    const char text[] =
      "Notcurses provides several widgets to quickly build vivid TUIs.\n\n"
      "This NCReader widget facilitates free-form text entry complete with readline-style bindings. "
      "NCSelector allows a single option to be selected from a list. "
      "NCMultiselector allows 0..n options to be selected from a list of n items. "
      "NCFdplane streams a file descriptor, while NCSubproc spawns a subprocess and streams its output. "
      "A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.\n\n"
      "Widgets can be controlled with the keyboard and/or mouse. They are implemented atop ncplanes, and these planes can be manipulated like all others.";
    auto sp = ncplane_new(nc_, READER_ROWS, READER_COLS, 0, 0, nullptr);
    REQUIRE(sp);
    size_t bytes;
    ncplane_home(sp);
    CHECK(0 < ncplane_puttext(sp, 0, NCALIGN_LEFT, text, &bytes));
    CHECK(bytes == strlen(text));
    CHECK(0 == notcurses_render(nc_));
    char* line = ncplane_contents(sp, 0, 0, -1, -1);
    REQUIRE(line);
    CHECK(0 == strcmp(line, "Notcurses provides several widgets to quickly build vividTUIs.This NCReader widget facilitates free-form text entrycomplete with readline-style bindings. NCSelector allows asingleoption to be selected from a list. NCMultiselector allows 0..noptions to be selected from a list of n items. NCFdplane streamsa file descriptor, while NCSubproc spawns a subprocess andstreams its output. A variety of plots are supported, and menus can be placed along the top and/or bottom of any plane.Widgetscan be controlled with the keyboard and/or mouse. They areimplemented atop ncplanes, and these planes can be manipulatedlike all others."));
    free(line);
    ncplane_destroy(sp);
  }

  CHECK(0 == notcurses_stop(nc_));

}
