/* GM */

var GM = {};

GM.namespace = function ()
{
  var i, j,
      root,
      parts;
  
  for (i = 0; i < arguments.length; i += 1) {
    root  = GM;
    parts = arguments[i].split('.');

    for (j = 1; j < parts.length; j += 1) {
      root[parts[j] ] = root[parts[j] ] || {};
      root = root[parts[j] ];
    }
  }
  
  return root;
};

/* GM.event */

GM.namespace('GM.event');

GM.event = function ()
{
  var handlers = [];

  return {
   
    register: function (element, event, func, context)
    {
      context  = context || element;
      
      var i,
          wrap = function (e) {
            func.call(context, e);
          };
    
      for (i = 0; i < handlers.length; i += 1) {
        if (handlers[i][0] === element && handlers[i][1] === event && handlers[i][2] === func) {
          alert([handlers[i], element, event, func, context]);
          return;
        }
      }
      handlers.push([element, event, func, wrap]);
    
      if (event === 'readystatechange') {
        element.onreadystatechange = wrap;
      } else if (element.attachEvent) {
        element.attachEvent('on' + event, wrap);
      } else if (element.addEventListener) {
        element.addEventListener(event, wrap, false);
      }
    },
    
    unregister: function (element, event, func)
    {
      for (var i = 0; i < handlers.length; i += 1) {
        if (handlers[i][0] === element && handlers[i][1] === event && handlers[i][2] === func) {
          if (event === 'readystatechange') {
            element.onreadystatechange = null;
          } else if (element.detachEvent) {
            element.detachEvent('on' + event, handlers[i][3]);
          } else if (element.removeEventListener) {
            element.removeEventListener(event, handlers[i][3], false);
          }
          
          handlers.splice(i, 1);
          return;
        }
      }
    },
    
    get: function (e)
    {
      return e || window.event;
    },
    
    getTarget: function (e)
    {
      e = GM.event.get(e);
      
      return e.target || e.srcElement;
    },
    
    cancelBubble: function (e)
    {
      e = GM.event.get(e);
            
      if (e.stopPropagation) {
        e.stopPropagation();
      } else {
        e.cancelBubble = true;
      }
      
      return false;
    },
    
    preventDefault: function (e)
    {
      e = GM.event.get(e);
            
      if (e.preventDefault) {
        e.preventDefault();
      } else {
        e.returnValue = false;
      }
      
      return false;
    },
    
    mouseClicked: function (event, whichNumber, buttonNumber)
    {
      event = GM.event.get(event);
      buttonNumber = buttonNumber || whichNumber;
      
      if (event.which && whichNumber && event.which === whichNumber) {
        return true;
      }
      if (event.button && buttonNumber && event.button === buttonNumber) {
        return true;
      }
      
      return false;
    },
    
    keyPressed: function (event, number)
    {
      event = GM.event.get(event);
      
      return (event.keyCode === number);
    },
    
    getPosition: function (e)
    {
      e = GM.event.get(e);
      var position = {x: 0, y: 0};
      
      if (e.pageX || e.pageY)   {
        position.x = e.pageX;
        position.y = e.pageY;
      } else if (e.clientX || e.clientY)  {
        position.x = e.clientX + document.body.scrollLeft +
          document.documentElement.scrollLeft;
        position.y = e.clientY + document.body.scrollTop +
          document.documentElement.scrollTop;
      }
      
      return position;
    }
  };
}();

/* GM.dom */

GM.namespace('GM.dom');

GM.dom.body = function ()
{
  return {

    getHeight: function ()
    {
      var scrollHeight = 0,
          bodyHeight = 0,
          clientHeight = 0;
      
      if (document.documentElement && document.documentElement.scrollHeight) {
        scrollHeight = document.documentElement.scrollHeight;
      }
      if (document.documentElement && document.documentElement.clientHeight) {
        clientHeight = document.documentElement.clientHeight;
      }
      if (document.body && document.body.scrollHeight) {
        bodyHeight = document.body.scrollHeight;
      }
      
      return Math.max(scrollHeight, bodyHeight, clientHeight);
    },
    
    getWidth: function ()
    {
      var scrollWidth = 0,
          bodyWidth = 0,
          clientWidth = 0;
      
      if (document.documentElement && document.documentElement.scrollWidth) {
        scrollWidth = document.documentElement.scrollWidth;
      }
      if (document.documentElement && document.documentElement.clientWidth) {
        clientWidth = document.documentElement.clientWidth;
      }      
      if (document.body && document.body.scrollWidth) {
        bodyWidth = document.body.scrollWidth;
      }
    
      return Math.max(scrollWidth, bodyWidth, clientWidth);
    }
  };
}();

