/* Copyright (C) Click & Push Accessibility, Inc - All Rights Reserved
 * Unauthorized copying of this file, via any medium is strictly prohibited
 * Proprietary and confidential
 * Written and maintained by the Click & Push Development team 
 * <dev@clicknpush.ca>, January 2022
 */

import { createIconSetFromIcoMoon, FontAwesome } from "@expo/vector-icons";
import { RouteProp, useNavigationState } from "@react-navigation/native";
import { booleanPointInPolygon, circle } from '@turf/turf';
import * as Notifications from 'expo-notifications';
import { observer } from "mobx-react";
import React, { useEffect, useState } from "react";
import { Button, ActivityIndicator, Alert, Image, ImageBackground, Keyboard, Modal, Text, TouchableOpacity, TouchableWithoutFeedback, View, StyleProp, TextStyle } from "react-native";
import MapView, { LatLng, MapEvent, Marker, Polygon, Polyline } from "react-native-maps";
import { openSettings } from "react-native-permissions";
import Spokestack, { activate } from 'react-native-spokestack';
import { useAuth } from "../../../data/Auth/AuthContext";
import { Landmark } from '../../../data/landmarks';
import { NotifType } from "../../../data/notifications";
import { usePermissions } from "../../../data/PermissionsContext";
import { MainTabsNavigationProp, MainTabsParamList } from "../../../navigation/MainTabsNavigator";
import { MapStackNavigationProp, MapStackParamList } from "../../../navigation/MapNavigator";
import { colors, lmTypes } from "../../../utils/GlobalUtils";
import Badge from "../../Badge";
import { IconButton } from "../../Buttons";
import NearbyLandmarksPanel from "../Panels/NearbyLandmarksPanel";
import { VoicePanel } from "../Panels/VoicePanel";
import mapStyles from "./Map.styles";
import { useOutdoorMapState } from "./useMapState";

/**
 * An interface representing the user location retrieved from [expo-location]{@link https://docs.expo.dev/versions/latest/sdk/location/}.
 * @category Map
 */
export interface UserLocation {
    latitude: number;
    longitude: number;
    heading?: number;
}

export type MapStackRouteProp = RouteProp<MapStackParamList, 'Outdoor'>;

export type AuthTabsMapRouteProp = RouteProp<MainTabsParamList, 'Map'>;

/**
 * The screen component containing the Map and all related functionality. Uses [react-native-maps]{@link https://github.com/react-native-maps/react-native-maps}
 * @category Map
 * @component
 */

interface OutdoorMapProps {
    mapNavigation: MapStackNavigationProp,
    authNavigation: MainTabsNavigationProp,
    authNavIndex: number,
    route: AuthTabsMapRouteProp,
    focusLandmark: (landmark: Landmark, e: MapEvent<{action: "marker-press"; id: string;}>) => void,
    setSelectedLandmarkId: (id: string) => void,
    selectedLandmarkId: string,
    newLandmark: Landmark
    setNewLandmark: (landmark: Landmark) => void
    toggleLmDetails: (state: boolean) => void,
    toggleLmAdd: (state: boolean) => void,
    landmarks: Landmark[]
    applyFilters: (landmarks: Landmark[]) => Landmark[]
    promptAddLandmark: (longitude: number, latitude: number) => void
    setMarkerWindowPosition: (point: {x: number, y: number}) => void
}

