/*
 * The MIT License (MIT)
 * 
 * Copyright (c) 2019 Alibaba Group
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.
 */
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'container_manager.dart';
import '../flutter_boost.dart';
import 'boost_page_route.dart';
import '../support/logger.dart';

enum ContainerLifeCycle {
  Init,
  Appear,
  WillDisappear,
  Disappear,
  Destroy,
  Background,
  Foreground
}

typedef void BoostContainerLifeCycleObserver(
    ContainerLifeCycle state, BoostContainerSettings settings);

class BoostContainer extends Navigator {
  final BoostContainerSettings settings;

  const BoostContainer(
      {GlobalKey<BoostContainerState> key,
      this.settings = const BoostContainerSettings(),
      String initialRoute,
      RouteFactory onGenerateRoute,
      RouteFactory onUnknownRoute,
      List<NavigatorObserver> observers})
      : super(
            key: key,
            initialRoute: initialRoute,
            onGenerateRoute: onGenerateRoute,
            onUnknownRoute: onUnknownRoute,
            observers: observers);

  factory BoostContainer.copy(Navigator navigator,
          [BoostContainerSettings settings = const BoostContainerSettings()]) =>
      BoostContainer(
        key: GlobalKey<BoostContainerState>(),
        settings: settings,
        initialRoute: navigator.initialRoute,
        onGenerateRoute: navigator.onGenerateRoute,
        onUnknownRoute: navigator.onUnknownRoute,
        observers: navigator.observers,
      );

  factory BoostContainer.obtain(
          Navigator navigator, BoostContainerSettings settings) =>
      BoostContainer(
          key: GlobalKey<BoostContainerState>(),
          settings: settings,
          onGenerateRoute: (RouteSettings routeSettings) {
            if (routeSettings.name == '/') {
              return BoostPageRoute<dynamic>(
                  pageName: settings.name,
                  params: settings.params,
                  uniqueId: settings.uniqueId,
                  animated: false,
                  settings: routeSettings,
                  builder: settings.builder);
            } else {
              return navigator.onGenerateRoute(routeSettings);
            }
          },
          observers: <NavigatorObserver>[
            ContainerNavigatorObserver.bindContainerManager()
          ],
          onUnknownRoute: navigator.onUnknownRoute);

  @override
  BoostContainerState createState() => BoostContainerState();

  @override
  StatefulElement createElement() => ContainerElement(this);

  static BoostContainerState tryOf(BuildContext context) {
    final BoostContainerState container =
        context.ancestorStateOfType(const TypeMatcher<BoostContainerState>());
    return container;
  }

  static BoostContainerState of(BuildContext context) {
    final BoostContainerState container =
        context.ancestorStateOfType(const TypeMatcher<BoostContainerState>());
    assert(container != null, 'not in flutter boost');
    return container;
  }

  String desc() => '{uniqueId=${settings.uniqueId},name=${settings.name}}';
}

class BoostContainerState extends NavigatorState {
  VoidCallback backPressedHandler;

  String get uniqueId => widget.settings.uniqueId;

  String get name => widget.settings.name;

  Map get params => widget.settings.params;

  BoostContainerSettings get settings => widget.settings;

  bool get onstage =>
      BoostContainerManager.of(context).onstageContainer == this;

  bool get maybeOnstageNext =>
      BoostContainerManager.of(context).subContainer == this;

  @override
  BoostContainer get widget => super.widget as BoostContainer;

  List<Route<dynamic>> routerHistory = <Route<dynamic>>[];

  bool multipleRouteMode = false;

  ContainerNavigatorObserver findContainerNavigatorObserver(
      Navigator navigator) {
    for (NavigatorObserver observer in navigator.observers) {
      if (observer is ContainerNavigatorObserver) {
        return observer;
      }
    }

    return null;
  }

  @override
  void initState() {
    super.initState();
    backPressedHandler = () => maybePop();
  }

  @override
  void didUpdateWidget(Navigator oldWidget) {
    super.didUpdateWidget(oldWidget);
  }

  @override
  void dispose() {
    for (Route route in routerHistory) {
      GlobalRouteSettingsManager.instance.removeSettings(route);
    }

    routerHistory.clear();

    super.dispose();
  }

  void performBackPressed() {
    Logger.log('performBackPressed');

    backPressedHandler?.call();
  }

  Route get topRoute => routerHistory.isNotEmpty ? routerHistory.last : null;

  @override
  Future<bool> maybePop<T extends Object>([T result]) async {
    if(routerHistory.isEmpty)
      pop(result);
    
    final Route<T> route = routerHistory.last;
    final RoutePopDisposition disposition = await route.willPop();
    if (mounted) {
      switch (disposition) {
        case RoutePopDisposition.pop:
          pop(result);
          return true;
          break;
        case RoutePopDisposition.doNotPop:
          return false;
          break;
        case RoutePopDisposition.bubble:
          pop(result);
          return true;
          break;
      }
    }
    return false;
  }