GM.dom.form = function ()
{
  return {
    
    focus: function (form)
    {
      if (form.elements) {
        for (var i = 0; i < form.elements.length; i += 1) {
          if (form.elements[i].nodeName.toLowerCase() !== 'fieldset' && form.elements[i].type !== 'hidden') {
            form.elements[i].focus();
            return;
          }
        }
      }
    }
  };
}();

GM.dom.center = function (element)
{
  element.style.position   = 'absolute';
  element.style.left       = '50%';
  element.style.top        = '50%';
  element.style.marginLeft = '-' + (element.offsetWidth / 2) + 'px';
  element.style.marginTop  = '-' + (element.offsetHeight / 2) + 'px';
};

GM.dom.getOffset = function (object)
{
  var left = 0;
  var top  = 0;
    
  if (object.offsetParent) {
    while (object) {
      left += object.offsetLeft;
      top  += object.offsetTop;
      
      object = object.offsetParent
    }
      
    return {'left': left, 'top': top};
  } 
}

GM.dom.getElementsByClassName = function (className, tagName, root)
{
  tagName = tagName || '*';
  root    = root || document;
  
  var i,
      results = [],
      elements = root.getElementsByTagName(tagName);
  
  for (i = 0; i < elements.length; i += 1) {
    if (elements[i].className.indexOf(className) !== -1) {
      results.push(elements[i]);
    }
  }
  
  return results;
};

GM.dom.getParentByClassName = function (className, root)
{
  if (root.parentNode) {
    if (root.parentNode.className && root.parentNode.className.indexOf(className) !== -1) {
      return root.parentNode;
    } else {
      return GM.dom.getParentByClassName(className, root.parentNode);
    }
  }
};

GM.dom.getFamilyByClassName = function (className, root, results)
{
  results = results || [];

  for (var i = 0; i < root.childNodes.length; i += 1) {
    if (root.childNodes[i].className && root.childNodes[i].className.indexOf(className) !== -1) {
      results.push(root.childNodes[i]);
    }
  }
  
  if (root.parentNode && root.parentNode.childNodes) {
    GM.dom.getFamilyByClassName(className, root.parentNode, results);
  }
  
  return results;
}

GM.dom.ZoomManager = 
{
  baseObject: null,
  
  zoomObject: null,
  
  lock: null,

  zoomIn: function (img)
  {
    if (this.baseObject) {
      this.zoomOut();
    }
    
    this.baseObject = img;
    this.lock = new GM.dom.Lock({color: '000'});
    
    var zoomDiv = document.createElement('div');
    zoomDiv.className = 'zoom';
    document.body.appendChild(zoomDiv);
        
    this.zoomObject = document.createElement('img');
    this.zoomObject.alt          = img.alt;
    this.zoomObject.src          = img.src.replace(/&width=\d+/, '').replace(/&height=\d+/, '');
    this.zoomObject.style.height = img.style.maxHeight;
    this.zoomObject.style.width  = img.style.maxWidth;
    this.zoomObject.style.cursor = 'pointer';
    zoomDiv.appendChild(this.zoomObject);
    
    var captionDiv = document.createElement('div');
    captionDiv.className   = 'caption';
    captionDiv.innerHTML   = img.title;
    captionDiv.style.width = this.zoomObject.offsetWidth + 'px';
    zoomDiv.appendChild(captionDiv);
    
    GM.event.register(this.zoomObject, 'mousedown', this.zoomOut, this);
    
    GM.dom.center(zoomDiv); 
  },
  
  zoomOut: function ()
  {
    GM.event.unregister(this.zoomObject, 'mousedown', this.zoomOut);
  
    this.zoomObject.parentNode.parentNode.removeChild(this.zoomObject.parentNode);
    this.zoomObject = null;
    this.baseObject = null;
    
    this.lock.unlock();     
  }
};

