/*
 * Copyright (C) 2019-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 {ElementRef, Injectable} from '@angular/core';
import {
  SCBuilding,
  SCSearchFilter,
  SCSearchQuery,
  SCSearchResponse,
  SCThingType,
  SCUuid,
} from '@openstapps/core';
import {Point, Polygon} from 'geojson';
import {divIcon, geoJSON, icon, LatLng, Map, marker, Marker} from 'leaflet';
import {DataProvider} from '../data/data.provider';
import {MapPosition, PositionService} from './position.service';
import {hasValidLocation} from '../data/types/place/place-types';

/**
 * Provides methods for presenting the map
 */
@Injectable({
  providedIn: 'root',
})
export class MapProvider {
  /**
   * Area to show when the map is initialized (shown for the first time)
   */
  defaultPolygon: Polygon;

  /**
   * Provide a point marker for a leaflet map
   *
   * @param point Point to get marker for
   */
  static getPointMarker(point: Point) {
    return marker(geoJSON(point).getBounds().getCenter(), {
      icon: icon({
        iconAnchor: [13, 41],
        iconSize: [25, 41],
        iconUrl: '../assets/marker-icon.png',
        shadowUrl: '../assets/marker-shadow.png',
      }),
    });
  }

  /**
   * Provide a position marker for a leaflet map
   *
   * @param position Current position
   * @param className CSS class name
   * @param iconSize Size of the position icon
   */
  static getPositionMarker(
    position: MapPosition,
    className: string,
    iconSize: number,
  ) {
    return new Marker(new LatLng(position.latitude, position.longitude), {
      icon: divIcon({
        className: className,
        html:
          typeof position.heading !== 'undefined'
            ? `<ion-icon name="navigate-straight"
                style="transform-origin: center; transform: rotate(${position.heading}deg);">
            </ion-icon>`
            : '<ion-icon name="locate"></ion-icon>',
        iconSize: [iconSize, iconSize],
      }),
      zIndexOffset: 1000,
    });
  }

  /**
   * Fixes the issue of missing tiles when map renders before its container element
   *
   * @param map The initialized map
   * @param element The element containing the map
   * @param interval Interval to clear when map's appearance is corrected
   */
  static invalidateWhenRendered = (
    map: Map,
    element: ElementRef,
    interval: number,
  ) => {
    if (element.nativeElement.offsetWidth === 0) {
      return;
    }

    // map's container is ready
    map.invalidateSize();
    // stop repeating when it's rendered and invalidateSize done
    clearInterval(interval);
  };

  constructor(
    private dataProvider: DataProvider,
    private positionService: PositionService,
  ) {}

  /**
   * Provide the specific place by its UID
   *
   * @param uid UUID of the place to look for
   */
  async searchPlace(uid: SCUuid): Promise<SCSearchResponse> {
    const uidFilter: SCSearchFilter = {
      arguments: {
        field: 'uid',
        value: uid,
      },
      type: 'value',
    };

    return this.dataProvider.search({filter: uidFilter});
  }

  /**
   * Provide places (buildings and canteens)    const result = await this.dataProvider.search(query);
   
   *
   * @param contextFilter Additional contextual filter (e.g. from the context menu)
   * @param queryText Query (text) of the search query
   */
  async searchPlaces(
    contextFilter?: SCSearchFilter,
    queryText?: string,
  ): Promise<SCSearchResponse> {
    const buildingFilter: SCSearchFilter = {
      arguments: {
        field: 'type',
        value: SCThingType.Building,
      },
      type: 'value',
    };

    const mensaFilter: SCSearchFilter = {
      arguments: {
        filters: [
          {
            arguments: {
              field: 'categories',
              value: 'canteen',
            },
            type: 'value',
          },
          {
            arguments: {
              field: 'categories',
              value: 'student canteen',
            },
            type: 'value',
          },
          {
            arguments: {
              field: 'categories',
              value: 'cafe',
            },
            type: 'value',
          },
          {
            arguments: {
              field: 'categories',
              value: 'restaurant',
            },
            type: 'value',
          },
        ],
        operation: 'or',
      },
      type: 'boolean',
    };

    // initial filter for the places
    const baseFilter: SCSearchFilter = {
      arguments: {
        operation: 'or',
        filters: [buildingFilter, mensaFilter],
      },
      type: 'boolean',
    };

    let filter = baseFilter;

    if (typeof contextFilter !== 'undefined') {
      filter = {
        arguments: {
          operation: 'and',
          filters: [baseFilter, contextFilter],
        },
        type: 'boolean',
      };
    }

    const query: SCSearchQuery = {
      filter,
    };

    if (queryText && queryText.length > 0) {
      query.query = queryText;
    }

    if (this.positionService.position) {
      query.sort = [
        {
          type: 'distance',
          order: 'asc',
          arguments: {
            field: 'geo.point.coordinates',
            position: [
              this.positionService.position.longitude,
              this.positionService.position.latitude,
            ],
          },
        },
      ];
    }

    const result = await this.dataProvider.search(query);

    result.data = result.data.filter(place =>
      hasValidLocation(place as SCBuilding),
    );

    return result;
  }
}
