// Copyright 2016 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 'dart:ui';

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

import '../rendering/mock_canvas.dart';
import '../widgets/semantics_tester.dart';

void main() {
  testWidgets('BottomNavigationBar callback test', (WidgetTester tester) async {
    int mutatedIndex;

    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                icon: const Icon(Icons.ac_unit),
                title: const Text('AC')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_alarm),
                title: const Text('Alarm')
              )
            ],
            onTap: (int index) {
              mutatedIndex = index;
            }
          )
        )
      )
    );

    await tester.tap(find.text('Alarm'));

    expect(mutatedIndex, 1);
  });

  testWidgets('BottomNavigationBar content test', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                icon: const Icon(Icons.ac_unit),
                title: const Text('AC')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_alarm),
                title: const Text('Alarm')
              )
            ]
          )
        )
      )
    );

    final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
    expect(box.size.height, kBottomNavigationBarHeight);
    expect(find.text('AC'), findsOneWidget);
    expect(find.text('Alarm'), findsOneWidget);
  });

  testWidgets('BottomNavigationBar adds bottom padding to height', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        home: new MediaQuery(
          data: const MediaQueryData(padding: const EdgeInsets.only(bottom: 40.0)),
          child: new Scaffold(
            bottomNavigationBar: new BottomNavigationBar(
              items: const <BottomNavigationBarItem>[
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.ac_unit),
                  title: const Text('AC')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.access_alarm),
                  title: const Text('Alarm')
                )
              ]
            )
          )
        )
      )
    );

    const double labelBottomMargin = 8.0; // _kBottomMargin in implementation.
    const double additionalPadding = 40.0 - labelBottomMargin;
    const double expectedHeight = kBottomNavigationBarHeight + additionalPadding;
    expect(tester.getSize(find.byType(BottomNavigationBar)).height, expectedHeight);
  });

  testWidgets('BottomNavigationBar action size test', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            type: BottomNavigationBarType.shifting,
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                icon: const Icon(Icons.ac_unit),
                title: const Text('AC')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_alarm),
                title: const Text('Alarm')
              )
            ]
          )
        )
      )
    );

    Iterable<RenderBox> actions = tester.renderObjectList(find.byType(InkResponse));
    expect(actions.length, 2);
    expect(actions.elementAt(0).size.width, 480.0);
    expect(actions.elementAt(1).size.width, 320.0);

    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            currentIndex: 1,
            type: BottomNavigationBarType.shifting,
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                icon: const Icon(Icons.ac_unit),
                title: const Text('AC')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_alarm),
                title: const Text('Alarm')
              )
            ]
          )
        )
      )
    );

    await tester.pump(const Duration(milliseconds: 200));

    actions = tester.renderObjectList(find.byType(InkResponse));
    expect(actions.length, 2);
    expect(actions.elementAt(0).size.width, 320.0);
    expect(actions.elementAt(1).size.width, 480.0);
  });

  testWidgets('BottomNavigationBar multiple taps test', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            type: BottomNavigationBarType.shifting,
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                icon: const Icon(Icons.ac_unit),
                title: const Text('AC')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_alarm),
                title: const Text('Alarm')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.access_time),
                title: const Text('Time')
              ),
              const BottomNavigationBarItem(
                icon: const Icon(Icons.add),
                title: const Text('Add')
              )
            ]
          )
        )
      )
    );

    // We want to make sure that the last label does not get displaced,
    // irrespective of how many taps happen on the first N - 1 labels and how
    // they grow.

    Iterable<RenderBox> actions = tester.renderObjectList(find.byType(InkResponse));
    final Offset originalOrigin = actions.elementAt(3).localToGlobal(Offset.zero);

    await tester.tap(find.text('AC'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    actions = tester.renderObjectList(find.byType(InkResponse));
    expect(actions.elementAt(3).localToGlobal(Offset.zero), equals(originalOrigin));

    await tester.tap(find.text('Alarm'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    actions = tester.renderObjectList(find.byType(InkResponse));
    expect(actions.elementAt(3).localToGlobal(Offset.zero), equals(originalOrigin));

    await tester.tap(find.text('Time'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 100));

    actions = tester.renderObjectList(find.byType(InkResponse));
    expect(actions.elementAt(3).localToGlobal(Offset.zero), equals(originalOrigin));
  });

  testWidgets('BottomNavigationBar inherits shadowed app theme for shifting navbar', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(brightness: Brightness.light),
        home: new Theme(
          data: new ThemeData(brightness: Brightness.dark),
          child: new Scaffold(
            bottomNavigationBar: new BottomNavigationBar(
              type: BottomNavigationBarType.shifting,
              items: const <BottomNavigationBarItem>[
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.ac_unit),
                  title: const Text('AC')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.access_alarm),
                  title: const Text('Alarm')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.access_time),
                  title: const Text('Time')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.add),
                  title: const Text('Add')
                )
              ]
            )
          )
        )
      )
    );

    await tester.tap(find.text('Alarm'));
    await tester.pump(const Duration(seconds: 1));
    expect(Theme.of(tester.element(find.text('Alarm'))).brightness, equals(Brightness.dark));
  });

  testWidgets('BottomNavigationBar inherits shadowed app theme for fixed navbar', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        theme: new ThemeData(brightness: Brightness.light),
        home: new Theme(
          data: new ThemeData(brightness: Brightness.dark),
          child: new Scaffold(
            bottomNavigationBar: new BottomNavigationBar(
              type: BottomNavigationBarType.fixed,
              items: const <BottomNavigationBarItem>[
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.ac_unit),
                  title: const Text('AC')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.access_alarm),
                  title: const Text('Alarm')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.access_time),
                  title: const Text('Time')
                ),
                const BottomNavigationBarItem(
                  icon: const Icon(Icons.add),
                  title: const Text('Add')
                )
              ]
            )
          )
        )
      )
    );

    await tester.tap(find.text('Alarm'));
    await tester.pump(const Duration(seconds: 1));
    expect(Theme.of(tester.element(find.text('Alarm'))).brightness, equals(Brightness.dark));
  });

  testWidgets('BottomNavigationBar iconSize test', (WidgetTester tester) async {
    double builderIconSize;
    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            iconSize: 12.0,
            items: <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                title: const Text('A'),
                icon: const Icon(Icons.ac_unit),
              ),
              new BottomNavigationBarItem(
                title: const Text('B'),
                icon: new Builder(
                  builder: (BuildContext context) {
                    builderIconSize = IconTheme.of(context).size;
                    return new SizedBox(
                      width: builderIconSize,
                      height: builderIconSize,
                    );
                  },
                ),
              ),
            ],
          ),
        ),
      ),
    );

    final RenderBox box = tester.renderObject(find.byType(Icon));
    expect(box.size.width, equals(12.0));
    expect(box.size.height, equals(12.0));
    expect(builderIconSize, 12.0);
  });


  testWidgets('BottomNavigationBar responds to textScaleFactor', (WidgetTester tester) async {
    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            type: BottomNavigationBarType.fixed,
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                title: const Text('A'),
                icon: const Icon(Icons.ac_unit),
              ),
              const BottomNavigationBarItem(
                title: const Text('B'),
                icon: const Icon(Icons.battery_alert),
              ),
            ],
          ),
        ),
      ),
    );

    final RenderBox defaultBox = tester.renderObject(find.byType(BottomNavigationBar));
    expect(defaultBox.size.height, equals(kBottomNavigationBarHeight));

    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            type: BottomNavigationBarType.shifting,
            items: const <BottomNavigationBarItem>[
              const BottomNavigationBarItem(
                title: const Text('A'),
                icon: const Icon(Icons.ac_unit),
              ),
              const BottomNavigationBarItem(
                title: const Text('B'),
                icon: const Icon(Icons.battery_alert),
              ),
            ],
          ),
        ),
      ),
    );

    final RenderBox shiftingBox = tester.renderObject(find.byType(BottomNavigationBar));
    expect(shiftingBox.size.height, equals(kBottomNavigationBarHeight));

    await tester.pumpWidget(
      new MaterialApp(
        home: new MediaQuery(
          data: const MediaQueryData(textScaleFactor: 2.0),
          child: new Scaffold(
            bottomNavigationBar: new BottomNavigationBar(
              items: const <BottomNavigationBarItem>[
                const BottomNavigationBarItem(
                  title: const Text('A'),
                  icon: const Icon(Icons.ac_unit),
                ),
                const BottomNavigationBarItem(
                  title: const Text('B'),
                  icon: const Icon(Icons.battery_alert),
                ),
              ],
            ),
          ),
        ),
      ),
    );

    final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
    expect(box.size.height, equals(68.0));
  });

  testWidgets('BottomNavigationBar limits width of tiles with long titles', (WidgetTester tester) async {
    final Text longTextA = new Text(''.padLeft(100, 'A'));
    final Text longTextB = new Text(''.padLeft(100, 'B'));

    await tester.pumpWidget(
      new MaterialApp(
        home: new Scaffold(
          bottomNavigationBar: new BottomNavigationBar(
            items: <BottomNavigationBarItem>[
              new BottomNavigationBarItem(
                title: longTextA,
                icon: const Icon(Icons.ac_unit),
              ),
              new BottomNavigationBarItem(
                title: longTextB,
                icon: const Icon(Icons.battery_alert),
              ),
            ],
          ),
        ),
      ),
    );

    final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
    expect(box.size.height, equals(kBottomNavigationBarHeight));

    final RenderBox itemBoxA = tester.renderObject(find.text(longTextA.data));
    expect(itemBoxA.size, equals(const Size(400.0, 14.0)));
    final RenderBox itemBoxB = tester.renderObject(find.text(longTextB.data));
    expect(itemBoxB.size, equals(const Size(400.0, 14.0)));
  });

  testWidgets('BottomNavigationBar paints circles', (WidgetTester tester) async {
    await tester.pumpWidget(
      boilerplate(
        textDirection: TextDirection.ltr,
        bottomNavigationBar: new BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            const BottomNavigationBarItem(
              title: const Text('A'),
              icon: const Icon(Icons.ac_unit),
            ),
            const BottomNavigationBarItem(
              title: const Text('B'),
              icon: const Icon(Icons.battery_alert),
            ),
          ],
        ),
      ),
    );

    final RenderBox box = tester.renderObject(find.byType(BottomNavigationBar));
    expect(box, isNot(paints..circle()));

    await tester.tap(find.text('A'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 20));
    expect(box, paints..circle(x: 200.0));

    await tester.tap(find.text('B'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 20));
    expect(box, paints..circle(x: 200.0)..circle(x: 600.0));

    // Now we flip the directionality and verify that the circles switch positions.
    await tester.pumpWidget(
      boilerplate(
        textDirection: TextDirection.rtl,
        bottomNavigationBar: new BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            const BottomNavigationBarItem(
              title: const Text('A'),
              icon: const Icon(Icons.ac_unit),
            ),
            const BottomNavigationBarItem(
              title: const Text('B'),
              icon: const Icon(Icons.battery_alert),
            ),
          ],
        ),
      ),
    );

    expect(box, paints..circle(x: 600.0)..circle(x: 200.0));

    await tester.tap(find.text('A'));
    await tester.pump();
    await tester.pump(const Duration(milliseconds: 20));
    expect(box, paints..circle(x: 600.0)..circle(x: 200.0)..circle(x: 600.0));
  });

  testWidgets('BottomNavigationBar semantics', (WidgetTester tester) async {
    final SemanticsTester semantics = new SemanticsTester(tester);

    await tester.pumpWidget(
      boilerplate(
        textDirection: TextDirection.ltr,
        bottomNavigationBar: new BottomNavigationBar(
          items: const <BottomNavigationBarItem>[
            const BottomNavigationBarItem(
              icon: const Icon(Icons.ac_unit),
              title: const Text('AC'),
            ),
            const BottomNavigationBarItem(
              icon: const Icon(Icons.access_alarm),
              title: const Text('Alarm'),
            ),
            const BottomNavigationBarItem(
              icon: const Icon(Icons.hot_tub),
              title: const Text('Hot Tub'),
            ),
          ],
        ),
      ),
    );

    // TODO(goderbauer): traversal order is incorrect, https://github.com/flutter/flutter/issues/14375
    final TestSemantics expected = new TestSemantics.root(
      children: <TestSemantics>[
        new TestSemantics(
          id: 1,
          flags: <SemanticsFlag>[SemanticsFlag.isSelected],
          actions: <SemanticsAction>[SemanticsAction.tap],
          label: 'AC\nTab 1 of 3',
          textDirection: TextDirection.ltr,
          previousNodeId: 3, // Should be 2
        ),
        new TestSemantics(
          id: 2,
          actions: <SemanticsAction>[SemanticsAction.tap],
          label: 'Alarm\nTab 2 of 3',
          textDirection: TextDirection.ltr,
          nextNodeId: 3,
          previousNodeId: -1, // Should be 1
        ),
        new TestSemantics(
          id: 3,
          actions: <SemanticsAction>[SemanticsAction.tap],
          label: 'Hot Tub\nTab 3 of 3',
          textDirection: TextDirection.ltr,
          nextNodeId: 1, // Should be -1
          previousNodeId: 2,
        ),
      ],
    );
    expect(semantics, hasSemantics(expected, ignoreTransform: true, ignoreRect: true));

    semantics.dispose();
  });

}

Widget boilerplate({ Widget bottomNavigationBar, @required TextDirection textDirection }) {
  assert(textDirection != null);
  return new Localizations(
    locale: const Locale('en', 'US'),
    delegates: const <LocalizationsDelegate<dynamic>>[
      DefaultMaterialLocalizations.delegate,
      DefaultWidgetsLocalizations.delegate,
    ],
    child: new Directionality(
      textDirection: textDirection,
      child: new MediaQuery(
        data: const MediaQueryData(),
        child: new Material(
          child: new Scaffold(
            bottomNavigationBar: bottomNavigationBar,
          ),
        ),
      ),
    ),
  );
}