// 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/gestures.dart';
import 'package:flutter_test/flutter_test.dart';

import 'gesture_tester.dart';

class TestDrag extends Drag {
}

void main() {
  setUp(ensureGestureBinding);

  testGesture('Should recognize pan', (GestureTester tester) {
    final MultiTapGestureRecognizer tap = MultiTapGestureRecognizer(longTapDelay: kLongPressTimeout);

    final List<String> log = <String>[];

    tap.onTapDown = (int pointer, TapDownDetails details) { log.add('tap-down $pointer'); };
    tap.onTapUp = (int pointer, TapUpDetails details) { log.add('tap-up $pointer'); };
    tap.onTap = (int pointer) { log.add('tap $pointer'); };
    tap.onLongTapDown = (int pointer, TapDownDetails details) { log.add('long-tap-down $pointer'); };
    tap.onTapCancel = (int pointer) { log.add('tap-cancel $pointer'); };


    final TestPointer pointer5 = TestPointer(5);
    final PointerDownEvent down5 = pointer5.down(const Offset(10.0, 10.0));
    tap.addPointer(down5);
    tester.closeArena(5);
    expect(log, <String>['tap-down 5']);
    log.clear();
    tester.route(down5);
    expect(log, isEmpty);

    final TestPointer pointer6 = TestPointer(6);
    final PointerDownEvent down6 = pointer6.down(const Offset(15.0, 15.0));
    tap.addPointer(down6);
    tester.closeArena(6);
    expect(log, <String>['tap-down 6']);
    log.clear();
    tester.route(down6);
    expect(log, isEmpty);

    tester.route(pointer5.move(const Offset(11.0, 12.0)));
    expect(log, isEmpty);

    tester.route(pointer6.move(const Offset(14.0, 13.0)));
    expect(log, isEmpty);

    tester.route(pointer5.up());
    expect(log, <String>[
      'tap-up 5',
      'tap 5',
    ]);
    log.clear();

    tester.async.elapse(kLongPressTimeout + kPressTimeout);
    expect(log, <String>['long-tap-down 6']);
    log.clear();

    tester.route(pointer6.move(const Offset(40.0, 30.0))); // move more than kTouchSlop from 15.0,15.0
    expect(log, <String>['tap-cancel 6']);
    log.clear();

    tester.route(pointer6.up());
    expect(log, isEmpty);

    tap.dispose();
  });

  testGesture('Can filter based on device kind', (GestureTester tester) {
    final MultiTapGestureRecognizer tap =
        MultiTapGestureRecognizer(
          longTapDelay: kLongPressTimeout,
          kind: PointerDeviceKind.touch,
        );

    final List<String> log = <String>[];

    tap.onTapDown = (int pointer, TapDownDetails details) { log.add('tap-down $pointer'); };
    tap.onTapUp = (int pointer, TapUpDetails details) { log.add('tap-up $pointer'); };
    tap.onTap = (int pointer) { log.add('tap $pointer'); };
    tap.onLongTapDown = (int pointer, TapDownDetails details) { log.add('long-tap-down $pointer'); };
    tap.onTapCancel = (int pointer) { log.add('tap-cancel $pointer'); };


    final TestPointer touchPointer5 = TestPointer(5, PointerDeviceKind.touch);
    final PointerDownEvent down5 = touchPointer5.down(const Offset(10.0, 10.0));
    tap.addPointer(down5);
    tester.closeArena(5);
    expect(log, <String>['tap-down 5']);
    log.clear();
    tester.route(down5);
    expect(log, isEmpty);

    final TestPointer mousePointer6 = TestPointer(6, PointerDeviceKind.mouse);
    final PointerDownEvent down6 = mousePointer6.down(const Offset(20.0, 20.0));
    tap.addPointer(down6);
    tester.closeArena(6);
    // Mouse down should be ignored by the recognizer.
    expect(log, isEmpty);

    final TestPointer touchPointer7 = TestPointer(7, PointerDeviceKind.touch);
    final PointerDownEvent down7 = touchPointer7.down(const Offset(15.0, 15.0));
    tap.addPointer(down7);
    tester.closeArena(7);
    expect(log, <String>['tap-down 7']);
    log.clear();
    tester.route(down7);
    expect(log, isEmpty);

    tester.route(touchPointer5.move(const Offset(11.0, 12.0)));
    expect(log, isEmpty);

    // Move within the [kTouchSlop] range.
    tester.route(mousePointer6.move(const Offset(21.0, 18.0)));
    // Move beyond the slop range.
    tester.route(mousePointer6.move(const Offset(50.0, 40.0)));
    // Neither triggers any event because they originate from a mouse.
    expect(log, isEmpty);

    tester.route(touchPointer7.move(const Offset(14.0, 13.0)));
    expect(log, isEmpty);

    tester.route(touchPointer5.up());
    expect(log, <String>[
      'tap-up 5',
      'tap 5',
    ]);
    log.clear();

    // Mouse up should be ignored.
    tester.route(mousePointer6.up());
    expect(log, isEmpty);

    tester.async.elapse(kLongPressTimeout + kPressTimeout);
    // Only the touch pointer (7) triggers a long-tap, not the mouse pointer (6).
    expect(log, <String>['long-tap-down 7']);
    log.clear();

    tester.route(touchPointer7.move(const Offset(40.0, 30.0))); // move more than kTouchSlop from 15.0,15.0
    expect(log, <String>['tap-cancel 7']);
    log.clear();

    tap.dispose();
  });

  testWidgets('DoubleTapGestureRecognizer asserts when kind and supportedDevices are both set', (WidgetTester tester) async {
    expect(
      () {
        DoubleTapGestureRecognizer(
            kind: PointerDeviceKind.touch,
            supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.touch },
        );
      },
      throwsA(
        isA<AssertionError>().having((AssertionError error) => error.toString(),
        'description', contains('kind == null || supportedDevices == null')),
      ),
    );
  });

  testWidgets('MultiTapGestureRecognizer asserts when kind and supportedDevices are both set', (WidgetTester tester) async {
    expect(
      () {
        MultiTapGestureRecognizer(
            kind: PointerDeviceKind.touch,
            supportedDevices: <PointerDeviceKind>{ PointerDeviceKind.touch },
        );
      },
      throwsA(
        isA<AssertionError>().having((AssertionError error) => error.toString(),
        'description', contains('kind == null || supportedDevices == null')),
      ),
    );
  });
}
