import { Injectable } from '@angular/core';
import { fromEvent, Subject, Subscription } from 'rxjs';
import { map } from 'rxjs/operators';

@Injectable({
  providedIn: 'root',
})
export class SwipeService {
  private backNavigationSubscription: Subscription;
  private swipedLeftEvent: Subject<void> = new Subject();
  private swipedRightEvent: Subject<void> = new Subject();
  private startData;

  private verticalThreshold = 60;
  private speedThreshold = 200;
  private lengthThreshold = 140;

  constructor() {
    this.initSwipeListener();
  }

  public initBackNavigation(backNavigationFn) {
    if (this.backNavigationSubscription) throw new Error('Back navigation is already initalized.');

    this.backNavigationSubscription = this.swipedRightEvent.subscribe(() => {
      if (this.swipedRightEvent.observers.length == 1) backNavigationFn();
      else
        console.warn(
          'SwipeService',
          'Skipped back navigation because there are other subscribed events to the swipe.'
        );
    });
  }

  public subscribeToSwipeLeft(customFn): Subscription {
    return this.swipedLeftEvent.subscribe(() => customFn());
  }

  public subscribeToSwipeRight(customFn): Subscription {
    return this.swipedRightEvent.subscribe(() => customFn());
  }

  private initSwipeListener(): void {
    let cancelSwipe = false;

    document.addEventListener(
      'scroll',
      (ev: any) => {
        if (ev.srcElement.clientWidth < ev.srcElement.scrollWidth) cancelSwipe = true;
      },
      { capture: true }
    );

    let touchStart$ = fromEvent(window, 'touchstart').pipe(
      map((ev: TouchEvent) => this.mapTouchEvent(ev))
    );

    let touchEnd$ = fromEvent(window, 'touchend').pipe(
      map((ev: TouchEvent) => this.mapTouchEvent(ev))
    );

    touchStart$.subscribe((startData) => {
      this.startData = startData;
      cancelSwipe = false;
    });
    touchEnd$.subscribe((endData) => {
      if (!cancelSwipe) this.analyzeMovement({ startData: this.startData, endData: endData });
    });
  }

  private analyzeMovement(movementData): void {
    if (
      movementData.endData.y >= movementData.startData.y - this.verticalThreshold && //calc if horizontal movement (with threshold)
      movementData.endData.y <= movementData.startData.y + this.verticalThreshold && //calc if horizontal movement (with threshold)
      movementData.endData.date - movementData.startData.date < this.speedThreshold && //calc time passed (slow movement is not a swipe)
      Math.abs(movementData.endData.x - movementData.startData.x) > this.lengthThreshold //calc if enough distance swiped (with threshold)
    ) {
      if (movementData.endData.x > movementData.startData.x)
        //calc movement direction (left or right)
        this.swipedRightEvent.next();
      else this.swipedLeftEvent.next();
    }
  }

  private mapTouchEvent(ev: TouchEvent) {
    return { x: ev.changedTouches[0].clientX, y: ev.changedTouches[0].clientY, date: Date.now() };
  }
}
