import 'dart:async';

import 'package:flutter/material.dart';
import 'flip_side.dart';
import 'flip_card_controllers.dart';
import 'dart:math';

/// [FlipCard] A component that provides a flip card animation

class FlipCard extends StatefulWidget {
  /// [controller] used to ccontrol the flip
  final FlipCardController controller;

  ///[frontWidget] The Front side widget of the card
  final Widget frontWidget;

  ///[backWidget] The Back side widget of the card
  final Widget backWidget;

  /// [onTapFlipping]  When enabled, the card will flip automatically when touched.
  final bool onTapFlipping;

  /// [axis] The flip axis [Horizontal] and [Vertical]
  final FlipAxis axis;

  /// [rotateSide] The card rotate side
  final RotateSide rotateSide;

  /// [animationDuration] The amount of milliseconds a turn animation will take.
  final Duration animationDuration;

  /// [disableSplashEffect] The option for disable Inkwell widget's splash effect.
  final bool disableSplashEffect;

  /// [splashColor] The option for Inkwell widget's splashColor.
  final Color? splashColor;

  /// [focusColor] The option for Inkwell widget's focusColor.
  final Color? focusColor;

  const FlipCard({
    Key? key,
    this.focusColor,
    this.splashColor,
    this.onTapFlipping = false,
    this.disableSplashEffect = false,
    required this.frontWidget,
    required this.backWidget,
    required this.controller,
    this.axis = FlipAxis.vertical,
    required this.rotateSide,
    this.animationDuration = const Duration(milliseconds: 800),
  }) : super(key: key);

  @override
  FlipCardState createState() => FlipCardState();
}

class FlipCardState extends State<FlipCard> with TickerProviderStateMixin {
  late AnimationController animationController;
  double anglePlus = 0;
  late bool _isFront;
  StreamSubscription? _sub;

  @override
  void initState() {
    super.initState();
    _isFront = widget.controller.isFront;
    animationController =
        AnimationController(duration: widget.animationDuration, vsync: this);
    if (!_isFront) {
      animationController.value = 1.0;
      anglePlus = pi;
    }
    _sub ??= widget.controller.asStream.distinct().listen((isFront) async {
      if (this._isFront != isFront) {
        if (animationController.isAnimating) return;
        this._isFront = isFront;
        await animationController
            .forward(from: 0)
            .then((value) => anglePlus = pi);
      }
    });
  }

  @override
  void dispose() {
    animationController.dispose();
    _sub?.cancel();
    _sub = null;
    super.dispose();
  }

  @override
  Widget build(BuildContext context) => AnimatedBuilder(
      animation: animationController,
      builder: (context, child) {
        double piValue = 0.0;
        if (widget.rotateSide == RotateSide.top ||
            widget.rotateSide == RotateSide.left) {
          piValue = pi;
        } else {
          piValue = -pi;
        }
        print('animation controller ${animationController.value}');
        double angle = animationController.value * piValue;
        late Matrix4 transform;
        late Matrix4 transformForBack;
        if (_isFront) angle += anglePlus;
        if (widget.axis == FlipAxis.horizontal) {
          transform = Matrix4.identity()
            ..setEntry(3, 2, 0.001)
            ..rotateX(angle);
          transformForBack = Matrix4.identity()..rotateX(pi);
        } else {
          transform = Matrix4.identity()
            ..setEntry(3, 2, 0.001)
            ..rotateY(angle);
          transformForBack = Matrix4.identity()..rotateY(pi);
        }

        final isFrontWidget = _isFrontWidget(angle.abs());

        return Transform(
          alignment: Alignment.center,
          transform: transform,
          child: isFrontWidget
              ? widget.frontWidget
              : Transform(
                  transform: transformForBack,
                  alignment: Alignment.center,
                  child: widget.backWidget,
                ),
        );
      });

  bool _isFrontWidget(double angle) {
    const degrees90 = pi / 2;
    const degrees270 = 3 * pi / 2;
    return angle <= degrees90 || angle >= degrees270;
  }
}
