/*
 * Copyright 2013 Research In Motion Limited.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var PositionError = require('./PositionError'),
    ids = {},
    loc;

function ensureLocator() {
    var deferral;

    if (!loc) {
        loc = new Windows.Devices.Geolocation.Geolocator();

        if (typeof Windows.Devices.Geolocation.Geolocator.requestAccessAsync === 'function') {
            deferral = Windows.Devices.Geolocation.Geolocator.requestAccessAsync().then(function (result) {
                if (result === Windows.Devices.Geolocation.GeolocationAccessStatus.allowed) {
                    return loc;
                }

                return WinJS.Promise.wrapError({
                    code: PositionError.PERMISSION_DENIED,
                    message: 'Geolocation access has not been allowed by user.'
                });
            });
        } else {
            deferral = WinJS.Promise.wrap(loc);
        }
    } else {
        deferral = WinJS.Promise.wrap(loc);
    }    

    return deferral;
}

function createErrorCode() {
    switch (loc.locationStatus) {
        case Windows.Devices.Geolocation.PositionStatus.initializing:
            // This status indicates that a location device is still initializing
        case Windows.Devices.Geolocation.PositionStatus.noData:
            // No location data is currently available 
        case Windows.Devices.Geolocation.PositionStatus.notInitialized:
            // This status indicates that the app has not yet requested
            // location data by calling GetGeolocationAsync() or 
            // registering an event handler for the positionChanged event. 
        case Windows.Devices.Geolocation.PositionStatus.notAvailable:
            // Location is not available on this version of Windows
            return PositionError.POSITION_UNAVAILABLE;

        case Windows.Devices.Geolocation.PositionStatus.disabled:
            // The app doesn't have permission to access location,
            // either because location has been turned off.
            return PositionError.PERMISSION_DENIED;

        default:
            break;
    }
}
function createResult(pos) {
    var res = {
        accuracy: pos.coordinate.accuracy,
        heading: pos.coordinate.heading,
        velocity: pos.coordinate.speed,
        altitudeAccuracy: pos.coordinate.altitudeAccuracy,
        timestamp: pos.coordinate.timestamp
    };
    
    if (pos.coordinate.point) {
        res.latitude = pos.coordinate.point.position.latitude;
        res.longitude = pos.coordinate.point.position.longitude;
        res.altitude = pos.coordinate.point.position.altitude;
    } else { // compatibility with old windows8.0 api
        res.latitude = pos.coordinate.latitude;
        res.longitude = pos.coordinate.longitude;
        res.altitude = pos.coordinate.altitude;
    }
    
    return res;
}

module.exports = {
    getLocation: function (success, fail, args, env) {
        ensureLocator().done(function () {
            if (loc) {
                var highAccuracy = args[0],
                    maxAge = args[1];

                loc.desiredAccuracy = highAccuracy ?
                    Windows.Devices.Geolocation.PositionAccuracy.high :
                    Windows.Devices.Geolocation.PositionAccuracy.default;

                loc.reportInterval = maxAge ? maxAge : 0;

                loc.getGeopositionAsync().then(
                    function (pos) {
                        success(createResult(pos));
                    },
                    function (err) {
                        fail({
                            code: createErrorCode(),
                            message: err.message
                        });
                    }
                );
            }
            else {
                fail({
                    code: PositionError.POSITION_UNAVAILABLE,
                    message: "You do not have the required location services present on your system."
                });
            }
        }, fail);
    },

    addWatch: function (success, fail, args, env) {
        ensureLocator().done(function () {
            var clientId = args[0],
            highAccuracy = args[1],

            onPositionChanged = function (e) {
                success(createResult(e.position), { keepCallback: true });
            },

            onStatusChanged = function (e) {
                switch (e.status) {
                    case Windows.Devices.Geolocation.PositionStatus.noData:
                    case Windows.Devices.Geolocation.PositionStatus.notAvailable:
                        fail({
                            code: PositionError.POSITION_UNAVAILABLE,
                            message: "Data from location services is currently unavailable or you do not have the required location services present on your system."
                        });
                        break;

                    case Windows.Devices.Geolocation.PositionStatus.disabled:
                        fail({
                            code: PositionError.PERMISSION_DENIED,
                            message: "Your location is currently turned off."
                        });
                        break;

                    // case Windows.Devices.Geolocation.PositionStatus.initializing:
                    // case Windows.Devices.Geolocation.PositionStatus.ready:
                    default:
                        break;
                }
            };

            loc.desiredAccuracy = highAccuracy ?
                    Windows.Devices.Geolocation.PositionAccuracy.high :
                    Windows.Devices.Geolocation.PositionAccuracy.default;

            if (cordova.platformId == 'windows') {
                // 'positionchanged' event fails with error below if movementThreshold is not set
                // JavaScript runtime error: Operation aborted
                // You must set the MovementThreshold property or the ReportInterval property before adding event handlers.
                // WinRT information: You must set the MovementThreshold property or the ReportInterval property before adding event handlers
                loc.movementThreshold = Number.EPSILON;
            }

            loc.addEventListener("positionchanged", onPositionChanged);
            loc.addEventListener("statuschanged", onStatusChanged);

            ids[clientId] = { pos: onPositionChanged, status: onStatusChanged };
        }, fail);        
    },

    clearWatch: function (success, fail, args, env) {
        var clientId = args[0],
            callbacks = ids[clientId];

        if (callbacks) {
            loc.removeEventListener("positionchanged", callbacks.pos);
            loc.removeEventListener("statuschanged", callbacks.status);

            delete ids[clientId];
        }

        success();
    }
};

require("cordova/exec/proxy").add("Geolocation", module.exports);