var BlockManager = function() {
  var blocks = {};
  
  return {
    register: function(block) {
      blocks[block.id] = block;
    },
    
    get: function(id) {
      return blocks[id];
    }
  };
}(); // Singleton


var Block = Class.create({
  initialize: function(id, name, service_id, params) {
    // Store arguments
    this.id = id;
    this.name = name;
    this.service_id = service_id;
    this.params = $H(params);
    
    // Callback options defaults. Pass it to Prototype's hash convert function, so we can
    // call special methods on it.
    this.callbackOptions = $H({
      onRequestSuccess: Prototype.emptyFunction,
      onStart:      Prototype.emptyFunction,
      onSuccess:    this.reinitialize.bind(this),
      onComplete:   Prototype.emptyFunction,
      onException:  Prototype.emptyFunction,
      showLoading:  true,
      ajaxUrl:      Config.get('ajax_path')
    });
    
    // This attribute determines whether we are allowed to perform callbacks
    this.registered = false;
    
    // Allow this block to store attributes on itself.
    this.attributes = new Hash();
    
    // Find the block's content element
    this.el = $(id);
    if (this.el === null) throw "Block element for " + this.id + " not found.";
    
    // Find the block's outer container
    this.container = this.el.hasClassName('block') ? this.el : this.el.up('.block');
    if (this.container === null) throw "Container for block element " + this.id + " not found.";
    
    // Try to initialize the block's sub navigation
    this.registeredSubnav = this.initSubnav();
    
    // Assume we want to call the reinitialize function also on initialization of the block
    this.reinitialize();
    
    // Register the block with the BlockManager
    BlockManager.register(this);
    
    this.postInitialize();
  },
  
  // Utility method to do some extra initialization, without overloading the default initializer.
  postInitialize: Prototype.emptyFunction,
  
  // Method that gets called when an AJAX request has been performed. By default does nothing
  reinitialize: Prototype.emptyFunction,
  
  registerForCallbacks: function() {
    CallbackManager.register(this);
    
    this.registered = true;
  },
  
  // Perform a callback
  callback: function(action, params, options) {
    var onStart, onComplete, overlay;
    
    if (!this.registered) throw "Block " + this.id + " is not registered to receive callbacks.";
    
    // Overwrite defaults with any options that might have been passed in
    options = this.callbackOptions.merge(options || {});
    
    if (options.get('showLoading')) {
      onStart = options.get('onStart');
      options.set('onStart', function() {
        overlay = Overlay.drawOver(this.container.select('.content')[0]);

        onStart();
      }.bind(this));

      onComplete = options.get('onComplete');
      options.set('onComplete', function() {
        onComplete();

        overlay.remove();

        // Bind SortableTable behaviour to new data (fetched by Ajax Request)
        if (window.SortableTable != undefined && typeof(window.SortableTable === "object")) {
          SortableTable.load();
        }
      });
    }
    
    // Delegate the call to the CallbackManager, adding the block's id attribute to
    // the arguments list.
    CallbackManager.callback(this.id, action, params, options);
  },
  
  addCallbackObserver: function(element, action, params, options, eventtype) {
    if (!this.registered) throw "Block " + this.id + " is not registered to receive callbacks.";
    
    if (eventtype == null) eventtype = 'click';
    
    $(element).observe(eventtype, function() {
      this.callback(action, params, options);
    }.bind(this));
  },

  addCallbackSelectObserver: function(element, action, params, options, eventtype) {
    if (!this.registered) throw "Block " + this.id + " is not registered to receive callbacks.";
    if (eventtype == null) eventtype = 'change';

    var selectelement = $(element).select('select')[0];
    
    $(selectelement).observe(eventtype, function() {
      if($(selectelement).getValue()) {
        var value = $(selectelement).getValue();
        var selectparam = new Hash();
        selectparam.set(selectelement.readAttribute('name'), value);
      }

      var mergedparams = Object.extend(params, selectparam.toObject());
      
      var callbackparams = mergedparams;
      this.callback(action, callbackparams, options);
    }.bind(this));
  },

  setAttribute: function(key, value) {
    this.attributes.set(key, value);
  },
  
  getAttribute: function(key) {
    return this.attributes.get(key);
  },
  
  updateCallbackParams: function(params) {    
    this.params = $H(params);
  },
  
  setCallbackParam: function(key, value) {
    this.params.set(key, value);
  },
  
  getCallbackParam: function(key) {
    return this.params.get(key);
  },
  
  updateContent: function() {
    this.callback('updateContent', {});
  },
  
  initSubnav: function() {
    var subnavs = this.container.select('div.subnav > ul');
    var registeredSubnav = new Hash();
    
    // No point in continuing if there is no sub navigation available.
    if (subnavs.size() === 0) return false;
        
    // Fetch all links in the subnav list. Only continue if any items are found.
    subnavs.each(function(subnav) {
      var items,
          currentItem,
          clickHandler;
      
      items = subnav.select('a');
      if (items.size() === 0) return false;

      // Define the current item as the first item
      currentItem = items[0];
      
      clickHandler = function(current) {
        return function(evt) {
          current.up('li').removeClassName('selected');

          // Change the current item of the sub navigation to the button that was clicked.
          current = evt.element();

          current.up('li').addClassName('selected');
        }.bindAsEventListener()
      }(currentItem);

      // Add some default listeners to the sub navigation. These are mostly cosmetic.
      items.invoke('observe', 'click', clickHandler);

      // Register subnavs that want to be registered
      for (var i = 0; i < items.length; i++) {
        currentItem = items[i];
        var register = currentItem.readAttribute('register');
        if (register) {
          registeredSubnav.set(register, currentItem);
        }
      }
    });
    return registeredSubnav;
  }
});
