// 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 'package:sky/animation/animated_value.dart';
import 'package:sky/animation/animation_performance.dart';
import 'package:sky/animation/curves.dart';
import 'package:sky/widgets/basic.dart';
import 'package:sky/widgets/focus.dart';
import 'package:sky/widgets/transitions.dart';

typedef Widget RouteBuilder(Navigator navigator, RouteBase route);

abstract class RouteBase {
  Widget build(Navigator navigator, RouteBase route);
  bool get isOpaque;
  void popState([dynamic result]) { assert(result == null); }
  TransitionBase buildTransition({ Key key });
}

class Route extends RouteBase {
  Route({ this.name, this.builder });

  final String name;
  final RouteBuilder builder;

  Widget build(Navigator navigator, RouteBase route) => builder(navigator, route);
  bool get isOpaque => true;
  TransitionBase buildTransition({ Key key }) => new SlideUpFadeTransition(key: key);
}

class RouteState extends RouteBase {
  RouteState({ this.callback, this.route, this.owner });

  Function callback;
  RouteBase route;
  StatefulComponent owner;

  Widget build(Navigator navigator, RouteBase route) => null;
  bool get isOpaque => false;

  void popState([dynamic result]) {
    assert(result == null);
    if (callback != null)
      callback(this);
  }

  TransitionBase buildTransition({ Key key }) {
    // Custom state routes shouldn't be asked to construct a transition
    assert(false);
    return null;
  }
}

// TODO(jackson): Refactor this into its own file
const Duration _kTransitionDuration = const Duration(milliseconds: 150);
const Point _kTransitionStartPoint = const Point(0.0, 75.0);
class SlideUpFadeTransition extends TransitionBase {
  SlideUpFadeTransition({
    Key key,
    Widget child,
    Direction direction,
    Function onDismissed,
    Function onCompleted
  }): super(key: key,
            child: child,
            duration: _kTransitionDuration,
            direction: direction,
            onDismissed: onDismissed,
            onCompleted: onCompleted);

  Widget buildWithChild(Widget child) {
    // TODO(jackson): Hit testing should ignore transform
    // TODO(jackson): Block input unless content is interactive
    return new SlideTransition(
      performance: performance,
      direction: direction,
      position: new AnimatedValue<Point>(_kTransitionStartPoint, end: Point.origin, curve: easeOut),
      child: new FadeTransition(
        performance: performance,
        direction: direction,
        opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
        child: child
      )
    );
  }
}

class HistoryEntry {
  HistoryEntry({ this.route });
  final RouteBase route;
  bool fullyOpaque = false;
  // TODO(jackson): Keep track of the requested transition
}

class NavigationState {

  NavigationState(List<Route> routes) {
    for (Route route in routes) {
      if (route.name != null)
        namedRoutes[route.name] = route;
    }
    history.add(new HistoryEntry(route: routes[0]));
  }

  List<HistoryEntry> history = new List<HistoryEntry>();
  int historyIndex = 0;
  Map<String, RouteBase> namedRoutes = new Map<String, RouteBase>();

  RouteBase get currentRoute => history[historyIndex].route;
  bool hasPrevious() => historyIndex > 0;

  void pushNamed(String name) {
    Route route = namedRoutes[name];
    assert(route != null);
    push(route);
  }

  void push(RouteBase route) {
    HistoryEntry historyEntry = new HistoryEntry(route: route);
    history.insert(historyIndex + 1, historyEntry);
    historyIndex++;
  }

  void pop([dynamic result]) {
    if (historyIndex > 0) {
      HistoryEntry entry = history[historyIndex];
      entry.route.popState(result);
      entry.fullyOpaque = false;
      historyIndex--;
    }
  }
}

class Navigator extends StatefulComponent {

  Navigator(this.state, { Key key }) : super(key: key);

  NavigationState state;

  void syncConstructorArguments(Navigator source) {
    state = source.state;
  }

  RouteBase get currentRoute => state.currentRoute;

  void pushState(StatefulComponent owner, Function callback) {
    RouteBase route = new RouteState(
      owner: owner,
      callback: callback,
      route: state.currentRoute
    );
    push(route);
  }

  void pushNamed(String name) {
    setState(() {
      state.pushNamed(name);
    });
  }

  void push(RouteBase route) {
    setState(() {
      state.push(route);
    });
  }

  void pop([dynamic result]) {
    setState(() {
      state.pop(result);
    });
  }

  Widget build() {
    List<Widget> visibleRoutes = new List<Widget>();
    for (int i = 0; i < state.history.length; i++) {
      // Avoid building routes that are not visible
      if (i + 1 < state.history.length && state.history[i + 1].fullyOpaque)
        continue;
      HistoryEntry historyEntry = state.history[i];
      Widget child = historyEntry.route.build(this, historyEntry.route);
      if (i == 0) {
        visibleRoutes.add(child);
        continue;
      }
      if (child == null)
        continue;
      TransitionBase transition = historyEntry.route.buildTransition(key: new Key.fromObjectIdentity(historyEntry))
        ..child = child
        ..direction = (i <= state.historyIndex) ? Direction.forward : Direction.reverse
        ..onDismissed = () {
          setState(() {
            state.history.remove(historyEntry);
          });
        }
        ..onCompleted = () {
          setState(() {
            historyEntry.fullyOpaque = historyEntry.route.isOpaque;
          });
        };
      visibleRoutes.add(transition);
    }
    return new Focus(child: new Stack(visibleRoutes));
  }
}
