/*
 * Copyright (C) 2021 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 {Diagnostic} from '@ionic-native/diagnostic/ngx';
import {
  Geolocation,
  GeolocationOptions,
  Geoposition,
  PositionError,
} from '@ionic-native/geolocation/ngx';
import {Point} from 'geojson';
import {geoJSON, LatLng} from 'leaflet';
import {Observable} from 'rxjs';
import {map} from 'rxjs/operators';

/**
 * Check if provided position object is a position error
 *
 * @param position A position object to be checked
 */
function isPositionError(
  position: Geoposition | PositionError,
): position is PositionError {
  return (
    typeof (position as PositionError).code !== 'undefined' &&
    typeof (position as PositionError).message !== 'undefined'
  );
}

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;
}

export interface LocationStatus {
  /**
   * Does the app have permission to use the location?
   */
  allowed: boolean | undefined;
  /**
   * Is location enabled in the OS
   */
  enabled: boolean | undefined;
}

@Injectable({
  providedIn: 'root',
})
export class PositionService {
  /**
   * Current location status
   */
  locationStatus: LocationStatus;

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

  constructor(
    private geolocation: Geolocation,
    private diagnostic: Diagnostic,
  ) {}

  /**
   * Gets current coordinates information of the device
   *
   * @param options Options which define which data should be provided (e.g. how accurate or how old)
   */
  async getCurrentLocation(options?: GeolocationOptions): Promise<MapPosition> {
    const geoPosition = await this.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 (typeof this.position === 'undefined') {
      return undefined;
    }

    return new LatLng(
      this.position.latitude,
      this.position.longitude,
    ).distanceTo(geoJSON(point).getBounds().getCenter());
  }

  /**
   * Provides the information about the availability of the location service (ONLY ON DEVICES / when cordova exists)
   */
  async getLocationStatus(): Promise<LocationStatus> {
    const enabled = await this.diagnostic.isLocationEnabled();
    const allowed = await this.diagnostic.isLocationAuthorized();

    return {enabled, allowed};
  }

  /**
   * Watches (continuously gets) 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?: GeolocationOptions): Observable<MapPosition> {
    return this.geolocation.watchPosition(options).pipe(
      map(geoPosition => {
        if (isPositionError(geoPosition)) {
          throw geoPosition;
        }

        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;
      }),
    );
  }
}