GM.dom.Lock = function (values)
{
  var hidden = [];

  values = values || {};
  values.color   = (typeof values.color   !== 'undefined') ? values.color   : '333';
  values.opacity = (typeof values.opacity !== 'undefined') ? values.opacity : 0.5;
  values.cursor  = (typeof values.cursor  !== 'undefined') ? values.cursor  : null;
  values.zIndex  = (typeof values.zIndex  !== 'undefined') ? values.zIndex  : 1;

  var lock = document.createElement('div');
  document.body.appendChild(lock);
  
  lock.style.position        = 'absolute';
  lock.style.left            = 0;
  lock.style.top             = 0;
  lock.style.width           = GM.dom.body.getWidth() + 'px';
  lock.style.height          = GM.dom.body.getHeight() + 'px';
  lock.style.backgroundColor = '#' + values.color;
  lock.style.cursor          = values.cursor;
  lock.style.opacity         = values.opacity;
  lock.style.filter          = 'alpha(opacity=' + (values.opacity * 100) + ')';
  lock.style.zIndex          = values.zIndex;
    
  if (values.opacity > 0) {
    var selects = document.getElementsByTagName('select');
          
    for (i = 0; i < selects.length; i += 1) {
      if (selects[i].style.visibility != 'hidden') {
        selects[i].style.visibility = 'hidden';
        hidden.push(selects[i]);
      }
    }
  }
  
  return {
  
    getElement: function ()
    {
      return lock;
    },
  
    unlock: function ()
    {
      lock.style.cursor = null;
      lock.parentNode.removeChild(lock);
      
      for (var i = 0; i < hidden.length; i += 1) {
        hidden[i].style.visibility = 'visible';
      }
    }  
  };
};

GM.dom.Boundaries = function (left, right, top, bottom)
{
  this.set(left, right, top, bottom);
};

GM.dom.Boundaries.prototype =
{
  checkX: function (object)
  {
    if (object.right <= this.right && object.left >= this.left) {
      return true;
    }
    return false;
  },
  
  checkY: function (object)
  {    
    if (object.bottom <= this.bottom && object.top >= this.top) {
      return true;
    }
    return false;                    
  },
  
  set: function (left, right, top, bottom)
  {
    this.left   = left;
    this.right  = right;
    this.top    = top;
    this.bottom = bottom;
  }
};

GM.dom.Drag = function (object, handler, parent, zIndex)
{
  this.init(object, handler, parent, zIndex);
};

