var messages = {};

/**
 * Manages messages of all kinds and
 * notifies about addition and removal
 */
(function() {

  var $this = $(this);
  var messages = [];
  
  this.addInfo = function(title, text, context) {
  
    return this.addMessage(new Message(title, text, Message.INFO, context));
  };
  
  this.addQuestion = function(title, text, context) {
  
    return this.addMessage(new Message(title, text, Message.QUESTION, context));
  };
  
  this.addWarning = function(title, text, context) {
  
    return this.addMessage(new Message(title, text, Message.WARNING, context));
  };
  
  this.addError = function(title, text, context) {
  
    return this.addMessage(new Message(title, text, Message.ERROR, context));
  };
  
  this.addMessage = function(message) {

    // Automatically remove non-critical messages after a delay
    if (message.type == Message.INFO) {
  
      setTimeout(this.removeMessage, 5000, message);
    }
    
    messages.push(message);
    triggerMessageAddedEvent(message);
    
    return message;
  };
  
  this.removeMessage = function(message) {
  
    messages.splice(messages.indexOf(message), 1);
    triggerMessageRemovedEvent(message);
  };
  
  this.toString = function() {
  
    return "[object MessageManager]";
  };
  
  function triggerMessageAddedEvent(message) {
    
    // Trigger specific message type event
    var messageTypeAddedEvent = new jQuery.Event(message.type + "MessageAdded");
    messageTypeAddedEvent.message = message;
    $this.trigger(messageTypeAddedEvent);
  
    // Trigger generic message event
    var messageAddedEvent = new jQuery.Event("messageAdded");
    messageAddedEvent.message = message;
    $this.trigger(messageAddedEvent);
  }
  
  function triggerMessageRemovedEvent(message) {
    
    // Trigger specific message type event
    var messageTypeRemovedEvent = new jQuery.Event(message.type + "MessageRemoved");
    messageTypeRemovedEvent.message = message;
    $this.trigger(messageTypeRemovedEvent);
  
    // Trigger generic message event
    var messageRemovedEvent = new jQuery.Event("messageRemoved");
    messageRemovedEvent.message = message;
    $this.trigger(messageRemovedEvent);
  }
}).call(messages);


/**
 * Represents a context-sensual message including actions
 */
function Message(title, text, type, context, actions) {

  var $this = $(this);
  // Short title describing the message
  this.title = (title ? title : "");
  // Long text describing the message
  this.text = (text ? text : "");
  // The general type
  this.type = (type ? type : Message.INFO);
  // Where the message was triggered
  this.context = (context ? context : null);
  // Possible ways of interaction with the message and responses
  this.actions = actions;
  // The progress state for Message.PROGRESS
  var _progress = 0; // [0..1]
  
  (function() {
  
    var properties = {
      progress: {
        get: function() {
        
          if (this.type != Message.PROGRESS) {
          
            return 0;
          }
        
          return _progress;
        },
        set: function(progress) {
        
          if (this.type != Message.PROGRESS) {
          
            return;
          }
          
          if (progress < 0) {
          
            _progress = -1;
            $this.trigger("cancelled");
            
            return;
          }
        
          progress *= 1.0; // Ensure float
          progress = Math.max(0, progress);
          progress = Math.min(progress, 1);
          
          if (progress != _progress) {
          
            _progress = progress;

            var progressEvent = new jQuery.Event("progress");
            progressEvent.progress = progress;

            $this.trigger(progressEvent);
            
            if (progress == 1) {
            
              $this.trigger("done");
            }
          }
        }
      }
    };
    
    try {
    
      Object.defineProperties(this, properties);
    } catch (e) {
    
      for (var property in properties) {
      
        this[property] = properties[property];
      }
    }
  }).call(this);
  
  if (!this.actions) {
  
    switch (this.type) {
    
      case Message.QUESTION:
    
        this.actions = {}
        this.actions[Action.OK] = Response.OK;
        this.actions[Action.CANCEL] = Response.CANCEL;
        break;
        
      case Message.PROGRESS:
    
        this.actions = {}
        this.actions[Action.CANCEL] = Response.CANCEL;
        break;
        
      default:
  
        this.actions = {};
        this.actions[Action.CLOSE] = Response.CLOSE;
        break;
    }
  }
  
  this.toString = function() {
  
    if (this.type == Message.PROGRESS) {
    
      try { var progress = this.progress.get(); }
      catch (e) { var progress = this.progress; }
      
      return $(this.text).text() + ": " + parseInt(progress * 100) + "%";
    }
  
    return $(this.text).text();
  };
}
// Constant definition of message types
Message.INFO = "info";         // Regular notice
Message.PROGRESS = "progress"; // Progress indication
Message.QUESTION = "question"; // Confirmation
Message.WARNING = "warning";   // Recoverable error
Message.ERROR = "error";       // Non-recoverable error
