/*
 * Mangrove User Interface Library
 */
var MUIL = {};
MUIL.Version = '1.0';
MUIL.CompatibleWithPrototype = '1.6';

if (Prototype.Version.indexOf(MUIL.CompatibleWithPrototype) !== 0 && console && console.warn)
  console.warn("This version of MUIL is tested with Prototype " + MUIL.CompatibleWithPrototype + 
                  " it may not work as expected with this version (" + Prototype.Version + ")");

/*
 * Controls
 */
MUIL.Control = {};

/*
 * ComboBoxes
 */
MUIL.Control.ComboBox = {};

/**
 * Base ComboBox, abstract class
 */
MUIL.Control.ComboBox.Base = Class.create({
    /**
     * Constructor
     * @param Element selectElement The form select box from which we parse data
     * @param Element container The container to put the generated combobox in
     * @param Array options The combobox control's options
     */
    baseInitialize: function(selectElement, container, controlOptions){
        this.selectElement = $(selectElement);
        this.container = $(container);
        this.controlOptions = controlOptions || {};
        this.labels = this.controlOptions.labels || {};
        this.selected = new Hash();

        /*
         * Create new containers and event handlers
         */
        /*
         * The container element to show when combobox is closed
         */
        if (this.controlOptions.closedContainer) {
            this.closedContainer = this.controlOptions.closedContainer;
        } else {
            this.closedContainer = new Element('div', {
                'class': 'combobox_summary'
            });
            this.container.appendChild(this.closedContainer);
        }
        Event.observe(this.closedContainer, 'click', this.handleOpenClick.bindAsEventListener(this));
        
        /*
         * The container element to show when combobox is closed
         */
        if (this.controlOptions.openContainer) {
            this.openContainer = this.controlOptions.openContainer;
        } else {
            this.openContainer = new Element('div', {
                'class': 'combobox_details'
            });
            this.container.appendChild(this.openContainer);
        }
        this.openContainer.addClassName('closed');

        if (this.controlOptions.optionsContainer) {
            this.optionsContainer = this.controlOptions.optionsContainer;
        } else {
            this.optionsContainer = new Element('div', {
                'class': 'combobox_options'
            });
            this.openContainer.appendChild(this.optionsContainer);
        }
        Event.observe(this.optionsContainer, 'click', this.handleOptionClick.bindAsEventListener(this));

        if (this.controlOptions.actionContainer) {
            this.actionContainer = this.controlOptions.actionContainer;
        } else {
            this.actionContainer = new Element('div', {
                'class': 'combobox_action'
            });
            this.actionContainer.update('<a href="#">'+this.labels.action+'</a>');
            this.openContainer.appendChild(this.actionContainer);
        }
        Event.observe(this.actionContainer, 'click', this.handleActionClick.bindAsEventListener(this));
    },
    /**
     * Add captions and labels to closed div
     */
    displayClosed: function() {
      if(this.labels.closedContainer) {
        var size = this.selected.size();
        if (Object.isArray(this.labels.closedContainer)) {
          if (size === 0) {
            this.closedContainer.update('<p>'+this.labels.closedContainer[0]+'</p>');
          } else if (size === 1) {
            this.closedContainer.update('<p>'+this.labels.closedContainer[1]+'</p>');
          } else {
            this.closedContainer.update('<p>'+this.labels.closedContainer[2].interpolate({size: size})+'</p>');
          }
        } else {
          this.closedContainer.update('<p>'+this.labels.closedContainer.interpolate({size: size})+'</p>');
        }
      }
    },
    /**
     * Event listener for clicks on options
     * Add option to selected list
     */
    handleOptionClick: function(e){
        var me = e.findElement('li');
        if (me) {
            var optionElement = this.options.get(me.innerHTML.unescapeHTML());
            if (optionElement) {
                if (this.selected.get(me.innerHTML.unescapeHTML())) {
                  optionElement.selected = false;
                  me.removeClassName('active');
                  this.selected.unset(me.innerHTML.unescapeHTML());
                } else {
                  optionElement.selected = true;
                  me.addClassName('active');
                  this.selected.set(me.innerHTML.unescapeHTML(), optionElement);
                }
            }
            this.displayClosed();
        }
    },
    /**
     * Event listener for clicks on closed div
     */
    handleOpenClick: function(e) {
      this.closedContainer.addClassName('closed'); 
      this.openContainer.removeClassName('closed'); 
      this.container.removeClassName('closed'); 
    },
    /**
     * Event listener for clicks on action div
     */
    handleActionClick: function(e) {
      var me = e.findElement('a');
      if (me) {
        this.closedContainer.removeClassName('closed');
        this.openContainer.addClassName('closed');
        e.stop();
      }
    }
});

