/**
 * Request Analyzer
 * Belongs to Decentraleyes.
 *
 * @author      Thomas Rientjes
 * @since       2014-05-30
 * @license     MPL 2.0
 *
 * This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
 * You can obtain one at http://mozilla.org/MPL/2.0/.
 */

'use strict';

/**
 * Imports
 */

/**
 * Resource version mappings.
 * @var {object} mappings
 */
var mappings = require('./mappings');

/**
 * Resource version shorthands.
 * @var {object} shorthands
 */
var shorthands = require('./shorthands');

/**
 * Gets and sets add-on specific preferences.
 * @var {object} simplePreferences
 */
var simplePreferences = require('sdk/simple-prefs');

/**
 * Constants
 */

const MAPPING_FILE_EXPRESSION = new RegExp('.map$', 'i');
const VERSION_EXPRESSION = /(?:\d{1,2}\.){1,3}\d{1,2}/;
const VERSION_PLACEHOLDER = '{version}';
const WEB_PREFIX_VALUE = 'www.';
const WEB_PREFIX_LENGTH = WEB_PREFIX_VALUE.length;
const VALUE_SEPARATOR = ';';

/**
 * Variables
 */

var preferences = simplePreferences.prefs;
var whitelistedDomains = {};

/**
 * Initializations
 */

_applyWhitelistPreference();

/**
 * Event Handlers
 */

simplePreferences.on('domainWhitelist', _applyWhitelistPreference);

/**
 * Public Methods
 */

exports.isValidCandidate = function (httpChannel) {

    var initiatorDomain;

    // See if the request uses a supported URI scheme.
    if (!httpChannel.URI.schemeIs('http') &&
        !httpChannel.URI.schemeIs('https')) {
        return false;
    }

    // See if the request is targeted at a Content Delivery Network.
    if (mappings[httpChannel.URI.host] === undefined) {
        return false;
    }

    // Attempt to determine the domain of the request initiator.
    initiatorDomain =
        httpChannel.loadInfo && httpChannel.loadInfo.loadingDocument && httpChannel.loadInfo.loadingDocument.domain ||
        httpChannel.referrer && httpChannel.referrer.host;

    // If the request initiator could be determined and is whitelisted.
    if (initiatorDomain && whitelistedDomains[_normalizeDomain(initiatorDomain)]) {

        // Remove sensitive headers from the request.
        httpChannel.setRequestHeader('Referer', null, false);
        httpChannel.setRequestHeader('Origin', null, false);
        httpChannel.setRequestHeader('Cookie', null, false);

        return false;
    }

    // Only requests of type GET can be valid candidates.
    return httpChannel.requestMethod === 'GET';
};

exports.getLocalTarget = function (channelHost, channelPath) {

    var hostMappings, basePath, resourceMappings;

    // Use the proper mappings for the targeted host.
    hostMappings = mappings[channelHost];

    // Resource mapping files are never locally available.
    if (MAPPING_FILE_EXPRESSION.test(channelPath)) {
        return false;
    }

    basePath = _matchBasePath(hostMappings, channelPath);
    resourceMappings = hostMappings[basePath];

    if (!resourceMappings) {
        return false;
    }

    // Return either the local target's path or false.
    return _findLocalTarget(resourceMappings, basePath, channelHost, channelPath);
};

/**
 * Private Methods
 */

function _matchBasePath (hostMappings, channelPath) {

    for (let basePath of Object.keys(hostMappings)) {

        if (channelPath.startsWith(basePath)) {
            return basePath;
        }
    }

    return false;
}

function _findLocalTarget (resourceMappings, basePath, channelHost, channelPath) {

    var resourcePath, versionNumber, resourcePattern;

    resourcePath = channelPath.replace(basePath, '');

    versionNumber = resourcePath.match(VERSION_EXPRESSION);
    resourcePattern = resourcePath.replace(versionNumber, VERSION_PLACEHOLDER);

    // Determine if the resource path has a static mapping.
    if (resourceMappings[resourcePath]) {

        // Prepare and return a local target.
        return {
            'path': resourceMappings[resourcePath].path,
            'type': resourceMappings[resourcePath].type
        };
    }

    // Determine if the resource path fits into a resource mold.
    for (let resourceMold of Object.keys(resourceMappings)) {

        if (resourcePattern.startsWith(resourceMold)) {

            let targetPath, targetType, hostShorthands;

            targetPath = resourceMappings[resourceMold].path;
            targetPath = targetPath.replace(VERSION_PLACEHOLDER, versionNumber);
            targetType = resourceMappings[resourceMold].type;

            hostShorthands = shorthands[channelHost];

            if (hostShorthands && hostShorthands[targetPath]) {

                let shorthand = hostShorthands[targetPath];

                targetPath = shorthand.path;
                targetType = shorthand.type;
            }

            // Prepare and return a local target.
            return {
                'path': targetPath,
                'type': targetType
            };
        }
    }

    return false;
}

function _normalizeDomain (domain) {

    domain = domain.toLowerCase().trim();

    if (domain.startsWith(WEB_PREFIX_VALUE)) {
        domain = domain.slice(WEB_PREFIX_LENGTH);
    }

    return domain;
}

function _applyWhitelistPreference () {

    whitelistedDomains = {};

    //noinspection JSUnresolvedVariable
    preferences.domainWhitelist.split(VALUE_SEPARATOR).forEach(function (domain) {
        whitelistedDomains[_normalizeDomain(domain)] = true;
    });
}