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

import 'arena.dart';
import 'recognizer.dart';
import 'constants.dart';
import 'events.dart';

enum ScaleState {
  ready,
  possible,
  accepted,
  started
}

typedef void GestureScaleStartCallback(ui.Point focalPoint);
typedef void GestureScaleUpdateCallback(double scale, ui.Point focalPoint);
typedef void GestureScaleEndCallback();

class ScaleGestureRecognizer extends OneSequenceGestureRecognizer {
  ScaleGestureRecognizer({ PointerRouter router, this.onStart, this.onUpdate, this.onEnd })
    : super(router: router);

  GestureScaleStartCallback onStart;
  GestureScaleUpdateCallback onUpdate;
  GestureScaleEndCallback onEnd;

  ScaleState _state = ScaleState.ready;

  double _initialSpan;
  double _currentSpan;
  Map<int, ui.Point> _pointerLocations;

  double get _scaleFactor => _initialSpan > 0.0 ? _currentSpan / _initialSpan : 1.0;

  void addPointer(PointerInputEvent event) {
    startTrackingPointer(event.pointer);
    if (_state == ScaleState.ready) {
      _state = ScaleState.possible;
      _initialSpan = 0.0;
      _currentSpan = 0.0;
      _pointerLocations = new Map<int, ui.Point>();
    }
  }

  void handleEvent(PointerInputEvent event) {
    assert(_state != ScaleState.ready);
    bool configChanged = false;
    switch(event.type) {
      case 'pointerup':
        configChanged = true;
        _pointerLocations.remove(event.pointer);
        break;
      case 'pointerdown':
        configChanged = true;
        _pointerLocations[event.pointer] = new ui.Point(event.x, event.y);
        break;
      case 'pointermove':
        _pointerLocations[event.pointer] = new ui.Point(event.x, event.y);
        break;
    }

    _update(configChanged);

    stopTrackingIfPointerNoLongerDown(event);
  }

  void _update(bool configChanged) {
    int count = _pointerLocations.keys.length;

    // Compute the focal point
    ui.Point focalPoint = ui.Point.origin;
    for (int pointer in _pointerLocations.keys)
      focalPoint += _pointerLocations[pointer].toOffset();
    focalPoint = new ui.Point(focalPoint.x / count, focalPoint.y / count);

    // Span is the average deviation from focal point
    double totalDeviation = 0.0;
    for (int pointer in _pointerLocations.keys)
      totalDeviation += (focalPoint - _pointerLocations[pointer]).distance;
    _currentSpan = count > 0 ? totalDeviation / count : 0.0;

    if (configChanged) {
      _initialSpan = _currentSpan;
      if (_state == ScaleState.started) {
        if (onEnd != null)
          onEnd();
        _state = ScaleState.accepted;
      }
    }

    if (_state == ScaleState.ready)
      _state = ScaleState.possible;

    if (_state == ScaleState.possible &&
        (_currentSpan - _initialSpan).abs() > kScaleSlop) {
      resolve(GestureDisposition.accepted);
    }

    if (_state == ScaleState.accepted && !configChanged) {
      _state = ScaleState.started;
      if (onStart != null)
        onStart(focalPoint);
    }

    if (_state == ScaleState.started && onUpdate != null)
      onUpdate(_scaleFactor, focalPoint);
  }

  void acceptGesture(int pointer) {
    if (_state != ScaleState.accepted) {
      _state = ScaleState.accepted;
      _update(false);
    }
  }

  void didStopTrackingLastPointer(int pointer) {
    switch(_state) {
      case ScaleState.possible:
        resolve(GestureDisposition.rejected);
        break;
      case ScaleState.ready:
        assert(false);  // We should have not seen a pointer yet
        break;
      case ScaleState.accepted:
        break;
      case ScaleState.started:
        assert(false);  // We should be in the accepted state when user is done
        break;
    }
    _state = ScaleState.ready;
  }
}