var Hammer = require('../../module/hammer');
var util = require('../../util');
var Component = require('./Component');
var moment = require('../../module/moment');
var locales = require('../locales');

/**
 * A custom time bar
 * @param {{range: Range, dom: Object}} body
 * @param {Object} [options]        Available parameters:
 *                                  {number | string} id
 *                                  {string} locales
 *                                  {string} locale
 * @constructor CustomTime
 * @extends Component
 */

function CustomTime (body, options) {
  this.body = body;

  // default options
  this.defaultOptions = {
    moment: moment,
    locales: locales,
    locale: 'en',
    id: undefined,
    title: undefined
  };
  this.options = util.extend({}, this.defaultOptions);

  if (options && options.time) {
    this.customTime = options.time;
  } else {
    this.customTime = new Date();
  }

  this.eventParams = {}; // stores state parameters while dragging the bar

  this.setOptions(options);

  // create the DOM
  this._create();
}

CustomTime.prototype = new Component();

/**
 * Set options for the component. Options will be merged in current options.
 * @param {Object} options  Available parameters:
 *                                  {number | string} id
 *                                  {string} locales
 *                                  {string} locale
 */
CustomTime.prototype.setOptions = function(options) {
  if (options) {
    // copy all options that we know
    util.selectiveExtend(['moment', 'locale', 'locales', 'id'], this.options, options);
  }
};

/**
 * Create the DOM for the custom time
 * @private
 */
CustomTime.prototype._create = function() {
  var bar = document.createElement('div');
  bar['custom-time'] = this;
  bar.className = 'vis-custom-time ' + (this.options.id || '');
  bar.style.position = 'absolute';
  bar.style.top = '0px';
  bar.style.height = '100%';
  this.bar = bar;

  var drag = document.createElement('div');
  drag.style.position = 'relative';
  drag.style.top = '0px';
  drag.style.left = '-10px';
  drag.style.height = '100%';
  drag.style.width = '20px';
  bar.appendChild(drag);

  // attach event listeners
  this.hammer = new Hammer(drag);
  this.hammer.on('panstart', this._onDragStart.bind(this));
  this.hammer.on('panmove',  this._onDrag.bind(this));
  this.hammer.on('panend',   this._onDragEnd.bind(this));
  this.hammer.get('pan').set({threshold:5, direction:30}); // 30 is ALL_DIRECTIONS in hammer.
};

/**
 * Destroy the CustomTime bar
 */
CustomTime.prototype.destroy = function () {
  this.hide();

  this.hammer.destroy();
  this.hammer = null;

  this.body = null;
};

/**
 * Repaint the component
 * @return {boolean} Returns true if the component is resized
 */
CustomTime.prototype.redraw = function () {
  var parent = this.body.dom.backgroundVertical;
  if (this.bar.parentNode != parent) {
    // attach to the dom
    if (this.bar.parentNode) {
      this.bar.parentNode.removeChild(this.bar);
    }
    parent.appendChild(this.bar);
  }

  var x = this.body.util.toScreen(this.customTime);

  var locale = this.options.locales[this.options.locale];
  if (!locale) {
    if (!this.warned) {
      console.log('WARNING: options.locales[\'' + this.options.locale + '\'] not found. See http://visjs.org/docs/timeline.html#Localization');
      this.warned = true;
    }
    locale = this.options.locales['en']; // fall back on english when not available
  }

  var title = this.options.title;
  // To hide the title completely use empty string ''.
  if (title === undefined) {
    title = locale.time + ': ' + this.options.moment(this.customTime).format('dddd, MMMM Do YYYY, H:mm:ss');
    title = title.charAt(0).toUpperCase() + title.substring(1);
  }

  this.bar.style.left = x + 'px';
  this.bar.title = title;

  return false;
};

/**
 * Remove the CustomTime from the DOM
 */
CustomTime.prototype.hide = function () {
  // remove the line from the DOM
  if (this.bar.parentNode) {
    this.bar.parentNode.removeChild(this.bar);
  }
};

/**
 * Set custom time.
 * @param {Date | number | string} time
 */
CustomTime.prototype.setCustomTime = function(time) {
  this.customTime = util.convert(time, 'Date');
  this.redraw();
};

/**
 * Retrieve the current custom time.
 * @return {Date} customTime
 */
CustomTime.prototype.getCustomTime = function() {
  return new Date(this.customTime.valueOf());
};

/**
  * Set custom title.
  * @param {Date | number | string} title
  */
CustomTime.prototype.setCustomTitle = function(title) {
  this.options.title = title;
};

/**
 * Start moving horizontally
 * @param {Event} event
 * @private
 */
CustomTime.prototype._onDragStart = function(event) {
  this.eventParams.dragging = true;
  this.eventParams.customTime = this.customTime;

  event.stopPropagation();
};

/**
 * Perform moving operating.
 * @param {Event} event
 * @private
 */
CustomTime.prototype._onDrag = function (event) {
  if (!this.eventParams.dragging) return;

  var x = this.body.util.toScreen(this.eventParams.customTime) + event.deltaX;
  var time = this.body.util.toTime(x);

  this.setCustomTime(time);

  // fire a timechange event
  this.body.emitter.emit('timechange', {
    id: this.options.id,
    time: new Date(this.customTime.valueOf())
  });

  event.stopPropagation();
};

/**
 * Stop moving operating.
 * @param {Event} event
 * @private
 */
CustomTime.prototype._onDragEnd = function (event) {
  if (!this.eventParams.dragging) return;

  // fire a timechanged event
  this.body.emitter.emit('timechanged', {
    id: this.options.id,
    time: new Date(this.customTime.valueOf())
  });

  event.stopPropagation();
};

/**
 * Find a custom time from an event target:
 * searches for the attribute 'custom-time' in the event target's element tree
 * @param {Event} event
 * @return {CustomTime | null} customTime
 */
CustomTime.customTimeFromTarget = function(event) {
  var target = event.target;
  while (target) {
    if (target.hasOwnProperty('custom-time')) {
      return target['custom-time'];
    }
    target = target.parentNode;
  }

  return null;
};

module.exports = CustomTime;
