/* <copyright>
 This file contains proprietary software owned by Motorola Mobility, Inc.<br/>
 No rights, expressed or implied, whatsoever to this software are provided by Motorola Mobility, Inc. hereunder.<br/>
 (c) Copyright 2011 Motorola Mobility, Inc.  All Rights Reserved.
 </copyright> */

var FS = require("fs");
var PATH = require("path");

var MontageBoot = require("./montage");

var Require = require("./require/require");
var Promise = require("./core/promise");
var URL = require("./core/url");

var jsdom = require("jsdom").jsdom;
var Node = require("jsdom").level(1).Node;
var domToHtml = require("jsdom/lib/jsdom/browser/domtohtml").domToHtml;

exports.bootstrap = function (callback) {
    var command = process.argv.slice(0, 3);
    var args = process.argv.slice(2);
    var program = args.shift();
    FS.realpath(program, function (error, program) {
        if (error) {
            throw new Error(error);
        }
        findPackage(program, function (error, directory) {
            if (error === "Can't find package") {
                loadFreeModule(program, command, args);
            } else if (error) {
                throw new Error(error);
            } else {
                loadPackagedModule(directory, program, command, args);
            }
        });
    });
};

var findPackage = function (path, callback) {
    var directory = PATH.dirname(path);
    if (directory === path)
        return callback("Can't find package");
    var packageJson = PATH.join(directory, "package.json");
    FS.stat(path, function (error, stat) {
        if (error) callback(error);
        if (stat.isFile()) {
            callback(null, directory);
        } else {
            findPackage(directory, callback);
        }
    });
}

var loadFreeModule = function (program, command, args) {
    throw new Error("Can't load module that is not in a package");
};

var loadPackagedModule = function (directory, program, command, args) {
    MontageBoot.loadPackage(directory)
    .then(function (require) {
        var id = program.slice(directory.length + 1);
        return require.async(id);
    })
    .end();
};

MontageBoot.loadPackage = function (location, config) {
    var config = {};

    config.location = URL.resolve(Require.getLocation(), location + '/');

    // setup the reel loader
    config.makeLoader = function (config) {
        return MontageBoot.ReelLoader(
            config,
            Require.makeLoader(config)
        );
    };

    // setup serialization compiler
    config.makeCompiler = function (config) {
        return MontageBoot.TemplateCompiler(
            config,
            MontageBoot.SerializationCompiler(
                config,
                Require.makeCompiler(config)
            )
        );
    };

    return Require.loadPackage(config.location, config);
};

MontageBoot.TemplateLoader = function (config, load) {
    return function(id, module) {
        var html = id.match(/(.*\/)?(?=[^\/]+\.html$)/);
        var serialization = id.match(/(?=[^\/]+\.json$)/); // XXX this is not necessarily a strong indicator of a serialization alone
        if (html) {
            return load(id, module)
            .then(function () {
                module.dependencies = parseHtmlDependencies(module.text, module.location);
                return module;
            });
        } else if (serialization) {
            return load(id, module)
            .then(function () {
                module.dependencies = collectSerializationDependencies(module.text, []);
                return module;
            });
        } else {
            return load(id, module);
        }
    };
};

// add the TemplateLoader to the middleware chain
Require.makeLoader = (function (makeLoader) {
    return function (config) {
        return MontageBoot.TemplateLoader(config, makeLoader(config));
    };
})(Require.makeLoader);

var parseHtmlDependencies = function (text, location) {
    var dependencies = [];
    var document = jsdom(text, null, {
        "features": {
            "FetchExternalResources": false,
            "ProcessExternalResources": false
        }
    });
    collectHtmlDependencies(document, dependencies);
    return dependencies;
};

var collectHtmlDependencies = function (document, dependencies) {
    visit(document, function (element) {
        if (element.nodeType == Node.ELEMENT_NODE) {
            if (element.tagName === "SCRIPT") {
                if (element.getAttribute("type") === "text/montage-serialization") {
                    collectSerializationDependencies(getText(element), dependencies);
                }
            } else if (element.tagName === "LINK") {
                if (element.getAttribute("type") === "text/montage-serialization") {
                    dependencies.push(element.getAttribute("href"));
                }
            }
        }
    });
};

function visit(element, visitor) {
    var pruned;
    var prune = function () {
        pruned = true;
    };
    visitor(element, prune);
    if (pruned) {
        return;
    }
    element = element.firstChild;
    while (element) {
        visit(element, visitor);
        element = element.nextSibling;
    }
}

function getText(element) {
    return domToHtml(element._childNodes, true, true);
}

var collectSerializationDependencies = function (text, dependencies) {
    var serialization = JSON.parse(text);
    Object.keys(serialization).forEach(function (label) {
        var description = serialization[label];
        if (typeof description.module === "string") {
            dependencies.push(description.module);
        }
    });
    return dependencies;
};