import 'dart:sky' as sky;

import 'package:sky/animation.dart';
import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart';

import '../engine/mock_events.dart';

typedef Widget WidgetBuilder();

class TestApp extends App {

  WidgetBuilder _builder;
  void set builder (WidgetBuilder value) {
    setState(() {
      _builder = value;
    });
  }

  Widget build() {
    if (_builder != null)
      return _builder();
    return new Container();
  }
}

class WidgetTester {
  WidgetTester() {
    _app = new TestApp();
    runApp(_app);
    scheduler.beginFrame(0.0); // to initialise the app
  }

  TestApp _app;

  List<Layer> _layers(Layer layer) {
    List<Layer> result = [layer];
    if (layer is ContainerLayer) {
      ContainerLayer root = layer;
      Layer child = root.firstChild;
      while(child != null) {
        result.addAll(_layers(child));
        child = child.nextSibling;
      }
    }
    return result;
  }
  List<Layer> get layers => _layers(SkyBinding.instance.renderView.layer);

  void walkWidgets(WidgetTreeWalker walker) {
    void walk(Widget widget) {
      walker(widget);
      widget.walkChildren(walk);
    }

    _app.walkChildren(walk);
  }

  Widget findWidget(bool predicate(Widget widget)) {
    try {
      walkWidgets((Widget widget) {
        if (predicate(widget))
          throw widget;
      });
    } catch (e) {
      if (e is Widget)
        return e;
      rethrow;
    }
    return null;
  }

  Text findText(String text) {
    return findWidget((Widget widget) {
      return widget is Text && widget.data == text;
    });
  }

  Point _getWidgetPoint(Widget widget, Function sizeToPoint) {
    assert(widget != null);
    RenderBox box = widget.renderObject as RenderBox;
    assert(box != null);
    return box.localToGlobal(sizeToPoint(box.size));
  }

  Point getCenter(Widget widget) {
    return _getWidgetPoint(widget, (Size size) => size.center(Point.origin));
  }

  Point getTopLeft(Widget widget) {
    return _getWidgetPoint(widget, (_) => Point.origin);
  }

  Point getTopRight(Widget widget) {
    return _getWidgetPoint(widget, (Size size) => size.topRight(Point.origin));
  }

  Point getBottomLeft(Widget widget) {
    return _getWidgetPoint(widget, (Size size) => size.bottomLeft(Point.origin));
  }

  Point getBottomRight(Widget widget) {
    return _getWidgetPoint(widget, (Size size) => size.bottomRight(Point.origin));
  }

  HitTestResult _hitTest(Point location) => SkyBinding.instance.hitTest(location);

  void _dispatchEvent(sky.Event event, HitTestResult result) {
    SkyBinding.instance.dispatchEvent(event, result);
  }

  void tap(Widget widget, { int pointer: 1 }) {
    tapAt(getCenter(widget), pointer: pointer);
  }

  void tapAt(Point location, { int pointer: 1 }) {
    HitTestResult result = _hitTest(location);
    TestPointer p = new TestPointer(pointer);
    _dispatchEvent(p.down(location), result);
    _dispatchEvent(p.up(), result);
  }

  void scroll(Widget widget, Offset offset, { int pointer: 1 }) {
    Point startLocation = getCenter(widget);
    Point endLocation = startLocation + offset;
    TestPointer p = new TestPointer(pointer);
    // Events for the entire press-drag-release gesture are dispatched
    // to the widgets "hit" by the pointer down event.
    HitTestResult result = _hitTest(startLocation);
    _dispatchEvent(p.down(startLocation), result);
    _dispatchEvent(p.move(endLocation), result);
    _dispatchEvent(p.up(), result);
  }

  void dispatchEvent(sky.Event event, Point location) {
    _dispatchEvent(event, _hitTest(location));
  }

  void pumpFrame(WidgetBuilder builder, [double frameTimeMs = 0.0]) {
    _app.builder = builder;
    scheduler.beginFrame(frameTimeMs);
  }

  void pumpFrameWithoutChange([double frameTimeMs = 0.0]) {
    scheduler.beginFrame(frameTimeMs);
  }

}