text: var nunjucks = require('nunjucks');
  var Immutable = require('immutable');
  
  var TemplateEngine = Immutable.Record({
      // Map of {TemplateBlock}
      blocks:     Immutable.Map(),
  
      // Map of Extension
      extensions: Immutable.Map(),
  
      // Map of filters: {String} name -> {Function} fn
      filters:    Immutable.Map(),
  
      // Map of globals: {String} name -> {Mixed}
      globals:    Immutable.Map(),
  
      // Context for filters / blocks
      context:    Object(),
  
      // Nunjucks loader
      loader:     new nunjucks.FileSystemLoader('views')
  }, 'TemplateEngine');
  
  TemplateEngine.prototype.getBlocks = function() {
      return this.get('blocks');
  };
  
  TemplateEngine.prototype.getGlobals = function() {
      return this.get('globals');
  };
  
  TemplateEngine.prototype.getFilters = function() {
      return this.get('filters');
  };
  
  TemplateEngine.prototype.getShortcuts = function() {
      return this.get('shortcuts');
  };
  
  TemplateEngine.prototype.getLoader = function() {
      return this.get('loader');
  };
  
  TemplateEngine.prototype.getContext = function() {
      return this.get('context');
  };
  
  TemplateEngine.prototype.getExtensions = function() {
      return this.get('extensions');
  };
  
  /**
      Return a block by its name (or undefined)
  
      @param {String} name
      @return {TemplateBlock}
  */
  TemplateEngine.prototype.getBlock = function(name) {
      var blocks = this.getBlocks();
      return blocks.find(function(block) {
          return block.getName() === name;
      });
  };
  
  /**
      Return a nunjucks environment from this configuration
  
      @return {Nunjucks.Environment}
  */
  TemplateEngine.prototype.toNunjucks = function(blocksOutput) {
      var loader = this.getLoader();
      var blocks = this.getBlocks();
      var filters = this.getFilters();
      var globals = this.getGlobals();
      var extensions = this.getExtensions();
      var context = this.getContext();
  
      var env = new nunjucks.Environment(
          loader,
          {
              // Escaping is done after by the asciidoc/markdown parser
              autoescape: false,
  
              // Syntax
              tags: {
                  blockStart: '{%',
                  blockEnd: '%}',
                  variableStart: '{{',
                  variableEnd: '}}',
                  commentStart: '{###',
                  commentEnd: '###}'
              }
          }
      );
  
      // Add filters
      filters.forEach(function(filterFn, filterName) {
          env.addFilter(filterName, filterFn.bind(context));
      });
  
      // Add blocks
      blocks.forEach(function(block) {
          var extName = block.getExtensionName();
          var Ext = block.toNunjucksExt(context, blocksOutput);
  
          env.addExtension(extName, new Ext());
      });
  
      // Add globals
      globals.forEach(function(globalValue, globalName) {
          env.addGlobal(globalName, globalValue);
      });
  
      // Add other extensions
      extensions.forEach(function(ext, extName) {
          env.addExtension(extName, ext);
      });
  
      return env;
  };
  
  /**
      Create a template engine
  
      @param {Object} def
      @return {TemplateEngine}
  */
  TemplateEngine.create = function(def) {
      return new TemplateEngine({
          blocks:     Immutable.List(def.blocks || []),
          extensions: Immutable.Map(def.extensions || {}),
          filters:    Immutable.Map(def.filters || {}),
          globals:    Immutable.Map(def.globals || {}),
          context:    def.context,
          loader:     def.loader
      });
  };
  
  module.exports = TemplateEngine;
  