/**
 * @license Handlebars hbs 0.4.0 - Alex Sexton, but Handlebars has it's own licensing junk
 *
 * Available via the MIT or new BSD license.
 * see: http://github.com/jrburke/require-cs for details on the plugin this was based off of
 */

/* Yes, deliciously evil. */
/*jslint evil: true, strict: false, plusplus: false, regexp: false */
/*global require: false, XMLHttpRequest: false, ActiveXObject: false,
define: false, process: false, window: false */
define([
    //>>excludeStart('excludeHbs', pragmas.excludeHbs)
    'handlebars', 'underscore', 'i18nprecompile'
    //>>excludeEnd('excludeHbs')
], function(
    //>>excludeStart('excludeHbs', pragmas.excludeHbs)
    Handlebars, _, precompile
    //>>excludeEnd('excludeHbs')
) {
    //>>excludeStart('excludeHbs', pragmas.excludeHbs)
    var fs, getXhr,
        progIds = ['Msxml2.XMLHTTP', 'Microsoft.XMLHTTP', 'Msxml2.XMLHTTP.4.0'],
        fetchText = function() {
            throw new Error('Environment unsupported.');
        },
        buildMap = [],
        filecode = "w+",
        templateExtension = "hbs",
        customNameExtension = "@hbs",
        devStyleDirectory = "/styles/",
        buildStyleDirectory = "/demo-build/styles/",
        helperDirectory = "template/helpers/",
        i18nDirectory = "template/i18n/",
        buildCSSFileName = "screen.build.css";

    Handlebars.registerHelper('$', function() {
        //placeholder for translation helper
    });

    if (typeof window !== "undefined" && window.navigator && window.document && !window.navigator.userAgent.match(/Node.js/)) {
        // Browser action
        getXhr = function() {
            //Would love to dump the ActiveX crap in here. Need IE 6 to die first.
            var xhr, i, progId;
            if (typeof XMLHttpRequest !== "undefined") {
                return new XMLHttpRequest();
            } else {
                for (i = 0; i < 3; i++) {
                    progId = progIds[i];
                    try {
                        xhr = new ActiveXObject(progId);
                    } catch (e) {}

                    if (xhr) {
                        progIds = [progId]; // so faster next time
                        break;
                    }
                }
            }

            if (!xhr) {
                throw new Error("getXhr(): XMLHttpRequest not available");
            }

            return xhr;
        };

        fetchText = function(url, callback) {
            var prependComment = "<!--START: " + url + "--> == <!--END: " + url + "-->";
            var xhr = getXhr();
            xhr.open('GET', url, true);
            xhr.onreadystatechange = function(evt) {
                //Do not explicitly handle errors, those should be
                //visible via console output in the browser.
                if (xhr.readyState === 4) {
                    callback(xhr.responseText);
                    //TODO FIXME Gautam:Not working with backbone-forms when it does this : $($.trim(this.template(_.result(this, 'templateData'))));
                    // callback(prependComment.replace('==',xhr.responseText));
                }
            };
            xhr.send(null);
        };

    } else if (typeof process !== "undefined" &&
        process.versions &&
        !!process.versions.node) {
        //Using special require.nodeRequire, something added by r.js.
        fs = require.nodeRequire('fs');
        fetchText = function(path, callback) {
            var body = fs.readFileSync(path, 'utf8') || "";
            // we need to remove BOM stuff from the file content
            body = body.replace(/^\uFEFF/, '');
            callback(body);
        };
    } else if (typeof java !== "undefined" && typeof java.io !== "undefined") {
        fetchText = function(path, callback) {
            var f = new java.io.File(path);
            var is = new java.io.FileReader(f);
            var reader = new java.io.BufferedReader(is);
            var line;
            var text = "";
            while ((line = reader.readLine()) !== null) {
                text += new String(line) + "\n";
            }
            reader.close();
            callback(text);
        };
    }

    var cache = {};
    var fetchOrGetCached = function(path, callback) {
        if (cache[path]) {
            callback(cache[path]);
        } else {
            fetchText(path, function(data) {
                cache[path] = data;
                callback.call(this, data);
            });
        }
    };
    var styleList = [],
        styleMap = {};
    //>>excludeEnd('excludeHbs')

    return {

        get: function() {
            return Handlebars;
        },

        write: function(pluginName, name, write) {

            if ((name + customNameExtension) in buildMap) {
                var text = buildMap[name + customNameExtension];
                write.asModule(pluginName + "!" + name, text);
            }
        },

        version: '0.4.0',

        load: function(name, parentRequire, load, config) {
            //>>excludeStart('excludeHbs', pragmas.excludeHbs)

            var compiledName = name + customNameExtension,
                disableI18n = (config.hbs && config.hbs.disableI18n),
                partialDeps = [];

            function recursiveNodeSearch(statements, res) {
                _(statements).forEach(function(statement) {
                    if (statement && statement.type && statement.type === 'partial') {
                        res.push(statement.partialName.name);
                    }
                    if (statement && statement.program && statement.program.statements) {
                        recursiveNodeSearch(statement.program.statements, res);
                    }
                    if (statement && statement.program && statement.program.inverse && statement.program.inverse.statements) {
                        recursiveNodeSearch(statement.program.inverse.statements, res);
                    }
                });
                return res;
            }

            // TODO :: use the parser to do this!
            function findPartialDeps(nodes) {
                var res = [];
                if (nodes && nodes.statements) {
                    res = recursiveNodeSearch(nodes.statements, []);
                }
                return _(res).unique();
            }

            // See if the first item is a comment that's json
            function getMetaData(nodes) {
                var statement, res, test;
                if (nodes && nodes.statements) {
                    statement = nodes.statements[0];
                    if (statement && statement.type === "comment") {
                        try {
                            res = (statement.comment).replace(new RegExp('^[\\s]+|[\\s]+$', 'g'), '');
                            test = JSON.parse(res);
                            return res;
                        } catch (e) {
                            return "{}";
                        }
                    }
                }
                return "{}";
            }

            function composeParts(parts) {
                if (!parts) {
                    return [];
                }
                var res = [parts[0]],
                    cur = parts[0],
                    i;

                for (i = 1; i < parts.length; ++i) {
                    if (parts.hasOwnProperty(i)) {
                        cur += "." + parts[i];
                        res.push(cur);
                    }
                }
                return res;
            }

            function recursiveVarSearch(statements, res, prefix, helpersres) {
                prefix = prefix ? prefix + "." : "";

                var newprefix = "",
                    flag = false;

                // loop through each statement
                _(statements).forEach(function(statement) {
                    var parts, part, sideways;

                    // if it's a mustache block
                    if (statement && statement.type && statement.type === 'mustache') {

                        // If it has params, the first part is a helper or something
                        if (!statement.params || !statement.params.length) {
                            parts = composeParts(statement.id.parts);
                            for (part in parts) {
                                if (parts[part]) {
                                    newprefix = parts[part] || newprefix;
                                    res.push(prefix + parts[part]);
                                }
                            }
                            res.push(prefix + statement.id.string);
                        }

                        var paramsWithoutParts = ['this', '.', '..', './..', '../..', '../../..'];

                        // grab the params
                        if (statement.params && typeof Handlebars.helpers[statement.id.string] === 'undefined') {
                            _(statement.params).forEach(function(param) {
                                if (_(paramsWithoutParts).contains(param.original) || param instanceof Handlebars.AST.StringNode || param instanceof Handlebars.AST.IntegerNode || param instanceof Handlebars.AST.BooleanNode) {
                                    helpersres.push(statement.id.string);
                                }

                                parts = composeParts(param.parts);

                                for (var part in parts) {
                                    if (parts[part]) {
                                        newprefix = parts[part] || newprefix;
                                        helpersres.push(statement.id.string);
                                        res.push(prefix + parts[part]);
                                    }
                                }
                            });
                        }
                    }

                    // If it's a meta block
                    if (statement && statement.mustache) {
                        recursiveVarSearch([statement.mustache], res, prefix + newprefix, helpersres);
                    }

                    // if it's a whole new program
                    if (statement && statement.program && statement.program.statements) {
                        sideways = recursiveVarSearch([statement.mustache], [], "", helpersres)[0] || "";
                        if (statement.program.inverse && statement.program.inverse.statements) {
                            recursiveVarSearch(statement.program.inverse.statements, res, prefix + newprefix + (sideways ? (prefix + newprefix) ? "." + sideways : sideways : ""), helpersres);
                        }
                        recursiveVarSearch(statement.program.statements, res, prefix + newprefix + (sideways ? (prefix + newprefix) ? "." + sideways : sideways : ""), helpersres);
                    }
                });
                return res;
            }

            // This finds the Helper dependencies since it's soooo similar
            function getExternalDeps(nodes) {
                var res = [];
                var helpersres = [];

                if (nodes && nodes.statements) {
                    res = recursiveVarSearch(nodes.statements, [], undefined, helpersres);
                }

                var defaultHelpers = ["helperMissing", "blockHelperMissing", "each", "if", "unless", "with"];

                return {
                    vars: _(res).chain().unique().map(function(e) {
                        if (e === "") {
                            return '.';
                        }
                        if (e.length && e[e.length - 1] === '.') {
                            return e.substr(0, e.length - 1) + '[]';
                        }
                        return e;
                    }).value(),
                    helpers: _(helpersres).chain().unique().map(function(e) {
                        if (_(defaultHelpers).contains(e)) {
                            return undefined;
                        }
                        return e;
                    }).compact().value()
                };
            }

            function fetchAndRegister(langMap) {
                fetchText(path, function(text) {
                    // for some reason it doesn't include hbs _first_ when i don't add it here...
                    var nodes = Handlebars.parse(text),
                        deps = findPartialDeps(nodes),
                        meta = getMetaData(nodes),
                        extDeps = getExternalDeps(nodes),
                        vars = extDeps.vars,
                        helps = extDeps.helpers || [],
                        depStr = deps.join("', 'hbs!").replace(/_/g, '/'),
                        helpDepStr = config.hbs && config.hbs.disableHelpers ?
                        "" : (function() {
                            var i, paths = [],
                                pathGetter = config.hbs && config.hbs.helperPathCallback ? config.hbs.helperPathCallback : function(name) {
                                    return (config.hbs && config.hbs.helperDirectory ? config.hbs.helperDirectory : helperDirectory) + name;
                                };

                            for (i = 0; i < helps.length; i++) {
                                paths[i] = "'" + pathGetter(helps[i], path) + "'"
                            }
                            return paths;
                        })().join(','),
                        debugOutputStart = "",
                        debugOutputEnd = "",
                        debugProperties = "",
                        metaObj, head, linkElem;

                    if (depStr) {
                        depStr = ",'hbs!" + depStr + "'";
                    }
                    if (helpDepStr) {
                        helpDepStr = "," + helpDepStr;
                    }

                    if (meta !== "{}") {
                        try {
                            metaObj = JSON.parse(meta);
                            if (metaObj && metaObj.styles) {
                                styleList = _.union(styleList, metaObj.styles);

                                // In dev mode in the browser
                                if (require.isBrowser && !config.isBuild) {
                                    head = document.head || document.getElementsByTagName('head')[0];
                                    _(metaObj.styles).forEach(function(style) {
                                        if (!styleMap[style]) {
                                            linkElem = document.createElement('link');
                                            linkElem.href = config.baseUrl + devStyleDirectory + style + '.css';
                                            linkElem.media = 'all';
                                            linkElem.rel = 'stylesheet';
                                            linkElem.type = 'text/css';
                                            head.appendChild(linkElem);
                                            styleMap[style] = linkElem;
                                        }
                                    });
                                } else if (config.isBuild) {
                                    (function() {
                                        var fs = require.nodeRequire('fs'),
                                            str = _(metaObj.styles).map(function(style) {
                                                if (!styleMap[style]) {
                                                    styleMap[style] = true;
                                                    return "@import url(" + style + ".css);\n";
                                                }
                                                return "";
                                            }).join("\n");

                                        // I write out my import statements to a file in order to help me build stuff.
                                        // Then I use a tool to inline my import statements afterwards. (you can run r.js on it too)
                                        fs.open(__dirname + buildStyleDirectory + buildCSSFileName, filecode, '0666', function(e, id) {
                                            fs.writeSync(id, str, null, encoding = 'utf8');
                                            fs.close(id);
                                        });
                                        filecode = "a";
                                    })();
                                }
                            }
                        } catch (e) {
                            // Error injecting styles
                        }
                    }

                    if (!config.isBuild && !config.serverRender) {
                        debugOutputStart = "<!-- START - " + name + " -->";
                        debugOutputEnd = "<!-- END - " + name + " -->";
                        debugProperties = "t.meta = " + meta + ";\n" +
                            "t.helpers = " + JSON.stringify(helps) + ";\n" +
                            "t.deps = " + JSON.stringify(deps) + ";\n" +
                            "t.vars = " + JSON.stringify(vars) + ";\n";
                    }

                    var mapping = disableI18n ? false : _.extend(langMap, config.localeMapping),
                        configHbs = config.hbs || {},
                        options = _.extend(configHbs.compileOptions || {}, {
                            originalKeyFallback: configHbs.originalKeyFallback
                        }),
                        prec = precompile(text, mapping, options);

                    text = "/* START_TEMPLATE */\n" +
                        "define(['hbs','handlebars'" + depStr + helpDepStr + "], function( hbs, Handlebars ){ \n" +
                        "var t = Handlebars.template(" + prec + ");\n" +
                        "Handlebars.registerPartial('" + name.replace(/\//g, '_') + "', t);\n" +
                        debugProperties +
                        "return t;\n" +
                        "});\n" +
                        "/* END_TEMPLATE */\n";

                    //Hold on to the transformed text if a build.
                    if (config.isBuild) {
                        buildMap[compiledName] = text;
                    }

                    //IE with conditional comments on cannot handle the
                    //sourceURL trick, so skip it if enabled.
                    /*@if (@_jscript) @else @*/
                    if (!config.isBuild) {
                        text += "\r\n//@ sourceURL=" + path;
                    }
                    /*@end@*/

                    for (var i in deps) {
                        if (deps.hasOwnProperty(i)) {
                            deps[i] = 'hbs!' + deps[i].replace(/_/g, '/');
                        }
                    }

                    if (!config.isBuild) {
                        require(deps, function() {
                            load.fromText(text);

                            //Give result to load. Need to wait until the module
                            //is fully parse, which will happen after this
                            //execution.
                            parentRequire([name], function(value) {
                                load(value);
                            });
                        });
                    } else {
                        load.fromText(name, text);

                        //Give result to load. Need to wait until the module
                        //is fully parse, which will happen after this
                        //execution.
                        parentRequire([name], function(value) {
                            load(value);
                        });
                    }

                    if (config.removeCombined) {
                        fs.unlinkSync(path);
                    }
                });
            }

            var path,
                omitExtension = config.hbs && config.hbs.templateExtension === false;
            if (omitExtension) {
                path = parentRequire.toUrl(name);
            } else {
                path = parentRequire.toUrl(name + '.' + (config.hbs && config.hbs.templateExtension ? config.hbs.templateExtension : templateExtension));
            }

            if (disableI18n) {
                fetchAndRegister(false);
            } else {
                // Workaround until jam is able to pass config info or we move i18n to a separate module.
                // This logs a warning and disables i18n if there's an error loading the language file
                var langMapPath = (config.hbs && config.hbs.i18nDirectory ? config.hbs.i18nDirectory : i18nDirectory) + (config.locale || "en_us") + '.json';
                try {
                    fetchOrGetCached(parentRequire.toUrl(langMapPath), function(langMap) {
                        fetchAndRegister(JSON.parse(langMap));
                    });
                } catch (er) {
                    // if there's no configuration at all, log a warning and disable i18n for this and subsequent templates
                    if (!config.hbs) {
                        console.warn('hbs: Error reading ' + langMapPath + ', disabling i18n. Ignore this if you\'re using jam, otherwise check your i18n configuration.\n');
                        config.hbs = {
                            disableI18n: true
                        };
                        fetchAndRegister(false);
                    } else {
                        throw er;

                    }
                }
            }
            //>>excludeEnd('excludeHbs')
        }
    };
});
/* END_hbs_PLUGIN */
