/**
 * Custom HammerJS configuration forked from Angular Material. With Angular v9,
 * Angular Material dropped HammerJS as a dependency. This configuration was added
 * automatically to this application because ng-update detected that this application
 * directly used custom HammerJS gestures defined by Angular Material.
 *
 * Read more in the dedicated guide: https://git.io/ng-material-v9-hammer-migration
 */

import {Injectable, Inject, Optional, Type} from '@angular/core';
import {HammerGestureConfig} from '@angular/platform-browser';
import {MAT_HAMMER_OPTIONS} from '@angular/material/core';

/**
 * Noop hammer instance that is used when an instance is requested, but
 * Hammer has not been loaded on the page yet.
 */
const noopHammerInstance = {
  on: () => {},
  off: () => {},
};

/**
 * Gesture config that provides custom Hammer gestures on top of the default Hammer
 * gestures. These gestures will be available as events in component templates.
 */
@Injectable()
export class GestureConfig extends HammerGestureConfig {
  /** List of event names to add to the Hammer gesture plugin list */
  events = [
    'longpress',
    'slide',
    'slidestart',
    'slideend',
    'slideright',
    'slideleft'
  ];

  constructor(@Optional() @Inject(MAT_HAMMER_OPTIONS) private hammerOptions?: any) {
    super();
  }

  /**
   * Builds Hammer instance manually to add custom recognizers that match the
   * Material Design specification. Gesture names originate from the Material Design
   * gestures: https://material.io/design/#gestures-touch-mechanics
   *
   * More information on default recognizers can be found in the Hammer docs:
   *   http://hammerjs.github.io/recognizer-pan/
   *   http://hammerjs.github.io/recognizer-press/
   * @param element Element to which to assign the new HammerJS gestures.
   * @returns Newly-created HammerJS instance.
   */
  buildHammer(element: HTMLElement): any {
    const hammer: any = typeof window !== 'undefined' ? (window as any).Hammer : null;

    if (!hammer) {
      return noopHammerInstance;
    }

    const mc = new hammer(element, this.hammerOptions || undefined);

    // Default Hammer Recognizers.
    const pan = new hammer.Pan();
    const swipe = new hammer.Swipe();
    const press = new hammer.Press();

    // Notice that a HammerJS recognizer can only depend on one other recognizer once.
    // Otherwise the previous `recognizeWith` will be dropped.
    const slide = this._createRecognizer(pan, {event: 'slide', threshold: 0}, swipe);
    const longpress = this._createRecognizer(press, {event: 'longpress', time: 500});

    // Overwrite the default `pan` event to use the swipe event.
    pan.recognizeWith(swipe);

    // Since the slide event threshold is set to zero, the slide recognizer can fire and
    // accidentally reset the longpress recognizer. In order to make sure that the two
    // recognizers can run simultaneously but don't affect each other, we allow the slide
    // recognizer to recognize while a longpress is being processed.
    // See: https://github.com/hammerjs/hammer.js/blob/master/src/manager.js#L123-L124
    longpress.recognizeWith(slide);

    // Add customized gestures to Hammer manager
    mc.add([swipe, press, pan, slide, longpress]);

    return mc;
  }

  /** Creates a new recognizer, without affecting the default recognizers of HammerJS */
  private _createRecognizer(base: object, options: any, ...inheritances: object[]) {
    const recognizer = new (base.constructor as Type<any>)(options);
    inheritances.push(base);
    inheritances.forEach(item => recognizer.recognizeWith(item));
    return recognizer;
  }
}
