// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_
#define FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_

#include <sstream>

#include "flutter/fml/logging.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/embedder/embedder_engine.h"
#include "flutter/testing/assertions.h"
#include "gtest/gtest.h"
#include "third_party/skia/include/core/SkPoint.h"
#include "third_party/skia/include/core/SkSize.h"

//------------------------------------------------------------------------------
// Equality
//------------------------------------------------------------------------------
inline bool operator==(const FlutterPoint& a, const FlutterPoint& b) {
  return flutter::testing::NumberNear(a.x, b.x) &&
         flutter::testing::NumberNear(a.y, b.y);
}

inline bool operator==(const FlutterRect& a, const FlutterRect& b) {
  return flutter::testing::NumberNear(a.left, b.left) &&
         flutter::testing::NumberNear(a.top, b.top) &&
         flutter::testing::NumberNear(a.right, b.right) &&
         flutter::testing::NumberNear(a.bottom, b.bottom);
}

inline bool operator==(const FlutterSize& a, const FlutterSize& b) {
  return flutter::testing::NumberNear(a.width, b.width) &&
         flutter::testing::NumberNear(a.height, b.height);
}

inline bool operator==(const FlutterRoundedRect& a,
                       const FlutterRoundedRect& b) {
  return a.rect == b.rect &&
         a.upper_left_corner_radius == b.upper_left_corner_radius &&
         a.upper_right_corner_radius == b.upper_right_corner_radius &&
         a.lower_right_corner_radius == b.lower_right_corner_radius &&
         a.lower_left_corner_radius == b.lower_left_corner_radius;
}

inline bool operator==(const FlutterTransformation& a,
                       const FlutterTransformation& b) {
  return a.scaleX == b.scaleX && a.skewX == b.skewX && a.transX == b.transX &&
         a.skewY == b.skewY && a.scaleY == b.scaleY && a.transY == b.transY &&
         a.pers0 == b.pers0 && a.pers1 == b.pers1 && a.pers2 == b.pers2;
}

inline bool operator==(const FlutterOpenGLTexture& a,
                       const FlutterOpenGLTexture& b) {
  return a.target == b.target && a.name == b.name && a.format == b.format &&
         a.user_data == b.user_data &&
         a.destruction_callback == b.destruction_callback;
}

inline bool operator==(const FlutterOpenGLFramebuffer& a,
                       const FlutterOpenGLFramebuffer& b) {
  return a.target == b.target && a.name == b.name &&
         a.user_data == b.user_data &&
         a.destruction_callback == b.destruction_callback;
}

inline bool operator==(const FlutterMetalTexture& a,
                       const FlutterMetalTexture& b) {
  return a.texture_id == b.texture_id && a.texture == b.texture;
}

inline bool operator==(const FlutterVulkanImage& a,
                       const FlutterVulkanImage& b) {
  return a.image == b.image && a.format == b.format;
}

inline bool operator==(const FlutterVulkanBackingStore& a,
                       const FlutterVulkanBackingStore& b) {
  return a.image == b.image;
}

inline bool operator==(const FlutterMetalBackingStore& a,
                       const FlutterMetalBackingStore& b) {
  return a.texture == b.texture;
}

inline bool operator==(const FlutterOpenGLBackingStore& a,
                       const FlutterOpenGLBackingStore& b) {
  if (!(a.type == b.type)) {
    return false;
  }

  switch (a.type) {
    case kFlutterOpenGLTargetTypeTexture:
      return a.texture == b.texture;
    case kFlutterOpenGLTargetTypeFramebuffer:
      return a.framebuffer == b.framebuffer;
  }

  return false;
}

inline bool operator==(const FlutterSoftwareBackingStore& a,
                       const FlutterSoftwareBackingStore& b) {
  return a.allocation == b.allocation && a.row_bytes == b.row_bytes &&
         a.height == b.height && a.user_data == b.user_data &&
         a.destruction_callback == b.destruction_callback;
}

