/**
 * @class mw.template
 * @singleton
 */
( function ( mw, $ ) {
	var compiledTemplates = {},
		compilers = {};

	mw.template = {
		/**
		 * Register a new compiler and template.
		 *
		 * @param {string} name of compiler. Should also match with any file extensions of templates that want to use it.
		 * @param {Function} compiler which must implement a compile function
		 */
		registerCompiler: function ( name, compiler ) {
			if ( !compiler.compile ) {
				throw new Error( 'Compiler must implement compile method.' );
			}
			compilers[ name ] = compiler;
		},

		/**
		 * Get the name of the compiler associated with a template based on its name.
		 *
		 * @param {string} templateName Name of template (including file suffix)
		 * @return {String} Name of compiler
		 */
		getCompilerName: function ( templateName ) {
			var templateParts = templateName.split( '.' );

			if ( templateParts.length < 2 ) {
				throw new Error( 'Unable to identify compiler. Template name must have a suffix.' );
			}
			return templateParts[ templateParts.length - 1 ];
		},

		/**
		 * Get the compiler for a given compiler name.
		 *
		 * @param {string} compilerName Name of the compiler
		 * @return {Object} The compiler associated with that name
		 */
		getCompiler: function ( compilerName ) {
			var compiler = compilers[ compilerName ];
			if ( !compiler ) {
				throw new Error( 'Unknown compiler ' + compilerName );
			}
			return compiler;
		},

		/**
		 * Register a template associated with a module.
		 *
		 * Compiles the newly added template based on the suffix in its name.
		 *
		 * @param {string} moduleName Name of ResourceLoader module to get the template from
		 * @param {string} templateName Name of template to add including file extension
		 * @param {string} templateBody Contents of a template (e.g. html markup)
		 * @return {Function} Compiled template
		 */
		add: function ( moduleName, templateName, templateBody ) {
			var compiledTemplate,
				compilerName = this.getCompilerName( templateName );

			if ( !compiledTemplates[ moduleName ] ) {
				compiledTemplates[ moduleName ] = {};
			}

			compiledTemplate = this.compile( templateBody, compilerName );
			compiledTemplates[ moduleName ][ templateName ] = compiledTemplate;
			return compiledTemplate;
		},

		/**
		 * Retrieve a template by module and template name.
		 *
		 * @param {string} moduleName Name of the module to retrieve the template from
		 * @param {string} templateName Name of template to be retrieved
		 * @return {Object} Compiled template
		 */
		get: function ( moduleName, templateName ) {
			var moduleTemplates, compiledTemplate;

			// Check if the template has already been compiled, compile it if not
			if ( !compiledTemplates[ moduleName ] || !compiledTemplates[ moduleName ][ templateName ] ) {
				moduleTemplates = mw.templates.get( moduleName );
				if ( !moduleTemplates || !moduleTemplates[ templateName ] ) {
					throw new Error( 'Template ' + templateName + ' not found in module ' + moduleName );
				}

				// Add compiled version
				compiledTemplate = this.add( moduleName, templateName, moduleTemplates[ templateName ] );
			} else {
				compiledTemplate = compiledTemplates[ moduleName ][ templateName ];
			}
			return compiledTemplate;
		},

		/**
		 * Wrap our template engine of choice.
		 *
		 * @param {string} templateBody Template body
		 * @param {string} compilerName The name of a registered compiler
		 * @return {Object} Template interface
		 */
		compile: function ( templateBody, compilerName ) {
			return this.getCompiler( compilerName ).compile( templateBody );
		}
	};

	// Register basic html compiler
	mw.template.registerCompiler( 'html', {
		compile: function ( src ) {
			return {
				render: function () {
					return $( $.parseHTML( $.trim( src ) ) );
				}
			};
		}
	} );

}( mediaWiki, jQuery ) );
