'use strict';

var angular = require('angular'),
  _ = require('jsUtil');
require('../modules');

function Point(x, y) {
  this.x = x;
  this.y = y;
}

Point.prototype = {
  move: function (deltaX, deltaY) {
    return new Point(this.x + deltaX, this.y + deltaY);
  },
  relative: function (x, y) {
    return new Point(x - this.x, y - this.y);
  }
};

function SinoModelListHitTests(items) {
  this.items = items;

  this.xNumber = 4;

  this.gapWidth = 0.3;
  this.gapHeight = 0.3;

  this.xItemWidth = document.body.clientWidth / 4;
  this.yItemHeight = 60;

  this.origin = new Point(0, 80);
}

SinoModelListHitTests.prototype = {
  setItems: function (items) {
    this.items = items;
  },

  toIndex: function (x, y) {
    return y * this.xNumber + x;
  },

  hitTestOnStartMoving: function (x, y) {
    var point = this.origin.relative(x, y);
    var xIndex = Math.floor(point.x / this.xItemWidth);
    var yIndex = Math.floor(point.y / this.yItemHeight);
    this.index = this.toIndex(xIndex, yIndex);
    this.point = new Point(xIndex * this.xItemWidth, yIndex * this.yItemHeight);

    return this.index;
  },

  hitRegion: function (index, gap) {
    if (index - Math.floor(index) <= gap) {
      index = Math.floor(index);
    } else if (Math.ceil(index) - index <= gap) {
      index = Math.ceil(index);
    } else {
      index = -1;
    }
    return index;
  },

  hitTestOnMoving: function (deltaX, deltaY) {
    var point = this.point.move(deltaX, deltaY),
      xIndex = point.x / this.xItemWidth,
      yIndex = point.y / this.yItemHeight,
      index = -1,
      originIndex = this.index,
      change = false;

    xIndex = this.hitRegion(xIndex, this.gapWidth);
    yIndex = this.hitRegion(yIndex, this.gapHeight);

    if (xIndex >= 0 && yIndex >= 0) {
      index = this.toIndex(xIndex, yIndex);
      if (index >= this.items.length) {
        return null;
      }
      change = index !== originIndex;
      this.index = index;
      return change? { from: originIndex, to: index } : null;
    }

    return null;
  }
};

angular
  .module('infoDisplay.directives')
  .directive('sinoModelDrag', sinoModelDrag)
  .controller('sinoModelDragCtrl', sinoModelDragCtrl);

/** @ngInject */
function sinoModelDrag() {

  return {
    restrict: 'AC',
    controller: 'sinoModelDragCtrl',
    require: 'sinoModelDrag'
  }
}

/** @ngInject */
function sinoModelDragCtrl($scope, $window, $timeout,
  $ionicGesture, $element, $attrs, $ionicScrollDelegate,
  Timer) {
  var self = this, floatElement, element,
    ionScroll = $ionicScrollDelegate.$getByHandle('sinoModelListScroll'),
    inDragging = false;
  self.listHitTests = new SinoModelListHitTests($scope.selectedItems);

  var dragStartGesture = $ionicGesture.on('dragstart', onDragStart, $element);
  var dragGesture = $ionicGesture.on('drag', onDrag, $element);
  var dragEndGesture = $ionicGesture.on('dragend', onDragEnd, $element);

  function getRelatedTouchPoint(touch) {
    if (ionScroll) {
      var scrollTop = ionScroll.getScrollPosition().top;
      return new Point(touch.pageX, touch.pageY + scrollTop);
    } else {
      return new Point(touch.pageX, touch.pageY);
    }
  }

  function onDragStart(event) {
    var touch = event.gesture.touches[0],
      point = getRelatedTouchPoint(touch);
    self.listHitTests.hitTestOnStartMoving(point.x, point.y);

    if (self.listHitTests.index >= $scope.selectedItems.length) {
      return false;
    }
    ionScroll && ionScroll.freezeScroll(true);
    event.preventDefault();
    inDragging = true;

    element = $element[0].querySelector('.sino-model-button-bar:nth-child(' + (self.listHitTests.index + 1) + ')');
    element = angular.element(element);
    floatElement = element.clone();
    $element.append(floatElement);

    floatElement.css({
      zIndex: '100',
      position: 'absolute',
      top: element[0].offsetTop + 'px',
      left: element[0].offsetLeft + 'px'
    });
    element.addClass('dragging');
  }

  function onDrag(event) {
    if (!inDragging) {
      return;
    }
    var deltaX = event.gesture.deltaX;
    var deltaY = event.gesture.deltaY;
    var index = self.listHitTests.hitTestOnMoving(deltaX, deltaY);
    floatElement.css({
      transform: 'translate3d(' + deltaX + 'px,' + deltaY + 'px, 0)'
    });
    if (index) {
      orderIndex(index.from, index.to);
    }
  }

  function onDragEnd() {
    ionScroll && ionScroll.freezeScroll(false);
    if (inDragging) {
      inDragging = false;
      floatElement.remove();
      element.removeClass('dragging');
    }
  }

  $scope.$on('$destroy', function () {
    $ionicGesture.off(dragStartGesture,'dragstart', onDragStart);
    $ionicGesture.off(dragGesture, 'drag', onDrag);
    $ionicGesture.off(dragEndGesture, 'dragend', onDragEnd);
  });

  var orderChanges = [],
    updatingIndex = false,
    timerId;

  function updateIndex() {
    if (orderChanges && !updatingIndex) {
      updatingIndex = true;
      var changes = orderChanges,
        change;
      orderChanges = [];
      for (var i = 0, len = changes.length; i < len; i++) {
        change = changes[i];
        exchangeIndex(change.from, change.to);
      }

      $scope.refreshItems($scope.selectedItems, $scope.notSelectedItems);
      updatingIndex = false;
    }
  }

  function exchangeIndex(from, to) {
    var items = $scope.selectedItems,
      fromItem = items[from],
      dataLength = items.length;
    if (from >= 0 && from < dataLength &&
      to >= 0 && to < dataLength && fromItem) {
      items.splice(from, 1);
      items.splice(to, 0, fromItem);
    }
  }

  function updateIndexOnTimer() {
    timerId = Timer.interval(updateIndex, 16.67);
    $scope.$on('$destroy', function() {
      Timer.clear(timerId);
    });
  }

  updateIndexOnTimer();

  function orderIndex(from, to) {
    orderChanges.push({from: from, to: to});
  }
}
