/*jshint esversion: 6 */
/*
* This script is responsible for the core functionalities.
*/
var providers = [];
var prvKeys = [];
var badges = [];
var tabid = 0;
var siteBlockedAlert = 'javascript:void(0)';
var dataHash;
var localDataHash;
var os;
var currentURL;

function start()
{
    changeIcon();

    /**
    * Save OS Version
    */
    browser.runtime.getPlatformInfo(function(info) {

        os = info.os;


        /**
        * Initialize the JSON provider object keys.
        *
        * @param {JSON Object} obj
        */
        function getKeys(obj){
            for(var key in obj){
                prvKeys.push(key);
            }
        }

        /**
        * Initialize the providers form the JSON object.
        *
        */
        function createProviders()
        {
            data = storage.ClearURLsData;

            for(var p = 0; p < prvKeys.length; p++)
            {
                //Create new provider
                providers.push(new Provider(prvKeys[p],data.providers[prvKeys[p]].completeProvider));

                //Add URL Pattern
                providers[p].setURLPattern(data.providers[prvKeys[p]].urlPattern);

                //Add rules to provider
                for(var r = 0; r < data.providers[prvKeys[p]].rules.length; r++)
                {
                    providers[p].addRule(data.providers[prvKeys[p]].rules[r]);
                }

                //Add exceptions to provider
                for(var e = 0; e < data.providers[prvKeys[p]].exceptions.length; e++)
                {
                    providers[p].addException(data.providers[prvKeys[p]].exceptions[e]);
                }

                //Add redirections to provider
                for(var re = 0; re < data.providers[prvKeys[p]].redirections.length; re++)
                {
                    providers[p].addRedirection(data.providers[prvKeys[p]].redirections[re]);
                }
            }
        }

        /**
        * Convert the external data to Objects and
        * call the create provider function.
        *
        * @param  {String} retrievedText - pure data form github
        */
        function toObject(retrievedText) {
            getKeys(storage.ClearURLsData.providers);
            createProviders();
        }

        /**
        * Get the hash for the rule file on github.
        * Check the hash with the hash form the local file.
        * If the hash has changed, then download the new rule file.
        * Else do nothing.
        */
        function getHash()
        {
            //Get the target hash from github
            fetch(storage.hashURL)
            .then(function(response){
                var responseTextHash = response.clone().text().then(function(responseTextHash){
                    if(response.ok && $.trim(responseTextHash))
                    {
                        dataHash = responseTextHash;

                        if($.trim(dataHash) !== $.trim(localDataHash))
                        {
                            fetchFromURL();
                        }
                        else {
                            toObject(storage.ClearURLsData);
                            storeHashStatus(1);
                        }
                    }
                    else {
                        dataHash = false;
                    }
                });
            });
        }

        /*
        * ##################################################################
        * # Fetch Rules & Exception from URL                               #
        * ##################################################################
        */
        function fetchFromURL()
        {
            fetch(storage.ruleURL)
            .then(checkResponse);

            function checkResponse(response)
            {
                var responseText = response.clone().text().then(function(responseText){
                    if(response.ok && $.trim(responseText))
                    {
                        var downloadedFileHash = $.sha256(responseText);

                        if($.trim(downloadedFileHash) === $.trim(dataHash))
                        {
                            storage.ClearURLsData = responseText;
                            storage.dataHash = downloadedFileHash;
                            storeHashStatus(2);
                        }
                        else {
                            storeHashStatus(3);
                        }
                        storage.ClearURLsData = JSON.parse(storage.ClearURLsData);
                        toObject(storage.ClearURLsData);
                    }
                });
            }
        }

        // ##################################################################

        /*
        * ##################################################################
        * # Supertyp Provider                                              #
        * ##################################################################
        */
        /**
        * Declare constructor
        *
        * @param {String} _name                Provider name
        * @param {boolean} _completeProvider    Set URL Pattern as rule
        * @param {boolean} _isActive            Is the provider active?
        */
        function Provider(_name, _completeProvider = false, _isActive = true){
            var name = _name;
            var urlPattern;
            var rules = [];
            var exceptions = [];
            var canceling = _completeProvider;
            var redirections = [];
            var active = _isActive;

            if(_completeProvider){
                rules.push(".*");
            }

            /**
            * Returns the provider name.
            * @return {String}
            */
            this.getName = function() {
                return name;
            };

            /**
            * Add URL pattern.
            *
            * @require urlPatterns as RegExp
            */
            this.setURLPattern = function(urlPatterns) {
                urlPattern = new RegExp(urlPatterns, "i");
            };

            /**
            * Return if the Provider Request is canceled
            * @return {Boolean} isCanceled
            */
            this.isCaneling = function() {
                return canceling;
            };

            /**
            * Check the url is matching the ProviderURL.
            *
            * @return {boolean}    ProviderURL as RegExp
            */
            this.matchURL = function(url) {
                return !(this.matchException(url)) && urlPattern.test(url);
            };

            /**
            * Add a rule to the rule array
            * and replace old rule with new rule.
            *
            * @param {String} rule        RegExp as string
            * @param {boolean} isActive   Is this rule active?
            */
            this.addRule = function(rule, isActive = true) {
                for(var i=0; i < rules.length; i++)
                {
                    if(rules[i][0] === rule)
                    {
                        rules[i] = [rule, isActive];

                        return;
                    }
                }

                rules.push([rule, isActive]);
            };

            /**
            * Return all active rules as an array.
            *
            * @return Array RegExp strings
            */
            this.getRules = function() {
                var rawRules = [];

                rules.forEach(rule => {
                    if(rule[1] === true)
                    {
                        rawRules.push(rule[0]);
                    }
                });

                return rawRules;
            };

            /**
            * Add a exception to the exceptions array
            * and replace old with new exception.
            *
            * @param {String} exception   RegExp as string
            * @param {Boolean} isActive   Is this exception acitve?
            */
            this.addException = function(exception, isActive = true) {
                for(var i=0; i < exceptions.length; i++)
                {
                    if(exceptions[i][0] === exception)
                    {
                        exceptions[i] = [exception, isActive];
                        return;
                    }
                }

                exceptions.push([exception, isActive]);
            };

            /**
            * Private helper method to check if the url
            * an exception.
            *
            * @param  {String} url     RegExp as string
            * @return {boolean}        if matching? true: false
            */
            this.matchException = function(url) {
                var result = false;

                //Add the site blocked alert to every exception
                if(url == siteBlockedAlert) return true;

                for (var i = 0; i < exceptions.length; i++) {
                    if(result) { break; }

                    if(exceptions[i][1])
                    {
                        exception_regex = new RegExp(exceptions[i][0], "i");
                        result = exception_regex.test(url);
                    }
                }

                return result;
            };

            /**
            * Add a redirection to the redirections array
            * and replace old with new redirection.
            *
            * @param {String} redirection   RegExp as string
            * @param {Boolean} isActive     Is this redirection active?
            */
            this.addRedirection = function(redirection, isActive = true) {
                for(var i=0; i < redirections.length; i++)
                {
                    if(redirections[i][0] === redirection) {
                        redirections[i] = [redirection, isActive];
                        return;
                    }
                }

                redirections.push([redirection, isActive]);
            };

            /**
            * Return all redirection.
            *
            * @return url
            */
            this.getRedirection = function(url) {
                var re = null;

                for(var i = 0; i < redirections.length; i++)
                {
                    result = (url.match(new RegExp(redirections[i][0], "i")));

                    if (result && result.length > 0 && redirections[i][1])
                    {
                        re = (new RegExp(redirections[i][0], "i")).exec(url)[1];

                        break;
                    }
                }

                return re;
            };
        }
        // ##################################################################

        /**
        * Helper function which remove the tracking fields
        * for each provider given as parameter.
        *
        * @param  {Provider} provider      Provider-Object
        * @param  {webRequest} request     webRequest-Object
        * @return {Array}                  Array with changes and url fields
        */
        function removeFieldsFormURL(provider, request)
        {
            var url = request.url;
            var domain = url.replace(new RegExp("\\?.*", "i"), "");
            var fields = "";
            var rules = provider.getRules();
            var changes = false;
            var cancel = false;

            /*
            * Expand the url by provider redirections. So no tracking on
            * url redirections form sites to sites.
            */
            var re = provider.getRedirection(url);
            if(re !== null)
            {
                url = decodeURIComponent(re);
                //Log the action
                pushToLog(request.url, re, translate('log_redirect'));

                return {
                    "redirect": true,
                    "url": url
                };
            }

            /**
            * Only test for matches, if there are fields that can be cleaned.
            */
            if(existsFields(url))
            {
                /**
                * It must be non-greedy, because by default .* will match
                * all ? chars. So the replace function delete everything
                * before the last ?. With adding a ? on the quantifier *,
                * we fixed this problem.
                */
                fields = url.replace(new RegExp(".*?\\?", "i"), "");

                for (var i = 0; i < rules.length; i++) {
                    var beforReplace = fields;

                    fields = fields.replace(new RegExp(rules[i], "i"), "");

                    if(beforReplace != fields)
                    {
                        //Log the action
                        pushToLog(domain+"?"+beforReplace, domain+"?"+fields, rules[i]);

                        if(badges[tabid] == null)
                        {
                            badges[tabid] = 0;
                        }

                        increaseURLCounter();

                        if(!checkOSAndroid())
                        {
                            if(storage.badgedStatus) {
                                browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid});
                            }
                            else
                            {
                                browser.browserAction.setBadgeText({text: "", tabId: tabid});
                            }
                        }

                        changes = true;
                    }
                }
                url = domain+"?"+fields;
            }
            else {
                if(domain != url)
                {
                    url = domain;
                    changes = true;
                }
            }

            if(provider.isCaneling()){
                pushToLog(request.url, request.url, translate('log_domain_blocked'));
                if(badges[tabid] == null)
                {
                    badges[tabid] = 0;
                }

                increaseURLCounter();

                if(!checkOSAndroid())
                {
                    if(storage.badgedStatus) {
                        browser.browserAction.setBadgeText({text: (++badges[tabid]).toString(), tabId: tabid});
                    }
                    else
                    {
                        browser.browserAction.setBadgeText({text: "", tabId: tabid});
                    }
                }

                cancel = true;
            }

            return {
                "changes": changes,
                "url": url,
                "cancel": cancel
            };
        }

        /**
        * Function which called from the webRequest to
        * remove the tracking fields from the url.
        *
        * @param  {webRequest} request     webRequest-Object
        * @return {Array}                  redirectUrl or none
        */
        function clearUrl(request)
        {
            var URLbeforeReplaceCount = countFields(request.url);

            //Add Fields form Request to global url counter
            increaseGlobalURLCounter(URLbeforeReplaceCount);

            if(storage.globalStatus){

                var result = {
                    "changes": false,
                    "url": "",
                    "redirect": false,
                    "cancel": false
                };

                /*
                * Call for every provider the removeFieldsFormURL method.
                */
                for (var i = 0; i < providers.length; i++) {

                    if(providers[i].matchURL(request.url))
                    {
                        result = removeFieldsFormURL(providers[i], request);
                    }

                    /*
                    * Expand urls and bypass tracking.
                    * Cancel the active request.
                    */
                    if(result.redirect)
                    {
                        browser.tabs.update(request.tabId, {url: result.url});
                        return {cancel: true};
                    }

                    /*
                    * Cancel the Request and redirect to the site blocked alert page,
                    * to inform the user about the full url blocking.
                    */
                    if(result.cancel){
                        return {
                            redirectUrl: siteBlockedAlert
                        };
                    }

                    /*
                    * Ensure that the function go not into
                    * a loop.
                    */
                    if(result.changes){
                        return {
                            redirectUrl: result.url
                        };
                    }
                }
            }

            // Default case
            return {};
        }

        /**
        * Function to log all activities from ClearUrls.
        * Only logging when activated.
        * The log is only temporary saved in the cache and will
        * permanently saved with the saveLogOnClose function.
        *
        * @param beforeProcessing  the url before the clear process
        * @param afterProcessing   the url after the clear process
        * @param rule              the rule that triggered the process
        */
        function pushToLog(beforeProcessing, afterProcessing, rule)
        {
            if(storage.loggingStatus)
            {
                storage.log.log.push(
                    {
                        "before": beforeProcessing,
                        "after": afterProcessing,
                        "rule": rule,
                        "timestamp": Date.now()
                    }
                );
            }
        }

        /**
        * Call loadOldDataFromStore, getHash, counter, status and log functions
        */

        loadOldDataFromStore();
        getHash();
        setBadgedStatus();

        /**
        * Call by each tab is updated.
        * And if url has changed.
        */
        function handleUpdated(tabId, changeInfo, tabInfo) {
            if(changeInfo.url)
            {
                delete badges[tabId];
            }
            currentURL = tabInfo.url;
        }

        /**
        * Call by each tab is updated.
        */
        browser.tabs.onUpdated.addListener(handleUpdated);

        /**
        * Call by each tab change to set the actual tab id
        */
        function handleActivated(activeInfo) {
            tabid = activeInfo.tabId;
            browser.tabs.get(tabid).then(function (tab) {
                currentURL = tab.url;
            });
        }

        /**
        * Call by each tab change.
        */
        browser.tabs.onActivated.addListener(handleActivated);

        /**
        * Check the request.
        */
        function promise(requestDetails)
        {
            if(isDataURL(requestDetails))
            {
                return {};
            }
            else {
                var ret = clearUrl(requestDetails);
                return ret;
            }

        }

        /**
        * To prevent long loading on data urls
        * we will check here for data urls.
        *
        * @type {requestDetails}
        * @return {boolean}
        */
        function isDataURL(requestDetails) {
            var s = requestDetails.url;

            return s.substring(0,4) == "data";
        }

        /**
        * Call by each Request and checking the url.
        *
        * @type {Array}
        */
        browser.webRequest.onBeforeRequest.addListener(
            promise,
            {urls: ["<all_urls>"], types: getData("types")},
            ["blocking"]
        );
    });
}