inline bool operator==(const FlutterSoftwareBackingStore2& a,
                       const FlutterSoftwareBackingStore2& b) {
  return a.allocation == b.allocation && a.row_bytes == b.row_bytes &&
         a.height == b.height && a.user_data == b.user_data &&
         a.destruction_callback == b.destruction_callback &&
         a.pixel_format == b.pixel_format;
}

inline bool operator==(const FlutterBackingStore& a,
                       const FlutterBackingStore& b) {
  if (!(a.struct_size == b.struct_size && a.user_data == b.user_data &&
        a.type == b.type && a.did_update == b.did_update)) {
    return false;
  }

  switch (a.type) {
    case kFlutterBackingStoreTypeOpenGL:
      return a.open_gl == b.open_gl;
    case kFlutterBackingStoreTypeSoftware:
      return a.software == b.software;
    case kFlutterBackingStoreTypeMetal:
      return a.metal == b.metal;
    case kFlutterBackingStoreTypeVulkan:
      return a.vulkan == b.vulkan;
    case kFlutterBackingStoreTypeSoftware2:
      return a.software2 == b.software2;
  }

  return false;
}

inline bool operator==(const FlutterPlatformViewMutation& a,
                       const FlutterPlatformViewMutation& b) {
  if (a.type != b.type) {
    return false;
  }

  switch (a.type) {
    case kFlutterPlatformViewMutationTypeOpacity:
      return flutter::testing::NumberNear(a.opacity, b.opacity);
    case kFlutterPlatformViewMutationTypeClipRect:
      return a.clip_rect == b.clip_rect;
    case kFlutterPlatformViewMutationTypeClipRoundedRect:
      return a.clip_rounded_rect == b.clip_rounded_rect;
    case kFlutterPlatformViewMutationTypeTransformation:
      return a.transformation == b.transformation;
  }

  return false;
}

inline bool operator==(const FlutterPlatformView& a,
                       const FlutterPlatformView& b) {
  if (!(a.struct_size == b.struct_size && a.identifier == b.identifier &&
        a.mutations_count == b.mutations_count)) {
    return false;
  }

  for (size_t i = 0; i < a.mutations_count; ++i) {
    if (!(*a.mutations[i] == *b.mutations[i])) {
      return false;
    }
  }

  return true;
}

inline bool operator==(const FlutterLayer& a, const FlutterLayer& b) {
  if (!(a.struct_size == b.struct_size && a.type == b.type &&
        a.offset == b.offset && a.size == b.size)) {
    return false;
  }

  switch (a.type) {
    case kFlutterLayerContentTypeBackingStore:
      return *a.backing_store == *b.backing_store;
    case kFlutterLayerContentTypePlatformView:
      return *a.platform_view == *b.platform_view;
  }

  return false;
}

//------------------------------------------------------------------------------
// Printing
//------------------------------------------------------------------------------

inline std::ostream& operator<<(std::ostream& out, const FlutterPoint& point) {
  return out << "(" << point.x << ", " << point.y << ")";
}

inline std::ostream& operator<<(std::ostream& out, const FlutterRect& r) {
  return out << "LTRB (" << r.left << ", " << r.top << ", " << r.right << ", "
             << r.bottom << ")";
}

inline std::ostream& operator<<(std::ostream& out, const FlutterSize& size) {
  return out << "(" << size.width << ", " << size.height << ")";
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterRoundedRect& r) {
  out << "Rect: " << r.rect << ", ";
  out << "Upper Left Corner Radius: " << r.upper_left_corner_radius << ", ";
  out << "Upper Right Corner Radius: " << r.upper_right_corner_radius << ", ";
  out << "Lower Right Corner Radius: " << r.lower_right_corner_radius << ", ";
  out << "Lower Left Corner Radius: " << r.lower_left_corner_radius;
  return out;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterTransformation& t) {
  out << "Scale X: " << t.scaleX << ", ";
  out << "Skew X: " << t.skewX << ", ";
  out << "Trans X: " << t.transX << ", ";
  out << "Skew Y: " << t.skewY << ", ";
  out << "Scale Y: " << t.scaleY << ", ";
  out << "Trans Y: " << t.transY << ", ";
  out << "Pers 0: " << t.pers0 << ", ";
  out << "Pers 1: " << t.pers1 << ", ";
  out << "Pers 2: " << t.pers2;
  return out;
}

