// 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 'dart:async';
import 'dart:ui';

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

import 'recorder.dart';

class _NestedMouseRegion extends StatelessWidget {
  const _NestedMouseRegion({this.nests, this.child});

  final int nests;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Widget current = child;
    for (int i = 0; i < nests; i++) {
      current = MouseRegion(
        onEnter: (_) {},
        child: child,
      );
    }
    return current;
  }
}

class _NestedListener extends StatelessWidget {
  const _NestedListener({this.nests, this.child});

  final int nests;
  final Widget child;

  @override
  Widget build(BuildContext context) {
    Widget current = child;
    for (int i = 0; i < nests; i++) {
      current = Listener(
        onPointerDown: (_) {},
        child: child,
      );
    }
    return current;
  }
}

/// Creates a grid of mouse regions, then continuously hovers over them.
///
/// Measures our ability to hit test mouse regions.
class BenchMouseRegionMixedGridHover extends WidgetRecorder {
  BenchMouseRegionMixedGridHover() : super(name: benchmarkName) {
    _tester = _Tester(onDataPoint: handleDataPoint);
  }

  static const String benchmarkName = 'bench_mouse_region_mixed_grid_hover';

  _Tester _tester;

  void handleDataPoint(Duration duration) {
    profile.addDataPoint('hitTestDuration', duration, reported: true);
  }

  // Use a non-trivial border to force Web to switch painter
  Border _getBorder(int columnIndex, int rowIndex) {
    const BorderSide defaultBorderSide = BorderSide();

    return Border(
      left: columnIndex == 0 ? defaultBorderSide : BorderSide.none,
      top: rowIndex == 0 ? defaultBorderSide : BorderSide.none,
      right: defaultBorderSide,
      bottom: defaultBorderSide,
    );
  }

  bool started = false;

  @override
  void frameDidDraw() {
    if (!started) {
      started = true;
      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) async {
        _tester.start();
        registerDidStop(_tester.stop);
      });
    }
    super.frameDidDraw();
  }

  @override
  Widget createWidget() {
    const int rowsCount = 60;
    const int columnsCount = 20;
    const double containerSize = 20;
    return Directionality(
      textDirection: TextDirection.ltr,
      child: Align(
        alignment: Alignment.topLeft,
        child: SizedBox(
          width: 400,
          height: 400,
          child: ListView.builder(
            itemCount: rowsCount,
            cacheExtent: rowsCount * containerSize,
            physics: const ClampingScrollPhysics(),
            itemBuilder: (BuildContext context, int rowIndex) => _NestedMouseRegion(
              nests: 10,
              child: Row(
                children: List<Widget>.generate(
                  columnsCount,
                  (int columnIndex) => _NestedListener(
                    nests: 40,
                    child: _NestedMouseRegion(
                      nests: 10,
                      child: Container(
                        decoration: BoxDecoration(
                          border: _getBorder(columnIndex, rowIndex),
                          color: Color.fromARGB(255, rowIndex * 20 % 256, 127, 127),
                        ),
                        width: containerSize,
                        height: containerSize,
                      ),
                    ),
                  ),
                ),
              ),
            ),
          ),
        ),
      ),
    );
  }
}

class _UntilNextFrame {
  _UntilNextFrame._();

  static Completer<void> _completer;

  static Future<void> wait() {
    if (_UntilNextFrame._completer == null) {
      _UntilNextFrame._completer = Completer<void>();
      SchedulerBinding.instance.addPostFrameCallback((_) {
        _UntilNextFrame._completer.complete(null);
        _UntilNextFrame._completer = null;
      });
    }
    return _UntilNextFrame._completer.future;
  }
}

class _Tester {
  _Tester({this.onDataPoint});

  final ValueSetter<Duration> onDataPoint;

  static const Duration hoverDuration = Duration(milliseconds: 20);

  bool _stopped = false;

  TestGesture get gesture {
    return _gesture ??= TestGesture(
      dispatcher: (PointerEvent event) async {
        RendererBinding.instance.handlePointerEvent(event);
      },
      kind: PointerDeviceKind.mouse,
    );
  }
  TestGesture _gesture;

  Duration currentTime = Duration.zero;

  Future<void> _hoverTo(Offset location, Duration duration) async {
    currentTime += duration;
    final Stopwatch stopwatch = Stopwatch()..start();
    await gesture.moveTo(location, timeStamp: currentTime);
    stopwatch.stop();
    if (onDataPoint != null)
      onDataPoint(stopwatch.elapsed);
    await _UntilNextFrame.wait();
  }

  Future<void> start() async {
    await Future<void>.delayed(Duration.zero);
    while (!_stopped) {
      await _hoverTo(const Offset(30, 10), hoverDuration);
      await _hoverTo(const Offset(10, 370), hoverDuration);
      await _hoverTo(const Offset(370, 390), hoverDuration);
      await _hoverTo(const Offset(390, 30), hoverDuration);
    }
  }

  void stop() {
    _stopped = true;
  }
}
