// 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 'basic.dart';
import 'framework.dart';

class SmoothlyResizingOverflowBox extends StatefulComponent {
  SmoothlyResizingOverflowBox({
    Key key,
    this.child,
    this.size,
    this.duration,
    this.curve: Curves.linear
  }) : super(key: key) {
    assert(duration != null);
    assert(curve != null);
  }

  final Widget child;
  final Size size;
  final Duration duration;
  final Curve curve;

  _SmoothlyResizingOverflowBoxState createState() => new _SmoothlyResizingOverflowBoxState();
}

class _SmoothlyResizingOverflowBoxState extends State<SmoothlyResizingOverflowBox> {
  SizeTween _sizeTween;
  CurveTween _curveTween;
  Animation<Size> _size;
  AnimationController _sizeController;

  void initState() {
    super.initState();
    _sizeController = new AnimationController(duration: config.duration);
    _sizeTween = new SizeTween(begin: config.size);
    _curveTween = new CurveTween(curve: config.curve);
    _size = _sizeTween.chain(_curveTween).animate(_sizeController)
      ..addListener(() {
        setState(() {});
      });
  }

  void didUpdateConfig(SmoothlyResizingOverflowBox oldConfig) {
    bool needsAnimation = false;
    if (config.size != oldConfig.size) {
      _sizeTween
        ..begin = _size.value
        ..end = config.size;
      needsAnimation = true;
    }
    _sizeController.duration = config.duration;
    _curveTween.curve = config.curve;
    if (needsAnimation) {
      _sizeController
        ..value = 0.0
        ..forward();
    }
  }

  void dispose() {
    _sizeController.stop();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return new SizedOverflowBox(
      size: _size.value,
      child: config.child
    );
  }
}

class _Entry {
  _Entry({
    this.child,
    this.enterController,
    this.enterTransition
  });

  final Widget child;
  final AnimationController enterController;
  final Widget enterTransition;

  Size childSize = Size.zero;

  AnimationController exitController;
  Widget exitTransition;

  Widget get currentTransition => exitTransition ?? enterTransition;

  void dispose() {
    enterController?.stop();
    exitController?.stop();
  }
}

typedef Widget TransitionBuilderCallback(Animation<double> animation, Widget child);

Widget _identityTransition(Animation<double> animation, Widget child) => child;

class EnterExitTransition extends StatefulComponent {
  EnterExitTransition({
    Key key,
    this.child,
    this.duration,
    this.curve: Curves.linear,
    this.onEnter: _identityTransition,
    this.onExit: _identityTransition
  }) : super(key: key) {
    assert(child != null);
    assert(duration != null);
    assert(curve != null);
    assert(onEnter != null);
    assert(onExit != null);
  }

  final Widget child;
  final Duration duration;
  final Curve curve;
  final TransitionBuilderCallback onEnter;
  final TransitionBuilderCallback onExit;

  _EnterExitTransitionState createState() => new _EnterExitTransitionState();
}

class _EnterExitTransitionState extends State<EnterExitTransition> {
  final List<_Entry> _entries = new List<_Entry>();

  void initState() {
    super.initState();
    _entries.add(_createEnterTransition());
  }

  _Entry _createEnterTransition() {
    AnimationController enterController = new AnimationController(duration: config.duration)..forward();
    return new _Entry(
      child: config.child,
      enterController: enterController,
      enterTransition: config.onEnter(enterController, new KeyedSubtree(
        key: new GlobalKey(),
        child: config.child
      ))
    );
  }

  Future _createExitTransition(_Entry entry) async {
    AnimationController exitController = new AnimationController(duration: config.duration);
    entry
      ..exitController = exitController
      ..exitTransition = config.onExit(exitController, entry.enterTransition);
    await exitController.forward();
    if (!mounted)
      return;
    setState(() {
      _entries.remove(entry);
    });
  }

  void didUpdateConfig(EnterExitTransition oldConfig) {
    if (config.child.key != oldConfig.child.key) {
      _createExitTransition(_entries.last);
      _entries.add(_createEnterTransition());
    }
  }

  void dispose() {
    for (_Entry entry in new List<_Entry>.from(_entries))
      entry.dispose();
    super.dispose();
  }

  Widget build(BuildContext context) {
    return new SmoothlyResizingOverflowBox(
      size: _entries.last.childSize,
      duration: config.duration,
      curve: config.curve,
      child: new Stack(
        children: _entries.map((_Entry entry) {
          return new SizeObserver(
            key: new ObjectKey(entry),
            onSizeChanged: (Size newSize) {
              setState(() {
                entry.childSize = newSize;
              });
            },
            child: entry.currentTransition
          );
        }).toList()
      )
    );
  }
}
