/**
Virtual rollup of the `template-base` and `template-micro` modules.

@module template
@main template
@since 3.8.0
**/

/**
Provides a generic API for using template engines such as Handlebars and
`Y.Template.Micro`.

@module template
@submodule template-base
@since 3.8.0
**/

/**
Provides a generic API for using template engines such as Handlebars and
`Y.Template.Micro`.

### Examples

Using with `Y.Template.Micro` (the default template engine):

    YUI().use('template', function (Y) {
        var micro = new Y.Template(),
            html  = micro.render('<%= data.message %>', {message: 'hello!'});

        // ...
    });

Using with Handlebars:

    YUI().use('template-base', 'handlebars', function (Y) {
        var handlebars = new Y.Template(Y.Handlebars),
            html       = handlebars.render('{{message}}', {message: 'hello!'});

        // ...
    });

@class Template
@param {Mixed} [engine=Y.Template.Micro] Template engine to use, such as
    `Y.Template.Micro` or `Y.Handlebars`. Defaults to `Y.Template.Micro` if not
    specified.
@param {Object} [defaults] Default options to use when instance methods are
    invoked.
@constructor
@since 3.8.0
**/

function Template(engine, defaults) {
    /**
    Default options.

    @property {Object} defaults
    @since 3.8.1
    **/
    this.defaults = defaults;

    /**
    Template engine class.

    @property {Mixed} engine
    @since 3.8.0
    **/
    this.engine = engine || Y.Template.Micro;

    if (!this.engine) {
        Y.error('No template engine loaded.');
    }
}

/**
Registry that maps template names to revived template functions.

@property _registry
@type Object
@static
@protected
@since 3.12.0
**/
Template._registry = {};

/**
Registers a pre-compiled template into the central template registry with a
given template string, allowing that template to be called and rendered by
that name using the `Y.Template.render()` static method.

For example, given the following simple Handlebars template, in `foo.hbs`:
@example
    <p>{{tagline}}</p>

It can be precompiled using the Handlebars CLI, and added into a YUI module
in the following way. Alternatively, `locator` can be used to automate this
process for you:
@example
    YUI.add('templates-foo', function (Y) {

        var engine = new Y.Template(Y.Handlebars),
            precompiled;

        precompiled = // Long precompiled template function here //

        Y.Template.register('foo', engine.revive(precompiled));

    }, '0.0.1', {requires: ['template-base', 'handlebars-base']});

See the `Y.Template#render` method to see how a registered template is used.

@method register
@param {String} templateName The template name.
@param {Function} template The function that returns the rendered string. The
    function should take the following parameters. If a pre-compiled template
    does not accept these parameters, it is up to the developer to normalize it.
  @param {Object} [template.data] Data object to provide when rendering the
    template.
  @param {Object} [template.options] Options to pass along to the template
    engine. See template engine docs for options supported by each engine.
@return {Function} revivedTemplate This is the same function as in `template`,
    and is done to maintain compatibility with the `Y.Template#revive()` method.
@static
@since 3.12.0
**/
Template.register = function (templateName, template) {
    Template._registry[templateName] = template;
    return template;
};

/**
Returns the registered template function, given the template name. If an
unregistered template is accessed, this will return `undefined`.

@method get
@param {String} templateName The template name.
@return {Function} revivedTemplate The revived template function, or `undefined`
    if it has not been registered.
@static
@since 3.12.0
**/

Template.get = function (templateName) {
    return Template._registry[templateName];
}

/**
Renders a template into a string, given the registered template name and data
to be interpolated. The template name must have been registered previously with
`register()`.

Once the template has been registered and built into a YUI module, it can be
listed as a dependency for any other YUI module. Continuing from the above
example, the registered template can be used in the following way:

@example
    YUI.add('bar', function (Y) {

        var html = Y.Template.render('foo', {
            tagline: '"bar" is now template language agnostic'
        });

    }, '0.0.1', {requires: ['template-base', 'templates-foo']});

The template can now be used without having to know which specific rendering
engine generated it.

@method render
@param {String} templateName The abstracted name to reference the template.
@param {Object} [data] The data to be interpolated into the template.
@param {Object} [options] Any additional options to be passed into the template.
@return {String} output The rendered result.
@static
@since 3.12.0
**/
Template.render = function (templateName, data, options) {
    var template = Template._registry[templateName],
        result   = '';

    if (template) {
        result = template(data, options);
    } else {
        Y.error('Unregistered template: "' + templateName + '"');
    }

    return result;
};

Template.prototype = {
    /**
    Compiles a template with the current template engine and returns a compiled
    template function.

    @method compile
    @param {String} text Template text to compile.
    @param {Object} [options] Options to pass along to the template engine. See
        template engine docs for options supported by each engine.
    @return {Function} Compiled template function.
    @since 3.8.0
    **/
    compile: function (text, options) {
        options = options ? Y.merge(this.defaults, options) : this.defaults;
        return this.engine.compile(text, options);
    },

    /**
    Precompiles a template with the current template engine and returns a string
    containing JavaScript source code for the precompiled template.

    @method precompile
    @param {String} text Template text to compile.
    @param {Object} [options] Options to pass along to the template engine. See
        template engine docs for options supported by each engine.
    @return {String} Source code for the precompiled template.
    @since 3.8.0
    **/
    precompile: function (text, options) {
        options = options ? Y.merge(this.defaults, options) : this.defaults;
        return this.engine.precompile(text, options);
    },

    /**
    Compiles and renders a template with the current template engine in a single
    step, and returns the rendered result.

    @method render
    @param {String} text Template text to render.
    @param {Object} data Data object to provide when rendering the template.
    @param {Object} [options] Options to pass along to the template engine. See
        template engine docs for options supported by each engine.
    @return {String} Rendered result.
    @since 3.8.0
    **/
    render: function (text, data, options) {
        options = options ? Y.merge(this.defaults, options) : this.defaults;

        if (this.engine.render) {
            return this.engine.render(text, data, options);
        }

        return this.engine.compile(text, options)(data, options);
    },

    /**
    Revives a precompiled template function into an executable template function
    using the current template engine. The precompiled code must already have
    been evaluated; this method won't evaluate it for you.

    @method revive
    @param {Function} precompiled Precompiled template function.
    @param {Object} [options] Options to pass along to the template engine. See
        template engine docs for options supported by each engine.
    @return {Function} Compiled template function.
    @since 3.8.0
    **/
    revive: function (precompiled, options) {
        options = options ? Y.merge(this.defaults, options) : this.defaults;

        return this.engine.revive ? this.engine.revive(precompiled, options) :
                precompiled;
    }
};

// Copy existing namespaced properties from Y.Template to the Template function
// if Y.Template already exists, then make the function the new Y.Template.
// This ensures that other modules can safely add stuff to the Y.Template
// namespace even if they're loaded before this one.
Y.Template = Y.Template ? Y.mix(Template, Y.Template) : Template;