/**
 * MUIL.Control.ComboBox.Checked
 * 
 * A ComboBox in which options are displayed as checkboxes
 */
MUIL.Control.ComboBox.Checked = Class.create(MUIL.Control.ComboBox.Base, {
    /**
     * Constructor
     * @param Element|String selectElement The form select box
     * @param Element|String container The container to put the combobox in
     * @param Array options The combobox's options
     */
    initialize: function(selectElement, container, controlOptions){
        this.baseInitialize(selectElement, container, controlOptions);

        /*
         * Parse selectElement data
         */
        this.options = new Hash();
        this.selectElement.select('option').each(function(optionElement){
          this.options.set(optionElement.label, optionElement);
          if (optionElement.selected === true) {
            this.selected.set(optionElement.label, optionElement);
          }
        }, this);
        
        /*
         * Show the stuff
         */
        this.displayClosed();
        this.displayOptions();
        this.selectElement.up().hide();
        this.container.removeClassName('closed');
    },
    /**
     * Destructor, unregister all event handlers
     */
    dispose: function(){
        Event.stopObserving(this.optionsContainer, 'click', this.handleOptionClick);
        Event.stopObserving(this.selectedContainer, 'click', this.handleSelectedClick);
    },
    /**
     * Display the list of options for a specific optgroup
     * @param String optgroup The optgroup's name
     */
    displayOptions: function(){
        var html = '';
        if (this.labels.optionsContainer) {
          html = '<p>' + this.labels.optionsContainer + '</p>';
        }
        html += '<ul>';
        this.options.each(function(option){
            if (this.selected.get(option.key)) {
              html += '<li class="active">' + option.key + '</li>';
            } else {
              html += '<li>' + option.key + '</li>';
            }
        }, this);
        html += '</ul>';
        this.optionsContainer.update(html);
    },
    /**
     * Event listener for clicks on options
     * Add option to selected list
     */
    handleOptionClick: function(e){
        var me = e.findElement('li');
        if (me) {
            var optionElement = this.options.get(me.innerHTML.unescapeHTML());
            if (optionElement) {
                if (this.selected.get(me.innerHTML.unescapeHTML())) {
                  optionElement.selected = false;
                  me.removeClassName('active');
                  this.selected.unset(me.innerHTML.unescapeHTML());
                } else {
                  optionElement.selected = true;
                  me.addClassName('active');
                  this.selected.set(me.innerHTML.unescapeHTML(), optionElement);
                }
            }
            this.displayClosed();
        }
    }
});


/**
 * MUIL.Control.ComboBox.Grouped
 * 
 * A ComboBox in which optgroups and options are displayed in columns
 */
