// Copyright 2019 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 'framework.dart';

/// An [InheritedWidget] that defines visual properties like colors
/// and text styles, which the [child]'s subtree depends on.
///
/// The [wrap] method is used by [captureAll] to construct a widget
/// that will wrap a child in all of the inherited themes which
/// are present in a build context but are not present in the
/// context that the returned widget is eventually built in.
///
/// A widget that's shown in a different context from the one it's
/// built in, like the contents of a new route or an overlay, will
/// be able to depend on inherited widget ancestors of the context
/// it's built in.
///
/// {@tool snippet --template=freeform}
/// This example demonstrates how `InheritedTheme.captureAll()` can be used
/// to wrap the contents of a new route with the inherited themes that
/// are present when the route is built - but are not present when route
/// is actually shown.
///
/// If the same code is run without `InheritedTheme.captureAll(), the
/// new route's Text widget will inherit the "something must be wrong"
/// fallback text style, rather than the default text style defined in MyApp.
///
/// ```dart imports
/// import 'package:flutter/material.dart';
/// ```
///
/// ```dart main
/// void main() {
///   runApp(MyApp());
/// }
/// ```
///
/// ```dart
/// class MyAppBody extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return GestureDetector(
///       onTap: () {
///         Navigator.of(context).push(
///           MaterialPageRoute(
///             builder: (BuildContext _) {
///               // InheritedTheme.captureAll() saves references to themes that
///               // are found above the context provided to this widget's build
///               // method, notably the DefaultTextStyle defined in MyApp. The
///               // context passed to the MaterialPageRoute's builder is not used,
///               // because its ancestors are above MyApp's home.
///               return InheritedTheme.captureAll(context, Container(
///                 alignment: Alignment.center,
///                 color: Theme.of(context).colorScheme.surface,
///                 child: Text('Hello World'),
///               ));
///             },
///           ),
///         );
///       },
///       child: Center(child: Text('Tap Here')),
///     );
///   }
/// }
///
/// class MyApp extends StatelessWidget {
///   @override
///   Widget build(BuildContext context) {
///     return MaterialApp(
///       home: Scaffold(
///         // Override the DefaultTextStyle defined by the Scaffold.
///         // Descendant widgets will inherit this big blue text style.
///         body: DefaultTextStyle(
///           style: TextStyle(fontSize: 48, color: Colors.blue),
///           child: MyAppBody(),
///         ),
///       ),
///     );
///   }
/// }
/// ```
/// {@end-tool}
abstract class InheritedTheme extends InheritedWidget {
  /// Abstract const constructor. This constructor enables subclasses to provide
  /// const constructors so that they can be used in const expressions.

  const InheritedTheme({
    Key key,
    @required Widget child,
  }) : super(key: key, child: child);

  /// Return a copy of this inherited theme with the specified [child].
  ///
  /// If the identical inherited theme is already visible from [context] then
  /// just return the [child].
  ///
  /// This implementation for [TooltipTheme] is typical:
  /// ```dart
  /// Widget wrap(BuildContext context, Widget child) {
  ///   final TooltipTheme ancestorTheme = context.ancestorWidgetOfExactType(TooltipTheme);
  ///   return identical(this, ancestorTheme) ? child : TooltipTheme(data: data, child: child);
  /// }
  /// ```
  Widget wrap(BuildContext context, Widget child);

  /// Returns a widget that will [wrap] child in all of the inherited themes
  /// which are visible from [context].
  static Widget captureAll(BuildContext context, Widget child) {
    assert(child != null);
    assert(context != null);

    final List<InheritedTheme> themes = <InheritedTheme>[];
    final Set<Type> themeTypes = <Type>{};
    context.visitAncestorElements((Element ancestor) {
      if (ancestor is InheritedElement && ancestor.widget is InheritedTheme) {
        final InheritedTheme theme = ancestor.widget;
        final Type themeType = theme.runtimeType;
        // Only remember the first theme of any type. This assumes
        // that inherited themes completely shadow ancestors of the
        // the same type.
        if (!themeTypes.contains(themeType)) {
          themeTypes.add(themeType);
          themes.add(theme);
        }
      }
      return true;
    });

    return _CaptureAll(themes: themes, child: child);
  }
}

class _CaptureAll extends StatelessWidget {
  const _CaptureAll({
    Key key,
    @required this.themes,
    @required this.child,
  }) : assert(themes != null), assert(child != null), super(key: key);

  final List<InheritedTheme> themes;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Widget wrappedChild = child;
    for (InheritedTheme theme in themes)
      wrappedChild = theme.wrap(context, wrappedChild);
    return wrappedChild;
  }
}
