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

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

class CountButton extends StatefulWidget {
  const CountButton({Key? key}) : super(key: key);

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

class _CountButtonState extends State<CountButton> {
  int counter = 0;
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      child: Text('Counter $counter'),
      onPressed: () {
        setState(() {
          counter += 1;
        });
      },
    );
  }
}

class AnimateSample extends StatefulWidget {
  const AnimateSample({Key? key}) : super(key: key);

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

class _AnimateSampleState extends State<AnimateSample>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      vsync: this,
      duration: const Duration(seconds: 1),
    )..forward();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (BuildContext context, _) => Text('Value: ${_controller.value}'),
    );
  }
}

void main() {
  test('Test pump on LiveWidgetController', () async {
    runApp(const MaterialApp(home: Center(child: CountButton())));

    await SchedulerBinding.instance!.endOfFrame;
    final WidgetController controller =
        LiveWidgetController(WidgetsBinding.instance!);
    await controller.tap(find.text('Counter 0'));
    expect(find.text('Counter 0'), findsOneWidget);
    expect(find.text('Counter 1'), findsNothing);
    await controller.pump();
    expect(find.text('Counter 0'), findsNothing);
    expect(find.text('Counter 1'), findsOneWidget);
  });

  test('Test pumpAndSettle on LiveWidgetController', () async {
    runApp(const MaterialApp(home: Center(child: AnimateSample())));
    await SchedulerBinding.instance!.endOfFrame;
    final WidgetController controller =
        LiveWidgetController(WidgetsBinding.instance!);
    expect(find.text('Value: 1.0'), findsNothing);
    await controller.pumpAndSettle();
    expect(find.text('Value: 1.0'), findsOneWidget);
  });

  test('Input event array on LiveWidgetController', () async {
    final List<String> logs = <String>[];
    runApp(
      MaterialApp(
        home: Listener(
          onPointerDown: (PointerDownEvent event) => logs.add('down ${event.buttons}'),
          onPointerMove: (PointerMoveEvent event) => logs.add('move ${event.buttons}'),
          onPointerUp: (PointerUpEvent event) => logs.add('up ${event.buttons}'),
          child: const Text('test'),
        ),
      ),
    );
    await SchedulerBinding.instance!.endOfFrame;
    final WidgetController controller =
        LiveWidgetController(WidgetsBinding.instance!);

    final Offset location = controller.getCenter(find.text('test'));
    final List<PointerEventRecord> records = <PointerEventRecord>[
      PointerEventRecord(Duration.zero, <PointerEvent>[
        // Typically PointerAddedEvent is not used in testers, but for records
        // captured on a device it is usually what start a gesture.
        PointerAddedEvent(
          timeStamp: Duration.zero,
          position: location,
        ),
        PointerDownEvent(
          timeStamp: Duration.zero,
          position: location,
          buttons: kSecondaryMouseButton,
          pointer: 1,
        ),
      ]),
      ...<PointerEventRecord>[
        for (Duration t = const Duration(milliseconds: 5);
            t < const Duration(milliseconds: 80);
            t += const Duration(milliseconds: 16))
          PointerEventRecord(t, <PointerEvent>[
            PointerMoveEvent(
              timeStamp: t - const Duration(milliseconds: 1),
              position: location,
              buttons: kSecondaryMouseButton,
              pointer: 1,
            )
          ])
      ],
      PointerEventRecord(const Duration(milliseconds: 80), <PointerEvent>[
        PointerUpEvent(
          timeStamp: const Duration(milliseconds: 79),
          position: location,
          buttons: kSecondaryMouseButton,
          pointer: 1,
        )
      ])
    ];
    final List<Duration> timeDiffs =
        await controller.handlePointerEventRecord(records);

    expect(timeDiffs.length, records.length);
    for (final Duration diff in timeDiffs) {
      // Allow some freedom of time delay in real world.
      assert(diff.inMilliseconds > -1);
    }

    const String b = '$kSecondaryMouseButton';
    expect(logs.first, 'down $b');
    for (int i = 1; i < logs.length - 1; i++) {
      expect(logs[i], 'move $b');
    }
    expect(logs.last, 'up $b');
  });
}
