
#include "roo_display/filter/clip_mask.h"

#include "roo_display.h"
#include "roo_display/color/color.h"
#include "testing.h"
#include "testing_display_device.h"

using namespace testing;

namespace roo_display {

class SimpleRoundMask {
 public:
  SimpleRoundMask(Box extents) {}

  template <typename ColorMode>
  void writePixel(BlendingMode mode, int16_t x, int16_t y, Color color,
                  FakeOffscreen<ColorMode>* offscreen) {
    static const char mask[] =
        "                "
        "   *******      "
        "  *********     "
        " ***********    "
        "  *********     "
        "   *******      "
        "                ";
    if (Box(1, 2, 16, 8).contains(x, y) && mask[x - 1 + (y - 2) * 16] == '*')
      return;
    offscreen->writePixel(mode, x, y, color);
  }

  static ClipMaskFilter* Create(DisplayOutput& output, Box extents) {
    static const uint8_t clip_mask_data[] = {
        0x00, 0x00, 0x1F, 0xC0, 0x3F, 0xE0, 0x7F,
        0xF0, 0x3F, 0xE0, 0x1F, 0xC0, 0x00, 0x00,
    };
    static ClipMask mask(clip_mask_data, Box(1, 2, 16, 8));
    return new ClipMaskFilter(output, &mask);
  }
};

class LargeMask {
 public:
  LargeMask(Box extents) {}

  template <typename ColorMode>
  void writePixel(BlendingMode mode, int16_t x, int16_t y, Color color,
                  FakeOffscreen<ColorMode>* offscreen) {
    static const char mask[] =
        "                                "
        "   ***********************      "
        "  *************************     "
        "  *************************     "
        " ***************************    "
        " ***************************    "
        " ***************************    "
        "  *************************     "
        "  *************************     "
        "   ***********************      "
        "   ***********************      "
        "   ***********************      "
        "  *************************     "
        "  *************************     "
        " ***************************    "
        " ***************************    "
        " ***************************    "
        "  *************************     "
        "  *************************     "
        "   ***********************      "
        "                                ";
    if (Box(1, 2, 32, 22).contains(x, y) && mask[x - 1 + (y - 2) * 32] == '*')
      return;
    offscreen->writePixel(mode, x, y, color);
  }

  static ClipMaskFilter* Create(DisplayOutput& output, Box extents) {
    static const uint8_t clip_mask_data[] = {
        0b00000000, 0b00000000, 0b00000000, 0b00000000,  // NOFORMAT
        0b00011111, 0b11111111, 0b11111111, 0b11000000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00011111, 0b11111111, 0b11111111, 0b11000000,  // NOFORMAT
        0b00011111, 0b11111111, 0b11111111, 0b11000000,  // NOFORMAT
        0b00011111, 0b11111111, 0b11111111, 0b11000000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b01111111, 0b11111111, 0b11111111, 0b11110000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00111111, 0b11111111, 0b11111111, 0b11100000,  // NOFORMAT
        0b00011111, 0b11111111, 0b11111111, 0b11000000,  // NOFORMAT
        0b00000000, 0b00000000, 0b00000000, 0b00000000,  // NOFORMAT
    };
    static ClipMask mask(clip_mask_data, Box(1, 2, 32, 22));
    return new ClipMaskFilter(output, &mask);
  }
};

typedef FakeFilteringOffscreen<Grayscale4, SimpleRoundMask> RefDeviceSimple;
typedef FilteredOutput<Grayscale4, SimpleRoundMask> TestDeviceSimple;

TEST(ClipMask, SimpleTests) {
  TestFillRects<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                   Orientation());
  TestFillHLines<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                    Orientation());
  TestFillVLines<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                    Orientation());
  TestFillDegeneratePixels<TestDeviceSimple, RefDeviceSimple>(
      BlendingMode::kSource, Orientation());
  TestFillPixels<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                    Orientation());

  TestWriteRects<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                    Orientation());
  TestWriteHLines<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                     Orientation());
  TestWriteVLines<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                     Orientation());
  TestWriteDegeneratePixels<TestDeviceSimple, RefDeviceSimple>(
      BlendingMode::kSource, Orientation());
  TestWritePixels<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                     Orientation());
  TestWritePixelsSnake<TestDeviceSimple, RefDeviceSimple>(BlendingMode::kSource,
                                                          Orientation());
  TestWriteRectWindowSimple<TestDeviceSimple, RefDeviceSimple>(
      BlendingMode::kSource, Orientation());
}

