// Copyright 2015 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 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
  testWidgets('WidgetInspector smoke test', (WidgetTester tester) async {
    // This is a smoke test to verify that adding the inspector doesn't crash.
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new Stack(
          children: <Widget>[
            const Text('a', textDirection: TextDirection.ltr),
            const Text('b', textDirection: TextDirection.ltr),
            const Text('c', textDirection: TextDirection.ltr),
          ],
        ),
      ),
    );

    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new WidgetInspector(
          selectButtonBuilder: null,
          child: new Stack(
            children: <Widget>[
              const Text('a', textDirection: TextDirection.ltr),
              const Text('b', textDirection: TextDirection.ltr),
              const Text('c', textDirection: TextDirection.ltr),
            ],
          ),
        ),
      ),
    );

    expect(true, isTrue); // Expect that we reach here without crashing.
  });

  testWidgets('WidgetInspector interaction test', (WidgetTester tester) async {
    final List<String> log = <String>[];
    final GlobalKey selectButtonKey = new GlobalKey();
    final GlobalKey inspectorKey = new GlobalKey();
    final GlobalKey topButtonKey = new GlobalKey();

    Widget selectButtonBuilder(BuildContext context, VoidCallback onPressed) {
      return new Material(child: new RaisedButton(onPressed: onPressed, key: selectButtonKey));
    }
    // State type is private, hence using dynamic.
    dynamic getInspectorState() => inspectorKey.currentState;
    String paragraphText(RenderParagraph paragraph) => paragraph.text.text;

    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new WidgetInspector(
          key: inspectorKey,
          selectButtonBuilder: selectButtonBuilder,
          child: new Material(
            child: new ListView(
              children: <Widget>[
                new RaisedButton(
                  key: topButtonKey,
                  onPressed: () {
                    log.add('top');
                  },
                  child: const Text('TOP'),
                ),
                new RaisedButton(
                  onPressed: () {
                    log.add('bottom');
                  },
                  child: const Text('BOTTOM'),
                ),
              ],
            ),
          ),
        ),
      ),
    );

    expect(getInspectorState().selection.current, isNull);
    await tester.tap(find.text('TOP'));
    await tester.pump();
    // Tap intercepted by the inspector
    expect(log, equals(<String>[]));
    final InspectorSelection selection = getInspectorState().selection;
    expect(paragraphText(selection.current), equals('TOP'));
    final RenderObject topButton = find.byKey(topButtonKey).evaluate().first.renderObject;
    expect(selection.candidates.contains(topButton), isTrue);

    await tester.tap(find.text('TOP'));
    expect(log, equals(<String>['top']));
    log.clear();

    await tester.tap(find.text('BOTTOM'));
    expect(log, equals(<String>['bottom']));
    log.clear();
    // Ensure the inspector selection has not changed to bottom.
    expect(paragraphText(getInspectorState().selection.current), equals('TOP'));

    await tester.tap(find.byKey(selectButtonKey));
    await tester.pump();

    // We are now back in select mode so tapping the bottom button will have
    // not trigger a click but will cause it to be selected.
    await tester.tap(find.text('BOTTOM'));
    expect(log, equals(<String>[]));
    log.clear();
    expect(paragraphText(getInspectorState().selection.current), equals('BOTTOM'));
  });

  testWidgets('WidgetInspector scroll test', (WidgetTester tester) async {
    final Key childKey = new UniqueKey();
    final GlobalKey selectButtonKey = new GlobalKey();
    final GlobalKey inspectorKey = new GlobalKey();

    Widget selectButtonBuilder(BuildContext context, VoidCallback onPressed) {
      return new Material(child: new RaisedButton(onPressed: onPressed, key: selectButtonKey));
    }
    // State type is private, hence using dynamic.
    dynamic getInspectorState() => inspectorKey.currentState;

    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new WidgetInspector(
          key: inspectorKey,
          selectButtonBuilder: selectButtonBuilder,
          child: new ListView(
            children: <Widget>[
              new Container(
                key: childKey,
                height: 5000.0,
              ),
            ],
          ),
        ),
      ),
    );

    expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0));

    await tester.fling(find.byType(ListView), const Offset(0.0, -200.0), 200.0);
    await tester.pump();

    // Fling does nothing as are in inspect mode.
    expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0));

    await tester.fling(find.byType(ListView), const Offset(200.0, 0.0), 200.0);
    await tester.pump();

    // Fling still does nothing as are in inspect mode.
    expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0));

    await tester.tap(find.byType(ListView));
    await tester.pump();
    expect(getInspectorState().selection.current, isNotNull);

    // Now out of inspect mode due to the click.
    await tester.fling(find.byType(ListView), const Offset(0.0, -200.0), 200.0);
    await tester.pump();

    expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(-200.0));

    await tester.fling(find.byType(ListView), const Offset(0.0, 200.0), 200.0);
    await tester.pump();

    expect(tester.getTopLeft(find.byKey(childKey)).dy, equals(0.0));
  });

  testWidgets('WidgetInspector long press', (WidgetTester tester) async {
    bool didLongPress = false;

    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new WidgetInspector(
          selectButtonBuilder: null,
          child: new GestureDetector(
            onLongPress: () {
              expect(didLongPress, isFalse);
              didLongPress = true;
            },
            child: const Text('target', textDirection: TextDirection.ltr),
          ),
        ),
      ),
    );

    await tester.longPress(find.text('target'));
    // The inspector will swallow the long press.
    expect(didLongPress, isFalse);
  });

  testWidgets('WidgetInspector offstage', (WidgetTester tester) async {
    final GlobalKey inspectorKey = new GlobalKey();
    final GlobalKey clickTarget = new GlobalKey();

    Widget createSubtree({ double width, Key key }) {
      return new Stack(
        children: <Widget>[
          new Positioned(
            key: key,
            left: 0.0,
            top: 0.0,
            width: width,
            height: 100.0,
            child: new Text(width.toString(), textDirection: TextDirection.ltr),
          ),
        ],
      );
    }
    await tester.pumpWidget(
      new Directionality(
        textDirection: TextDirection.ltr,
        child: new WidgetInspector(
          key: inspectorKey,
          selectButtonBuilder: null,
          child: new Overlay(
            initialEntries: <OverlayEntry>[
              new OverlayEntry(
                opaque: false,
                maintainState: true,
                builder: (BuildContext _) => createSubtree(width: 94.0),
              ),
              new OverlayEntry(
                opaque: true,
                maintainState: true,
                builder: (BuildContext _) => createSubtree(width: 95.0),
              ),
              new OverlayEntry(
                opaque: false,
                maintainState: true,
                builder: (BuildContext _) => createSubtree(width: 96.0, key: clickTarget),
              ),
            ],
          ),
        ),
      ),
    );

    await tester.longPress(find.byKey(clickTarget));
    // State type is private, hence using dynamic.
    final dynamic inspectorState = inspectorKey.currentState;
    // The object with width 95.0 wins over the object with width 94.0 because
    // the subtree with width 94.0 is offstage.
    expect(inspectorState.selection.current.semanticBounds.width, equals(95.0));

    // Exactly 2 out of the 3 text elements should be in the candidate list of
    // objects to select as only 2 are onstage.
    expect(inspectorState.selection.candidates.where((RenderObject object) => object is RenderParagraph).length, equals(2));
  });
}