import {
  INPUT_START,
  INPUT_MOVE,
  INPUT_END,
  INPUT_CANCEL,
  INPUT_TYPE_TOUCH
} from '../inputjs/input-consts';
import Input from '../inputjs/input-constructor';
import toArray from '../utils/to-array';
import hasParent from '../utils/has-parent';
import uniqueArray from '../utils/unique-array';

const TOUCH_INPUT_MAP = {
  touchstart: INPUT_START,
  touchmove: INPUT_MOVE,
  touchend: INPUT_END,
  touchcancel: INPUT_CANCEL
};

const TOUCH_TARGET_EVENTS = 'touchstart touchmove touchend touchcancel';

/**
 * @private
 * Multi-user touch events input
 * @constructor
 * @extends Input
 */
export default class TouchInput extends Input {
  constructor() {
    TouchInput.prototype.evTarget = TOUCH_TARGET_EVENTS;
    super(...arguments);
    this.targetIds = {};
    // this.evTarget = TOUCH_TARGET_EVENTS;
  }
  handler(ev) {
    let type = TOUCH_INPUT_MAP[ev.type];
    let touches = getTouches.call(this, ev, type);
    if (!touches) {
      return;
    }

    this.callback(this.manager, type, {
      pointers: touches[0],
      changedPointers: touches[1],
      pointerType: INPUT_TYPE_TOUCH,
      srcEvent: ev
    });
  }
}

/**
 * @private
 * @this {TouchInput}
 * @param {Object} ev
 * @param {Number} type flag
 * @returns {undefined|Array} [all, changed]
 */
function getTouches(ev, type) {
  let allTouches = toArray(ev.touches);
  let { targetIds } = this;

  // when there is only one touch, the process can be simplified
  if (type & (INPUT_START | INPUT_MOVE) && allTouches.length === 1) {
    targetIds[allTouches[0].identifier] = true;
    return [allTouches, allTouches];
  }

  let i;
  let targetTouches;
  let changedTouches = toArray(ev.changedTouches);
  let changedTargetTouches = [];
  let { target } = this;

  // get target touches from touches
  targetTouches = allTouches.filter((touch) => {
    return hasParent(touch.target, target);
  });

  // collect touches
  if (type === INPUT_START) {
    i = 0;
    while (i < targetTouches.length) {
      targetIds[targetTouches[i].identifier] = true;
      i++;
    }
  }

  // filter changed touches to only contain touches that exist in the collected target ids
  i = 0;
  while (i < changedTouches.length) {
    if (targetIds[changedTouches[i].identifier]) {
      changedTargetTouches.push(changedTouches[i]);
    }

    // cleanup removed touches
    if (type & (INPUT_END | INPUT_CANCEL)) {
      delete targetIds[changedTouches[i].identifier];
    }
    i++;
  }

  if (!changedTargetTouches.length) {
    return;
  }

  return [
    // merge targetTouches with changedTargetTouches so it contains ALL touches, including 'end' and 'cancel'
    uniqueArray(targetTouches.concat(changedTargetTouches), 'identifier', true),
    changedTargetTouches
  ];
}