GM.dom.Drag.prototype =
{
  object: null,
  
  handler: null,
  
  parent: null,
  
  zIndex: 1,

  beginPosition: null,
  
  stylePosition: null,
  
  windowBoundaries: new GM.dom.Boundaries(0, GM.dom.body.getWidth(), 0, GM.dom.body.getHeight() ),
  
  objectBoundaries: new GM.dom.Boundaries(0, 0, 0, 0),

  init: function (object, handler, parent, zIndex)
  {
    this.object  = object;
    this.handler = handler;
    this.parent  = parent;
    this.zIndex  = zIndex || this.zIndex;
    
    if (handler) {
      GM.event.register(handler, 'mousedown', this.startDrag, this);
      handler.style.cursor = 'move';
    } else {
      GM.event.register(object, 'mousedown', this.startDrag, this);
    }    
  },
  
  removeHandlers: function ()
  {
    var element = (this.handler) ? this.handler : this.object;
    GM.event.unregister(element, 'mousedown', this.startDrag);
  },

  startDrag: function (e)
  {    
    if (GM.event.mouseClicked(e, 1) ) {
      var object = this.object;
      
      this.beginPosition = GM.event.getPosition(e);
      this.stylePosition = object.style.position;
            
      object.oldZIndex = object.style.zIndex;
            
      object.style.zIndex   = this.zIndex;
      object.style.cursor   = 'move';
      object.style.position = (!object.style.position) ? 'relative' : object.style.position;
  
      object.style.top  = (object.style.position === 'relative') ? 0 : object.offsetTop - parseInt(object.style.marginTop, 10) + 'px';
      object.style.left = (object.style.position === 'relative') ? 0 : object.offsetLeft - parseInt(object.style.marginLeft, 10) + 'px';
  
      object.direction = {};
  
      GM.event.register(document, 'mousemove', this.drag, this);
      GM.event.register(document, 'mouseup', this.stopDrag, this);
      
      return GM.event.preventDefault(e);
    }    
  },
  
  drag: function (e)
  {
    var position         = GM.event.getPosition(e),
        difX             = position.x - this.beginPosition.x,
        difY             = position.y - this.beginPosition.y,
        newLeft          = this.object.offsetLeft + difX,
        newRight         = newLeft + this.object.offsetWidth,
        newTop           = this.object.offsetTop + difY,
        newBottom        = newTop  + this.object.offsetHeight,
        parentBoundaries = (this.parent && this.parent.boundaries) ? this.parent.boundaries : this.windowBoundaries;
      
    this.beginPosition = position;
    this.objectBoundaries.set(newLeft, newRight, newTop, newBottom);
      
    if (parentBoundaries.checkX(this.objectBoundaries)) {
      this.object.style.left = (parseInt(this.object.style.left, 10) + difX) + 'px';
    }
    if (parentBoundaries.checkY(this.objectBoundaries)) {
      this.object.style.top = (parseInt(this.object.style.top, 10) + difY) + 'px';
    }
    
    this.object.direction.x = (difX < 0) ? 'left' : 'right';
    this.object.direction.y = (difY < 0) ? 'up' : 'down';
                
    return GM.event.preventDefault(e);
  },
  
  stopDrag: function (e)
  {
    this.object.style.zIndex   = this.object.oldZIndex;
    this.object.style.cursor   = '';
    this.object.style.position = this.stylePosition;
    
    this.beginPosition = null;
    this.stylePosition = null;
    
    this.object.direction = null;
    
    GM.event.unregister(document, 'mousemove', this.drag);
    GM.event.unregister(document, 'mouseup', this.stopDrag);
    
    return GM.event.preventDefault(e);
  }
};

GM.dom.Sort = function (list, callback)
{
  this.init(list, callback);
};

