// // browser.js - client-side engine // /*global less, window, document, XMLHttpRequest, location */

var isFileProtocol = /^(file|chrome(-extension)?|resource|qrc|app):/.test(location.protocol);

less.env = less.env || (location.hostname == ‘127.0.0.1’ ||

location.hostname == '0.0.0.0'   ||
location.hostname == 'localhost' ||
(location.port &&
  location.port.length > 0)      ||
isFileProtocol                   ? 'development'
                                 : 'production');

var logLevel = {

debug: 3,
info: 2,
errors: 1,
none: 0

};

// The amount of logging in the javascript console. // 3 - Debug, information and errors // 2 - Information and errors // 1 - Errors // 0 - None // Defaults to 2 less.logLevel = typeof(less.logLevel) != ‘undefined’ ? less.logLevel : (less.env === ‘development’ ? logLevel.debug : logLevel.errors);

// Load styles asynchronously (default: false) // // This is set to ‘false` by default, so that the body // doesn’t start loading before the stylesheets are parsed. // Setting this to ‘true` can result in flickering. // less.async = less.async || false; less.fileAsync = less.fileAsync || false;

// Interval between watch polls less.poll = less.poll || (isFileProtocol ? 1000 : 1500);

//Setup user functions if (less.functions) {

 for(var func in less.functions) {
     if (less.functions.hasOwnProperty(func)) {
         less.tree.functions[func] = less.functions[func];
     }
}

}

var dumpLineNumbers = /!dumpLineNumbers:(comments|mediaquery|all)/.exec(location.hash); if (dumpLineNumbers) {

less.dumpLineNumbers = dumpLineNumbers[1];

}

var typePattern = /^text/(x-)?less$/; var cache = null; var fileCache = {};

function log(str, level) {

if (typeof(console) !== 'undefined' && less.logLevel >= level) {
    console.log('less: ' + str);
}

}

