// FIXME works for View2D only
// FIXME should possibly show tooltip when dragging?
// FIXME should possibly be adjustable by clicking on container

goog.provide('ol.control.ZoomSlider');

goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.dom');
goog.require('goog.dom.TagName');
goog.require('goog.events');
goog.require('goog.events.EventType');
goog.require('goog.fx.Dragger');
goog.require('goog.fx.Dragger.EventType');
goog.require('goog.math');
goog.require('goog.math.Rect');
goog.require('goog.style');
goog.require('ol.animation');
goog.require('ol.control.Control');
goog.require('ol.css');
goog.require('ol.easing');


/**
 * @define {number} Animation duration.
 */
ol.control.ZOOMSLIDER_ANIMATION_DURATION = 200;



/**
 * A slider type of control for zooming.
 *
 * Example:
 *
 *     var zoomslider = new ol.control.ZoomSlider({
 *       map: map
 *     });
 *
 * @constructor
 * @extends {ol.control.Control}
 * @param {ol.control.ZoomSliderOptions=} opt_options Zoom slider options.
 */
ol.control.ZoomSlider = function(opt_options) {

  var options = goog.isDef(opt_options) ? opt_options : {};

  /**
   * Will hold the current resolution of the view.
   *
   * @type {number|undefined}
   * @private
   */
  this.currentResolution_ = undefined;

  /**
   * The direction of the slider. Will be determined from actual display of the
   * container and defaults to ol.control.ZoomSlider.direction.VERTICAL.
   *
   * @type {ol.control.ZoomSlider.direction}
   * @private
   */
  this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;

  /**
   * @private
   * @type {Array.<?number>}
   */
  this.draggerListenerKeys_ = null;

  var className = goog.isDef(options.className) ?
      options.className : 'ol-zoomslider';
  var sliderCssCls = className + ' ' + ol.css.CLASS_UNSELECTABLE;
  var thumbCssCls = className + '-thumb' + ' ' + ol.css.CLASS_UNSELECTABLE;
  var element = goog.dom.createDom(goog.dom.TagName.DIV, sliderCssCls,
      goog.dom.createDom(goog.dom.TagName.DIV, thumbCssCls));

  this.dragger_ = this.createDraggable_(element);

  // FIXME currently only a do nothing function is bound.
  goog.events.listen(element, [
    goog.events.EventType.TOUCHEND,
    goog.events.EventType.CLICK
  ], this.handleContainerClick_, false, this);

  goog.base(this, {
    element: element,
    map: options.map
  });
};
goog.inherits(ol.control.ZoomSlider, ol.control.Control);


/**
 * The enum for available directions.
 *
 * @enum {number}
 */
ol.control.ZoomSlider.direction = {
  VERTICAL: 0,
  HORIZONTAL: 1
};


/**
 * @inheritDoc
 */
ol.control.ZoomSlider.prototype.setMap = function(map) {
  goog.base(this, 'setMap', map);
  this.initSlider_();
  var resolution = map.getView().getView2D().getResolution();
  if (goog.isDef(resolution)) {
    this.currentResolution_ = resolution;
    this.positionThumbForResolution_(resolution);
  }
};


/**
 * Initializes the slider element. This will determine and set this controls
 * direction_ and also constrain the dragging of the thumb to always be within
 * the bounds of the container.
 *
 * @private
 */
ol.control.ZoomSlider.prototype.initSlider_ = function() {
  var container = this.element,
      thumb = goog.dom.getFirstElementChild(container),
      elemSize = goog.style.getContentBoxSize(container),
      thumbBounds = goog.style.getBounds(thumb),
      thumbMargins = goog.style.getMarginBox(thumb),
      thumbBorderBox = goog.style.getBorderBox(thumb),
      w = elemSize.width -
          thumbMargins.left - thumbMargins.right -
          thumbBorderBox.left - thumbBorderBox.right -
          thumbBounds.width,
      h = elemSize.height -
          thumbMargins.top - thumbMargins.bottom -
          thumbBorderBox.top - thumbBorderBox.bottom -
          thumbBounds.height,
      limits;
  if (elemSize.width > elemSize.height) {
    this.direction_ = ol.control.ZoomSlider.direction.HORIZONTAL;
    limits = new goog.math.Rect(0, 0, w, 0);
  } else {
    this.direction_ = ol.control.ZoomSlider.direction.VERTICAL;
    limits = new goog.math.Rect(0, 0, 0, h);
  }
  this.dragger_.setLimits(limits);
};


/**
 * @inheritDoc
 */
ol.control.ZoomSlider.prototype.handleMapPostrender = function(mapEvent) {
  var res = mapEvent.frameState.view2DState.resolution;
  if (res !== this.currentResolution_) {
    this.currentResolution_ = res;
    this.positionThumbForResolution_(res);
  }
};


