'use strict';

(function (angular, _)
{
    angular.module('gamesite.services', []);
    angular.module('engine.services', []);
    var app = angular.module('game.host', ['gamesite.services', 'engine.services']);

    var baseUrl = "https://firefoxos.local";
    app.value('baseUrl', baseUrl);
    app.value('slug', 'denkiblocks');

    app.config(function ($routeProvider, $httpProvider, $provide)
    {
        $routeProvider.when('/play', {
            controller: 'play',
            templateUrl: 'js/app/game.partial.html'
        });

        $routeProvider.when('/verify', {
            controller: 'verify',
            templateUrl: 'js/app/verify.partial.html'
        });

        $routeProvider.when('/error', {
            controller: 'error',
            templateUrl: 'js/app/error.partial.html'
        });

        $routeProvider.otherwise({redirectTo: '/verify'});

        // Prepend http requests with turbulenz.com for api and dynamic
        // http endpoints

        var wrapURLPrefix = function ($delegate)
        {
            function wrapURL(url)
            {
                if (/^\/((api\/v1\/)|(dynamic\/))/.test(url))
                {
                    return (baseUrl + url);
                }
                else
                {
                    return url;
                }
            }

            function wrapCall(original)
            {
                return function (url, param1, param2)
                {
                    return $delegate[original](wrapURL(url), param1, param2);
                };
            }

            var wrappedHttp = function (config)
            {
                config.url = wrapURL(config.url);
                return $delegate(config);
            };

            var methods = ['get', 'head', 'post', 'put', 'delete', 'jsonp'];
            _.forEach(methods, function (methodName)
            {
                wrappedHttp[methodName] = wrapCall(methodName);
            });

            wrappedHttp.setCacheProvider = $delegate.setCacheProvider;

            return wrappedHttp;
        };

        var wrapRetry = function ($delegate, $q, $timeout)
        {
            var maxTries = 10;
            var baseRetryTime = 500;
            var maxRetryTime = 8000;
            var tryAgain = function (postRequest, deferred, numTries)
            {
                postRequest().then(function (response)
                {
                    deferred.resolve(response);
                },
                function (errResponse)
                {
                    var retry_codes = [0, 408, 429, 480];
                    if (numTries > 0 && _.contains(retry_codes, errResponse.status))
                    {
                        var timeout = Math.min(baseRetryTime * Math.pow(2, (maxTries - numTries)), maxRetryTime);
                        $timeout(function ()
                        {
                            tryAgain(postRequest, deferred, numTries - 1);
                        }, timeout);
                    }
                    else
                    {
                        deferred.reject(errResponse);
                    }
                });
            };


            function wrapCall(original)
            {
                return function (url, param1, param2)
                {
                    var deferred = $q.defer();
                    tryAgain(function ()
                    {
                        return $delegate[original](url, param1, param2);
                    }, deferred, maxTries);
                    return deferred.promise;
                };
            }

            var wrappedHttp = function (config)
            {
                var deferred = $q.defer();
                tryAgain(function ()
                {
                    return $delegate(config);
                }, deferred, maxTries);
                return deferred.promise;
            };

            var methods = ['get', 'head', 'post', 'put', 'delete', 'jsonp'];
            _.forEach(methods, function (methodName)
            {
                wrappedHttp[methodName] = wrapCall(methodName);
            });

            return wrappedHttp;
        };

        var wrapCachedResponses = function ($delegate, $q, $log)
        {
            var cacheProvider = null;

            var getFromCache = function (unsanitisedKey)
            {
                var key = unsanitisedKey.toLowerCase();
                var deferred = $q.defer();
                var cache = cacheProvider && cacheProvider.httpCache;
                if (cache && key in cache)
                {
                    deferred.resolve(cache[key]);
                }
                else
                {
                    $log.warn('MISS', key);
                    deferred.reject({
                        status: 404
                    });
                }
                return deferred.promise;
            };

            function wrapCall(original)
            {
                return function (url, param1, param2)
                {
                    var domain = /^https?:\/\/([^\/?#]+)/.exec(url);
                    if (domain && domain[0] === baseUrl)
                    {
                        return getFromCache(original + ':' + url);
                    }
                    else
                    {
                        return $delegate[original](url, param1, param2);
                    }
                };
            }

            var wrappedHttp = function (config)
            {
                var domain = /^https?:\/\/([^\/?#]+)/.exec(config.url);
                if (domain && domain[0] === baseUrl)
                {
                    return getFromCache(config.method + ':' + config.url);
                }
                else
                {
                    return $delegate(config);
                }
            };

            var methods = ['get', 'head', 'post', 'put', 'delete', 'jsonp'];
            _.forEach(methods, function (methodName)
            {
                wrappedHttp[methodName] = wrapCall(methodName);
            });

            wrappedHttp.setCacheProvider = function (newCacheProvider)
            {
                cacheProvider = newCacheProvider;
            };

            return wrappedHttp;
        };

        $provide.decorator('$http', wrapRetry);
        $provide.decorator('$http', wrapCachedResponses);
        $provide.decorator('$http', wrapURLPrefix);


        // Use x-www-form-urlencoded Content-Type
        $httpProvider.defaults.headers.post['Content-Type'] = 'application/x-www-form-urlencoded;charset=utf-8';

        // Override $http service's default transformRequest
        $httpProvider.defaults.transformRequest = [function (data)
        {
            /**
             * from http://victorblog.com/2012/12/20/make-angularjs-http-service-behave-like-jquery-ajax/
             *
             * The workhorse; converts an object to x-www-form-urlencoded serialization.
             * @param {Object} obj
             * @return {String}
             */
            var param = function (obj)
            {
                var query = '';
                var name, value, fullSubName, subValue, innerObj, i;

                for (name in obj)
                {
                    value = obj[name];

                    if (value instanceof Array)
                    {
                        for (i = 0; i < value.length; i += 1)
                        {
                            subValue = value[i];
                            fullSubName = name + '[' + i + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value instanceof Object)
                    {
                        for (var subName in value)
                        {
                            subValue = value[subName];
                            fullSubName = name + '[' + subName + ']';
                            innerObj = {};
                            innerObj[fullSubName] = subValue;
                            query += param(innerObj) + '&';
                        }
                    }
                    else if (value !== undefined && value !== null)
                    {
                        query += encodeURIComponent(name) + '=' + encodeURIComponent(value) + '&';
                    }
                }

                return query.length ? query.substr(0, query.length - 1) : query;
            };

            return angular.isObject(data) && String(data) !== '[object File]' ? param(data) : data;
        }];

        $httpProvider.responseInterceptors.push(['$q', function ($q)
        {
            return function (promise)
            {
                return promise.then(function (response)
                {
                    var data = response.data;
                    if (data && !angular.isUndefined(data.ok) && !data.ok)
                    {
                        if (data.status)
                        {
                            response.status = data.status;
                        }
                        return $q.reject(response);
                    }
                    else
                    {
                        return response;
                    }
                });
            };
        }]);

    });

    app.controller('verify', function ($rootScope, $timeout, $scope, $http, $window, $location, slug, localStorage)
    {
        var hideVerifying = function ()
        {
            $rootScope.showVerifying = false;
        };

        var showVerifying = function ()
        {
            $rootScope.showVerifying = true;
            $rootScope.dots = '';
            var updateDots = function ()
            {
                if ($rootScope.dots.length > 8)
                {
                    $rootScope.dots = '';
                }
                $rootScope.dots += '.';
                if ($rootScope.showVerifying)
                {
                    $timeout(updateDots, 500);
                }
            };
            updateDots();
        };


        showVerifying();
        var verifier = new mozmarket.receipts.Verifier();
        verifier.clearCache();
        verifier.verify(function (verifier)
        {
            hideVerifying();
            // Look at verifier.state to see how it went
            if (verifier.state instanceof verifier.states.OK ||
                verifier.state instanceof verifier.states.NetworkError)
            {
                $location.path('/play');
                $rootScope.$apply();
                return;
            }
            if (verifier.state instanceof verifier.states.MozAppsNotSupported)
            {
                $rootScope.errorMsg = "Mozilla Applications not supported on this platform"
                $location.path('/error');
                $rootScope.$apply();
                return;
            }
            if (verifier.state instanceof verifier.states.InternalError)
            {
                if (this.ignoreInternalError)
                {
                    return;
                }
                $rootScope.errorMsg = "Unexpected error validating purchase, if this repeats please contact support"
                $location.path('/error');
                $rootScope.$apply();
                return;
            }
            // FIXME: we need an option for rejecting a stale cache here
            if (verifier.state instanceof verifier.states.NeedsInstall)
            {
                $rootScope.errorMsg = "Application is not correctly installed, please visit the Mozilla Marketplace to install the app"
                $location.path('/error');
                $rootScope.$apply();
                return;
            }
            if (verifier.state instanceof verifier.states.NoValidReceipts) {
                var bestReason = null;
                verifier.iterReceiptErrors(function (receipt, error)
                {
                    if (bestReason === null)
                    {
                        bestReason = error;
                    }
                    else if (bestReason instanceof verifier.states.NetworkError)
                    {
                        bestReason = error;
                    }
                });
                $rootScope.errorMsg = "Application purchase verification failed : " + bestReason.detail;
                $location.path('/error');
                $rootScope.$apply();
                return;
            }
        });
    });

    app.controller('error', function ($rootScope, $scope, $http, $window, $location, slug, localStorage)
    {
    });

    app.controller('play', function ($scope, $http, $window, $location, slug, localStorage)
    {
        var initialiseCache = function (cache)
        {
            var addToCache = function (url, resource)
            {
                var key = ("get:" + baseUrl + url).toLowerCase();
                if (!cache[key] || cache[key].status !== 200)
                {
                    if (_.isString(resource))
                    {
                        $http.get(resource).then(function (response)
                        {
                            cache[key] = {data: response.data, status: 200};
                        });
                        cache[key] = {data: {}, status: 503};
                    }
                    else
                    {
                        cache[key] = {data: resource, status: 200};
                    }
                }
            };

            addToCache('/api/v1/badges/read/' + slug, '/badges.json');
            addToCache('/api/v1/leaderboards/read/' + slug, '/leaderboards.json');
            addToCache('/api/v1/store/items/read/' + slug, '/storeitems.json');
            addToCache('/api/v1/store/user/items/read/' + slug, {
                data: {userItems: {}},
                ok: true
            });
            addToCache('/api/v1/profiles/user', {
                data: {
                    username: "Player1",
                    displayname: "Player 1",
                    guest: false,
                    language: "en",
                    country: "GB",
                    anonymous: false
                },
                ok: true
            });
        };

        $scope.play = function ()
        {
            localStorage.initialise();
            var cache = {};
            cache.httpCache = {};
            initialiseCache(cache.httpCache);
            $http.setCacheProvider(cache);

            if ($scope.game)
            {
                $scope.game.playing = true;
            }
            else
            {
                $scope.game = {
                    slug: slug,
                    playing: true
                };
            }
        };
    });

    app.run(['$rootScope', function ($rootScope) {
        // Add safe apply for non-deterministically asynchronous updates
        $rootScope.safeApply = function (functionOrExpression)
        {
            if (!this.$$phase)
            {
                this.$apply(functionOrExpression);
            }
            else
            {
                this.$eval(functionOrExpression);
            }
        };
    }]);

})(window.angular, window._);
