// Copyright 2015 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:async';
import 'dart:ui' as ui show window;

import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';

import 'asset_vendor.dart';
import 'banner.dart';
import 'basic.dart';
import 'binding.dart';
import 'framework.dart';
import 'locale_query.dart';
import 'media_query.dart';
import 'navigator.dart';
import 'performance_overlay.dart';
import 'semantics_debugger.dart';
import 'title.dart';

typedef Future<LocaleQueryData> LocaleChangedCallback(Locale locale);

/// A convenience class that wraps a number of widgets that are commonly
/// required for an application.
///
/// See also: [CheckedModeBanner], [DefaultTextStyle], [MediaQuery],
/// [LocaleQuery], [AssetVendor], [Title], [Navigator], [Overlay],
/// [SemanticsDebugger] (the widgets wrapped by this one).
///
/// The [onGenerateRoute] argument is required, and corresponds to
/// [Navigator.onGenerateRoute].
class WidgetsApp extends StatefulWidget {
  WidgetsApp({
    Key key,
    this.title,
    this.textStyle,
    this.color,
    this.navigatorObserver,
    this.onGenerateRoute,
    this.onLocaleChanged,
    this.showPerformanceOverlay: false,
    this.showSemanticsDebugger: false,
    this.debugShowCheckedModeBanner: true
  }) : super(key: key) {
    assert(onGenerateRoute != null);
    assert(showPerformanceOverlay != null);
    assert(showSemanticsDebugger != null);
  }

  /// A one-line description of this app for use in the window manager.
  final String title;

  /// The default text style for [Text] in the application.
  final TextStyle textStyle;

  /// The primary color to use for the application in the operating system
  /// interface.
  ///
  /// For example, on Android this is the color used for the application in the
  /// application switcher.
  final Color color;

  /// The route generator callback used when the app is navigated to a
  /// named route.
  final RouteFactory onGenerateRoute;

  /// Callback that is invoked when the operating system changes the
  /// current locale.
  final LocaleChangedCallback onLocaleChanged;

  /// Turns on a performance overlay.
  /// https://flutter.io/debugging/#performanceoverlay
  final bool showPerformanceOverlay;

  /// Turns on an overlay that shows the accessibility information
  /// reported by the framework.
  final bool showSemanticsDebugger;

  /// Turns on a "SLOW MODE" little banner in checked mode to indicate
  /// that the app is in checked mode. This is on by default (in
  /// checked mode), to turn it off, set the constructor argument to
  /// false. In release mode this has no effect.
  ///
  /// To get this banner in your application if you're not using
  /// WidgetsApp, include a [CheckedModeBanner] widget in your app.
  ///
  /// This banner is intended to avoid people complaining that your
  /// app is slow when it's in checked mode. In checked mode, Flutter
  /// enables a large number of expensive diagnostics to aid in
  /// development, and so performance in checked mode is not
  /// representative of what will happen in release mode.
  final bool debugShowCheckedModeBanner;

  /// The observer for the Navigator created for this app.
  final NavigatorObserver navigatorObserver;

  static bool showPerformanceOverlayOverride = false;

  @override
  WidgetsAppState<WidgetsApp> createState() => new WidgetsAppState<WidgetsApp>();
}

class WidgetsAppState<T extends WidgetsApp> extends State<T> implements WidgetsBindingObserver {

  GlobalObjectKey _navigator;

  LocaleQueryData _localeData;

  @override
  void initState() {
    super.initState();
    _navigator = new GlobalObjectKey(this);
    didChangeLocale(ui.window.locale);
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    super.dispose();
  }

  @override
  bool didPopRoute() {
    assert(mounted);
    NavigatorState navigator = _navigator.currentState;
    assert(navigator != null);
    bool result = false;
    navigator.openTransaction((NavigatorTransaction transaction) {
      result = transaction.pop();
    });
    return result;
  }

  @override
  void didChangeMetrics() {
    setState(() {
      // The properties of ui.window have changed. We use them in our build
      // function, so we need setState(), but we don't cache anything locally.
    });
  }

  @override
  void didChangeLocale(Locale locale) {
    if (config.onLocaleChanged != null) {
      config.onLocaleChanged(locale).then((LocaleQueryData data) {
        if (mounted)
          setState(() { _localeData = data; });
      });
    }
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) { }

  @override
  Widget build(BuildContext context) {
    if (config.onLocaleChanged != null && _localeData == null) {
      // If the app expects a locale but we don't yet know the locale, then
      // don't build the widgets now.
      // TODO(ianh): Make this unnecessary. See https://github.com/flutter/flutter/issues/1865
      // TODO(ianh): The following line should not be included in release mode, only in profile and debug modes.
      WidgetsBinding.instance.preventThisFrameFromBeingReportedAsFirstFrame();
      return new Container();
    }

    Widget result = new MediaQuery(
      data: new MediaQueryData.fromWindow(ui.window),
      child: new LocaleQuery(
        data: _localeData,
        child: new AssetVendor(
          bundle: rootBundle,
          devicePixelRatio: ui.window.devicePixelRatio,
          child: new Title(
            title: config.title,
            color: config.color,
            child: new Navigator(
              key: _navigator,
              initialRoute: ui.window.defaultRouteName,
              onGenerateRoute: config.onGenerateRoute,
              observer: config.navigatorObserver
            )
          )
        )
      )
    );
    if (config.textStyle != null) {
      new DefaultTextStyle(
        style: config.textStyle,
        child: result
      );
    }
    if (config.showPerformanceOverlay || WidgetsApp.showPerformanceOverlayOverride) {
      result = new Stack(
        children: <Widget>[
          result,
          new Positioned(top: 0.0, left: 0.0, right: 0.0, child: new PerformanceOverlay.allEnabled()),
        ]
      );
    }
    if (config.showSemanticsDebugger) {
      result = new SemanticsDebugger(
        child: result
      );
    }
    assert(() {
      if (config.debugShowCheckedModeBanner) {
        result = new CheckedModeBanner(
          child: result
        );
      }
      return true;
    });
    return result;
  }

}