/*
 * Copyright (C) 2022 StApps
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the Free
 * Software Foundation, version 3.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program. If not, see <https://www.gnu.org/licenses/>.
 */
import {Injectable} from '@angular/core';
import {Point} from 'geojson';
import {Observable} from 'rxjs';
import {Geolocation, Position} from '@capacitor/geolocation';

export interface Coordinates {
  /**
   * Geographic latitude from a device
   */
  latitude: number;
  /**
   * Geographic longitude from a device
   */
  longitude: number;
}

export interface MapPosition extends Coordinates {
  /**
   * Where is the device pointed
   */
  heading?: number;
}

@Injectable({
  providedIn: 'root',
})
export class PositionService {
  geoLocation = new Observable<Position>(subscriber => {
    const watcherID = Geolocation.checkPermissions().then(permissions => {
      if (permissions.location === 'granted') {
        return Geolocation.watchPosition({}, (position, error) => {
          if (error) {
            subscriber.error(position);
          } else if (position) {
            subscriber.next(position);
          }
        });
      }
      return;
    });
    return {
      unsubscribe() {
        watcherID.then(id => {
          if (id) {
            Geolocation.clearWatch({id});
          }
        });
      },
    };
  });

  /**
   * Current position
   */
  position?: MapPosition;

  /**
   * Gets current coordinates information of the device
   * @param options Options which define which data should be provided (e.g. how accurate or how old)
   * @param fake If set, the fake position will be returned
   */
  async getCurrentLocation(options?: PositionOptions, fake?: Position): Promise<MapPosition> {
    const geoPosition = fake ?? (await Geolocation.getCurrentPosition(options));

    this.position = {
      heading:
        Number.isNaN(geoPosition.coords.heading) || geoPosition.coords.heading == undefined
          ? undefined
          : geoPosition.coords.heading,
      latitude: geoPosition.coords.latitude,
      longitude: geoPosition.coords.longitude,
    };

    return this.position;
  }

  /**
   * Provides distance from users position
   * @param point Point to which distance should be calculated
   */
  getDistance(point: Point): number | undefined {
    if (this.position === undefined) {
      return undefined;
    }

    return Math.hypot(
      this.position.longitude - point.coordinates[0],
      this.position.latitude - point.coordinates[1],
    );
  }

  /**
   * Watches (continuously gets) the current coordinates information of the device
   * @param options Options which define which data should be provided (e.g., how accurate or how old)
   */
  watchCurrentLocation(options: PositionOptions = {}): Observable<MapPosition> {
    return new Observable(subscriber => {
      const watcherID = Geolocation.watchPosition(options, (position, error) => {
        if (error) {
          subscriber.error(position);
        } else {
          this.position = {
            // TODO use native compass heading instead
            // waiting for https://github.com/ionic-team/capacitor-plugins/issues/1192
            heading: undefined,
            latitude: position?.coords.latitude ?? 0,
            longitude: position?.coords.longitude ?? 0, // TODO: handle null position
          };

          subscriber.next(this.position);
        }
      });
      return {
        unsubscribe() {
          watcherID.then(id => {
            void Geolocation.clearWatch({id});
          });
        },
      };
    });
  }
}