GM.dom.Sort.prototype =
{
  list: null,
  
  callbackFunction: null,
  
  selected: null,
  
  items: null,

  drags: [],

  threshold: 7,
  
  init: function (list, callback)
  {
    this.list = list;
    this.callbackFunction = callback;
    
    this.addHandlers();
  },

  reInit: function ()
  {
    if (this.list && this.callbackFunction) {
      this.removeHandlers();
      this.addHandlers();
    }
  },

  addHandlers: function ()
  {
    this.items = GM.dom.getElementsByClassName('drag', 'div', this.list);
    for (var i = 0; i < this.items.length; i += 1) {
      this.drags[i] = new GM.dom.Drag(this.items[i], '', this.list);
      GM.event.register(this.items[i], 'mousedown', this.startSort, this);        
    }
  },

  removeHandlers: function ()
  {
    for (var i = 0; i < this.items.length; i += 1) {
      this.drags[i].removeHandlers();
      GM.event.unregister(this.items[i], 'mousedown', this.startSort);        
    }
  },

  startSort: function (e)
  {
    if (GM.event.mouseClicked(e, 1) ) {
      this.selected = GM.dom.getParentByClassName('drag', GM.event.getTarget(e) );
      
      this.list.boundaries     = new GM.dom.Boundaries(0, this.list.offsetWidth, 0, this.list.offsetHeight);
      this.list.style.position = 'relative';

      GM.event.register(document, 'mousemove', this.sort, this);
      GM.event.register(document, 'mouseup', this.stopSort, this);
    }
  },
  
  sort: function ()
  {
    var i, j,
        positionItem,
        positionSelected,
        sBoundaries,
        iBoundaries;
  
    for (i = 0; i < this.items.length; i += 1) {
      if (this.items[i] !== this.selected) {
        for (j = 0; j < this.selected.parentNode.childNodes.length; j += 1) {
          if (this.selected.parentNode.childNodes[j] === this.selected) {
            positionSelected = j;
          } else if (this.selected.parentNode.childNodes[j] === this.items[i]) {
            positionItem = j;
          }
        }

        if (this.selected.direction.y === 'up' && positionItem < positionSelected) {
          sBoundaries = new GM.dom.Boundaries(0, 0, this.selected.offsetTop, this.selected.offsetTop);
          iBoundaries = new GM.dom.Boundaries(0, 0, this.items[i].offsetTop, this.items[i].offsetTop + this.threshold);  
          if (iBoundaries.checkY(sBoundaries)) {
            this.selected.style.top = this.threshold + 'px';
            this.list.insertBefore(this.selected, this.items[i]);
            this.list.boundaries = new GM.dom.Boundaries(0, this.list.offsetWidth, 0, this.list.offsetHeight);
            return;
          }
        } else if (this.selected.direction.y === 'down' && positionItem > positionSelected) {
          sBoundaries = new GM.dom.Boundaries(0, 0, this.selected.offsetTop + this.selected.offsetHeight,
                                             this.selected.offsetTop + this.selected.offsetHeight);
          iBoundaries = new GM.dom.Boundaries(0, 0, this.items[i].offsetTop + this.items[i].offsetHeight - this.threshold,
                                             this.items[i].offsetTop + this.items[i].offsetHeight);          
          if (iBoundaries.checkY(sBoundaries)) {
            this.selected.style.top = (-1) * this.threshold + 'px';
            if (this.items[i].nextSibling) {
              this.list.insertBefore(this.selected, this.items[i].nextSibling);
            } else {
              this.list.appendChild(this.selected);
            }
            this.list.boundaries = new GM.dom.Boundaries(0, this.list.offsetWidth, 0, this.list.offsetHeight);
            return;
          }
        }
      }
    }      
  },
  
  stopSort: function ()
  {
    var selected = this.selected,
        items    = GM.dom.getElementsByClassName('drag', 'div', this.list);
      
    this.selected.style.top  = 0;
    this.selected.style.left = 0;
    this.list.style.position = '';  
    
    this.selected = null;
      
    GM.event.unregister(document, 'mousemove', this.sort);
    GM.event.unregister(document, 'mouseup', this.stopSort);
    
    if (this.callbackFunction) {
      this.callbackFunction(items, selected);
    }
  }  
};

/* GM.Request */

