// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

@Tags(<String>['reduced-test-set'])

import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  Future<void> testBorder(WidgetTester tester, String name, StarBorder border,
      {ShapeBorder? lerpTo, ShapeBorder? lerpFrom, double lerpAmount = 0}) async {
    assert(lerpTo == null || lerpFrom == null); // They can't both be set.
    ShapeBorder shape;
    if (lerpTo != null) {
      shape = border.lerpTo(lerpTo, lerpAmount)!;
    } else if (lerpFrom != null) {
      shape = border.lerpFrom(lerpFrom, lerpAmount)!;
    } else {
      shape = border;
    }
    await tester.pumpWidget(
      Container(
        alignment: Alignment.center,
        width: 200,
        height: 100,
        decoration: ShapeDecoration(
          color: const Color(0xff000000),
          shape: shape,
        ),
      ),
    );
    await expectLater(
      find.byType(Container),
      matchesGoldenFile('painting.star_border.$name.png'),
    );
  }

  test('StarBorder defaults', () {
    const StarBorder star = StarBorder();
    expect(star.side, BorderSide.none);
    expect(star.points, 5);
    expect(star.innerRadiusRatio, 0.4);
    expect(star.rotation, 0);
    expect(star.pointRounding, 0);
    expect(star.valleyRounding, 0);
    expect(star.squash, 0);

    const StarBorder polygon = StarBorder.polygon();
    expect(polygon.points, 5);
    expect(polygon.pointRounding, 0);
    expect(polygon.rotation, 0);
    expect(polygon.squash, 0);
  });

  test('StarBorder copyWith, ==, hashCode', () {
    const BorderSide side = BorderSide(width: 10.0, color: Color(0xff123456));
    final StarBorder copy = const StarBorder().copyWith(
      side: side,
      points: 3,
      innerRadiusRatio: 0.1,
      pointRounding: 0.2,
      valleyRounding: 0.3,
      rotation: 180,
      squash: 0.4,
    );
    const StarBorder expected = StarBorder(
      side: side,
      points: 3,
      innerRadiusRatio: 0.1,
      pointRounding: 0.2,
      valleyRounding: 0.3,
      rotation: 180,
      squash: 0.4,
    );
    expect(const StarBorder(), equals(const StarBorder().copyWith()));
    expect(copy, equals(expected));
    expect(copy.hashCode, equals(expected.hashCode));

    // Test that all properties are checked in operator==
    expect(const StarBorder(), isNot(equals(const StarBorderSubclass())));
    expect(copy, isNot(equals('Not a StarBorder')));

    // Test that two StarBorders where the only difference is polygon vs star
    // constructor compare as different (which they are, because
    // _innerRadiusRatio is null on the polygon).
    expect(
      const StarBorder(
        points: 3,
        innerRadiusRatio: 1,
        pointRounding: 0.2,
        rotation: 180,
        squash: 0.4,
      ),
      isNot(equals(
        const StarBorder.polygon(
          sides: 3,
          pointRounding: 0.2,
          rotation: 180,
          squash: 0.4,
        ),
      )),
    );

    // Test that copies are unequal whenever any one of the properties changes.
    expect(copy, equals(copy));
    expect(copy, isNot(equals(copy.copyWith(side: const BorderSide()))));
    expect(copy, isNot(equals(copy.copyWith(points: 10))));
    expect(copy, isNot(equals(copy.copyWith(innerRadiusRatio: 0.5))));
    expect(copy, isNot(equals(copy.copyWith(pointRounding: 0.5))));
    expect(copy, isNot(equals(copy.copyWith(valleyRounding: 0.5))));
    expect(copy, isNot(equals(copy.copyWith(rotation: 10))));
    expect(copy, isNot(equals(copy.copyWith(squash: 0.0))));
  });

  testWidgets('StarBorder basic geometry', (WidgetTester tester) async {
    await testBorder(tester, 'basic_star', const StarBorder());
    await testBorder(tester, 'basic_polygon', const StarBorder.polygon());
  });

  testWidgets('StarBorder parameters', (WidgetTester tester) async {
    await testBorder(tester, 'points_6', const StarBorder(points: 6));
    await testBorder(tester, 'points_2', const StarBorder(points: 2));
    await testBorder(tester, 'inner_radius_0', const StarBorder(innerRadiusRatio: 0.0));
    await testBorder(tester, 'inner_radius_20', const StarBorder(innerRadiusRatio: 0.2));
    await testBorder(tester, 'inner_radius_70', const StarBorder(innerRadiusRatio: 0.7));
    await testBorder(tester, 'point_rounding_20', const StarBorder(pointRounding: 0.2));
    await testBorder(tester, 'point_rounding_70', const StarBorder(pointRounding: 0.7));
    await testBorder(tester, 'point_rounding_100', const StarBorder(pointRounding: 1.0));
    await testBorder(tester, 'valley_rounding_20', const StarBorder(valleyRounding: 0.2));
    await testBorder(tester, 'valley_rounding_70', const StarBorder(valleyRounding: 0.7));
    await testBorder(tester, 'valley_rounding_100', const StarBorder(valleyRounding: 1.0));
    await testBorder(tester, 'squash_2', const StarBorder(squash: 0.2));
    await testBorder(tester, 'squash_7', const StarBorder(squash: 0.7));
    await testBorder(tester, 'squash_10', const StarBorder(squash: 1.0));
    await testBorder(tester, 'rotate_27', const StarBorder(rotation: 27));
    await testBorder(tester, 'rotate_270', const StarBorder(rotation: 270));
    await testBorder(tester, 'rotate_360', const StarBorder(rotation: 360));
    await testBorder(tester, 'side_none', const StarBorder(side: BorderSide(style: BorderStyle.none)));
    await testBorder(tester, 'side_1', const StarBorder(side: BorderSide(color: Color(0xffff0000))));
    await testBorder(tester, 'side_10', const StarBorder(side: BorderSide(color: Color(0xffff0000), width: 10)));
    await testBorder(tester, 'side_align_center', const StarBorder(side: BorderSide(color: Color(0xffff0000), strokeAlign: BorderSide.strokeAlignCenter)));
    await testBorder(tester, 'side_align_outside', const StarBorder(side: BorderSide(color: Color(0xffff0000), strokeAlign: BorderSide.strokeAlignOutside)));
  });

  testWidgets('StarBorder.polygon parameters', (WidgetTester tester) async {
    await testBorder(tester, 'poly_sides_6', const StarBorder.polygon(sides: 6));
    await testBorder(tester, 'poly_sides_2', const StarBorder.polygon(sides: 2));
    await testBorder(tester, 'poly_point_rounding_20', const StarBorder.polygon(pointRounding: 0.2));
    await testBorder(tester, 'poly_point_rounding_70', const StarBorder.polygon(pointRounding: 0.7));
    await testBorder(tester, 'poly_point_rounding_100', const StarBorder.polygon(pointRounding: 1.0));
    await testBorder(tester, 'poly_squash_20', const StarBorder.polygon(squash: 0.2));
    await testBorder(tester, 'poly_squash_70', const StarBorder.polygon(squash: 0.7));
    await testBorder(tester, 'poly_squash_100', const StarBorder.polygon(squash: 1.0));
    await testBorder(tester, 'poly_rotate_27', const StarBorder.polygon(rotation: 27));
    await testBorder(tester, 'poly_rotate_270', const StarBorder.polygon(rotation: 270));
    await testBorder(tester, 'poly_rotate_360', const StarBorder.polygon(rotation: 360));
    await testBorder(tester, 'poly_side_none', const StarBorder.polygon(side: BorderSide(style: BorderStyle.none)));
    await testBorder(tester, 'poly_side_1', const StarBorder.polygon(side: BorderSide(color: Color(0xffff0000))));
    await testBorder(tester, 'poly_side_10', const StarBorder.polygon(side: BorderSide(color: Color(0xffff0000), width: 10)));
    await testBorder(tester, 'poly_side_align_center', const StarBorder.polygon(side: BorderSide(color: Color(0xffff0000), strokeAlign: BorderSide.strokeAlignCenter)));
    await testBorder(tester, 'poly_side_align_outside', const StarBorder.polygon(side: BorderSide(color: Color(0xffff0000), strokeAlign: BorderSide.strokeAlignOutside)));
  });

  testWidgets("StarBorder doesn't try to scale an infinite scale matrix", (WidgetTester tester) async {
    await tester.pumpWidget(
      Directionality(
        textDirection: TextDirection.ltr,
        child: Center(
          child: SizedBox(
            width: 100,
            height: 100,
            child: Stack(
              children: <Widget>[
                Positioned.fromRelativeRect(
                  rect: const RelativeRect.fromLTRB(100, 100, 100, 100),
                  child: Container(
                    decoration: const ShapeDecoration(
                      color: Colors.green,
                      shape: StarBorder(),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
    expect(tester.takeException(), isNull);
  });

  testWidgets('StarBorder lerped with StarBorder', (WidgetTester tester) async {
    const StarBorder from = StarBorder();
    const ShapeBorder otherBorder = StarBorder(
      points: 6,
      pointRounding: 0.5,
      valleyRounding: 0.5,
      innerRadiusRatio: 0.5,
      rotation: 90,
    );
    await testBorder(tester, 'to_star_border_20', from, lerpTo: otherBorder, lerpAmount: 0.2);
    await testBorder(tester, 'to_star_border_70', from, lerpTo: otherBorder, lerpAmount: 0.7);
    await testBorder(tester, 'to_star_border_100', from, lerpTo: otherBorder, lerpAmount: 1.0);
    await testBorder(tester, 'from_star_border_20', from, lerpFrom: otherBorder, lerpAmount: 0.2);
    await testBorder(tester, 'from_star_border_70', from, lerpFrom: otherBorder, lerpAmount: 0.7);
    await testBorder(tester, 'from_star_border_100', from, lerpFrom: otherBorder, lerpAmount: 1.0);
  });

  testWidgets('StarBorder lerped with CircleBorder', (WidgetTester tester) async {
    const StarBorder from = StarBorder();
    const ShapeBorder otherBorder = CircleBorder();
    const ShapeBorder eccentricCircle = CircleBorder(eccentricity: 0.6);
    await testBorder(tester, 'to_circle_border_20', from, lerpTo: otherBorder, lerpAmount: 0.2);
    await testBorder(tester, 'to_circle_border_70', from, lerpTo: otherBorder, lerpAmount: 0.7);
    await testBorder(tester, 'to_circle_border_100', from, lerpTo: otherBorder, lerpAmount: 1.0);
    await testBorder(tester, 'from_circle_border_20', from, lerpFrom: otherBorder, lerpAmount: 0.2);
    await testBorder(tester, 'from_circle_border_70', from, lerpFrom: otherBorder, lerpAmount: 0.7);
    await testBorder(tester, 'from_circle_border_100', from, lerpFrom: otherBorder, lerpAmount: 1.0);
    await testBorder(tester, 'to_eccentric_circle_border_20', from, lerpTo: eccentricCircle, lerpAmount: 0.2);
    await testBorder(tester, 'to_eccentric_circle_border_70', from, lerpTo: eccentricCircle, lerpAmount: 0.7);
    await testBorder(tester, 'to_eccentric_circle_border_100', from, lerpTo: eccentricCircle, lerpAmount: 1.0);
    await testBorder(tester, 'from_eccentric_circle_border_20', from, lerpFrom: eccentricCircle, lerpAmount: 0.2);
    await testBorder(tester, 'from_eccentric_circle_border_70', from, lerpFrom: eccentricCircle, lerpAmount: 0.7);
    await testBorder(tester, 'from_eccentric_circle_border_100', from, lerpFrom: eccentricCircle, lerpAmount: 1.0);
  });

  testWidgets('StarBorder lerped with RoundedRectangleBorder', (WidgetTester tester) async {
    const StarBorder from = StarBorder();
    const RoundedRectangleBorder rectangleBorder = RoundedRectangleBorder();
    await testBorder(tester, 'to_rect_border_20', from, lerpTo: rectangleBorder, lerpAmount: 0.2);
    await testBorder(tester, 'to_rect_border_70', from, lerpTo: rectangleBorder, lerpAmount: 0.7);
    await testBorder(tester, 'to_rect_border_100', from, lerpTo: rectangleBorder, lerpAmount: 1.0);
    await testBorder(tester, 'from_rect_border_20', from, lerpFrom: rectangleBorder, lerpAmount: 0.2);
    await testBorder(tester, 'from_rect_border_70', from, lerpFrom: rectangleBorder, lerpAmount: 0.7);
    await testBorder(tester, 'from_rect_border_100', from, lerpFrom: rectangleBorder, lerpAmount: 1.0);

    const RoundedRectangleBorder roundedRectBorder = RoundedRectangleBorder(
      borderRadius: BorderRadius.only(
        bottomLeft: Radius.circular(10.0),
        bottomRight: Radius.circular(10.0),
      ),
    );
    await testBorder(tester, 'to_rrect_border_20', from, lerpTo: roundedRectBorder, lerpAmount: 0.2);
    await testBorder(tester, 'to_rrect_border_70', from, lerpTo: roundedRectBorder, lerpAmount: 0.7);
    await testBorder(tester, 'to_rrect_border_100', from, lerpTo: roundedRectBorder, lerpAmount: 1.0);
    await testBorder(tester, 'from_rrect_border_20', from, lerpFrom: roundedRectBorder, lerpAmount: 0.2);
    await testBorder(tester, 'from_rrect_border_70', from, lerpFrom: roundedRectBorder, lerpAmount: 0.7);
    await testBorder(tester, 'from_rrect_border_100', from, lerpFrom: roundedRectBorder, lerpAmount: 1.0);
  });

  testWidgets('StarBorder lerped with StadiumBorder', (WidgetTester tester) async {
    const StarBorder from = StarBorder();
    const StadiumBorder stadiumBorder = StadiumBorder();

    await testBorder(tester, 'to_stadium_border_20', from, lerpTo: stadiumBorder, lerpAmount: 0.2);
    await testBorder(tester, 'to_stadium_border_70', from, lerpTo: stadiumBorder, lerpAmount: 0.7);
    await testBorder(tester, 'to_stadium_border_100', from, lerpTo: stadiumBorder, lerpAmount: 1.0);
    await testBorder(tester, 'from_stadium_border_20', from, lerpFrom: stadiumBorder, lerpAmount: 0.2);
    await testBorder(tester, 'from_stadium_border_70', from, lerpFrom: stadiumBorder, lerpAmount: 0.7);
    await testBorder(tester, 'from_stadium_border_100', from, lerpFrom: stadiumBorder, lerpAmount: 1.0);
  });
}

class StarBorderSubclass extends StarBorder {
  const StarBorderSubclass();
}
