// 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:flutter/animation.dart';

import 'basic.dart';
import 'framework.dart';

import 'package:vector_math/vector_math_64.dart';

class AnimatedBoxConstraintsValue extends AnimatedValue<BoxConstraints> {
  AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve, Curve reverseCurve })
    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);

  BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t);
}

class AnimatedBoxDecorationValue extends AnimatedValue<BoxDecoration> {
  AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve, Curve reverseCurve })
    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);

  BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t);
}

class AnimatedEdgeDimsValue extends AnimatedValue<EdgeDims> {
  AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve, Curve reverseCurve })
    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);

  EdgeDims lerp(double t) => EdgeDims.lerp(begin, end, t);
}

class AnimatedMatrix4Value extends AnimatedValue<Matrix4> {
  AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve, Curve reverseCurve })
    : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);

  Matrix4 lerp(double t) {
    // TODO(mpcomplete): Animate the full matrix. Will animating the cells
    // separately work?
    Vector3 beginT = begin.getTranslation();
    Vector3 endT = end.getTranslation();
    Vector3 lerpT = beginT*(1.0-t) + endT*t;
    return new Matrix4.identity()..translate(lerpT);
  }
}

class AnimatedContainer extends StatefulComponent {
  AnimatedContainer({
    Key key,
    this.child,
    this.constraints,
    this.decoration,
    this.foregroundDecoration,
    this.margin,
    this.padding,
    this.transform,
    this.width,
    this.height,
    this.curve: Curves.linear,
    this.duration
  }) : super(key: key) {
    assert(margin == null || margin.isNonNegative);
    assert(padding == null || padding.isNonNegative);
    assert(curve != null);
    assert(duration != null);
  }

  final Widget child;

  final BoxConstraints constraints;
  final BoxDecoration decoration;
  final BoxDecoration foregroundDecoration;
  final EdgeDims margin;
  final EdgeDims padding;
  final Matrix4 transform;
  final double width;
  final double height;

  final Curve curve;
  final Duration duration;

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

class _AnimatedContainerState extends State<AnimatedContainer> {
  AnimatedBoxConstraintsValue _constraints;
  AnimatedBoxDecorationValue _decoration;
  AnimatedBoxDecorationValue _foregroundDecoration;
  AnimatedEdgeDimsValue _margin;
  AnimatedEdgeDimsValue _padding;
  AnimatedMatrix4Value _transform;
  AnimatedValue<double> _width;
  AnimatedValue<double> _height;

  Performance _performance;

  void initState() {
    super.initState();
    _performance = new Performance(duration: config.duration, debugLabel: '${config.toStringShort()}')
      ..timing = new AnimationTiming(curve: config.curve)
      ..addListener(_updateAllVariables);
    _configAllVariables();
  }

  void didUpdateConfig(AnimatedContainer oldConfig) {
    _performance
      ..duration = config.duration
      ..timing.curve = config.curve;
    if (_configAllVariables()) {
      _performance.progress = 0.0;
      _performance.play();
    }
  }

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

  void _updateVariable(Animatable variable) {
    if (variable != null)
      _performance.updateVariable(variable);
  }

  void _updateAllVariables() {
    setState(() {
      _updateVariable(_constraints);
      _updateVariable(_decoration);
      _updateVariable(_foregroundDecoration);
      _updateVariable(_margin);
      _updateVariable(_padding);
      _updateVariable(_transform);
      _updateVariable(_width);
      _updateVariable(_height);
    });
  }

  bool _configVariable(AnimatedValue variable, dynamic targetValue) {
    dynamic currentValue = variable.value;
    variable.end = targetValue;
    variable.begin = currentValue;
    return currentValue != targetValue;
  }

  bool _configAllVariables() {
    bool needsAnimation = false;
    if (config.constraints != null) {
      _constraints ??= new AnimatedBoxConstraintsValue(config.constraints);
      if (_configVariable(_constraints, config.constraints))
        needsAnimation = true;
    } else {
      _constraints = null;
    }

    if (config.decoration != null) {
      _decoration ??= new AnimatedBoxDecorationValue(config.decoration);
      if (_configVariable(_decoration, config.decoration))
        needsAnimation = true;
    } else {
      _decoration = null;
    }

    if (config.foregroundDecoration != null) {
      _foregroundDecoration ??= new AnimatedBoxDecorationValue(config.foregroundDecoration);
      if (_configVariable(_foregroundDecoration, config.foregroundDecoration))
        needsAnimation = true;
    } else {
      _foregroundDecoration = null;
    }

    if (config.margin != null) {
      _margin ??= new AnimatedEdgeDimsValue(config.margin);
      if (_configVariable(_margin, config.margin))
        needsAnimation = true;
    } else {
      _margin = null;
    }

    if (config.padding != null) {
      _padding ??= new AnimatedEdgeDimsValue(config.padding);
      if (_configVariable(_padding, config.padding))
        needsAnimation = true;
    } else {
      _padding = null;
    }

    if (config.transform != null) {
      _transform ??= new AnimatedMatrix4Value(config.transform);
      if (_configVariable(_transform, config.transform))
        needsAnimation = true;
    } else {
      _transform = null;
    }

    if (config.width != null) {
      _width ??= new AnimatedValue<double>(config.width);
      if (_configVariable(_width, config.width))
        needsAnimation = true;
    } else {
      _width = null;
    }

    if (config.height != null) {
      _height ??= new AnimatedValue<double>(config.height);
      if (_configVariable(_height, config.height))
        needsAnimation = true;
    } else {
      _height = null;
    }

    return needsAnimation;
  }

  Widget build(BuildContext context) {
    return new Container(
      child: config.child,
      constraints: _constraints?.value,
      decoration: _decoration?.value,
      foregroundDecoration: _foregroundDecoration?.value,
      margin: _margin?.value,
      padding: _padding?.value,
      transform: _transform?.value,
      width: _width?.value,
      height: _height?.value
    );
  }

  void debugFillDescription(List<String> description) {
    super.debugFillDescription(description);
    if (_constraints != null)
      description.add('has constraints');
    if (_decoration != null)
      description.add('has background');
    if (_foregroundDecoration != null)
      description.add('has foreground');
    if (_margin != null)
      description.add('has margin');
    if (_padding != null)
      description.add('has padding');
    if (_transform != null)
      description.add('has transform');
    if (_width != null)
      description.add('has width');
    if (_height != null)
      description.add('has height');
  }
}
