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

import 'dart:typed_data' show ByteData;
import 'dart:ui' hide window;

import 'package:meta/meta.dart';

/// [Window] that wraps another [Window] and allows faking of some properties
/// for testing purposes.
///
/// Tests for certain widgets, e.g., [MaterialApp], might require faking certain
/// properties of a [Window]. [TestWindow] facilitates the faking of these
/// properties by overriding the properties of a real [Window] with desired fake
/// values. The binding used within tests, [TestWidgetsFlutterBinding], contains
/// a [TestWindow] that is used by all tests.
///
/// ## Sample Code
///
/// A test can utilize a [TestWindow] in the following way:
///
/// ```dart
/// testWidgets('your test name here', (WidgetTester tester) async {
///   // Retrieve the TestWidgetsFlutterBinding.
///   final TestWidgetsFlutterBinding testBinding = tester.binding;
///
///   // Fake the desired properties of the TestWindow. All code running
///   // within this test will perceive the following fake text scale
///   // factor as the real text scale factor of the window.
///   testBinding.window.textScaleFactorFakeValue = 2.5;
///
///   // Test code that depends on text scale factor here.
/// });
/// ```
///
/// The [TestWidgetsFlutterBinding] is recreated for each test and
/// therefore any fake values defined in one test will not persist
/// to the next.
///
/// If a test needs to override a real [Window] property and then later
/// return to using the real [Window] property, [TestWindow] provides
/// methods to clear each individual test value, e.g., [clearLocaleTestValue()].
///
/// To clear all fake test values in a [TestWindow], consider using
/// [clearAllTestValues()].
class TestWindow implements Window {
  /// Constructs a [TestWindow] that defers all behavior to the given [Window]
  /// unless explicitly overridden for test purposes.
  TestWindow({
    @required Window window,
  }) : _window = window;

  /// The [Window] that is wrapped by this [TestWindow].
  final Window _window;

  @override
  double get devicePixelRatio => _devicePixelRatio ?? _window.devicePixelRatio;
  double _devicePixelRatio;
  /// Hides the real device pixel ratio and reports the given [devicePixelRatio]
  /// instead.
  set devicePixelRatioTestValue(double devicePixelRatio) {
    _devicePixelRatio = devicePixelRatio;
    onMetricsChanged();
  }
  /// Deletes any existing test device pixel ratio and returns to using the real
  /// device pixel ratio.
  void clearDevicePixelRatioTestValue() {
    _devicePixelRatio = null;
    onMetricsChanged();
  }