  @override
  bool pop<T extends Object>([T result]) {
    Route removedRoute;
    if (routerHistory.length > 1) {
      removedRoute = routerHistory.removeLast();
    }

    if (canPop()) {
         super.pop<T>(result);
         if (removedRoute != null) {
           GlobalRouteSettingsManager.instance.removeSettings(removedRoute);
         }
         if (Platform.isIOS && multipleRouteMode && !canPop()) {
           FlutterBoost.singleton.channel
               .invokeMethod<dynamic>('enablePopGesture', null);
           //开启native返回手势
         }
    } else {
      if (T is Map<String, dynamic>) {
        FlutterBoost.singleton
            .closeInternal(uniqueId, result: result as Map<String, dynamic>);
      } else {
        FlutterBoost.singleton.closeInternal(uniqueId,);
      }
    }
    return true;
  }

  @override
  Future<T> push<T extends Object>(Route<T> route) {
    Route<T> newRoute;
    if (FlutterBoost.containerManager.prePushRoute != null) {
      newRoute = FlutterBoost.containerManager
          .prePushRoute(name, uniqueId, params, route);
    }

    if (multipleRouteMode) {
      ContainerNavigatorObserver.bindContainerManager().willPush(route, routerHistory.last);
    }

    Future<T> future = super.push<T>(newRoute ?? route);

    routerHistory.add(route);

    // 复用XPlatformPlugin后,每次进入页面时都需要在这里反复通知Native更新Theme
    SystemChrome.restoreSystemUIOverlays();
    if (FlutterBoost.containerManager.postPushRoute != null) {
      FlutterBoost.containerManager
          .postPushRoute(name, uniqueId, params, newRoute ?? route, future);
    }

    if (Platform.isIOS && multipleRouteMode && canPop()) {
      FlutterBoost.singleton.channel
          .invokeMethod<dynamic>('disablePopGesture', null);
    }

    return future;
  }

  VoidCallback addLifeCycleObserver(BoostContainerLifeCycleObserver observer) {
    return FlutterBoost.singleton.addBoostContainerLifeCycleObserver(
        (ContainerLifeCycle state, BoostContainerSettings settings) {
      if (settings.uniqueId == uniqueId) {
        observer(state, settings);
      }
    });
  }
}

class BoostContainerSettings {
  final String uniqueId;
  final String name;
  final Map params;
  final WidgetBuilder builder;

  const BoostContainerSettings(
      {this.uniqueId = 'default',
      this.name = 'default',
      this.params,
      this.builder});
}

class ContainerElement extends StatefulElement {
  ContainerElement(StatefulWidget widget) : super(widget);
}

class ContainerNavigatorObserver extends NavigatorObserver {
  static final Set<NavigatorObserver> boostObservers = Set<NavigatorObserver>();

  ContainerNavigatorObserver();

  factory ContainerNavigatorObserver.bindContainerManager() =>
      ContainerNavigatorObserver();

  VoidCallback addBoostNavigatorObserver(NavigatorObserver observer) {
    boostObservers.add(observer);

    return () => boostObservers.remove(observer);
  }

  void removeBoostNavigatorObserver(NavigatorObserver observer) {
    boostObservers.remove(observer);
  }

  void willPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    for (NavigatorObserver observer in boostObservers) {
      if(observer is ContainerNavigatorObserver){
        if (observer == this) continue;
        ContainerNavigatorObserver  containerNavigatorObserver = observer as ContainerNavigatorObserver;
        containerNavigatorObserver.willPush(route, previousRoute);
      }
    }
  }
  @override
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) {
    for (NavigatorObserver observer in boostObservers) {
      if (observer == this) continue;
      observer.didPush(route, previousRoute);
    }
  }

  @override
  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) {
    for (NavigatorObserver observer in boostObservers) {
      if (observer == this) continue;
      observer.didPop(route, previousRoute);
    }
  }

  @override
  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) {
    for (NavigatorObserver observer in boostObservers) {
      if (observer == this) continue;
      observer.didRemove(route, previousRoute);
    }
  }

  @override
  void didReplace({Route<dynamic> newRoute, Route<dynamic> oldRoute}) {
    for (NavigatorObserver observer in boostObservers) {
      if (observer == this) continue;
      observer.didReplace(newRoute: newRoute, oldRoute: oldRoute);
    }
  }
}

class GlobalRouteSettingsManager {

  GlobalRouteSettingsManager._();

  static GlobalRouteSettingsManager instance = GlobalRouteSettingsManager._();

  final Map<Route,BoostRouteSettings> _routeSettingsMap = <Route,BoostRouteSettings>{};

  void addSettings(Route route,BoostRouteSettings settings) {
    _routeSettingsMap[route] = settings;
  }

  void removeSettings(Route route) {
    _routeSettingsMap.remove(route);
  }

  BoostRouteSettings getSettings(Route route) {
    return _routeSettingsMap[route];
  }

  bool contains(Route route) {
    return _routeSettingsMap[route] != null;
  }
}