/*
 * 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.
 */

/* global Windows, WinJS */

var PositionError   = require('./PositionError');
var callbacks       = {};
var locs            = {};

// constants
var FALLBACK_EPSILON = 0.001;

function ensureAndCreateLocator() {
    var deferral;

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

    return deferral;
}

function createErrorCode(loc) {
    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) {
        ensureAndCreateLocator().done(function (loc) {
            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(loc),
                            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) {
        ensureAndCreateLocator().done(function (loc) {
            var clientId = args[0];
            var highAccuracy = args[1];

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

            var 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
                if (Number.EPSILON) {
                    loc.movementThreshold = Number.EPSILON;
                } else {
                    loc.movementThreshold = FALLBACK_EPSILON;
                }
            }

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

            callbacks[clientId] = { pos: onPositionChanged, status: onStatusChanged };
            locs[clientId] = loc;
        }, fail);
    },

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

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

            delete callbacks[clientId];
            delete locs[clientId];
        }

        success();
    }
};

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