TEST(ClipMask, StressTests) {
  TestWritePixelsStress<TestDeviceSimple, RefDeviceSimple>(
      BlendingMode::kSource, Orientation());
  TestWriteRectWindowStress<TestDeviceSimple, RefDeviceSimple>(
      BlendingMode::kSource, Orientation());
}

typedef FakeFilteringOffscreen<Grayscale4, LargeMask> RefDeviceLarge;
typedef FilteredOutput<Grayscale4, LargeMask> TestDeviceLarge;

TEST(ClipMask, SimpleLargeTests) {
  TestFillRects<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                 Orientation());
  TestFillHLines<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                  Orientation());
  TestFillVLines<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                  Orientation());
  TestFillDegeneratePixels<TestDeviceLarge, RefDeviceLarge>(
      BlendingMode::kSource, Orientation());
  TestFillPixels<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                  Orientation());

  TestWriteRects<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                  Orientation());
  TestWriteHLines<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                   Orientation());
  TestWriteVLines<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                   Orientation());
  TestWriteDegeneratePixels<TestDeviceLarge, RefDeviceLarge>(
      BlendingMode::kSource, Orientation());
  TestWritePixels<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                   Orientation());
  TestWritePixelsSnake<TestDeviceLarge, RefDeviceLarge>(BlendingMode::kSource,
                                                        Orientation());
  TestWriteRectWindowSimple<TestDeviceLarge, RefDeviceLarge>(
      BlendingMode::kSource, Orientation());
}

TEST(ClipMask, ClipMaskWrite) {
  FakeOffscreen<Rgb565> test_screen(16, 7);
  Display display(test_screen);
  const uint8_t clip_mask_data[] = {
      0xFF, 0xFF, 0xE0, 0x3F, 0xC0, 0x1F, 0x80,
      0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xFF, 0xFF,
  };
  ClipMask mask(clip_mask_data, Box(0, 0, 15, 6));
  {
    DrawingContext dc(display);
    dc.setClipMask(&mask);
    dc.draw(MakeTestStreamable(WhiteOnBlack(), Box(0, 0, 13, 5),
                               "**************"
                               "          ****"
                               "**************"
                               "**************"
                               "***           "
                               "***           "
                               "**************"),
            1, 0);
  }
  EXPECT_THAT(test_screen, MatchesContent(WhiteOnBlack(), 16, 7,
                                          "                "
                                          "                "
                                          "  *********     "
                                          " ***********    "
                                          "  **            "
                                          "   *            "
                                          "                "));
}

TEST(ClipMask, ClipMaskStreamableSemiTransparent) {
  FakeOffscreen<Rgb565> test_screen(16, 7);
  Display display(test_screen);
  const uint8_t clip_mask_data[] = {
      0xFF, 0xFF, 0xE0, 0x3F, 0xC0, 0x1F, 0x80,
      0x0F, 0xC0, 0x1F, 0xE0, 0x3F, 0xFF, 0xFF,
  };
  ClipMask mask(clip_mask_data, Box(0, 0, 15, 6));
  {
    DrawingContext dc(display);
    dc.setClipMask(&mask);
    dc.draw(MakeTestStreamable(Alpha4(color::White), Box(0, 0, 13, 5),
                               "**************"
                               "          ****"
                               "**************"
                               "**************"
                               "***           "
                               "***           "
                               "**************"),
            1, 0);
  }
  EXPECT_THAT(test_screen, MatchesContent(WhiteOnBlack(), 16, 7,
                                          "                "
                                          "                "
                                          "  *********     "
                                          " ***********    "
                                          "  **            "
                                          "   *            "
                                          "                "));
}

}  // namespace roo_display
