/// Copyright 2016 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 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';

import 'framework.dart';
import 'scroll_behavior.dart';
import 'scroll_physics.dart';
import 'overscroll_indicator.dart';

class ScrollBehavior2 {
  const ScrollBehavior2();

  /// The platform whose scroll physics should be implemented.
  ///
  /// Defaults to the current platform.
  TargetPlatform getPlatform(BuildContext context) => defaultTargetPlatform;

  /// The color to use for the glow effect when [platform] indicates a platform
  /// that uses a [GlowingOverscrollIndicator].
  ///
  /// Defaults to white.
  Color getGlowColor(BuildContext context) => const Color(0xFFFFFFFF);

  Widget buildViewportChrome(BuildContext context, Widget child, AxisDirection axisDirection) {
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
        return child;
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return new GlowingOverscrollIndicator(
          child: child,
          axisDirection: axisDirection,
          color: getGlowColor(context),
        );
    }
    return null;
  }

  /// The scroll physics to use for the given platform.
  ///
  /// Used by [createScrollPosition] to get the scroll physics for newly created
  /// scroll positions.
  ScrollPhysics getScrollPhysics(BuildContext context) {
    switch (getPlatform(context)) {
      case TargetPlatform.iOS:
        return const BouncingScrollPhysics();
      case TargetPlatform.android:
      case TargetPlatform.fuchsia:
        return const ClampingScrollPhysics();
    }
    return null;
  }

  bool shouldNotify(@checked ScrollBehavior2 oldDelegate) => false;
}

class ScrollConfiguration2 extends InheritedWidget {
  const ScrollConfiguration2({
    Key key,
    @required this.behavior,
    @required Widget child,
  }) : super(key: key, child: child);

  final ScrollBehavior2 behavior;

  static ScrollBehavior2 of(BuildContext context) {
    final ScrollConfiguration2 configuration = context.inheritFromWidgetOfExactType(ScrollConfiguration2);
    return configuration?.behavior ?? const ScrollBehavior2();
  }

  @override
  bool updateShouldNotify(ScrollConfiguration2 old) {
    assert(behavior != null);
    return behavior.runtimeType != old.behavior.runtimeType
        || behavior.shouldNotify(old.behavior);
  }
}

////////////////////////////////////////////////////////////////////////////////
// DELETE EVERYTHING BELOW THIS LINE WHEN REMOVING LEGACY SCROLLING CODE
////////////////////////////////////////////////////////////////////////////////

/// Controls how [Scrollable] widgets in a subtree behave.
///
/// Used by [ScrollConfiguration].
abstract class ScrollConfigurationDelegate {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.
  const ScrollConfigurationDelegate();

  /// Returns the platform whose scroll physics should be approximated. See
  /// [ScrollBehavior.platform].
  TargetPlatform get platform;

  /// Returns the ScrollBehavior to be used by generic scrolling containers like
  /// [Block].
  ExtentScrollBehavior createScrollBehavior();

  /// Generic scrolling containers like [Block] will apply this function to the
  /// Scrollable they create. It can be used to add widgets that wrap the
  /// Scrollable, like scrollbars or overscroll indicators. By default the
  /// [scrollWidget] parameter is returned unchanged.
  Widget wrapScrollWidget(BuildContext context, Widget scrollWidget) => scrollWidget;

  /// Overrides should return true if this ScrollConfigurationDelegate differs
  /// from the provided old delegate in a way that requires rebuilding its
  /// scrolling container descendants.
  bool updateShouldNotify(@checked ScrollConfigurationDelegate old);
}

class _DefaultScrollConfigurationDelegate extends ScrollConfigurationDelegate {
  const _DefaultScrollConfigurationDelegate();

  @override
  TargetPlatform get platform => defaultTargetPlatform;

  @override
  ExtentScrollBehavior createScrollBehavior() => new OverscrollWhenScrollableBehavior(platform: platform);

  @override
  bool updateShouldNotify(ScrollConfigurationDelegate old) => false;
}

/// A widget that controls descendant [Scrollable] widgets.
///
/// Classes that create Scrollables are not required to depend on this
/// Widget. The following general purpose scrolling widgets do depend
/// on [ScrollConfiguration]: [Block], [LazyBlock], [ScrollableViewport],
/// [ScrollableList], [ScrollableLazyList]. The [Scrollable] base class uses
/// [ScrollConfiguration] to create its [ScrollBehavior].
class ScrollConfiguration extends InheritedWidget {
  /// Creates a widget that controls descendant [Scrollable] widgets.
  ///
  /// If the [delegate] argument is null, the scroll configuration for this
  /// subtree is controlled by the default implementation of
  /// [ScrollConfigurationDelegate].
  ScrollConfiguration({
    Key key,
    this.delegate,
    @required Widget child
  }) : super(key: key, child: child);

  static const ScrollConfigurationDelegate _defaultDelegate = const _DefaultScrollConfigurationDelegate();

  /// Defines the ScrollBehavior and scrollable wrapper for descendants.
  final ScrollConfigurationDelegate delegate;

  /// The delegate property of the closest instance of this class that encloses
  /// the given context.
  ///
  /// If no such instance exists, returns a default
  /// [ScrollConfigurationDelegate] that approximates the scrolling physics of
  /// the current platform (see [defaultTargetPlatform]) using a
  /// [OverscrollWhenScrollableBehavior] behavior model.
  ///
  /// Typical usage is as follows:
  ///
  /// ```dart
  /// ScrollConfigurationDelegate scrollConfiguration = ScrollConfiguration.of(context);
  /// ```
  static ScrollConfigurationDelegate of(BuildContext context) {
    ScrollConfiguration configuration = context.inheritFromWidgetOfExactType(ScrollConfiguration);
    return configuration?.delegate ?? _defaultDelegate;
  }

  /// A utility function that calls [ScrollConfigurationDelegate.wrapScrollWidget].
  static Widget wrap(BuildContext context, Widget scrollWidget) {
    return ScrollConfiguration.of(context).wrapScrollWidget(context, scrollWidget);
  }

  @override
  bool updateShouldNotify(ScrollConfiguration old) {
    return delegate?.updateShouldNotify(old.delegate) ?? false;
  }
}