// Copyright 2016 PDFium 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 "core/fxge/cfx_defaultrenderdevice.h"
#include "core/fxge/cfx_font.h"
#include "core/fxge/cfx_graphstatedata.h"
#include "core/fxge/cfx_pathdata.h"
#include "core/fxge/cfx_renderdevice.h"
#include "core/fxge/skia/fx_skia_device.h"
#include "core/fxge/text_char_pos.h"
#include "fpdfsdk/cpdfsdk_helpers.h"
#include "public/cpp/fpdf_scopers.h"
#include "public/fpdfview.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/skia/include/core/SkPictureRecorder.h"

namespace {

struct State {
  enum class Change { kNo, kYes };
  enum class Save { kNo, kYes };
  enum class Clip { kNo, kSame, kDifferentPath, kDifferentMatrix };
  enum class Graphic { kNone, kPath, kText };

  Change m_change;
  Save m_save;
  Clip m_clip;
  Graphic m_graphic;
  uint32_t m_pixel;
};

void EmptyTest(CFX_SkiaDeviceDriver* driver, const State&) {
  driver->SaveState();
  driver->RestoreState(true);
  driver->RestoreState(false);
}

void CommonTest(CFX_SkiaDeviceDriver* driver, const State& state) {
  TextCharPos charPos[1];
  charPos[0].m_Origin = CFX_PointF(0, 1);
  charPos[0].m_GlyphIndex = 1;
  charPos[0].m_FontCharWidth = 4u;

  CFX_Font font;
  float fontSize = 1;
  CFX_PathData clipPath, clipPath2;
  clipPath.AppendRect(0, 0, 3, 1);
  clipPath2.AppendRect(0, 0, 2, 1);
  CFX_Matrix clipMatrix;
  CFX_Matrix clipMatrix2(1, 0, 0, 1, 0, 1);
  driver->SaveState();
  CFX_PathData path1;
  path1.AppendRect(0, 0, 1, 2);

  CFX_Matrix matrix;
  CFX_Matrix matrix2;
  matrix2.Translate(1, 0);
  CFX_GraphStateData graphState;
  if (state.m_save == State::Save::kYes)
    driver->SaveState();
  if (state.m_clip != State::Clip::kNo)
    driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  if (state.m_graphic == State::Graphic::kPath) {
    driver->DrawPath(&path1, &matrix, &graphState, 0xFF112233, 0,
                     FXFILL_WINDING, BlendMode::kNormal);
  } else if (state.m_graphic == State::Graphic::kText) {
    driver->DrawDeviceText(SK_ARRAY_COUNT(charPos), charPos, &font, matrix,
                           fontSize, 0xFF445566);
  }
  if (state.m_save == State::Save::kYes)
    driver->RestoreState(true);
  CFX_PathData path2;
  path2.AppendRect(0, 0, 2, 2);
  if (state.m_change == State::Change::kYes) {
    if (state.m_graphic == State::Graphic::kPath)
      graphState.m_LineCap = CFX_GraphStateData::LineCapRound;
    else if (state.m_graphic == State::Graphic::kText)
      fontSize = 2;
  }
  if (state.m_clip == State::Clip::kSame)
    driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  else if (state.m_clip == State::Clip::kDifferentPath)
    driver->SetClip_PathFill(&clipPath2, &clipMatrix, 0);
  else if (state.m_clip == State::Clip::kDifferentMatrix)
    driver->SetClip_PathFill(&clipPath, &clipMatrix2, 0);
  if (state.m_graphic == State::Graphic::kPath) {
    driver->DrawPath(&path2, &matrix2, &graphState, 0xFF112233, 0,
                     FXFILL_WINDING, BlendMode::kNormal);
  } else if (state.m_graphic == State::Graphic::kText) {
    driver->DrawDeviceText(SK_ARRAY_COUNT(charPos), charPos, &font, matrix2,
                           fontSize, 0xFF445566);
  }
  if (state.m_save == State::Save::kYes)
    driver->RestoreState(false);
  driver->RestoreState(false);
}

void OutOfSequenceClipTest(CFX_SkiaDeviceDriver* driver, const State&) {
  CFX_PathData clipPath;
  clipPath.AppendRect(1, 0, 3, 1);
  CFX_Matrix clipMatrix;
  driver->SaveState();
  driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  driver->RestoreState(true);
  driver->SaveState();
  driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  driver->RestoreState(false);
  driver->RestoreState(false);

  driver->SaveState();
  driver->SaveState();
  driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  driver->RestoreState(true);
  driver->SetClip_PathFill(&clipPath, &clipMatrix, 0);
  driver->RestoreState(false);
  driver->RestoreState(false);
}

void Harness(void (*Test)(CFX_SkiaDeviceDriver*, const State&),
             const State& state) {
  constexpr int kWidth = 4;
  constexpr int kHeight = 1;
  ScopedFPDFBitmap bitmap(FPDFBitmap_Create(kWidth, kHeight, 1));
  ASSERT_TRUE(bitmap);
  FPDFBitmap_FillRect(bitmap.get(), 0, 0, kWidth, kHeight, 0x00000000);
  CFX_DefaultRenderDevice device;
  RetainPtr<CFX_DIBitmap> pBitmap(CFXDIBitmapFromFPDFBitmap(bitmap.get()));
  device.Attach(pBitmap, false, nullptr, false);
  auto* driver = static_cast<CFX_SkiaDeviceDriver*>(device.GetDeviceDriver());
  (*Test)(driver, state);
  driver->Flush();
  uint32_t pixel = pBitmap->GetPixel(0, 0);
  EXPECT_EQ(state.m_pixel, pixel);
#ifdef SK_DEBUG
  if (!driver)  // force dump to be linked in so it can be called from debugger
    driver->Dump();
#endif
}

}  // namespace

TEST(fxge, SkiaStateEmpty) {
  Harness(&EmptyTest, {});
}

TEST(fxge, SkiaStatePath) {
  Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
                        State::Clip::kSame, State::Graphic::kPath, 0xFF112233});
  Harness(&CommonTest,
          {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentPath,
           State::Graphic::kPath, 0xFF112233});
  Harness(&CommonTest, {State::Change::kNo, State::Save::kYes, State::Clip::kNo,
                        State::Graphic::kPath, 0xFF112233});
  Harness(&CommonTest, {State::Change::kYes, State::Save::kNo, State::Clip::kNo,
                        State::Graphic::kPath, 0xFF112233});
  Harness(&CommonTest, {State::Change::kNo, State::Save::kNo, State::Clip::kNo,
                        State::Graphic::kPath, 0xFF112233});
}

#ifdef _SKIA_SUPPORT_
// TODO(crbug.com/pdfium/11): Fix this test and enable.
TEST(fxge, DISABLED_SkiaStateText) {
  Harness(&CommonTest,
          {State::Change::kNo, State::Save::kYes, State::Clip::kDifferentMatrix,
           State::Graphic::kText, 0xFF445566});
  Harness(&CommonTest, {State::Change::kNo, State::Save::kYes,
                        State::Clip::kSame, State::Graphic::kText, 0xFF445566});
}
#endif

TEST(fxge, SkiaStateOOSClip) {
  Harness(&OutOfSequenceClipTest, {});
}
