import { m as macro } from '../../macros2.js';
import vtkCompositeCameraManipulator from './CompositeCameraManipulator.js';
import vtkCompositeMouseManipulator from './CompositeMouseManipulator.js';

const ANIMATION_REQUESTER = 'vtkMouseCameraTrackballFirstPersonManipulator';

// ----------------------------------------------------------------------------
// vtkMouseCameraTrackballFirstPersonManipulator methods
// ----------------------------------------------------------------------------

function vtkMouseCameraTrackballFirstPersonManipulator(publicAPI, model) {
  // Set our className
  model.classHierarchy.push('vtkMouseCameraTrackballFirstPersonManipulator');
  const internal = {
    interactor: null,
    renderer: null,
    previousPosition: null
  };

  //--------------------------------------------------------------------------

  publicAPI.onButtonDown = (interactor, renderer, position) => {
    internal.previousPosition = position;
    if (model.usePointerLock && !interactor.isPointerLocked()) {
      Object.assign(internal, {
        interactor,
        renderer
      });
      interactor.requestPointerLock();
      publicAPI.startPointerLockInteraction();
    }
  };

  //--------------------------------------------------------------------------

  publicAPI.startPointerLockInteraction = () => {
    const {
      interactor
    } = internal;

    // TODO: at some point, this should perhaps be done in
    // RenderWindowInteractor instead of here.
    // We need to hook into mousemove directly for two reasons:
    // 1. We need to keep receiving mouse move events after the mouse button
    //    is released. This is currently not possible with
    //    vtkInteractorStyleManipulator.
    // 2. Since the mouse is stationary in pointer lock mode, we need the
    //    event.movementX and event.movementY info, which are not currently
    //    passed via interactor.onMouseMove.
    document.addEventListener('mousemove', publicAPI.onPointerLockMove);
    let subscription = null;
    const endInteraction = () => {
      document.removeEventListener('mousemove', publicAPI.onPointerLockMove);
      subscription.unsubscribe();
    };
    subscription = interactor.onEndPointerLock(endInteraction);
  };

  //--------------------------------------------------------------------------

  publicAPI.onPointerLockMove = e => {
    const sensitivity = model.sensitivity;
    const yaw = -1 * e.movementX * sensitivity;
    const pitch = -1 * e.movementY * sensitivity;
    publicAPI.moveCamera(yaw, pitch);
  };

  //--------------------------------------------------------------------------

  publicAPI.onMouseMove = (interactor, renderer, position) => {
    // This is currently only being called for non pointer lock mode
    if (!position) {
      return;
    }
    const {
      previousPosition
    } = internal;
    const sensitivity = model.sensitivity;
    const yaw = (previousPosition.x - position.x) * sensitivity;
    const pitch = (position.y - previousPosition.y) * sensitivity;
    Object.assign(internal, {
      interactor,
      renderer
    });
    publicAPI.moveCamera(yaw, pitch);
    internal.previousPosition = position;
  };

  //--------------------------------------------------------------------------

  publicAPI.moveCamera = (yaw, pitch) => {
    const {
      renderer,
      interactor
    } = internal;
    const camera = renderer.getActiveCamera();

    // We need to pick a number of steps here that is not too few
    // (or the camera will be jittery) and not too many (or the
    // animations will take too long).
    // Perhaps this should be calculated?
    const numSteps = model.numAnimationSteps;
    const yawStep = yaw / numSteps;
    const pitchStep = pitch / numSteps;
    const now = performance.now().toString();
    const animationRequester = `${ANIMATION_REQUESTER}.${now}`;
    let curStep = 0;
    let animationSub = null;
    const performStep = () => {
      camera.yaw(yawStep);
      camera.pitch(pitchStep);
      camera.orthogonalizeViewUp();
      curStep += 1;
      if (curStep === numSteps) {
        animationSub.unsubscribe();
        renderer.resetCameraClippingRange();
        if (interactor.getLightFollowCamera()) {
          renderer.updateLightsGeometryToFollowCamera();
        }

        // This needs to be posted to the event loop so it isn't called
        // in the `handleAnimation` stack, or else the animation will
        // not be canceled.
        const cancelRequest = () => {
          internal.interactor.cancelAnimation(animationRequester);
        };
        setTimeout(cancelRequest, 0);
      }
    };
    interactor.requestAnimation(animationRequester);
    animationSub = interactor.onAnimation(() => performStep());
  };
}

// ----------------------------------------------------------------------------
// Object factory
// ----------------------------------------------------------------------------

const DEFAULT_VALUES = {
  numAnimationSteps: 5,
  sensitivity: 0.05,
  usePointerLock: true
};

// ----------------------------------------------------------------------------

function extend(publicAPI, model) {
  let initialValues = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
  Object.assign(model, DEFAULT_VALUES, initialValues);

  // Inheritance
  macro.obj(publicAPI, model);
  vtkCompositeCameraManipulator.extend(publicAPI, model, initialValues);
  vtkCompositeMouseManipulator.extend(publicAPI, model, initialValues);

  // Create get-set macros
  macro.setGet(publicAPI, model, ['numAnimationSteps', 'sensitivity', 'usePointerLock']);

  // Object specific methods
  vtkMouseCameraTrackballFirstPersonManipulator(publicAPI, model);
}

// ----------------------------------------------------------------------------

const newInstance = macro.newInstance(extend, 'vtkMouseCameraTrackballFirstPersonManipulator');

// ----------------------------------------------------------------------------

var vtkMouseCameraTrackballFirstPersonManipulator$1 = {
  newInstance,
  extend
};

export { vtkMouseCameraTrackballFirstPersonManipulator$1 as default, extend, newInstance };
