// Marionette.Handlebars, v2.0.0
// Copyright (c) 2015-2016 Michael Heim, Zeilenwechsel.de
// Distributed under MIT license
// http://github.com/hashchange/marionette.handlebars

;( function ( root, factory ) {
    "use strict";

    // UMD for a Marionette plugin. Supports AMD, Node.js, CommonJS and globals.
    //
    // - Code lives in the Marionette namespace.
    // - The module does not export a meaningful value.
    // - The module does not create a global.

    var supportsExports = typeof exports === "object" && exports && !exports.nodeType && typeof module === "object" && module && !module.nodeType;

    // AMD:
    // - Some AMD build optimizers like r.js check for condition patterns like the AMD check below, so keep it as is.
    // - Check for `exports` after `define` in case a build optimizer adds an `exports` object.
    // - The AMD spec requires the dependencies to be an array **literal** of module IDs. Don't use a variable there,
    //   or optimizers may fail.
    if ( typeof define === "function" && typeof define.amd === "object" && define.amd ) {

        // AMD module
        define( [ "exports", "underscore", "backbone", "handlebars", "marionette" ], factory );

    } else if ( supportsExports ) {

        // Node module, CommonJS module
        factory( exports, require( "underscore" ), require( "backbone" ), require( "handlebars" ), require( "marionette" ) );

    } else  {

        // Global (browser or Rhino)
        factory( {}, _, Backbone, Handlebars );

    }

}( this, function ( exports, _, Backbone, Handlebars ) {
    "use strict";

    var origLoadTemplate,
        Marionette = Backbone.Marionette;

    if ( ! Marionette ) throw new Error( "Load error: Backbone.Marionette is not available" );

    origLoadTemplate = Marionette.TemplateCache.prototype.loadTemplate;

    /** @type {boolean}  flag allowing the lazy loading of compiled templates */
    Marionette.TemplateCache.allowCompiledTemplatesOverHttp = false;

    Marionette.TemplateCache.MarionetteHandlebarsVersion = "2.0.0";

    _.extend( Marionette.TemplateCache.prototype, {

        /**
         * Loads and returns a template from the Handlebars cache, the DOM, or the server (if set up to do so). Throws
         * an error if the template can't be found.
         *
         * Unlike the original Marionette implementation, this version of loadTemplate does not always return the raw
         * template HTML. If the template is fetched from the Handlebars cache of precompiled templates, it is returned
         * as is, ie as a compiled template function.
         *
         * That does not cause issues in Marionette.TemplateCache, as long as the compileTemplate function can handle it
         * correctly. (And here, it obviously does.)
         *
         * If a template can't be found in either the Handlebars cache or the DOM, the job is passed on to the
         * lazyLoadTemplate() method. It is a noop by default. To use an actual loader and make it work for your needs,
         * assign your own implementation to Marionette.TemplateCache.prototype.lazyLoadTemplate.
         *
         * @param   {string}           templateId  a selector, usually, or the file ID if the Handlebars cache is used
         * @param   {Object|undefined} options
         * @returns {string|Function}
         */
        loadTemplate: function ( templateId, options ) {
            var templateHtml,
                precompiledTemplate = this.getPrecompiledTemplate( templateId );

            if ( ! precompiledTemplate || ! _.isFunction( precompiledTemplate ) ) {
                try {
                    templateHtml = origLoadTemplate.call( this, templateId, options );
                } catch ( err ) {}

                if ( ! isValidTemplateHtml( templateHtml ) ) templateHtml = this.lazyLoadTemplate( templateId, options );

                // Throw an error if the template is missing, just like the original implementation.
                if ( ! isValidTemplateReturnValue( templateHtml ) ) this.throwTemplateError( templateId );
            }

            return templateHtml || precompiledTemplate;
        },

        /**
         * Returns the compiled template.
         *
         * Unlike the original Marionette implementation, the method does not just accept a raw template HTML string as
         * its first argument, but an existing precompiled template as well. Such a template function is simply returned
         * as it is.
         *
         * @param   {string|Function}  template
         * @param   {Object|undefined} options
         * @returns {Function}
         */
        compileTemplate: function ( template, options ) {
            return _.isFunction( template ) ? template : Handlebars.compile( template, options );
        },

        /**
         * Returns the precompiled Handlebars template for a given template ID, if it exists.
         *
         * NB In this case, the template ID is not a selector, but derived from the file name of the original template.
         * See http://handlebarsjs.com/precompilation.html
         *
         * Override it if you have to perform some special magic for matching a Marionette templateId to the templateId
         * of the Handlebars cache.
         *
         * @param   {string} templateId
         * @returns {Function|undefined}
         */
        getPrecompiledTemplate: function ( templateId ) {
            return Handlebars.templates && Handlebars.templates[templateId];
        },

        /**
         * Lazy-load the template.
         *
         * Noop by default. Provide your own loader by implementing this method. It must return the templateHtml if
         * successful, or undefined otherwise. And it MUST NOT be async (set async: false in an $.ajax() call).
         *
         * @param   {string}           templateId
         * @param   {Object|undefined} options
         * @returns {string|undefined}
         */
        lazyLoadTemplate: function ( templateId, options ) {
            return undefined;
        },

        /**
         * Throws a NoTemplateError in a way which is compatible with any Marionette version.
         *
         * @param {string} templateId
         */
        throwTemplateError: function ( templateId ) {

            var errType = 'NoTemplateError',
                errMsg = 'Could not load template: "' + templateId + '". It does not exist, is of an illegal type, or has content which cannot be processed.';

            if ( Marionette.Error ) {
                // Error handling in Marionette 2.x
                throw new Marionette.Error( { name: errType, message: errMsg } );
            } else if ( typeof throwError === "function" ) {
                // Error handling in Marionette 1.x
                throwError( errMsg, errType );                                              // jshint ignore:line
            } else {
                // Being future proof, we throw our own errors if all else has failed
                throw new Error( errMsg );
            }

        }

    } );

    /**
     * Checks if the template data is a non-empty string.
     *
     * @param   {*} templateData
     * @returns {boolean}
     */
    function isValidTemplateHtml ( templateData ) {
        return _.isString( templateData ) && templateData.length > 0;
    }

    /**
     * Checks if the template data is a valid return value for loadTemplate().
     *
     * - A non-empty string always passes the test. This is the format of raw template HTML.
     * - A function may or may not be acceptable, depending on the allowCompiledTemplatesOverHttp flag.
     *
     * @param   {*} templateData
     * @returns {boolean}
     */
    function isValidTemplateReturnValue ( templateData ) {
        return isValidTemplateHtml( templateData ) || Marionette.TemplateCache.allowCompiledTemplatesOverHttp && _.isFunction( templateData );
    }


    // Module return value
    // -------------------
    //
    // A return value may be necessary for AMD to detect that the module is loaded. It ony exists for that reason and is
    // purely symbolic. Don't use it in client code. The functionality of this module lives in the Backbone namespace.
    exports.info = "Marionette.Handlebars has loaded. Don't use the exported value of the module. Its functionality is available inside the Backbone namespace.";

} ) );