function extractId(href) {

return href.replace(/^[a-z-]+:\/+?[^\/]+/, '' )  // Remove protocol & domain
    .replace(/^\//,                 '' )  // Remove root /
    .replace(/\.[a-zA-Z]+$/,        '' )  // Remove simple extension
    .replace(/[^\.\w-]+/g,          '-')  // Replace illegal characters
    .replace(/\./g,                 ':'); // Replace dots with colons(for valid id)

}

function errorConsole(e, rootHref) {

var template = '{line} {content}';
var filename = e.filename || rootHref;
var errors = [];
var content = (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
    " in " + filename + " ";

var errorline = function (e, i, classname) {
    if (e.extract[i] !== undefined) {
        errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
            .replace(/\{class\}/, classname)
            .replace(/\{content\}/, e.extract[i]));
    }
};

if (e.extract) {
    errorline(e, 0, '');
    errorline(e, 1, 'line');
    errorline(e, 2, '');
    content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':\n' +
        errors.join('\n');
} else if (e.stack) {
    content += e.stack;
}
log(content, logLevel.errors);

}

function createCSS(styles, sheet, lastModified) {

// Strip the query-string
var href = sheet.href || '';

// If there is no title set, use the filename, minus the extension
var id = 'less:' + (sheet.title || extractId(href));

// If this has already been inserted into the DOM, we may need to replace it
var oldCss = document.getElementById(id);
var keepOldCss = false;

// Create a new stylesheet node for insertion or (if necessary) replacement
var css = document.createElement('style');
css.setAttribute('type', 'text/css');
if (sheet.media) {
    css.setAttribute('media', sheet.media);
}
css.id = id;

if (css.styleSheet) { // IE
    try {
        css.styleSheet.cssText = styles;
    } catch (e) {
        throw new(Error)("Couldn't reassign styleSheet.cssText.");
    }
} else {
    css.appendChild(document.createTextNode(styles));

    // If new contents match contents of oldCss, don't replace oldCss
    keepOldCss = (oldCss !== null && oldCss.childNodes.length > 0 && css.childNodes.length > 0 &&
        oldCss.firstChild.nodeValue === css.firstChild.nodeValue);
}

var head = document.getElementsByTagName('head')[0];

// If there is no oldCss, just append; otherwise, only append if we need
// to replace oldCss with an updated stylesheet
if (oldCss === null || keepOldCss === false) {
    var nextEl = sheet && sheet.nextSibling || null;
    if (nextEl) {
        nextEl.parentNode.insertBefore(css, nextEl);
    } else {
        head.appendChild(css);
    }
}
if (oldCss && keepOldCss === false) {
    oldCss.parentNode.removeChild(oldCss);
}

// Don't update the local store if the file wasn't modified
if (lastModified && cache) {
    log('saving ' + href + ' to cache.', logLevel.info);
    try {
        cache.setItem(href, styles);
        cache.setItem(href + ':timestamp', lastModified);
    } catch(e) {
        //TODO - could do with adding more robust error handling
        log('failed to save', logLevel.errors);
    }
}

}

function postProcessCSS(styles) {

if (less.postProcessor && typeof less.postProcessor === 'function') {
    styles = less.postProcessor.call(styles, styles) || styles;
}
return styles;

}

function errorHTML(e, rootHref) {

var id = 'less-error-message:' + extractId(rootHref || "");
var template = '<li><label>{line}</label><pre class="{class}">{content}</pre></li>';
var elem = document.createElement('div'), timer, content, errors = [];
var filename = e.filename || rootHref;
var filenameNoPath = filename.match(/([^\/]+(\?.*)?)$/)[1];

elem.id        = id;
elem.className = "less-error-message";

content = '<h3>'  + (e.type || "Syntax") + "Error: " + (e.message || 'There is an error in your .less file') +
    '</h3>' + '<p>in <a href="' + filename   + '">' + filenameNoPath + "</a> ";

var errorline = function (e, i, classname) {
    if (e.extract[i] !== undefined) {
        errors.push(template.replace(/\{line\}/, (parseInt(e.line, 10) || 0) + (i - 1))
            .replace(/\{class\}/, classname)
            .replace(/\{content\}/, e.extract[i]));
    }
};

if (e.extract) {
    errorline(e, 0, '');
    errorline(e, 1, 'line');
    errorline(e, 2, '');
    content += 'on line ' + e.line + ', column ' + (e.column + 1) + ':</p>' +
        '<ul>' + errors.join('') + '</ul>';
} else if (e.stack) {
    content += '<br/>' + e.stack.split('\n').slice(1).join('<br/>');
}
elem.innerHTML = content;

// CSS for error messages
createCSS([
    '.less-error-message ul, .less-error-message li {',
    'list-style-type: none;',
    'margin-right: 15px;',
    'padding: 4px 0;',
    'margin: 0;',
    '}',
    '.less-error-message label {',
    'font-size: 12px;',
    'margin-right: 15px;',
    'padding: 4px 0;',
    'color: #cc7777;',
    '}',
    '.less-error-message pre {',
    'color: #dd6666;',
    'padding: 4px 0;',
    'margin: 0;',
    'display: inline-block;',
    '}',
    '.less-error-message pre.line {',
    'color: #ff0000;',
    '}',
    '.less-error-message h3 {',
    'font-size: 20px;',
    'font-weight: bold;',
    'padding: 15px 0 5px 0;',
    'margin: 0;',
    '}',
    '.less-error-message a {',
    'color: #10a',
    '}',
    '.less-error-message .error {',
    'color: red;',
    'font-weight: bold;',
    'padding-bottom: 2px;',
    'border-bottom: 1px dashed red;',
    '}'
].join('\n'), { title: 'error-message' });

elem.style.cssText = [
    "font-family: Arial, sans-serif",
    "border: 1px solid #e00",
    "background-color: #eee",
    "border-radius: 5px",
    "-webkit-border-radius: 5px",
    "-moz-border-radius: 5px",
    "color: #e00",
    "padding: 15px",
    "margin-bottom: 15px"
].join(';');

if (less.env == 'development') {
    timer = setInterval(function () {
        if (document.body) {
            if (document.getElementById(id)) {
                document.body.replaceChild(elem, document.getElementById(id));
            } else {
                document.body.insertBefore(elem, document.body.firstChild);
            }
            clearInterval(timer);
        }
    }, 10);
}

}

function error(e, rootHref) {

if (!less.errorReporting || less.errorReporting === "html") {
    errorHTML(e, rootHref);
} else if (less.errorReporting === "console") {
    errorConsole(e, rootHref);
} else if (typeof less.errorReporting === 'function') {
    less.errorReporting("add", e, rootHref);
}

}

function removeErrorHTML(path) {

var node = document.getElementById('less-error-message:' + extractId(path));
if (node) {
    node.parentNode.removeChild(node);
}

}

function removeErrorConsole(path) {

//no action

}

function removeError(path) {

if (!less.errorReporting || less.errorReporting === "html") {
    removeErrorHTML(path);
} else if (less.errorReporting === "console") {
    removeErrorConsole(path);
} else if (typeof less.errorReporting === 'function') {
    less.errorReporting("remove", path);
}

}

function loadStyles(modifyVars) {

var styles = document.getElementsByTagName('style'),
    style;
for (var i = 0; i < styles.length; i++) {
    style = styles[i];
    if (style.type.match(typePattern)) {
        var env = new less.tree.parseEnv(less),
            lessText = style.innerHTML || '';
        env.filename = document.location.href.replace(/#.*$/, '');

        if (modifyVars || less.globalVars) {
            env.useFileCache = true;
        }

        /*jshint loopfunc:true */
        // use closure to store current value of i
        var callback = (function(style) {
            return function (e, cssAST) {
                if (e) {
                    return error(e, "inline");
                }
                var css = cssAST.toCSS(less);
                style.type = 'text/css';
                if (style.styleSheet) {
                    style.styleSheet.cssText = css;
                } else {
                    style.innerHTML = css;
                }
            };
        })(style);
        new(less.Parser)(env).parse(lessText, callback, {globalVars: less.globalVars, modifyVars: modifyVars});
    }
}

}

function extractUrlParts(url, baseUrl) {

// urlParts[1] = protocol&hostname || /
// urlParts[2] = / if path relative to host base
// urlParts[3] = directories
// urlParts[4] = filename
// urlParts[5] = parameters

var urlPartsRegex = /^((?:[a-z-]+:)?\/+?(?:[^\/\?#]*\/)|([\/\\]))?((?:[^\/\\\?#]*[\/\\])*)([^\/\\\?#]*)([#\?].*)?$/i,
    urlParts = url.match(urlPartsRegex),
    returner = {}, directories = [], i, baseUrlParts;

if (!urlParts) {
    throw new Error("Could not parse sheet href - '"+url+"'");
}

// Stylesheets in IE don't always return the full path
if (!urlParts[1] || urlParts[2]) {
    baseUrlParts = baseUrl.match(urlPartsRegex);
    if (!baseUrlParts) {
        throw new Error("Could not parse page url - '"+baseUrl+"'");
    }
    urlParts[1] = urlParts[1] || baseUrlParts[1] || "";
    if (!urlParts[2]) {
        urlParts[3] = baseUrlParts[3] + urlParts[3];
    }
}

if (urlParts[3]) {
    directories = urlParts[3].replace(/\\/g, "/").split("/");

    // extract out . before .. so .. doesn't absorb a non-directory
    for(i = 0; i < directories.length; i++) {
        if (directories[i] === ".") {
            directories.splice(i, 1);
            i -= 1;
        }
    }

    for(i = 0; i < directories.length; i++) {
        if (directories[i] === ".." && i > 0) {
            directories.splice(i-1, 2);
            i -= 2;
        }
    }
}

returner.hostPart = urlParts[1];
returner.directories = directories;
returner.path = urlParts[1] + directories.join("/");
returner.fileUrl = returner.path + (urlParts[4] || "");
returner.url = returner.fileUrl + (urlParts[5] || "");
return returner;

}

function pathDiff(url, baseUrl) {

// diff between two paths to create a relative path

var urlParts = extractUrlParts(url),
    baseUrlParts = extractUrlParts(baseUrl),
    i, max, urlDirectories, baseUrlDirectories, diff = "";
if (urlParts.hostPart !== baseUrlParts.hostPart) {
    return "";
}
max = Math.max(baseUrlParts.directories.length, urlParts.directories.length);
for(i = 0; i < max; i++) {
    if (baseUrlParts.directories[i] !== urlParts.directories[i]) { break; }
}
baseUrlDirectories = baseUrlParts.directories.slice(i);
urlDirectories = urlParts.directories.slice(i);
for(i = 0; i < baseUrlDirectories.length-1; i++) {
    diff += "../";
}
for(i = 0; i < urlDirectories.length-1; i++) {
    diff += urlDirectories[i] + "/";
}
return diff;

}

function getXMLHttpRequest() {

if (window.XMLHttpRequest && (window.location.protocol !== "file:" || !window.ActiveXObject)) {
    return new XMLHttpRequest();
} else {
    try {
        /*global ActiveXObject */
        return new ActiveXObject("Microsoft.XMLHTTP");
    } catch (e) {
        log("browser doesn't support AJAX.", logLevel.errors);
        return null;
    }
}

}

function doXHR(url, type, callback, errback) {

var xhr = getXMLHttpRequest();
var async = isFileProtocol ? less.fileAsync : less.async;

if (typeof(xhr.overrideMimeType) === 'function') {
    xhr.overrideMimeType('text/css');
}
log("XHR: Getting '" + url + "'", logLevel.debug);
xhr.open('GET', url, async);
xhr.setRequestHeader('Accept', type || 'text/x-less, text/css; q=0.9, */*; q=0.5');
xhr.send(null);

function handleResponse(xhr, callback, errback) {
    if (xhr.status >= 200 && xhr.status < 300) {
        callback(xhr.responseText,
            xhr.getResponseHeader("Last-Modified"));
    } else if (typeof(errback) === 'function') {
        errback(xhr.status, url);
    }
}

if (isFileProtocol && !less.fileAsync) {
    if (xhr.status === 0 || (xhr.status >= 200 && xhr.status < 300)) {
        callback(xhr.responseText);
    } else {
        errback(xhr.status, url);
    }
} else if (async) {
    xhr.onreadystatechange = function () {
        if (xhr.readyState == 4) {
            handleResponse(xhr, callback, errback);
        }
    };
} else {
    handleResponse(xhr, callback, errback);
}

}

function loadFile(originalHref, currentFileInfo, callback, env, modifyVars) {

if (currentFileInfo && currentFileInfo.currentDirectory && !/^([a-z-]+:)?\//.test(originalHref)) {
    originalHref = currentFileInfo.currentDirectory + originalHref;
}

// sheet may be set to the stylesheet for the initial load or a collection of properties including
// some env variables for imports
var hrefParts = extractUrlParts(originalHref, window.location.href);
var href      = hrefParts.url;
var newFileInfo = {
    currentDirectory: hrefParts.path,
    filename: href
};

if (currentFileInfo) {
    newFileInfo.entryPath = currentFileInfo.entryPath;
    newFileInfo.rootpath = currentFileInfo.rootpath;
    newFileInfo.rootFilename = currentFileInfo.rootFilename;
    newFileInfo.relativeUrls = currentFileInfo.relativeUrls;
} else {
    newFileInfo.entryPath = hrefParts.path;
    newFileInfo.rootpath = less.rootpath || hrefParts.path;
    newFileInfo.rootFilename = href;
    newFileInfo.relativeUrls = env.relativeUrls;
}

if (newFileInfo.relativeUrls) {
    if (env.rootpath) {
        newFileInfo.rootpath = extractUrlParts(env.rootpath + pathDiff(hrefParts.path, newFileInfo.entryPath)).path;
    } else {
        newFileInfo.rootpath = hrefParts.path;
    }
}

if (env.useFileCache && fileCache[href]) {
    try {
        var lessText = fileCache[href];
        callback(null, lessText, href, newFileInfo, { lastModified: new Date() });
    } catch (e) {
        callback(e, null, href);
    }
    return;
}

doXHR(href, env.mime, function (data, lastModified) {
    // per file cache
    fileCache[href] = data;

    // Use remote copy (re-parse)
    try {
        callback(null, data, href, newFileInfo, { lastModified: lastModified });
    } catch (e) {
        callback(e, null, href);
    }
}, function (status, url) {
    callback({ type: 'File', message: "'" + url + "' wasn't found (" + status + ")" }, null, href);
});

}

function loadStyleSheet(sheet, callback, reload, remaining, modifyVars) {

var env = new less.tree.parseEnv(less);
env.mime = sheet.type;

if (modifyVars || less.globalVars) {
    env.useFileCache = true;
}

loadFile(sheet.href, null, function(e, data, path, newFileInfo, webInfo) {

    if (webInfo) {
        webInfo.remaining = remaining;

        var css       = cache && cache.getItem(path),
            timestamp = cache && cache.getItem(path + ':timestamp');

        if (!reload && timestamp && webInfo.lastModified &&
            (new(Date)(webInfo.lastModified).valueOf() ===
                new(Date)(timestamp).valueOf())) {
            // Use local copy
            createCSS(css, sheet);
            webInfo.local = true;
            callback(null, null, data, sheet, webInfo, path);
            return;
        }
    }

    //TODO add tests around how this behaves when reloading
    removeError(path);

    if (data) {
        env.currentFileInfo = newFileInfo;
        new(less.Parser)(env).parse(data, function (e, root) {
            if (e) { return callback(e, null, null, sheet); }
            try {
                callback(e, root, data, sheet, webInfo, path);
            } catch (e) {
                callback(e, null, null, sheet);
            }
        }, {modifyVars: modifyVars, globalVars: less.globalVars});
    } else {
        callback(e, null, null, sheet, webInfo, path);
    }
}, env, modifyVars);

}

function loadStyleSheets(callback, reload, modifyVars) {

for (var i = 0; i < less.sheets.length; i++) {
    loadStyleSheet(less.sheets[i], callback, reload, less.sheets.length - (i + 1), modifyVars);
}

}

function initRunningMode(){

if (less.env === 'development') {
    less.optimization = 0;
    less.watchTimer = setInterval(function () {
        if (less.watchMode) {
            loadStyleSheets(function (e, root, _, sheet, env) {
                if (e) {
                    error(e, sheet.href);
                } else if (root) {
                    var styles = root.toCSS(less);
                    styles = postProcessCSS(styles);
                    createCSS(styles, sheet, env.lastModified);
                }
            });
        }
    }, less.poll);
} else {
    less.optimization = 3;
}

}

// // Watch mode // less.watch = function () {

if (!less.watchMode ){
    less.env = 'development';
     initRunningMode();
}
this.watchMode = true;
return true;

};

less.unwatch = function () {clearInterval(less.watchTimer); this.watchMode = false; return false; };

if (/!watch/.test(location.hash)) {

less.watch();

}

if (less.env != ‘development’) {

try {
    cache = (typeof(window.localStorage) === 'undefined') ? null : window.localStorage;
} catch (_) {}

}

// // Get all <link> tags with the ‘rel’ attribute set to “stylesheet/less” // var links = document.getElementsByTagName(‘link’);

less.sheets = [];

for (var i = 0; i < links.length; i++) {

if (links[i].rel === 'stylesheet/less' || (links[i].rel.match(/stylesheet/) &&
   (links[i].type.match(typePattern)))) {
    less.sheets.push(links[i]);
}

}

// // With this function, it’s possible to alter variables and re-render // CSS without reloading less-files // less.modifyVars = function(record) {

less.refresh(false, record);

};

less.refresh = function (reload, modifyVars) {

var startTime, endTime;
startTime = endTime = new Date();

loadStyleSheets(function (e, root, _, sheet, env) {
    if (e) {
        return error(e, sheet.href);
    }
    if (env.local) {
        log("loading " + sheet.href + " from cache.", logLevel.info);
    } else {
        log("parsed " + sheet.href + " successfully.", logLevel.debug);
        var styles = root.toCSS(less);
        styles = postProcessCSS(styles);
        createCSS(styles, sheet, env.lastModified);
    }
    log("css for " + sheet.href + " generated in " + (new Date() - endTime) + 'ms', logLevel.info);
    if (env.remaining === 0) {
        log("less has finished. css generated in " + (new Date() - startTime) + 'ms', logLevel.info);
    }
    endTime = new Date();
}, reload, modifyVars);

loadStyles(modifyVars);

};

less.refreshStyles = loadStyles;

less.Parser.fileLoader = loadFile;

less.refresh(less.env === ‘development’);