  @override
  Size get physicalSize => _physicalSizeTestValue ?? _window.physicalSize;
  Size _physicalSizeTestValue;
  /// Hides the real physical size and reports the given [physicalSizeTestValue]
  /// instead.
  set physicalSizeTestValue (Size physicalSizeTestValue) {
    _physicalSizeTestValue = physicalSizeTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test physical size and returns to using the real
  /// physical size.
  void clearPhysicalSizeTestValue() {
    _physicalSizeTestValue = null;
    onMetricsChanged();
  }

  @override
  double get physicalDepth => _physicalDepthTestValue ?? _window.physicalDepth;
  double _physicalDepthTestValue;
  /// Hides the real physical depth and reports the given
  /// [physicalDepthTestValue] instead.
  set physicalDepthTestValue (double physicalDepthTestValue) {
    _physicalDepthTestValue = physicalDepthTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test physical depth and returns to using the real
  /// physical depth.
  void clearPhysicalDepthTestValue() {
    _physicalDepthTestValue = null;
    onMetricsChanged();
  }

  @override
  WindowPadding get viewInsets => _viewInsetsTestValue ??  _window.viewInsets;
  WindowPadding _viewInsetsTestValue;
  /// Hides the real view insets and reports the given [viewInsetsTestValue]
  /// instead.
  set viewInsetsTestValue(WindowPadding viewInsetsTestValue) {
    _viewInsetsTestValue = viewInsetsTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test view insets and returns to using the real view
  /// insets.
  void clearViewInsetsTestValue() {
    _viewInsetsTestValue = null;
    onMetricsChanged();
  }

  @override
  WindowPadding get viewPadding => _viewPaddingTestValue ?? _window.padding;
  WindowPadding _viewPaddingTestValue;
  /// Hides the real view padding and reports the given [paddingTestValue]
  /// instead.
  set viewPaddingTestValue(WindowPadding viewPaddingTestValue) {
    _viewPaddingTestValue = viewPaddingTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test view padding and returns to using the real
  /// viewPadding.
  void clearViewPaddingTestValue() {
    _viewPaddingTestValue = null;
    onMetricsChanged();
  }

  @override
  WindowPadding get padding => _paddingTestValue ?? _window.padding;
  WindowPadding _paddingTestValue;
  /// Hides the real padding and reports the given [paddingTestValue] instead.
  set paddingTestValue(WindowPadding paddingTestValue) {
    _paddingTestValue = paddingTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test padding and returns to using the real padding.
  void clearPaddingTestValue() {
    _paddingTestValue = null;
    onMetricsChanged();
  }

  @override
  WindowPadding get systemGestureInsets => _systemGestureInsetsTestValue ?? _window.systemGestureInsets;
  WindowPadding _systemGestureInsetsTestValue;
  /// Hides the real system gesture insets and reports the given [systemGestureInsetsTestValue] instead.
  set systemGestureInsetsTestValue(WindowPadding systemGestureInsetsTestValue) {
    _systemGestureInsetsTestValue = systemGestureInsetsTestValue;
    onMetricsChanged();
  }
  /// Deletes any existing test system gesture insets and returns to using the real system gesture insets.
  void clearSystemGestureInsetsTestValue() {
    _systemGestureInsetsTestValue = null;
    onMetricsChanged();
  }

  @override
  VoidCallback get onMetricsChanged => _window.onMetricsChanged;
  @override
  set onMetricsChanged(VoidCallback callback) {
    _window.onMetricsChanged = callback;
  }

  @override
  Locale get locale => _localeTestValue ?? _window.locale;
  Locale _localeTestValue;
  /// Hides the real locale and reports the given [localeTestValue] instead.
  set localeTestValue(Locale localeTestValue) {
    _localeTestValue = localeTestValue;
    onLocaleChanged();
  }
  /// Deletes any existing test locale and returns to using the real locale.
  void clearLocaleTestValue() {
    _localeTestValue = null;
    onLocaleChanged();
  }

  @override
  List<Locale> get locales => _localesTestValue ?? _window.locales;
  List<Locale> _localesTestValue;
  /// Hides the real locales and reports the given [localesTestValue] instead.
  set localesTestValue(List<Locale> localesTestValue) {
    _localesTestValue = localesTestValue;
    onLocaleChanged();
  }
  /// Deletes any existing test locales and returns to using the real locales.
  void clearLocalesTestValue() {
    _localesTestValue = null;
    onLocaleChanged();
  }

  @override
  VoidCallback get onLocaleChanged => _window.onLocaleChanged;
  @override
  set onLocaleChanged(VoidCallback callback) {
    _window.onLocaleChanged = callback;
  }

  @override
  String get initialLifecycleState => _initialLifecycleStateTestValue;
  String _initialLifecycleStateTestValue;
  /// Sets a faked initialLifecycleState for testing.
  set initialLifecycleStateTestValue(String state) {
    _initialLifecycleStateTestValue = state;
  }

  @override
  double get textScaleFactor => _textScaleFactorTestValue ?? _window.textScaleFactor;
  double _textScaleFactorTestValue;
  /// Hides the real text scale factor and reports the given
  /// [textScaleFactorTestValue] instead.
  set textScaleFactorTestValue(double textScaleFactorTestValue) {
    _textScaleFactorTestValue = textScaleFactorTestValue;
    onTextScaleFactorChanged();
  }
  /// Deletes any existing test text scale factor and returns to using the real
  /// text scale factor.
  void clearTextScaleFactorTestValue() {
    _textScaleFactorTestValue = null;
    onTextScaleFactorChanged();
  }

  @override
  Brightness get platformBrightness => _platformBrightnessTestValue ?? _window.platformBrightness;
  Brightness _platformBrightnessTestValue;
  @override
  VoidCallback get onPlatformBrightnessChanged => _window.onPlatformBrightnessChanged;
  @override
  set onPlatformBrightnessChanged(VoidCallback callback) {
    _window.onPlatformBrightnessChanged = callback;
  }
  /// Hides the real text scale factor and reports the given
  /// [platformBrightnessTestValue] instead.
  set platformBrightnessTestValue(Brightness platformBrightnessTestValue) {
    _platformBrightnessTestValue = platformBrightnessTestValue;
    onPlatformBrightnessChanged();
  }
  /// Deletes any existing test platform brightness and returns to using the
  /// real platform brightness.
  void clearPlatformBrightnessTestValue() {
    _platformBrightnessTestValue = null;
    onPlatformBrightnessChanged();
  }

  @override
  bool get alwaysUse24HourFormat => _alwaysUse24HourFormatTestValue ?? _window.alwaysUse24HourFormat;
  bool _alwaysUse24HourFormatTestValue;
  /// Hides the real clock format and reports the given
  /// [alwaysUse24HourFormatTestValue] instead.
  set alwaysUse24HourFormatTestValue(bool alwaysUse24HourFormatTestValue) {
    _alwaysUse24HourFormatTestValue = alwaysUse24HourFormatTestValue;
  }
  /// Deletes any existing test clock format and returns to using the real clock
  /// format.
  void clearAlwaysUse24HourTestValue() {
    _alwaysUse24HourFormatTestValue = null;
  }

  @override
  VoidCallback get onTextScaleFactorChanged => _window.onTextScaleFactorChanged;
  @override
  set onTextScaleFactorChanged(VoidCallback callback) {
    _window.onTextScaleFactorChanged = callback;
  }

  @override
  FrameCallback get onBeginFrame => _window.onBeginFrame;
  @override
  set onBeginFrame(FrameCallback callback) {
    _window.onBeginFrame = callback;
  }

  @override
  VoidCallback get onDrawFrame => _window.onDrawFrame;
  @override
  set onDrawFrame(VoidCallback callback) {
    _window.onDrawFrame = callback;
  }

  @override
  Stream<FrameTiming> get frameTimings => _window.frameTimings;

  @override
  PointerDataPacketCallback get onPointerDataPacket => _window.onPointerDataPacket;
  @override
  set onPointerDataPacket(PointerDataPacketCallback callback) {
    _window.onPointerDataPacket = callback;
  }

  @override
  String get defaultRouteName => _defaultRouteNameTestValue ?? _window.defaultRouteName;
  String _defaultRouteNameTestValue;
  /// Hides the real default route name and reports the given
  /// [defaultRouteNameTestValue] instead.
  set defaultRouteNameTestValue(String defaultRouteNameTestValue) {
    _defaultRouteNameTestValue = defaultRouteNameTestValue;
  }
  /// Deletes any existing test default route name and returns to using the real
  /// default route name.
  void clearDefaultRouteNameTestValue() {
    _defaultRouteNameTestValue = null;
  }

  @override
  void scheduleFrame() {
    _window.scheduleFrame();
  }

  @override
  void render(Scene scene) {
    _window.render(scene);
  }

  @override
  bool get semanticsEnabled => _semanticsEnabledTestValue ?? _window.semanticsEnabled;
  bool _semanticsEnabledTestValue;
  /// Hides the real semantics enabled and reports the given
  /// [semanticsEnabledTestValue] instead.
  set semanticsEnabledTestValue(bool semanticsEnabledTestValue) {
    _semanticsEnabledTestValue = semanticsEnabledTestValue;
    onSemanticsEnabledChanged();
  }
  /// Deletes any existing test semantics enabled and returns to using the real
  /// semantics enabled.
  void clearSemanticsEnabledTestValue() {
    _semanticsEnabledTestValue = null;
    onSemanticsEnabledChanged();
  }

  @override
  VoidCallback get onSemanticsEnabledChanged => _window.onSemanticsEnabledChanged;
  @override
  set onSemanticsEnabledChanged(VoidCallback callback) {
    _window.onSemanticsEnabledChanged = callback;
  }

  @override
  SemanticsActionCallback get onSemanticsAction => _window.onSemanticsAction;
  @override
  set onSemanticsAction(SemanticsActionCallback callback) {
    _window.onSemanticsAction = callback;
  }

  @override
  AccessibilityFeatures get accessibilityFeatures => _accessibilityFeaturesTestValue ?? _window.accessibilityFeatures;
  AccessibilityFeatures _accessibilityFeaturesTestValue;
  /// Hides the real accessibility features and reports the given
  /// [accessibilityFeaturesTestValue] instead.
  set accessibilityFeaturesTestValue(AccessibilityFeatures accessibilityFeaturesTestValue) {
    _accessibilityFeaturesTestValue = accessibilityFeaturesTestValue;
    onAccessibilityFeaturesChanged();
  }
  /// Deletes any existing test accessibility features and returns to using the
  /// real accessibility features.
  void clearAccessibilityFeaturesTestValue() {
    _accessibilityFeaturesTestValue = null;
    onAccessibilityFeaturesChanged();
  }

  @override
  VoidCallback get onAccessibilityFeaturesChanged => _window.onAccessibilityFeaturesChanged;
  @override
  set onAccessibilityFeaturesChanged(VoidCallback callback) {
    _window.onAccessibilityFeaturesChanged = callback;
  }

  @override
  void updateSemantics(SemanticsUpdate update) {
    _window.updateSemantics(update);
  }

  @override
  void setIsolateDebugName(String name) {
    _window.setIsolateDebugName(name);
  }

  @override
  void sendPlatformMessage(
    String name,
    ByteData data,
    PlatformMessageResponseCallback callback,
  ) {
    _window.sendPlatformMessage(name, data, callback);
  }

  @override
  PlatformMessageCallback get onPlatformMessage => _window.onPlatformMessage;
  @override
  set onPlatformMessage(PlatformMessageCallback callback) {
    _window.onPlatformMessage = callback;
  }

  /// Delete any test value properties that have been set on this [TestWindow]
  /// and return to reporting the real [Window] values for all [Window]
  /// properties.
  ///
  /// If desired, clearing of properties can be done on an individual basis,
  /// e.g., [clearLocaleTestValue()].
  void clearAllTestValues() {
    clearAccessibilityFeaturesTestValue();
    clearAlwaysUse24HourTestValue();
    clearDefaultRouteNameTestValue();
    clearDevicePixelRatioTestValue();
    clearPlatformBrightnessTestValue();
    clearLocaleTestValue();
    clearLocalesTestValue();
    clearPaddingTestValue();
    clearPhysicalSizeTestValue();
    clearPhysicalDepthTestValue();
    clearSemanticsEnabledTestValue();
    clearTextScaleFactorTestValue();
    clearViewInsetsTestValue();
  }

  /// This gives us some grace time when the dart:ui side adds something to
  /// Window, and makes things easier when we do rolls to give us time to catch
  /// up.
  @override
  dynamic noSuchMethod(Invocation invocation) {
    return null;
  }
}
