// Copyright 2013 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.

import 'dart:math' as math;

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/ui.dart';

import '../common/matchers.dart';
import '../common/test_initialization.dart';

const double kTolerance = 0.1;

void main() {
  internalBootstrapBrowserTest(() => testMain);
}

Future<void> testMain() async {
  setUpUnitTests();
  group('PathMetric length', () {
    test('empty path', () {
      final Path path = Path();
      expect(path.computeMetrics().isEmpty, isTrue);
    });

    test('simple line', () {
      final Path path = Path();
      path.moveTo(100.0, 50.0);
      path.lineTo(200.0, 100.0);
      expect(path.computeMetrics().isEmpty, isFalse);
      final List<PathMetric> metrics = path.computeMetrics().toList();
      expect(metrics.length, 1);
      expect(metrics[0].length, within(distance: kTolerance, from: 111.803));
    });

    test('2 lines', () {
      final Path path = Path();
      path.moveTo(100.0, 50.0);
      path.lineTo(200.0, 50.0);
      path.lineTo(100.0, 200.0);
      expect(path.computeMetrics().isEmpty, isFalse);
      final List<PathMetric> metrics = path.computeMetrics().toList();
      expect(metrics.length, 1);
      expect(metrics[0].length, within(distance: kTolerance, from: 280.277));
    });

    test('2 lines forceClosed', () {
      final Path path = Path();
      path.moveTo(100.0, 50.0);
      path.lineTo(200.0, 50.0);
      path.lineTo(100.0, 200.0);
      expect(path.computeMetrics(forceClosed: true).isEmpty, isFalse);
      final List<PathMetric> metrics =
          path.computeMetrics(forceClosed: true).toList();
      expect(metrics.length, 1);
      expect(metrics[0].length, within(distance: kTolerance, from: 430.277));
    });

    test('2 subpaths', () {
      final Path path = Path();
      path.moveTo(100.0, 50.0);
      path.lineTo(200.0, 100.0);
      path.moveTo(200.0, 100.0);
      path.lineTo(200.0, 200.0);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 2);
      expect(contourLengths[0], within(distance: kTolerance, from: 111.803));
      expect(contourLengths[1], within(distance: kTolerance, from: 100.0));
    });

    test('quadratic curve', () {
      final Path path = Path();
      path.moveTo(20, 100);
      path.quadraticBezierTo(80, 10, 140, 110);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 159.473));
    });

    test('cubic curve', () {
      final Path path = Path();
      path.moveTo(20, 100);
      path.cubicTo(80, 10, 120, 90, 140, 40);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 146.567));
    });

    test('addRect', () {
      final Path path = Path();
      path.addRect(const Rect.fromLTRB(20, 30, 220, 130));
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 600.0));
    });

    test('addRRect with zero radius', () {
      final Path path = Path();
      path.addRRect(RRect.fromLTRBR(20, 30, 220, 130, Radius.zero));
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 600.0));
    });

    test('addRRect with elliptical radius', () {
      final Path path = Path();
      path.addRRect(RRect.fromLTRBR(20, 30, 220, 130, const Radius.elliptical(8, 4)));
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 590.408));
    });

    test('arcToPoint < 90 degrees', () {
      const double rx = 100;
      const double ry = 100;
      const double cx = 150;
      const double cy = 100;
      const double startAngle = 0.0;
      const double endAngle = 90.0;
      const double startRad = startAngle * math.pi / 180.0;
      const double endRad = endAngle * math.pi / 180.0;

      final double startX = cx + (rx * math.cos(startRad));
      final double startY = cy + (ry * math.sin(startRad));
      final double endX = cx + (rx * math.cos(endRad));
      final double endY = cy + (ry * math.sin(endRad));

      final bool largeArc = (endAngle - startAngle).abs() > 180.0;
      final Path path = Path()
        ..moveTo(startX, startY)
        ..arcToPoint(Offset(endX, endY),
            radius: const Radius.elliptical(rx, ry),
            largeArc: largeArc);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 156.827));
    });

    test('arcToPoint 180 degrees', () {
      const double rx = 100;
      const double ry = 100;
      const double cx = 150;
      const double cy = 100;
      const double startAngle = 0.0;
      const double endAngle = 180.0;
      const double startRad = startAngle * math.pi / 180.0;
      const double endRad = endAngle * math.pi / 180.0;

      final double startX = cx + (rx * math.cos(startRad));
      final double startY = cy + (ry * math.sin(startRad));
      final double endX = cx + (rx * math.cos(endRad));
      final double endY = cy + (ry * math.sin(endRad));

      final bool largeArc = (endAngle - startAngle).abs() > 180.0;
      final Path path = Path()
        ..moveTo(startX, startY)
        ..arcToPoint(Offset(endX, endY),
            radius: const Radius.elliptical(rx, ry),
            largeArc: largeArc);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 313.654));
    });

    test('arcToPoint 270 degrees', () {
      const double rx = 100;
      const double ry = 100;
      const double cx = 150;
      const double cy = 100;
      const double startAngle = 0.0;
      const double endAngle = 270.0;
      const double startRad = startAngle * math.pi / 180.0;
      const double endRad = endAngle * math.pi / 180.0;

      final double startX = cx + (rx * math.cos(startRad));
      final double startY = cy + (ry * math.sin(startRad));
      final double endX = cx + (rx * math.cos(endRad));
      final double endY = cy + (ry * math.sin(endRad));

      final bool largeArc = (endAngle - startAngle).abs() > 180.0;
      final Path path = Path()
        ..moveTo(startX, startY)
        ..arcToPoint(Offset(endX, endY),
            radius: const Radius.elliptical(rx, ry),
            largeArc: largeArc);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 470.482));
    });

    test('arcToPoint 270 degrees rx!=ry', () {
      const double rx = 100;
      const double ry = 50;
      const double cx = 150;
      const double cy = 100;
      const double startAngle = 0.0;
      const double endAngle = 270.0;
      const double startRad = startAngle * math.pi / 180.0;
      const double endRad = endAngle * math.pi / 180.0;

      final double startX = cx + (rx * math.cos(startRad));
      final double startY = cy + (ry * math.sin(startRad));
      final double endX = cx + (rx * math.cos(endRad));
      final double endY = cy + (ry * math.sin(endRad));

      final bool largeArc = (endAngle - startAngle).abs() > 180.0;
      final Path path = Path()
        ..moveTo(startX, startY)
        ..arcToPoint(Offset(endX, endY),
            radius: const Radius.elliptical(rx, ry),
            largeArc: largeArc);
      final List<double> contourLengths = computeLengths(path.computeMetrics());
      expect(contourLengths.length, 1);
      expect(contourLengths[0], within(distance: kTolerance, from: 362.733));
    });
  });
}

List<double> computeLengths(PathMetrics pathMetrics) {
  final List<double> lengths = <double>[];
  for (final PathMetric metric in pathMetrics) {
    lengths.add(metric.length);
  }
  return lengths;
}