const OutdoorMap: React.FC<OutdoorMapProps> = (props) => {
    const {locationPermissionsGranted, checkLocationPermissions, voicePermissionsGranted, checkVoicePermissions} = usePermissions();
    const {setAlert} = useAuth()
    const mapState = useOutdoorMapState()

    const mapNavIndex = useNavigationState(state => state)

    const [coordinates] = useState([
        { latitude: 53.527086340019856, longitude: -113.52358410971608, }, // Cameron library
        { latitude: 53.52516024715472, longitude: -113.52154139033108, }, // University station
    ]);
    const [size, setSize] = useState(0.052344);

    /**
     * If the ReactNavigation route prop changes, check if it contains incoming selected landmarks, display them if there are. This will be triggered by incoming notifcations 
     * (See the AuthorizedNavigator page for the useEffect that will trigger this)
     */
    useEffect(() => {
        if (props.route?.params?.selectedLandmark) {
            props.setSelectedLandmarkId(props.route?.params?.selectedLandmark)
        }
        if (props.route?.params?.selectedLandmarks) {
            mapState.toggleNearbyLmPanel(true)
        }
    }, [props.route])

    useEffect(() => {
        const toMap = mapNavIndex.index == 0 && props.authNavIndex == 0

        if (toMap) mapState.setLoading(true)

        setTimeout(() => {
            mapState.setLoading(false)
        }, 500);

    }, [mapNavIndex, props.authNavIndex])

    /**
     * Toggle the lm details panel when a new selected landmark is detected (triggered by pressing on a map marker, or from the list of nearby landmarks)
     */
    useEffect(() => {
        console.log("[LandmarkDetails]: Landmark selected - " + props.selectedLandmarkId)
        if (props.selectedLandmarkId) {
            const landmark = props.landmarks.find(lm => lm.id == props.selectedLandmarkId)
            mapState.mapRef.current.animateToRegion({ latitude: landmark.latitude, longitude: landmark.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 })
            props.toggleLmDetails(true)
        }
    }, [props.selectedLandmarkId])

    /**
     * Move to pressed location when newlandmark changes
     */
    useEffect(() => {
        if (props.selectedLandmarkId) {
            mapState.mapRef.current.animateToRegion({ latitude: props.newLandmark?.latitude, longitude: props.newLandmark?.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 })
        }
    }, [props.newLandmark])
    /**
     * Animates the map to fly over to and focus on the user's location.
     */
    const flyToUser = () => {

        console.log('[Map]: Centering on user')
        if (mapState.userLocation) {
            mapState.mapRef.current?.animateToRegion({ latitude: mapState.userLocation.latitude, longitude: mapState.userLocation.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 })
        }
    }

    /**
     * Activates speech recognition and opens the voice panel
     */
    const startSpeech = async () => {
        if (!(voicePermissionsGranted && locationPermissionsGranted)) {
            setAlert({
                title: "Permissions",
                message: "Please enable location and voice permissions in the settings menu",
                type: "warning",
                callback: () => openSettings(),
                callbackButtonText: 'Open settings'
            })
            return
        }
        props.toggleLmDetails(false);
        props.toggleLmAdd(false);
        const spokestackInitialized = await Spokestack.isInitialized()
        const spokestackStarted = await Spokestack.isStarted()

        if (spokestackInitialized) 
            if (spokestackStarted) {
                mapState.toggleVoiceVisible(true)
                Spokestack.activate()
            }
    }

    /**
     * Gets initial region that map should zoom into from current user location
     */
    const getInitialRegion = () => {
        if (mapState.userLocation) {
            return { latitude: mapState.userLocation.latitude, longitude: mapState.userLocation.longitude, latitudeDelta: 0.01, longitudeDelta: 0.01 }
        }
    }

    /**
     * Method that runs every time user location changes, updates user location state in memory and checks if any landmarks are nearby
     */
    const updateLocation = async (coord: LatLng) => {

        mapState.setUserLocation(coord)
        // get 10m radius around user
        const userAlertRadius = circle([coord.longitude, coord.latitude], 10, { units: 'meters' })

        // check each landmark to see if its inside user radius. if it is, and it isn't already in the list of notified landmarks, add it
        const newLandmarksNearUser = props.landmarks?.filter(lm => {
            const landmarkNearUser = booleanPointInPolygon([lm.longitude, lm.latitude], userAlertRadius)
            return landmarkNearUser
        })

        // to prevent duplicate notifications make a list of landmarks that weren't previously near the user. 
        // these are the only ones that the user will be notified of
        const newLandmarksNotPreviouslyNearUser = newLandmarksNearUser?.filter(lm => mapState.landmarksNearUser.some(origLm => lm == origLm.id))

        // update list
        mapState.setLandmarksNearUser(newLandmarksNearUser)

        // if there are any new landmarks near user, create a notification for them and send it
        if (newLandmarksNotPreviouslyNearUser?.length > 0) {
            const body = newLandmarksNotPreviouslyNearUser.length > 1 ? "There are new landmarks near by. Tap here to view" : "There is a new landmark close by. Tap here to view"
            const notifType: NotifType = newLandmarksNotPreviouslyNearUser.length > 1 ? 'near-landmarks' : 'near-landmark'
            const data = { notif_type: notifType, landmarks: newLandmarksNotPreviouslyNearUser.length == 1 ? newLandmarksNearUser : null }
            await Notifications.scheduleNotificationAsync({
                content: {
                    title: "⚠ Landmarks close by ⚠",
                    body: body,
                    data: data
                },
                trigger: { seconds: 2 },
            });
        }
    }

    const focusNearbyLandmarks = () => {
        if (mapState.landmarksNearUser?.length > 1) {
            mapState.toggleNearbyLmPanel(true)
        }
        else if (mapState.landmarksNearUser?.length === 1) {
            props.setSelectedLandmarkId(mapState.landmarksNearUser[0].id)
        }
    }

    return (
        <TouchableWithoutFeedback>
            <>
                {/*Main map component*/}
                <Modal transparent={true} animationType="fade" visible={mapState.loading}>
                    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: 'rgba(0,0,0,0.3)' }}>
                        <View style={{ width: '60%', height: '30%', backgroundColor: colors.red, justifyContent: 'center', alignItems: 'center', borderRadius: 20 }}>
                            <ActivityIndicator size="large" color="white" style={{ marginBottom: 20 }} />
                            <Text style={{ fontSize: 15, color: 'white' }}>Refreshing</Text>
                        </View>
                    </View>
                </Modal>
                <MapView
                    key={mapState.refreshKey}
                    toolbarEnabled={false}
                    onPress={() => Keyboard.dismiss()}
                    testID="mapView"
                    ref={mapState.mapRef}
                    style={{ width: '100%', height: '100%' }}
                    initialRegion={getInitialRegion()}
                    onLongPress={async (e) => await props.promptAddLandmark(e.nativeEvent.coordinate.longitude, e.nativeEvent.coordinate.latitude)}
                    showsUserLocation={locationPermissionsGranted}
                    onUserLocationChange={e => updateLocation(e.nativeEvent.coordinate)}
                    followsUserLocation={mapState.followUser}
                    showsMyLocationButton={false}
                    
                    onRegionChangeComplete={async (Region) => {
                        if (props.selectedLandmarkId) {

                            const {x, y} = await mapState.mapRef.current.pointForCoordinate({latitude: Region.latitude, longitude: Region.longitude})
                            props.setMarkerWindowPosition({x: x, y: y})
                        }
                        setSize(Region.latitudeDelta) 
                      }}
                    >
                    <Polygon // polygon for cameron library
                        coordinates={[
                            { latitude: 53.527190, longitude: -113.524205 },
                            { latitude: 53.526510, longitude: -113.524205 },
                            { latitude: 53.526510, longitude: -113.523452 },
                            { latitude: 53.527190, longitude: -113.523452 },
                        ]}
                        fillColor={`rgba(100,100,200,0.3)`}
                        strokeWidth={2.5}
                        tappable={true}
                        onPress={() => props.mapNavigation.navigate("Indoor")}
                    />

                    {props.applyFilters(props.landmarks)?.map((landmark) => {
                        if (landmark.floor == null) {
                            let trackChanges = false;
                            if (landmark?.id == props.selectedLandmarkId) {
                                trackChanges = true;
                            }
                            return (
                                <Marker
                                    tracksViewChanges={trackChanges}
                                    onPress={e => props.focusLandmark(landmark, e)}
                                    key={landmark.id}
                                    coordinate={{ latitude: landmark.latitude as number, longitude: landmark.longitude as number }} >
                                    {landmark.landmark_type ? <Image style={{ height: 35, width: 25, zIndex: 10 }} source={lmTypes[landmark.landmark_type]?.image} /> : null}
                                </Marker>)
                        }
                    }
                    )}
                    
                    <Polyline
                        coordinates={[
                            {
                                latitude: 53.527192,
                                longitude: -113.523583, //Cameron
                            },
                            {
                                latitude: 53.527189,
                                longitude: -113.5233285,
                            },
                            {
                                latitude: 53.526942,
                                longitude: -113.523338,
                            },
                            {
                                latitude: 53.526934,
                                longitude: -113.523263,
                            },
                            {
                                latitude: 53.5264874,
                                longitude: -113.5232654,
                            },
                            {
                                latitude: 53.526492,
                                longitude: -113.522827,
                            },
                            {
                                latitude: 53.52620959999999,
                                longitude: -113.5228282,
                            },
                            {
                                latitude: 53.5259882,
                                longitude: -113.5222835,
                            },
                            {
                                latitude: 53.5254566,
                                longitude: -113.5222824,
                            },
                            {
                                latitude: 53.525456,
                                longitude: -113.522155,
                            },
                            {
                                latitude: 53.5251288,
                                longitude: -113.5216083, // University station
                            },
                        ]}
                        strokeColor="black"
                        strokeWidth={3}
                        onPress={() => Alert.alert("This is a route from University Station to Cameron Library")}
                        tappable={true}
                    >
                    </Polyline>
                    {/* <MapViewDirections
                        origin={coordinates[0]}
                        destination={coordinates[1]}
                        apikey={"xxxxxxxxxxxxxxx"} // insert your API Key here
                        strokeWidth={4}
                        strokeColor="#111111"
                        mode="WALKING"
                    /> */}
                    <Marker coordinate={coordinates[1]} pinColor={colors.red}/>

                    <Marker coordinate={{  latitude: 53.527189,longitude: -113.5233285, }} pinColor={colors.red}>
                        {/* <Image source={require('../../../../assets/accessibleEntrance.png')} /> */}
                        <Text style={{ fontSize: size>0.00327 ? 0 : 0.05/size , maxWidth:200, }}>Route from University Station to Cameron Library</Text>
                    </Marker>
                </MapView>
                {/*Map buttons*/}
                {mapState.landmarksNearUser?.length > 0 ?
                    <TouchableOpacity style={[mapStyles.lowerMapButton, mapStyles.alertButton]} onPress={focusNearbyLandmarks}>
                        <FontAwesome name='exclamation-triangle' size={20} color='white' />
                        <Badge positioning={{ bottom: 7, right: 4 }} value={mapState.landmarksNearUser.length} />
                    </TouchableOpacity> : null}
                {locationPermissionsGranted && voicePermissionsGranted ?<IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.voiceButton]} icon="microphone" onPress={async () => await startSpeech()} /> : null}
                <IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.addLandmarkButton]} icon="plus" onPress={async () => await props.promptAddLandmark(mapState.userLocation.longitude, mapState.userLocation.latitude)} />
                <IconButton size={20} color='white' style={[mapStyles.lowerMapButton, mapStyles.userLocationButton]} icon="location-arrow" onPress={flyToUser} />
                <NearbyLandmarksPanel
                    focusLandmark={props.focusLandmark}
                    nearbyLmPanelVisible={mapState.nearbyLmPanelVisible}
                    toggleAlertedLmPanel={mapState.toggleNearbyLmPanel}
                    nearbyLandmarks={mapState.landmarksNearUser} />
                {/*Map Panels*/}
                {locationPermissionsGranted && voicePermissionsGranted ?
                    <VoicePanel
                        landmarksNearby={mapState.landmarksNearUser?.length > 0}
                        toggleAlertedLandmarksVisible={mapState.toggleNearbyLmPanel}
                        navigation={props.authNavigation}
                        userCoords={{ longitude: mapState.userLocation?.longitude, latitude: mapState.userLocation?.latitude }}
                        toggleVoiceVisible={mapState.toggleVoiceVisible}
                        toggleLmDetails={props.toggleLmDetails}
                        setSelectedLandmarkId={props.setSelectedLandmarkId}
                        voiceVisible={mapState.voiceVisible}
                        newLandmark={props.newLandmark}
                        setNewLandmark={props.setNewLandmark}
                    /> : null}
            </>
        </TouchableWithoutFeedback>)
}

export default observer(OutdoorMap);