// Copyright 2016 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:math' as math;

import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';

void main() {
  testWidgets('toString control test', (WidgetTester tester) async {
    const Widget widget = FadeTransition(
      opacity: kAlwaysCompleteAnimation,
      child: Text('Ready', textDirection: TextDirection.ltr),
    );
    expect(widget.toString, isNot(throwsException));
  });

  group('DecoratedBoxTransition test', () {
    final DecorationTween decorationTween = DecorationTween(
      begin: BoxDecoration(
        color: const Color(0xFFFFFFFF),
        border: Border.all(
          color: const Color(0xFF000000),
          style: BorderStyle.solid,
          width: 4.0,
        ),
        borderRadius: BorderRadius.zero,
        shape: BoxShape.rectangle,
        boxShadow: const <BoxShadow> [BoxShadow(
          color: Color(0x66000000),
          blurRadius: 10.0,
          spreadRadius: 4.0,
        )],
      ),
      end: BoxDecoration(
        color: const Color(0xFF000000),
        border: Border.all(
          color: const Color(0xFF202020),
          style: BorderStyle.solid,
          width: 1.0,
        ),
        borderRadius: BorderRadius.circular(10.0),
        shape: BoxShape.rectangle,
        // No shadow.
      ),
    );

    AnimationController controller;

    setUp(() {
      controller = AnimationController(vsync: const TestVSync());
    });

    testWidgets(
      'decoration test',
      (WidgetTester tester) async {
        final DecoratedBoxTransition transitionUnderTest =
            DecoratedBoxTransition(
              decoration: decorationTween.animate(controller),
              child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr),
            );

        await tester.pumpWidget(transitionUnderTest);
        RenderDecoratedBox actualBox =
            tester.renderObject(find.byType(DecoratedBox));
        BoxDecoration actualDecoration = actualBox.decoration;

        expect(actualDecoration.color, const Color(0xFFFFFFFF));
        expect(actualDecoration.boxShadow[0].blurRadius, 10.0);
        expect(actualDecoration.boxShadow[0].spreadRadius, 4.0);
        expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));

        controller.value = 0.5;

        await tester.pump();
        actualBox = tester.renderObject(find.byType(DecoratedBox));
        actualDecoration = actualBox.decoration;

        expect(actualDecoration.color, const Color(0xFF7F7F7F));
        expect(actualDecoration.border, isInstanceOf<Border>());
        final Border border = actualDecoration.border;
        expect(border.left.width, 2.5);
        expect(border.left.style, BorderStyle.solid);
        expect(border.left.color, const Color(0xFF101010));
        expect(actualDecoration.borderRadius, BorderRadius.circular(5.0));
        expect(actualDecoration.shape, BoxShape.rectangle);
        expect(actualDecoration.boxShadow[0].blurRadius, 5.0);
        expect(actualDecoration.boxShadow[0].spreadRadius, 2.0);
        // Scaling a shadow doesn't change the color.
        expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));

        controller.value = 1.0;

        await tester.pump();
        actualBox = tester.renderObject(find.byType(DecoratedBox));
        actualDecoration = actualBox.decoration;

        expect(actualDecoration.color, const Color(0xFF000000));
        expect(actualDecoration.boxShadow, null);
      },
    );

    testWidgets('animations work with curves test', (WidgetTester tester) async {
      final Animation<Decoration> curvedDecorationAnimation =
          decorationTween.animate(CurvedAnimation(
            parent: controller,
            curve: Curves.easeOut,
          ));

      final DecoratedBoxTransition transitionUnderTest =
          DecoratedBoxTransition(
            decoration: curvedDecorationAnimation,
            position: DecorationPosition.foreground,
            child: const Text('Doesn\'t matter', textDirection: TextDirection.ltr),
          );

      await tester.pumpWidget(transitionUnderTest);
      RenderDecoratedBox actualBox =
          tester.renderObject(find.byType(DecoratedBox));
      BoxDecoration actualDecoration = actualBox.decoration;

      expect(actualDecoration.color, const Color(0xFFFFFFFF));
      expect(actualDecoration.boxShadow[0].blurRadius, 10.0);
      expect(actualDecoration.boxShadow[0].spreadRadius, 4.0);
      expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));

      controller.value = 0.5;

      await tester.pump();
      actualBox = tester.renderObject(find.byType(DecoratedBox));
      actualDecoration = actualBox.decoration;

      // Same as the test above but the values should be much closer to the
      // tween's end values given the easeOut curve.
      expect(actualDecoration.color, const Color(0xFF505050));
      expect(actualDecoration.border, isInstanceOf<Border>());
      final Border border = actualDecoration.border;
      expect(border.left.width, closeTo(1.9, 0.1));
      expect(border.left.style, BorderStyle.solid);
      expect(border.left.color, const Color(0xFF151515));
      expect(actualDecoration.borderRadius.resolve(TextDirection.ltr).topLeft.x, closeTo(6.8, 0.1));
      expect(actualDecoration.shape, BoxShape.rectangle);
      expect(actualDecoration.boxShadow[0].blurRadius, closeTo(3.1, 0.1));
      expect(actualDecoration.boxShadow[0].spreadRadius, closeTo(1.2, 0.1));
      // Scaling a shadow doesn't change the color.
      expect(actualDecoration.boxShadow[0].color, const Color(0x66000000));
    });
  });

  testWidgets('AlignTransition animates', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Animation<Alignment> alignmentTween = AlignmentTween(
      begin: const Alignment(-1.0, 0.0),
      end: const Alignment(1.0, 1.0),
    ).animate(controller);
    final Widget widget = AlignTransition(
      alignment: alignmentTween,
      child: const Text('Ready', textDirection: TextDirection.ltr),
    );

    await tester.pumpWidget(widget);

    final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align));

    Alignment actualAlignment = actualPositionedBox.alignment;
    expect(actualAlignment, const Alignment(-1.0, 0.0));

    controller.value = 0.5;
    await tester.pump();
    actualAlignment = actualPositionedBox.alignment;
    expect(actualAlignment, const Alignment(0.0, 0.5));
  });

  testWidgets('AlignTransition keeps width and height factors', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Animation<Alignment> alignmentTween = AlignmentTween(
      begin: const Alignment(-1.0, 0.0),
      end: const Alignment(1.0, 1.0),
    ).animate(controller);
    final Widget widget = AlignTransition(
      alignment: alignmentTween,
      child: const Text('Ready', textDirection: TextDirection.ltr),
      widthFactor: 0.3,
      heightFactor: 0.4,
    );

    await tester.pumpWidget(widget);

    final Align actualAlign = tester.widget(find.byType(Align));

    expect(actualAlign.widthFactor, 0.3);
    expect(actualAlign.heightFactor, 0.4);
  });

  testWidgets('SizeTransition clamps negative size factors - vertical axis', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller);

    final Widget widget =  Directionality(
        textDirection: TextDirection.ltr,
        child: SizeTransition(
          axis: Axis.vertical,
          sizeFactor: animation,
          child: const Text('Ready'),
        ),
      );

    await tester.pumpWidget(widget);

    final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align));
    expect(actualPositionedBox.heightFactor, 0.0);

    controller.value = 0.0;
    await tester.pump();
    expect(actualPositionedBox.heightFactor, 0.0);

    controller.value = 0.75;
    await tester.pump();
    expect(actualPositionedBox.heightFactor, 0.5);

    controller.value = 1.0;
    await tester.pump();
    expect(actualPositionedBox.heightFactor, 1.0);
  });

  testWidgets('SizeTransition clamps negative size factors - horizontal axis', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Animation<double> animation = Tween<double>(begin: -1.0, end: 1.0).animate(controller);

    final Widget widget =  Directionality(
        textDirection: TextDirection.ltr,
        child: SizeTransition(
          axis: Axis.horizontal,
          sizeFactor: animation,
          child: const Text('Ready'),
        ),
      );

    await tester.pumpWidget(widget);

    final RenderPositionedBox actualPositionedBox = tester.renderObject(find.byType(Align));
    expect(actualPositionedBox.widthFactor, 0.0);

    controller.value = 0.0;
    await tester.pump();
    expect(actualPositionedBox.widthFactor, 0.0);

    controller.value = 0.75;
    await tester.pump();
    expect(actualPositionedBox.widthFactor, 0.5);

    controller.value = 1.0;
    await tester.pump();
    expect(actualPositionedBox.widthFactor, 1.0);
  });

  testWidgets('RotationTransition animates', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Widget widget = RotationTransition(
      alignment: Alignment.topRight,
      turns: controller,
      child: const Text('Rotation', textDirection: TextDirection.ltr),
    );

    await tester.pumpWidget(widget);
    Transform actualRotatedBox = tester.widget(find.byType(Transform));
    Matrix4 actualTurns = actualRotatedBox.transform;
    expect(actualTurns, equals(Matrix4.rotationZ(0.0)));

    controller.value = 0.5;
    await tester.pump();
    actualRotatedBox = tester.widget(find.byType(Transform));
    actualTurns = actualRotatedBox.transform;
    expect(actualTurns, Matrix4.rotationZ(math.pi));

    controller.value = 0.75;
    await tester.pump();
    actualRotatedBox = tester.widget(find.byType(Transform));
    actualTurns = actualRotatedBox.transform;
    expect(actualTurns, Matrix4.rotationZ(math.pi * 1.5));
  });

  testWidgets('RotationTransition maintains chosen alignment during animation', (WidgetTester tester) async {
    final AnimationController controller = AnimationController(vsync: const TestVSync());
    final Widget widget = RotationTransition(
      alignment: Alignment.topRight,
      turns: controller,
      child: const Text('Rotation', textDirection: TextDirection.ltr),
    );

    await tester.pumpWidget(widget);
    RotationTransition actualRotatedBox =
        tester.widget(find.byType(RotationTransition));
    Alignment actualAlignment = actualRotatedBox.alignment;
    expect(actualAlignment, const Alignment(1.0, -1.0));

    controller.value = 0.5;
    await tester.pump();
    actualRotatedBox = tester.widget(find.byType(RotationTransition));
    actualAlignment = actualRotatedBox.alignment;
    expect(actualAlignment, const Alignment(1.0, -1.0));
  });
}