import Map from '../../../../src/ol/Map.js';
import MapBrowserEventHandler from '../../../../src/ol/MapBrowserEventHandler.js';
import OlEvent from '../../../../src/ol/events/Event.js';
import {DEVICE_PIXEL_RATIO} from '../../../../src/ol/has.js';
import {listen} from '../../../../src/ol/events.js';

describe('ol/MapBrowserEventHandler', function () {
  describe('#emulateClick_', function () {
    let clock;
    let handler;
    let clickSpy;
    let singleclickSpy;
    let dblclickSpy;
    let target;

    beforeEach(function () {
      clock = sinon.useFakeTimers();
      target = document.createElement('div');
      handler = new MapBrowserEventHandler(
        new Map({
          target: target,
        })
      );

      clickSpy = sinon.spy();
      listen(handler, 'click', clickSpy);

      singleclickSpy = sinon.spy();
      listen(handler, 'singleclick', singleclickSpy);

      dblclickSpy = sinon.spy();
      listen(handler, 'dblclick', dblclickSpy);
    });

    afterEach(function () {
      clock.restore();
    });

    it('emulates click', function () {
      const event = new OlEvent();
      event.type = 'pointerdown';
      (event.target = target), (event.clientX = 0);
      event.clientY = 0;
      handler.emulateClick_(event);
      expect(clickSpy.called).to.be.ok();
    });

    it('emulates singleclick', function () {
      const event = new OlEvent();
      event.type = 'pointerdown';
      event.target = target;
      event.clientX = 0;
      event.clientY = 0;
      handler.emulateClick_(event);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      clock.tick(250);
      expect(singleclickSpy.calledOnce).to.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      handler.emulateClick_(event);
      expect(singleclickSpy.calledOnce).to.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();
    });

    it('emulates dblclick', function () {
      const event = new OlEvent();
      event.type = 'pointerdown';
      event.target = target;
      event.clientX = 0;
      event.clientY = 0;
      handler.emulateClick_(event);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      handler.emulateClick_(event);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.calledOnce).to.be.ok();

      clock.tick(250);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.calledOnce).to.be.ok();
    });
  });

  describe('#down_', function () {
    let handler;
    beforeEach(function () {
      handler = new MapBrowserEventHandler(new Map({}));
    });

    it('is null if no "down" type event has been handled', function () {
      expect(handler.down_).to.be(null);
    });

    it('is properly set after handlePointerDown_ has been called', function () {
      const event = new OlEvent('pointerdown');
      event.clientX = 42;
      event.clientY = 666;
      event.target = 'foo';
      handler.handlePointerDown_(event);
      expect(handler.down_.type).to.be('pointerdown');
      expect(handler.down_.clientX).to.be(42);
      expect(handler.down_.clientY).to.be(666);
      expect(handler.down_.target).to.be('foo');
    });
  });

  describe('#isMoving_', function () {
    let defaultHandler;
    let moveToleranceHandler;
    let pointerdownAt0;
    beforeEach(function () {
      defaultHandler = new MapBrowserEventHandler(new Map({}));
      moveToleranceHandler = new MapBrowserEventHandler(new Map({}), 8);
      pointerdownAt0 = new OlEvent();
      pointerdownAt0.type = 'pointerdown';
      pointerdownAt0.clientX = 0;
      pointerdownAt0.clientY = 0;
      defaultHandler.handlePointerDown_(pointerdownAt0);
      moveToleranceHandler.handlePointerDown_(pointerdownAt0);
    });

    it('is not moving if distance is 0', function () {
      pointerdownAt0 = new OlEvent();
      pointerdownAt0.type = 'pointerdown';
      pointerdownAt0.clientX = 0;
      pointerdownAt0.clientY = 0;
      expect(defaultHandler.isMoving_(pointerdownAt0)).to.be(false);
    });

    it('is moving if distance is 2', function () {
      const pointerdownAt2 = new OlEvent();
      pointerdownAt2.type = 'pointerdown';
      pointerdownAt2.clientX = DEVICE_PIXEL_RATIO + 1;
      pointerdownAt2.clientY = DEVICE_PIXEL_RATIO + 1;
      expect(defaultHandler.isMoving_(pointerdownAt2)).to.be(true);
    });

    it('is moving with negative distance', function () {
      const pointerdownAt2 = new OlEvent();
      pointerdownAt2.type = 'pointerdown';
      pointerdownAt2.clientX = -(DEVICE_PIXEL_RATIO + 1);
      pointerdownAt2.clientY = -(DEVICE_PIXEL_RATIO + 1);
      expect(defaultHandler.isMoving_(pointerdownAt2)).to.be(true);
    });

    it('is not moving if distance is less than move tolerance', function () {
      const pointerdownAt2 = new OlEvent();
      pointerdownAt2.type = 'pointerdown';
      pointerdownAt2.clientX = DEVICE_PIXEL_RATIO + 1;
      pointerdownAt2.clientY = DEVICE_PIXEL_RATIO + 1;
      expect(moveToleranceHandler.isMoving_(pointerdownAt2)).to.be(false);
    });

    it('is moving if distance is greater than move tolerance', function () {
      const pointerdownAt9 = new OlEvent();
      pointerdownAt9.type = 'pointerdown';
      pointerdownAt9.clientX = DEVICE_PIXEL_RATIO * 8 + 1;
      pointerdownAt9.clientY = DEVICE_PIXEL_RATIO * 8 + 1;
      expect(moveToleranceHandler.isMoving_(pointerdownAt9)).to.be(true);
    });

    it('is moving when moving back close to the down pixel', function () {
      const pointermoveAt9 = new OlEvent();
      pointermoveAt9.type = 'pointermove';
      pointermoveAt9.clientX = DEVICE_PIXEL_RATIO * 8 + 1;
      pointermoveAt9.clientY = DEVICE_PIXEL_RATIO * 8 + 1;
      moveToleranceHandler.handlePointerMove_(pointermoveAt9);
      expect(moveToleranceHandler.isMoving_(pointermoveAt9)).to.be(true);
      const pointermoveAt2 = new OlEvent();
      pointermoveAt2.type = 'pointermove';
      pointermoveAt2.clientX = DEVICE_PIXEL_RATIO + 1;
      pointermoveAt2.clientY = DEVICE_PIXEL_RATIO + 1;
      moveToleranceHandler.handlePointerMove_(pointermoveAt2);
      expect(moveToleranceHandler.isMoving_(pointermoveAt2)).to.be(true);
    });
  });

  describe('handleTouchMove_', function () {
    let handler;
    beforeEach(function () {
      handler = new MapBrowserEventHandler(new Map({}));
    });
    it('prevents default on touchmove event', function () {
      handler.originalPointerMoveEvent_ = {
        defaultPrevented: true,
      };
      const event = {
        preventDefault: sinon.spy(),
      };
      handler.handleTouchMove_(event);
      expect(event.preventDefault.callCount).to.be(1);
    });
  });

  describe('dblclick', function () {
    let clock;
    let handler;
    let clickSpy;
    let singleclickSpy;
    let dblclickSpy;
    let target;
    let element, down1, down2, up1, up2;

    beforeEach(function () {
      clock = sinon.useFakeTimers();
      target = document.createElement('div');
      handler = new MapBrowserEventHandler(
        new Map({
          target: target,
        })
      );

      element = handler.element_;
      down1 = new Event('pointerdown', {target: element});
      down1.clientX = 0;
      down1.clientY = 0;
      down1.button = 0;
      down1.pointerId = 1;
      down2 = new Event('pointerdown', {target: element});
      down2.clientX = 0;
      down2.clientY = 0;
      down2.button = 0;
      down2.pointerId = 2;
      up1 = new Event('pointerup', {target: element});
      up1.clientX = 0;
      up1.clientY = 0;
      up1.button = 0;
      up1.pointerId = 1;
      up2 = new Event('pointerup', {target: element});
      up2.clientX = 0;
      up2.clientY = 0;
      up2.button = 0;
      up2.pointerId = 2;

      clickSpy = sinon.spy();
      listen(handler, 'click', clickSpy);

      singleclickSpy = sinon.spy();
      listen(handler, 'singleclick', singleclickSpy);

      dblclickSpy = sinon.spy();
      listen(handler, 'dblclick', dblclickSpy);
    });

    afterEach(function () {
      clock.restore();
    });

    it('emulates dblclick', function () {
      element.dispatchEvent(down1);
      document.dispatchEvent(up1);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      element.dispatchEvent(down2);
      document.dispatchEvent(up2);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.be.ok();

      clock.tick(250);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.be.ok();
    });

    it('does not emulate dblclick and singleclick when multiple pointers are active', function () {
      element.dispatchEvent(down1);
      element.dispatchEvent(down2);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      document.dispatchEvent(up1);
      document.dispatchEvent(up2);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();

      clock.tick(250);
      expect(singleclickSpy.called).to.not.be.ok();
      expect(dblclickSpy.called).to.not.be.ok();
    });
  });
});
