// 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 'dart:sky' as sky;

import 'package:sky/src/gestures/arena.dart';
import 'package:sky/src/gestures/recognizer.dart';
import 'package:sky/src/gestures/constants.dart';

enum DragState {
  ready,
  possible,
  accepted
}

typedef void GestureDragStartCallback();
typedef void GestureDragUpdateCallback(double delta);
typedef void GestureDragEndCallback(sky.Offset velocity);

typedef void GesturePanStartCallback();
typedef void GesturePanUpdateCallback(sky.Offset delta);
typedef void GesturePanEndCallback(sky.Offset velocity);

typedef void _GesturePolymorphicUpdateCallback<T>(T delta);

int _eventTime(sky.PointerEvent event) => (event.timeStamp * 1000.0).toInt(); // microseconds

bool _isFlingGesture(sky.GestureVelocity velocity) {
  double velocitySquared = velocity.x * velocity.x + velocity.y * velocity.y;
  return velocity.isValid &&
    velocitySquared > kMinFlingVelocity * kMinFlingVelocity &&
    velocitySquared < kMaxFlingVelocity * kMaxFlingVelocity;
}

abstract class _DragGestureRecognizer<T extends dynamic> extends GestureRecognizer {
  _DragGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
    : super(router: router);

  GestureDragStartCallback onStart;
  _GesturePolymorphicUpdateCallback<T> onUpdate;
  GestureDragEndCallback onEnd;

  DragState _state = DragState.ready;
  T _pendingDragDelta;

  T get _initialPendingDragDelta;
  T _getDragDelta(sky.PointerEvent event);
  bool get _hasSufficientPendingDragDeltaToAccept;

  final sky.VelocityTracker _velocityTracker = new sky.VelocityTracker();

  void addPointer(sky.PointerEvent event) {
    startTrackingPointer(event.pointer);
    if (_state == DragState.ready) {
      _state = DragState.possible;
      _pendingDragDelta = _initialPendingDragDelta;
    }
  }

  void handleEvent(sky.PointerEvent event) {
    assert(_state != DragState.ready);
    if (event.type == 'pointermove') {
      _velocityTracker.addPosition(_eventTime(event), event.pointer, event.x, event.y);
      T delta = _getDragDelta(event);
      if (_state == DragState.accepted) {
        if (onUpdate != null)
          onUpdate(delta);
      } else {
        _pendingDragDelta += delta;
        if (_hasSufficientPendingDragDeltaToAccept)
          resolve(GestureDisposition.accepted);
      }
    }
    stopTrackingIfPointerNoLongerDown(event);
  }

  void acceptGesture(int pointer) {
    if (_state != DragState.accepted) {
      _state = DragState.accepted;
      T delta = _pendingDragDelta;
      _pendingDragDelta = _initialPendingDragDelta;
      if (onStart != null)
        onStart();
      if (delta != _initialPendingDragDelta && onUpdate != null)
        onUpdate(delta);
    }
  }

  void didStopTrackingLastPointer(int pointer) {
    if (_state == DragState.possible) {
      resolve(GestureDisposition.rejected);
      _state = DragState.ready;
      return;
    }
    bool wasAccepted = (_state == DragState.accepted);
    _state = DragState.ready;
    if (wasAccepted && onEnd != null) {
      sky.GestureVelocity gestureVelocity = _velocityTracker.getVelocity(pointer);
      sky.Offset velocity = sky.Offset.zero;
      if (_isFlingGesture(gestureVelocity))
        velocity = new sky.Offset(gestureVelocity.x, gestureVelocity.y);
      onEnd(velocity);
    }
    _velocityTracker.reset();
  }

  void dispose() {
    _velocityTracker.reset();
    super.dispose();
  }
}

class VerticalDragGestureRecognizer extends _DragGestureRecognizer<double> {
  VerticalDragGestureRecognizer({
    PointerRouter router,
    GestureDragStartCallback onStart,
    GestureDragUpdateCallback onUpdate,
    GestureDragEndCallback onEnd
  }) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);

  double get _initialPendingDragDelta => 0.0;
  double _getDragDelta(sky.PointerEvent event) => event.dy;
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragDelta.abs() > kTouchSlop;
}

class HorizontalDragGestureRecognizer extends _DragGestureRecognizer<double> {
  HorizontalDragGestureRecognizer({
    PointerRouter router,
    GestureDragStartCallback onStart,
    GestureDragUpdateCallback onUpdate,
    GestureDragEndCallback onEnd
  }) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);

  double get _initialPendingDragDelta => 0.0;
  double _getDragDelta(sky.PointerEvent event) => event.dx;
  bool get _hasSufficientPendingDragDeltaToAccept => _pendingDragDelta.abs() > kTouchSlop;
}

class PanGestureRecognizer extends _DragGestureRecognizer<sky.Offset> {
  PanGestureRecognizer({
    PointerRouter router,
    GesturePanStartCallback onStart,
    GesturePanUpdateCallback onUpdate,
    GesturePanEndCallback onEnd
  }) : super(router: router, onStart: onStart, onUpdate: onUpdate, onEnd: onEnd);

  sky.Offset get _initialPendingDragDelta => sky.Offset.zero;
  sky.Offset _getDragDelta(sky.PointerEvent event) => new sky.Offset(event.dx, event.dy);
  bool get _hasSufficientPendingDragDeltaToAccept {
    return _pendingDragDelta.distance > kPanSlop;
  }
}