1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
use std::sync::Arc;

use windows::{
    Devices::Geolocation::{
        BasicGeoposition, GeolocationAccessStatus, Geolocator as WindowsGeolocator,
        PositionAccuracy, PositionChangedEventArgs, PositionStatus, StatusChangedEventArgs,
    },
    Foundation::TypedEventHandler,
};

use crate::geolocation::core::{Error, Event, Geocoordinates, PowerMode, Status};

/// Represents the HAL's geolocator.
pub struct Geolocator {
    device_geolocator: WindowsGeolocator,
}

impl Geolocator {
    /// Create a new Geolocator for the device.
    pub fn new() -> Result<Self, Error> {
        // Check access
        let access_status = match WindowsGeolocator::RequestAccessAsync() {
            Ok(v) => v,
            Err(e) => return Err(Error::DeviceError(e.to_string())),
        };

        let access_status = match access_status.get() {
            Ok(v) => v,
            Err(e) => return Err(Error::DeviceError(e.to_string())),
        };

        if access_status != GeolocationAccessStatus::Allowed {
            return Err(Error::AccessDenied);
        }

        // Get geolocator
        let device_geolocator =
            WindowsGeolocator::new().map_err(|e| Error::DeviceError(e.to_string()))?;

        Ok(Self { device_geolocator })
    }
}

pub async fn get_coordinates(geolocator: &Geolocator) -> Result<Geocoordinates, Error> {
    let location = geolocator.device_geolocator.GetGeopositionAsync();

    let location = match location {
        Ok(v) => v,
        Err(e) => return Err(Error::DeviceError(e.to_string())),
    };

    let location = match location.get() {
        Ok(v) => v,
        Err(e) => return Err(Error::DeviceError(e.to_string())),
    };

    let location_coordinate = match location.Coordinate() {
        Ok(v) => v,
        Err(e) => return Err(Error::DeviceError(e.to_string())),
    };

    let location_point = match location_coordinate.Point() {
        Ok(v) => v,
        Err(e) => return Err(Error::DeviceError(e.to_string())),
    };

    let position = match location_point.Position() {
        Ok(v) => v,
        Err(e) => return Err(Error::DeviceError(e.to_string())),
    };

    Ok(position.into())
}

/// Listen to new events with a callback.
pub fn listen(
    geolocator: &Geolocator,
    callback: Arc<dyn Fn(Event) + Send + Sync>,
) -> Result<(), Error> {
    let callback1 = callback.clone();
    let callback2 = callback.clone();

    // Subscribe to status changed
    geolocator
        .device_geolocator
        .StatusChanged(&TypedEventHandler::new(
            move |_geolocator: &Option<WindowsGeolocator>,
                  event_args: &Option<StatusChangedEventArgs>| {
                if let Some(status) = event_args {
                    // Get status
                    let status = status.Status()?;

                    // Run callback
                    (callback1)(Event::StatusChanged(status.into()))
                }
                Ok(())
            },
        ))
        .map_err(|e| Error::DeviceError(e.to_string()))?;

    // Subscribe to position changed
    geolocator
        .device_geolocator
        .PositionChanged(&TypedEventHandler::new(
            move |_geolocator: &Option<WindowsGeolocator>,
                  event_args: &Option<PositionChangedEventArgs>| {
                if let Some(position) = event_args {
                    // Get coordinate
                    let position = position.Position()?.Coordinate()?.Point()?.Position()?;

                    // Run callback
                    (callback2)(Event::NewGeocoordinates(position.into()))
                }
                Ok(())
            },
        ))
        .map_err(|e| Error::DeviceError(e.to_string()))?;

    Ok(())
}

/// Set the device's power mode.
pub fn set_power_mode(geolocator: &mut Geolocator, power_mode: PowerMode) -> Result<(), Error> {
    match power_mode {
        PowerMode::High => geolocator
            .device_geolocator
            .SetDesiredAccuracy(PositionAccuracy::High)
            .map_err(|e| Error::DeviceError(e.to_string()))?,
        PowerMode::Low => geolocator
            .device_geolocator
            .SetDesiredAccuracy(PositionAccuracy::Default)
            .map_err(|e| Error::DeviceError(e.to_string()))?,
    };

    Ok(())
}

impl From<PositionStatus> for Status {
    fn from(value: PositionStatus) -> Self {
        match value.0 {
            0 => Status::Ready,
            1 => Status::Initializing,
            3 => Status::Disabled,
            5 => Status::NotAvailable,
            _ => Status::Unknown,
        }
    }
}

impl From<BasicGeoposition> for Geocoordinates {
    fn from(position: BasicGeoposition) -> Self {
        Geocoordinates {
            latitude: position.Latitude,
            longitude: position.Longitude,
        }
    }
}