/**
 * @param {goog.events.BrowserEvent} browserEvent The browser event to handle.
 * @private
 */
ol.control.ZoomSlider.prototype.handleContainerClick_ = function(browserEvent) {
  // TODO implement proper resolution calculation according to browserEvent
};


/**
 * Positions the thumb inside its container according to the given resolution.
 *
 * @param {number} res The res.
 * @private
 */
ol.control.ZoomSlider.prototype.positionThumbForResolution_ = function(res) {
  var amount = this.amountForResolution_(res),
      dragger = this.dragger_,
      thumb = goog.dom.getFirstElementChild(this.element);

  if (this.direction_ == ol.control.ZoomSlider.direction.HORIZONTAL) {
    var left = dragger.limits.left + dragger.limits.width * amount;
    goog.style.setPosition(thumb, left);
  } else {
    var top = dragger.limits.top + dragger.limits.height * amount;
    goog.style.setPosition(thumb, dragger.limits.left, top);
  }
};


/**
 * Calculates the amount the thumb has been dragged to allow for calculation
 * of the corresponding resolution.
 *
 * @param {goog.fx.DragDropEvent} e The dragdropevent.
 * @return {number} The amount the thumb has been dragged.
 * @private
 */
ol.control.ZoomSlider.prototype.amountDragged_ = function(e) {
  var draggerLimits = this.dragger_.limits,
      amount = 0;
  if (this.direction_ === ol.control.ZoomSlider.direction.HORIZONTAL) {
    amount = (e.left - draggerLimits.left) / draggerLimits.width;
  } else {
    amount = (e.top - draggerLimits.top) / draggerLimits.height;
  }
  return amount;
};


/**
 * Calculates the corresponding resolution of the thumb by the amount it has
 * been dragged from its minimum.
 *
 * @param {number} amount The amount the thumb has been dragged.
 * @return {number} The corresponding resolution.
 * @private
 */
ol.control.ZoomSlider.prototype.resolutionForAmount_ = function(amount) {
  // FIXME do we really need this affine transform?
  amount = (goog.math.clamp(amount, 0, 1) - 1) * -1;
  var fn = this.getMap().getView().getView2D().getResolutionForValueFunction();
  return fn(amount);
};


/**
 * Determines an amount of dragging relative to this minimum position by the
 * given resolution.
 *
 * @param {number} res The resolution to get the amount for.
 * @return {number} The corresponding value (between 0 and 1).
 * @private
 */
ol.control.ZoomSlider.prototype.amountForResolution_ = function(res) {
  var fn = this.getMap().getView().getView2D().getValueForResolutionFunction();
  var value = fn(res);
  // FIXME do we really need this affine transform?
  return (value - 1) * -1;
};


/**
 * Handles the user caused changes of the slider thumb and adjusts the
 * resolution of our map accordingly. Will be called both while dragging and
 * when dragging ends.
 *
 * @param {goog.fx.DragDropEvent} e The dragdropevent.
 * @private
 */
ol.control.ZoomSlider.prototype.handleSliderChange_ = function(e) {
  var map = this.getMap();
  var view = map.getView().getView2D();
  var resolution;
  if (e.type === goog.fx.Dragger.EventType.DRAG) {
    var amountDragged = this.amountDragged_(e);
    resolution = this.resolutionForAmount_(amountDragged);
    if (resolution !== this.currentResolution_) {
      this.currentResolution_ = resolution;
      view.setResolution(resolution);
    }
  } else {
    goog.asserts.assert(goog.isDef(this.currentResolution_));
    map.addPreRenderFunction(ol.animation.zoom({
      resolution: this.currentResolution_,
      duration: ol.control.ZOOMSLIDER_ANIMATION_DURATION,
      easing: ol.easing.easeOut
    }));
    resolution = view.constrainResolution(this.currentResolution_);
    view.setResolution(resolution);
  }
};


/**
 * Actually enable draggable behaviour for the thumb of the zoomslider and bind
 * relvant event listeners.
 *
 * @param {Element} elem The element for the slider.
 * @return {goog.fx.Dragger} The actual goog.fx.Dragger instance.
 * @private
 */
ol.control.ZoomSlider.prototype.createDraggable_ = function(elem) {
  if (!goog.isNull(this.draggerListenerKeys_)) {
    goog.array.forEach(this.draggerListenerKeys_, goog.events.unlistenByKey);
    this.draggerListenerKeys_ = null;
  }
  var dragger = new goog.fx.Dragger(elem.childNodes[0]);
  this.draggerListenerKeys_ = [
    goog.events.listen(dragger, [
      goog.fx.Dragger.EventType.DRAG,
      goog.fx.Dragger.EventType.END
    ], this.handleSliderChange_, undefined, this)
  ];
  return dragger;
};