inline std::string FlutterLayerContentTypeToString(
    FlutterLayerContentType type) {
  switch (type) {
    case kFlutterLayerContentTypeBackingStore:
      return "kFlutterLayerContentTypeBackingStore";
    case kFlutterLayerContentTypePlatformView:
      return "kFlutterLayerContentTypePlatformView";
  }
  return "Unknown";
}

inline std::string FlutterBackingStoreTypeToString(
    FlutterBackingStoreType type) {
  switch (type) {
    case kFlutterBackingStoreTypeOpenGL:
      return "kFlutterBackingStoreTypeOpenGL";
    case kFlutterBackingStoreTypeSoftware:
      return "kFlutterBackingStoreTypeSoftware";
    case kFlutterBackingStoreTypeMetal:
      return "kFlutterBackingStoreTypeMetal";
    case kFlutterBackingStoreTypeVulkan:
      return "kFlutterBackingStoreTypeVulkan";
    case kFlutterBackingStoreTypeSoftware2:
      return "kFlutterBackingStoreTypeSoftware2";
  }
  return "Unknown";
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterOpenGLTexture& item) {
  return out << "(FlutterOpenGLTexture) Target: 0x" << std::hex << item.target
             << std::dec << " Name: " << item.name << " Format: " << item.format
             << " User Data: " << item.user_data << " Destruction Callback: "
             << reinterpret_cast<void*>(item.destruction_callback);
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterOpenGLFramebuffer& item) {
  return out << "(FlutterOpenGLFramebuffer) Target: 0x" << std::hex
             << item.target << std::dec << " Name: " << item.name
             << " User Data: " << item.user_data << " Destruction Callback: "
             << reinterpret_cast<void*>(item.destruction_callback);
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterMetalTexture& item) {
  return out << "(FlutterMetalTexture) Texture ID: " << std::hex
             << item.texture_id << std::dec << " Handle: 0x" << std::hex
             << item.texture;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterVulkanImage& item) {
  return out << "(FlutterVulkanTexture) Image Handle: " << std::hex
             << item.image << std::dec << " Format: " << item.format;
}

inline std::string FlutterPlatformViewMutationTypeToString(
    FlutterPlatformViewMutationType type) {
  switch (type) {
    case kFlutterPlatformViewMutationTypeOpacity:
      return "kFlutterPlatformViewMutationTypeOpacity";
    case kFlutterPlatformViewMutationTypeClipRect:
      return "kFlutterPlatformViewMutationTypeClipRect";
    case kFlutterPlatformViewMutationTypeClipRoundedRect:
      return "kFlutterPlatformViewMutationTypeClipRoundedRect";
    case kFlutterPlatformViewMutationTypeTransformation:
      return "kFlutterPlatformViewMutationTypeTransformation";
  }
  return "Unknown";
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterPlatformViewMutation& m) {
  out << "(FlutterPlatformViewMutation) Type: "
      << FlutterPlatformViewMutationTypeToString(m.type) << " ";
  switch (m.type) {
    case kFlutterPlatformViewMutationTypeOpacity:
      out << "Opacity: " << m.opacity;
    case kFlutterPlatformViewMutationTypeClipRect:
      out << "Clip Rect: " << m.clip_rect;
    case kFlutterPlatformViewMutationTypeClipRoundedRect:
      out << "Clip Rounded Rect: " << m.clip_rounded_rect;
    case kFlutterPlatformViewMutationTypeTransformation:
      out << "Transformation: " << m.transformation;
  }
  return out;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterPlatformView& platform_view) {
  out << "["
      << "(FlutterPlatformView) Struct Size: " << platform_view.struct_size
      << " Identifier: " << platform_view.identifier
      << " Mutations Count: " << platform_view.mutations_count;

  if (platform_view.mutations_count > 0) {
    out << std::endl;
    for (size_t i = 0; i < platform_view.mutations_count; i++) {
      out << "Mutation " << i << ": " << *platform_view.mutations[i]
          << std::endl;
    }
  }

  out << "]";

  return out;
}

