var CallbackManager = function() {
  var executeCommand,
      successHandler,
      blocks = {},
      handlers;
  
  // Execute a command that's passed in from the XHR response
  executeCommand = function(block, command) {    
    // Make sure the command actually exists before trying to execute it.
    var func = handlers[command.name];
    if (!Object.isFunction(func)) throw "Not a function: " + command.name;
    
    // Call the function
    func(block, command.parameters);
  };
  
  // Handles the response of the AJAX request
  successHandler = function(transport, block) {
    // We can access the JSON response directly because evalJSON is set
    // to true by default.
    var response = transport.responseJSON;
    
    // Call the executeCommand function for every command thats passed
    // in by the server. The object of the current iteration is automatically
    // passed in by the iterator.
    response.commands.each(executeCommand.bind(null, block));
  };
  
  // Any handlers are appended to this list. Some common handlers are already
  // attached, because they are likely to be used on any page.
  handlers = {
    updateCallbackParams: function(block, parameters) {
      block.updateCallbackParams(parameters.params);
    },
    
    setBlockAttributes: function(block, parameters) {
      $H(parameters.attributes).each(function(attr) {
        block.setAttribute(attr.key, attr.value);
      });
    },
    
    updateContainer: function(block, parameters) {      
      var container;
      
      // Determine container to be updated
      if (parameters.container == null)
        container = block.el;
      else {
        // Always search within the context of the block's container element to limit
        // the number of elements.
        container = block.container.select(parameters.container);
        if (container.size() === 0) throw "Container with selector '" + parameters.container + "' not found.";
        container = container[0];
      }
      
      container.update(parameters.content);
      
      //TimestampFormatter.format(container);
    },
    
    insert: function(block, parameters) {
      var container,
          position = parameters.position || 'bottom',
          insertion = {};
      
      // Determine container to be updated
      if (parameters.container == null) {
        container = block.el;
      }
      else {
        // Always search within the context of the block's container element to limit
        // the number of elements.
        container = block.container.select(parameters.container);
        if (container.size() === 0) throw "Container with selector '" + parameters.container + "' not found.";
        container = container[0];
      }
      
      insertion[position] = parameters.content;
      container.insert(insertion);
      
      //TimestampFormatter.format(['before', 'after'].include(position) ? container.parentNode : container);
    },
    
    redirect: function(block, parameters) {
      window.location.href = parameters.uri;
    }
  };
  
  return {
    register: function(block) {
      blocks[block.id] = block;
    },

    callback: function(id, action, parameters, options) {      
      // See if the block is registered. Throw an error if not.
      var block = blocks[id],
          request;
        
      if (block === 'undefined') throw "Unknown block specified.";
      
      // Make sure the options list is a Prototype hash
      options = $H(options);
      
      options.get('onStart')();
      
      // Perform the request
      request = new Ajax.Request(Config.get('ajax_path'), {
    	  method: 'get',
        parameters: {
          block_id: block.id,
          block_name: block.name,
          callback_params: Object.toJSON(block.params),
          action: action,
          params: Object.toJSON(parameters || {}) // Always pass an object
        },
        onSuccess: function(transport) {          
          // First, call the success handler of the CallbackManager, executing any received commands.
          successHandler(transport, block);
          
          // If a valid success handler was passed in, now's the time to execute it.
          options.get('onSuccess')();
        },
        onComplete: options.get('onComplete')
      });
    },
    
    registerHandler: function(name, func) {
      handlers[name] = func;
    }
  };
}(); // Singleton