GM.request = function ()
{
  var parameters, queue = [], request, submitValue, iframe;

  GM.event.register(document, 'click', captureSubmit);
  
  function captureSubmit(e)
  {
    var target = GM.event.getTarget(e);
    
    if (target.nodeName.toLowerCase() === 'input' && target.type && target.type.toLowerCase() === 'submit') {
      submitValue = '&' + encodeURIComponent(target.name) + '=' + encodeURIComponent(target.value);
    }
  }

  function setRequest()
  {
    if (window.XMLHttpRequest) {
      request = new XMLHttpRequest();
    } else if (window.ActiveXObject) {
      request = new ActiveXObject("Microsoft.XMLHTTP");
    }
    return request;
  }
  
  function isUpload(form)
  {
    for (var i = 0; i < form.elements.length; i += 1) {
      if (form.elements[i].type && form.elements[i].type === 'file') {
        return true;
      }
    }
    return false;
  }
  
  function getFormData(form)
  {
    var i, j,
        element,
        hasSubmit = false,
        value,
        result = '';
        
    for (i = 0; i < form.elements.length; i += 1) {
      element = form.elements[i];
      
      if (!element.disabled) {
        switch(element.type) {
        case 'checkbox':
        case 'radio':
          if (element.checked) {
            result += '&' + encodeURIComponent(element.name) + '=' + encodeURIComponent(element.value);
          }
          break;
        case 'hidden':
        case 'text':
        case 'textarea':
          result += '&' + encodeURIComponent(element.name) + '=' + encodeURIComponent(element.value);
          break;
        case 'select-one':
        case 'select-multiple':
          for (j = 0; j < element.options.length; j += 1) {
            if (element.options[j].selected) {
              value   = (element.options[j].value) ? element.options[j].value : element.options[j].text;
              result += '&' + encodeURIComponent(element.name) + '=' + encodeURIComponent(value);
            }
          }
          break;
        case 'submit':
          if (!hasSubmit) {
            if (submitValue) {
              result += submitValue;
              submitValue = null;
            } else {
              result += '&' + encodeURIComponent(element.name) + '=' + encodeURIComponent(element.value);
            }
            
            hasSubmit = true;
          }
        default:
          break;
        }
      }
    }
    
    return result;
  }
  
  function addFormData(form, data)
  {
    data += '&iframe=true';
  
    var i,
        nodes = [],
        arguments = data.split('&');
        
    for (i = 0; i < arguments.length; i += 1) {
      if (arguments[i].indexOf('=') > 0) {
        nodes[i] = document.createElement('input');
        nodes[i].type = 'hidden';
        nodes[i].name  = arguments[i].substring(0, arguments[i].indexOf('=') );
        nodes[i].value = arguments[i].substring(arguments[i].indexOf('=') + 1);
        form.appendChild(nodes[i]);
      }
    }
    
    return nodes;        
  }
  
  return {
    
    send: function (params)
    {
      if (params.form && isUpload(params.form) ) {
        if (iframe) {
          queue.push(params);
          return;
        }  
        
        parameters = params;
          
        var i,
            dataElements = addFormData(parameters.form, parameters.data),
            iframeId = new Date().getTime();
      
        if (window.ActiveXObject) {
          iframe = document.createElement('<iframe name="iframe_' + iframeId + '" id="iframe_' + iframeId + '"/>');
        } else {
          iframe      = document.createElement('iframe');
          iframe.id   = 'iframe_' + iframeId;
          iframe.name = 'iframe_' + iframeId;
        }
        
        iframe.style.position  = 'absolute';
        iframe.style.visibility = 'hidden';
        iframe.style.left = '-1000px';
        iframe.style.top  = '-1000px';
        
        document.body.appendChild(iframe);
        
        GM.event.register(iframe, 'load', this.handleUpload, this);
        
        var oldTarget = parameters.form.target;
        
        parameters.form.target = iframe.id;
        parameters.form.submit();
        
        for (i = 0; i < dataElements.length; i += 1) {
          parameters.form.removeChild(dataElements[i]);
        }
        
        parameters.form.target = oldTarget;
      } else {
        if (request && request.readyState !== 4) {
          queue.push(params);
          return;
        } 
      
        parameters = params;
      
        parameters.data = parameters.data || '';
      
        if (parameters.form) {
          parameters.method = parameters.form.method;
          parameters.url    = parameters.form.action;
          parameters.data  += getFormData(parameters.form);
        }
        
        if (parameters.method === 'get' && parameters.data) {
          parameters.url += ( (parameters.url.indexOf('?') === -1) ? '?' : '&') + parameters.data;
          parameters.data = '';
        }
        
        if (parameters.async === undefined || parameters.async === null) {
          parameters.async = true;
        }
        
        request = request || setRequest();
        request.open(parameters.method, parameters.url, parameters.async);
        if (parameters.method === 'post') {
          request.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        }
        if (parameters.async || window.ActiveXObject) { // until https://bugzilla.mozilla.org/show_bug.cgi?id=313646 is fixed
          GM.event.register(request, 'readystatechange', this.handle, this);
        } else {
          GM.event.register(request, 'load', this.handle, this);
        }
        request.send(parameters.data);
      }
    },
    
    handle: function (e)
    {
      if (request.readyState === 4) {
        GM.event.unregister(request, 'readystatechange', this.handle);
        GM.event.unregister(request, 'load', this.handle);
        
        if (request.status === 200) {
          if (parameters.callback) {
            var context    = (parameters.context) ? parameters.context : window,
                object     = (parameters.object) ? parameters.object : {};
            object.request = request; 
            parameters.callback.call(context, e, object);
          }       
          this.finish();
        } else {
          alert(request.statusText + ' (' + request.status + '): ' + request.responseText);
          this.finish();
        }
      }
    },
    
    handleUpload: function (e)
    {
      GM.event.unregister(iframe, 'load', this.handleUpload);

      if (parameters.callback) {
        var context    = (parameters.context) ? parameters.context : window,
            object     = (parameters.object) ? parameters.object : {};
        object.request = {responseText: iframe.contentWindow.document.body.innerHTML}; 
        parameters.callback.call(context, e, object);
      }
     
      setTimeout(
        function () {
          iframe.parentNode.removeChild(iframe);
          iframe = null;
        }, 100
      );
     
      this.finish();
    },
    
    finish: function ()
    {
      if (queue[0]) {
        var params = queue[0];
        queue.splice(0, 1);
        this.send(params);          
      }
    }
  };
}();