inline std::string FlutterOpenGLTargetTypeToString(
    FlutterOpenGLTargetType type) {
  switch (type) {
    case kFlutterOpenGLTargetTypeTexture:
      return "kFlutterOpenGLTargetTypeTexture";
    case kFlutterOpenGLTargetTypeFramebuffer:
      return "kFlutterOpenGLTargetTypeFramebuffer";
  }
  return "Unknown";
}

inline std::string FlutterSoftwarePixelFormatToString(
    FlutterSoftwarePixelFormat pixfmt) {
  switch (pixfmt) {
    case kFlutterSoftwarePixelFormatGray8:
      return "kFlutterSoftwarePixelFormatGray8";
    case kFlutterSoftwarePixelFormatRGB565:
      return "kFlutterSoftwarePixelFormatRGB565";
    case kFlutterSoftwarePixelFormatRGBA4444:
      return "kFlutterSoftwarePixelFormatRGBA4444";
    case kFlutterSoftwarePixelFormatRGBA8888:
      return "kFlutterSoftwarePixelFormatRGBA8888";
    case kFlutterSoftwarePixelFormatRGBX8888:
      return "kFlutterSoftwarePixelFormatRGBX8888";
    case kFlutterSoftwarePixelFormatBGRA8888:
      return "kFlutterSoftwarePixelFormatBGRA8888";
    case kFlutterSoftwarePixelFormatNative32:
      return "kFlutterSoftwarePixelFormatNative32";
    default:
      FML_LOG(ERROR) << "Invalid software rendering pixel format";
  }
  return "Unknown";
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterOpenGLBackingStore& item) {
  out << "(FlutterOpenGLBackingStore) Type: "
      << FlutterOpenGLTargetTypeToString(item.type) << " ";
  switch (item.type) {
    case kFlutterOpenGLTargetTypeTexture:
      out << item.texture;
      break;
    case kFlutterOpenGLTargetTypeFramebuffer:
      out << item.framebuffer;
      break;
  }
  return out;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterSoftwareBackingStore& item) {
  return out << "(FlutterSoftwareBackingStore) Allocation: " << item.allocation
             << " Row Bytes: " << item.row_bytes << " Height: " << item.height
             << " User Data: " << item.user_data << " Destruction Callback: "
             << reinterpret_cast<void*>(item.destruction_callback);
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterMetalBackingStore& item) {
  return out << "(FlutterMetalBackingStore) Texture: " << item.texture;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterVulkanBackingStore& item) {
  return out << "(FlutterVulkanBackingStore) Image: " << item.image;
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterSoftwareBackingStore2& item) {
  return out << "(FlutterSoftwareBackingStore2) Allocation: " << item.allocation
             << " Row Bytes: " << item.row_bytes << " Height: " << item.height
             << " User Data: " << item.user_data << " Destruction Callback: "
             << reinterpret_cast<void*>(item.destruction_callback)
             << " Pixel Format: "
             << FlutterSoftwarePixelFormatToString(item.pixel_format);
}

inline std::ostream& operator<<(std::ostream& out,
                                const FlutterBackingStore& backing_store) {
  out << "(FlutterBackingStore) Struct size: " << backing_store.struct_size
      << " User Data: " << backing_store.user_data
      << " Type: " << FlutterBackingStoreTypeToString(backing_store.type)
      << " ";

  switch (backing_store.type) {
    case kFlutterBackingStoreTypeOpenGL:
      out << backing_store.open_gl;
      break;

    case kFlutterBackingStoreTypeSoftware:
      out << backing_store.software;
      break;

    case kFlutterBackingStoreTypeMetal:
      out << backing_store.metal;
      break;

    case kFlutterBackingStoreTypeVulkan:
      out << backing_store.vulkan;
      break;

    case kFlutterBackingStoreTypeSoftware2:
      out << backing_store.software2;
      break;
  }

  return out;
}