MUIL.Control.ComboBox.Grouped = Class.create(MUIL.Control.ComboBox.Base, {
    /**
     * Constructor
     * @param Element selectElement The form select box
     * @param Element container The container to put the combobox in
     * @param Array options The combobox's options
     */
    initialize: function(selectElement, container, controlOptions){
        this.baseInitialize(selectElement, container, controlOptions);
        this.currentOptgroup = '';
        
        /*
         * Create new containers and event handlers
         */
        if (this.controlOptions.optgroupsContainer) {
            this.optgroupsContainer = this.controlOptions.optgroupsContainer;
        } else {
            this.optgroupsContainer = new Element('div', {
                'class': 'combobox_optgroups'
            });
            this.openContainer.insertBefore(this.optgroupsContainer, this.optionsContainer);
        }
        Event.observe(this.optgroupsContainer, 'click', this.handleOptgroupClick.bindAsEventListener(this));

        if (this.controlOptions.selectedContainer) {
            this.selectedContainer = this.controlOptions.selectedContainer;
        } else {
            this.selectedContainer = new Element('div', {
                'class': 'combobox_selected'
            });
            this.openContainer.insertBefore(this.selectedContainer, this.actionContainer);
        }
        Event.observe(this.selectedContainer, 'click', this.handleSelectedClick.bindAsEventListener(this));

        /*
         * Parse selectElement data
         */
        this.optgroups = new Hash();
        this.selectElement.select('optgroup').each(function(optgroupElement){
            this.optgroups.set(optgroupElement.label, new Hash());
            optgroupElement.select('option').each(function(optionElement){
                this.optgroups.get(optgroupElement.label).set(optionElement.label, optionElement);
                if (optionElement.selected === true) {
                    this.selected.set(optionElement.label, optionElement);
                }
            }, this);
        }, this);
        
        /*
         * Show the stuff
         */
        this.displayClosed();
        this.displayOptgroups();
        this.displayOptions();
        this.displaySelected();
        this.selectElement.up().hide();
        this.container.removeClassName('closed');
    },
    /**
     * Destructor, unregister all event handlers
     */
    dispose: function(){
        Event.stopObserving(this.optgroupsContainer, 'click', this.handleOptgroupClick);
        Event.stopObserving(this.optionsContainer, 'click', this.handleOptionClick);
        Event.stopObserving(this.selectedContainer, 'click', this.handleSelectedClick);
    },
    /**
     * Display the list of optgroups
     */
    displayOptgroups: function(){
        var html = '';
        if (this.labels.optgroupsContainer) {
          html = '<p>' + this.labels.optgroupsContainer + '</p>';
        }
        html += '<ul>';
        this.optgroups.each(function(option){
            html += '<li>' + option.key + '</li>';
        });
        html += '</ul>';
        this.optgroupsContainer.update(html);
    },
    /**
     * Display the list of options for a specific optgroup
     */
    displayOptions: function(){
        var html = '';
        if (this.labels.optionsContainer) {
          html = '<p>' + this.labels.optionsContainer + '</p>';
        }
        html += '<ul>';
        if (this.currentOptgroup) {
          this.optgroups.get(this.currentOptgroup).each(function(option){
            html += '<li>' + option.key + '</li>';
          });
          html += '</ul>';
        }
        this.optionsContainer.update(html);
    },
    /**
     * Display the list of selected options
     */
    displaySelected: function(){
        var html = '';
        if (this.labels.selectedContainer) {
          html = '<p>' + this.labels.selectedContainer + '</p>';
        }
        html += '<ul>';
        this.selected.each(function(option){
            html += '<li>' + option.key + '</li>';
        });
        html += '</ul>';
        this.selectedContainer.update(html);
    },
    /**
     * Event listener for clicks on optgroups
     * Show appropriate list of options
     */
    handleOptgroupClick: function(e){
        var me = e.findElement('li');
        if (me) {
            me.siblings().each(function(el) {
              el.removeClassName('active');
            });
            me.addClassName('active');
            this.currentOptgroup = me.innerHTML.unescapeHTML();
            this.displayOptions();
        }
    },
    /**
     * Event listener for clicks on options
     * Add option to selected list
     */
    handleOptionClick: function(e){
        var me = e.findElement('li');
        if (me) {
            var optionElement = this.optgroups.get(this.currentOptgroup).get(me.innerHTML.unescapeHTML());
            if (optionElement) {
                optionElement.selected = true;
                this.selected.set(me.innerHTML.unescapeHTML(), optionElement);
                this.displaySelected();
                this.displayClosed();
            }
        }
    },
    /**
     * Event listener for clicks on selected options
     * Remove option from selected list
     */
    handleSelectedClick: function(e){
        var me = e.findElement('li');
        if (me) {
            var optionElement = this.selected.get(me.innerHTML.unescapeHTML());
            if (optionElement) {
                optionElement.selected = false;
                this.selected.unset(me.innerHTML.unescapeHTML());
                this.displaySelected();
                this.displayClosed();
            }
        }
    }
});