/* GM.mvc */

GM.namespace('GM.mvc');

GM.mvc.Controller = function (observable, observer)
{
  this.observable = observable;
  this.observer   = observer;
}

GM.mvc.Controller.prototype.close = function ()
{
  this.observable = null;
  this.observer   = null;
}

GM.mvc.ObservableManager = 
{
  observables: [],
  
  addObservable: function (observable)
  {
    for (var i = 0; i < this.observables.length; i += 1) {
      if (this.observables[i].id === observable.id) {
        this.observables[i].updateObservers(observable);
        this.observables[i] = observable;
        return;
      }
    }
    this.observables.push(observable);
  },
  
  removeObservable: function (observable)
  {
    for (var i = 0; i < this.observables.length; i += 1) {
      if (this.observables[i] === observable) {
        this.observables.splice(i, 1);
      }
    }
  }  
}

GM.mvc.Observable = function (id)
{
  this.id = id;
  this.observers = [];
  
  GM.mvc.ObservableManager.addObservable(this);
}

GM.mvc.Observable.prototype =
{
  addObserver: function (observer)
  {
    this.observers.push(observer);
  },
  
  removeObserver: function (observer)
  {
    for (var i = 0; i < this.observers.length; i += 1) {
      if (this.observers[i] === observer) {
        this.observers.splice(i, 1);
      }
    }
    
    if (this.observers.length === 0) {
      GM.mvc.ObservableManager.removeObservable(this);
    }
  },
  
  updateObservers: function (observable)
  {
    for (var i = 0; i < this.observers.length; i += 1) {
      observable.addObserver(this.observers[i]);
      this.observers[i].observable = observable;
    }
  },
  
  notifyObservers: function (updateObject)
  {
    var observers = this.observers.slice(0);
  
    for (var i = 0; i < observers.length; i += 1) {
      observers[i].update(this, updateObject);
    }
  }
}


GM.mvc.Observer = function (observable, controller)
{
  this.observable = observable;
  this.controller = controller;
  
  this.children = [];
}


GM.mvc.Observer.prototype.addChild = function (child)
{
  this.children.push(child);
  child.parent = this;
}

GM.mvc.Observer.prototype.removeChild = function (child)
{
  for (var i = 0; i < this.children.length; i += 1) {
    if (this.children[i] === child) {
      this.children.splice(i, 1);
      break;
    }
  }
}

GM.mvc.Observer.prototype.update = function (observable, object)
{
  if (object.events) {
    
    for (var i = 0; i < object.events.length; i += 1) {
    
      if (this[object.events[i].event]) {
    
        if (object.events[i].url) {
    
          GM.request.send( {
            data:     'async=true',
            method:   'get',
            url:      object.events[i].url,
            callback: this[object.events[i].event],
            context:  this,
            object:   object.events[i]
          } );
          
        } else {
        
          this[object.events[i].event].call(this, observable, object.events[i]);
        }
      }
    }
  }
}

GM.mvc.Observer.prototype.close = function (closeables)
{
  if (this.controller) {
    this.controller.close();
  }

  if (this.observable) {
    this.observable.removeObserver(this);
  }
  
  if (this.parent) {
      this.parent.removeChild(this);
  }
  
  for (var i = (this.children.length - 1); i >= 0; i -= 1) {
    if (!closeables || closeables.indexOf(this.children[i].objectName) !== -1 ) {
      this.children[i].close(closeables);
    }
  }
}

/* Generic prototype functions */

if (typeof Array.prototype.indexOf !== 'function') {
  Array.prototype.indexOf = function (o) {
    for (var i = 0; i < this.length; i += 1) {
      if (this[i] === o) {
        return i;
      }
    }
    return -1;  
  };
}

if (typeof Function.prototype.inherit !== 'function') {
  Function.prototype.inherit = function (parent) {
    for (var m in parent.prototype) {
      this.prototype[m] = parent.prototype[m];
    }     
  };
}