inline std::ostream& operator<<(std::ostream& out, const FlutterLayer& layer) {
  out << "(Flutter Layer) Struct size: " << layer.struct_size
      << " Type: " << FlutterLayerContentTypeToString(layer.type);

  switch (layer.type) {
    case kFlutterLayerContentTypeBackingStore:
      out << *layer.backing_store;
      break;
    case kFlutterLayerContentTypePlatformView:
      out << *layer.platform_view;
      break;
  }

  return out << " Offset: " << layer.offset << " Size: " << layer.size;
}

//------------------------------------------------------------------------------
// Factories and Casts
//------------------------------------------------------------------------------

inline FlutterPoint FlutterPointMake(double x, double y) {
  FlutterPoint point = {};
  point.x = x;
  point.y = y;
  return point;
}

inline FlutterSize FlutterSizeMake(double width, double height) {
  FlutterSize size = {};
  size.width = width;
  size.height = height;
  return size;
}

inline FlutterSize FlutterSizeMake(const SkVector& vector) {
  FlutterSize size = {};
  size.width = vector.x();
  size.height = vector.y();
  return size;
}

inline FlutterTransformation FlutterTransformationMake(const SkMatrix& matrix) {
  FlutterTransformation transformation = {};
  transformation.scaleX = matrix[SkMatrix::kMScaleX];
  transformation.skewX = matrix[SkMatrix::kMSkewX];
  transformation.transX = matrix[SkMatrix::kMTransX];
  transformation.skewY = matrix[SkMatrix::kMSkewY];
  transformation.scaleY = matrix[SkMatrix::kMScaleY];
  transformation.transY = matrix[SkMatrix::kMTransY];
  transformation.pers0 = matrix[SkMatrix::kMPersp0];
  transformation.pers1 = matrix[SkMatrix::kMPersp1];
  transformation.pers2 = matrix[SkMatrix::kMPersp2];
  return transformation;
}

inline SkMatrix SkMatrixMake(const FlutterTransformation& xformation) {
  return SkMatrix::MakeAll(xformation.scaleX,  //
                           xformation.skewX,   //
                           xformation.transX,  //
                           xformation.skewY,   //
                           xformation.scaleY,  //
                           xformation.transY,  //
                           xformation.pers0,   //
                           xformation.pers1,   //
                           xformation.pers2    //
  );
}

inline flutter::EmbedderEngine* ToEmbedderEngine(const FlutterEngine& engine) {
  return reinterpret_cast<flutter::EmbedderEngine*>(engine);
}

inline FlutterRect FlutterRectMake(const SkRect& rect) {
  FlutterRect r = {};
  r.left = rect.left();
  r.top = rect.top();
  r.right = rect.right();
  r.bottom = rect.bottom();
  return r;
}

inline FlutterRect FlutterRectMakeLTRB(double l, double t, double r, double b) {
  FlutterRect rect = {};
  rect.left = l;
  rect.top = t;
  rect.right = r;
  rect.bottom = b;
  return rect;
}

inline SkRect SkRectMake(const FlutterRect& rect) {
  return SkRect::MakeLTRB(rect.left, rect.top, rect.right, rect.bottom);
}

inline FlutterRoundedRect FlutterRoundedRectMake(const SkRRect& rect) {
  FlutterRoundedRect r = {};
  r.rect = FlutterRectMake(rect.rect());
  r.upper_left_corner_radius =
      FlutterSizeMake(rect.radii(SkRRect::Corner::kUpperLeft_Corner));
  r.upper_right_corner_radius =
      FlutterSizeMake(rect.radii(SkRRect::Corner::kUpperRight_Corner));
  r.lower_right_corner_radius =
      FlutterSizeMake(rect.radii(SkRRect::Corner::kLowerRight_Corner));
  r.lower_left_corner_radius =
      FlutterSizeMake(rect.radii(SkRRect::Corner::kLowerLeft_Corner));
  return r;
}

#endif  // FLUTTER_SHELL_PLATFORM_EMBEDDER_TESTS_EMBEDDER_ASSERTIONS_H_