MUIL.Dialog = {};
MUIL.Dialog.Base = Class.create({
  baseInitialize: function(options) {
    this.options = options || {};
    this.duration = this.options.duration || 0.5;
    this.opacity = this.options.opacity || 0.25;

    this.clickHandler = this.handleClickAction.bindAsEventListener(this);
    this.keyboardHandler = this.handleKeyboardAction.bindAsEventListener(this);

    var dimensions = document.viewport.getDimensions();
    var offsets = document.viewport.getScrollOffsets();
    this.middle = {
      x: dimensions.width/2 + offsets.left,
      y: dimensions.height/2 + offsets.top
    };
    var pageSize = {};
    if (window.innerHeight && window.scrollMaxY) {  
      pageSize.x = window.innerWidth + window.scrollMaxX;
      pageSize.y = window.innerHeight + window.scrollMaxY;
    } else if (document.body.scrollHeight > document.body.offsetHeight){ // all but Explorer Mac
      pageSize.x = document.body.scrollWidth;
      pageSize.y = document.body.scrollHeight;
    } else { // Explorer Mac...would also work in Explorer 6 Strict, Mozilla and Safari
      pageSize.x = document.body.offsetWidth;
      pageSize.y = document.body.offsetHeight;
    }
    
    // for small pages with total width less then width of the viewport
    if(pageSize.x < dimensions.x){  
      pageSize.x = dimensions.x;    
    }

    // for small pages with total height less then height of the viewport
    if(pageSize.y < dimensions.y){
      pageSize.y = dimensions.y;
    }
    // add a hidden overlay
    this.overlay = new Element('div', {id: 'overlay'}).setStyle({
      position: 'absolute',
      top: 0,
      left: 0,
      width: pageSize.x + 'px',
      height: pageSize.y + 'px',
      backgroundColor: '#000'
    }).hide();
  },
  enableKeyboardHandler: function() {
    document.observe('keydown', this.keyboardHandler); 
  },
  disableKeyboardHandler: function() {
    document.stopObserving('keydown', this.keyboardHandler); 
  },
  enableClickHandler: function() {
    document.observe('click', this.clickHandler); 
  },
  disableClickHandler: function() {
    document.stopObserving('click', this.clickHandler); 
  },
  handleKeyboardAction: function(event) {
    e.stop();
    this.dispose();
  },
  handleClickAction: function(event) {
    this.dispose();
  },
  baseDispose: function() {
    new Effect.Fade(this.overlay, { 
      duration: 0, 
      afterFinish: function(effect) {
        effect.element.remove();
      }
    });
    this.disableKeyboardHandler();
    this.disableClickHandler();
  }
});

MUIL.Dialog.Image = Class.create(MUIL.Dialog.Base, {
  /**
   * Constructor
   */
  initialize: function(srcElement, options) {
    this.baseInitialize(options);
    this.dialog = $(srcElement).cloneNode(false).hide();
    this.dialog.setStyle({
      left: this.middle.x - this.dialog.width/2 + 'px',
      top: this.middle.y - this.dialog.height/2 + 'px',
      width: this.dialog.width + 'px',
      height: this.dialog.height + 'px',
      backgroundColor: '#fff',
      position: 'absolute'
    });
    this.enableClickHandler();
    this.enableKeyboardHandler();
    var body = srcElement.up('body');
    body.appendChild(this.overlay);
    new Effect.Appear(this.overlay, { 
      duration: 0,
      from: 0.0,
      to: this.opacity
    });
    body.appendChild(this.dialog);
    Effect.Grow(this.dialog, {
      moveTransition: Effect.Transitions.spring,
      scaleTransition: Effect.Transitions.spring
    });
  },
  dispose: function() {
    Effect.Puff(this.dialog, {
      duration: this.duration,
      afterFinish: function(effect) {
        effect.effects[0].element.remove();
      }
    });
    this.baseDispose();
  }
});
