// 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 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';

import 'constants.dart';

const Duration _kToggleDuration = const Duration(milliseconds: 200);

// RenderToggleable is a base class for material style toggleable controls with
// toggle animations. It handles storing the current value, dispatching
// ValueChanged on a tap gesture and driving a changed animation. Subclasses are
// responsible for painting.
abstract class RenderToggleable extends RenderConstrainedBox {
  RenderToggleable({
    bool value,
    Size size,
    Color activeColor,
    Color inactiveColor,
    this.onChanged,
    double minRadialReactionRadius: 0.0
  }) : _value = value,
       _activeColor = activeColor,
       _inactiveColor = inactiveColor,
       super(additionalConstraints: new BoxConstraints.tight(size)) {
    assert(value != null);
    assert(activeColor != null);
    assert(inactiveColor != null);
    _tap = new TapGestureRecognizer(router: Gesturer.instance.pointerRouter, gestureArena: Gesturer.instance.gestureArena)
      ..onTapDown = _handleTapDown
      ..onTap = _handleTap
      ..onTapUp = _handleTapUp
      ..onTapCancel = _handleTapCancel;
    _position = new ValuePerformance<double>(
      variable: new AnimatedValue<double>(0.0, end: 1.0),
      duration: _kToggleDuration,
      progress: _value ? 1.0 : 0.0
    )..addListener(markNeedsPaint)
     ..addStatusListener(_handlePositionStateChanged);
    _reaction = new ValuePerformance<double>(
      variable: new AnimatedValue<double>(minRadialReactionRadius, end: kRadialReactionRadius, curve: Curves.ease),
      duration: kRadialReactionDuration
    )..addListener(markNeedsPaint);
  }

  bool get value => _value;
  bool _value;
  void set value(bool value) {
    assert(value != null);
    if (value == _value)
      return;
    _value = value;
    _position.variable
      ..curve = Curves.easeIn
      ..reverseCurve = Curves.easeOut;
    _position.play(value ? AnimationDirection.forward : AnimationDirection.reverse);
  }

  Color get activeColor => _activeColor;
  Color _activeColor;
  void set activeColor(Color value) {
    assert(value != null);
    if (value == _activeColor)
      return;
    _activeColor = value;
    markNeedsPaint();
  }

  Color get inactiveColor => _inactiveColor;
  Color _inactiveColor;
  void set inactiveColor(Color value) {
    assert(value != null);
    if (value == _inactiveColor)
      return;
    _inactiveColor = value;
    markNeedsPaint();
  }

  bool get isInteractive => onChanged != null;

  ValueChanged<bool> onChanged;

  ValuePerformance<double> get position => _position;
  ValuePerformance<double> _position;

  ValuePerformance<double> get reaction => _reaction;
  ValuePerformance<double> _reaction;

  TapGestureRecognizer _tap;

  void _handlePositionStateChanged(PerformanceStatus status) {
    if (isInteractive) {
      if (status == PerformanceStatus.completed && !_value)
        onChanged(true);
      else if (status == PerformanceStatus.dismissed && _value)
        onChanged(false);
    }
  }

  void _handleTapDown(Point globalPosition) {
    if (isInteractive)
      _reaction.forward();
  }

  void _handleTap() {
    if (isInteractive)
      onChanged(!_value);
  }

  void _handleTapUp(Point globalPosition) {
    if (isInteractive)
      _reaction.reverse();
  }

  void _handleTapCancel() {
    if (isInteractive)
      _reaction.reverse();
  }

  bool hitTestSelf(Point position) => true;

  void handleEvent(PointerEvent event, BoxHitTestEntry entry) {
    if (event is PointerDownEvent && isInteractive)
      _tap.addPointer(event);
  }

  void paintRadialReaction(Canvas canvas, Offset offset) {
    if (!reaction.isDismissed) {
      // TODO(abarth): We should have a different reaction color when position is zero.
      Paint reactionPaint = new Paint()..color = activeColor.withAlpha(kRadialReactionAlpha);
      canvas.drawCircle(offset.toPoint(), reaction.value, reactionPaint);
    }
  }
}