/* Copyright © 2006-09 Spiceworks, Inc.  All Rights Reserved.  http://www.spiceworks.com */
/*

js for the portal only, not the admin side of the portal

*/

var Portal = {
  initialize: function(options){
    this.options = Object.extend({
      loggedIn:false,
      admin:false
    }, options || {});

    if (this.options.admin) this.setupAdmin();
  },
  setupAdmin: function(){
    AdminBar.initialize();
    this.makeSortable();

    // konami
    (function(){var keyed = [], code = "38,38,40,40,37,39,37,39,66,65";document.observe('keydown', function(e){keyed.push(e.keyCode);if (keyed.toString().indexOf(code) > -1){keyed = [];var el = new Element('div', {id:'konami', style:'position:absolute;top:0;left:0;'}).update('<img src="/images/other/gradient-dark-to-light.gif" />');document.body.insert(el);var endingDims = document.viewport.getDimensions();new Effect.Morph(el, {style:'top:' + endingDims.height + 'px;', duration:5.0});new Effect.Morph(el, {style:'left:' + endingDims.width + 'px;', duration:5.0});Element.remove.delay(5, el);}});})();
  },
  articleMenuSelect: function(menu){
    var selected = menu.value;
    if (selected != '') document.location.href = '/portal/page/' + selected;
    else document.location.href = '/portal/new_page?article=true';
  },
  
  disableSubmit: function(form){
    form = $(form);
    // disable the button so the user can't keep clicking it again and again like a silly goose
    form.down('p.button input').disabled = true;
  },

  /* edit page methods
  **********************************************************************************************************/
  pageFormSubmit: function(){
    this.newPage = true;
    this.options.pageName = $F('user_portal_page_name');
    this.options.isProtected = $('user_portal_page_protected').checked;
    var oldPageName = $('navigation').down('li:last-child a').innerHTML;
    $('navigation').select('li:not(li.new-tab) a').last().update(this.options.pageName);
    document.title = document.title.replace(oldPageName, this.options.pageName);
    
    new Ajax.Request('/portal/check_page_name', {parameters:'name=' + escape(this.options.pageName)});
    return false;
  },
  newPageCreated: function(pageID){
    delete this.newPage;
    delete this.options.pageName;
    delete this.options.isProtected;
    this.options.page = pageID;
    SPICEWORKS.fire('portalv2:newPageCreated');
  },
  saveEdit: function(){
  },
  cancelEdit: function(){
  },
  removeBlock: function(activator){
    if (this.ensureRemovable(activator)){
      var block = $(activator).up('.content-block'), parent = block.up('div');
      var postBody = 'page=' + this.options.page + '&index=' + Portal.blockIndex(block.id) + '&location=' + parent.id + '&block=' + block.getAttribute('block') + '&element=' + escape(block.id);
      new Ajax.Request('/portal/remove_content', {parameters:postBody});
    }
  },
  ensureRemovable: function(activator){
    var block = $(activator).up('.content-block'), parent = block.up('div');
    
    // user is trying to remove a block that has never been saved, just remove it from the DOM and be done with it
    // return false so that the remove action isn't triggered on the server
    if (block.hasClassName('unsaved-content-block')) {
      block.remove();
      return false;
    } else if (parent.id == 'main' && parent.select('div.content-block').size() == 1) {
      // trying to remove the only main block on the page, this is not allowed
      alert('A page must have at least one main content block, this cannot be removed');
      return false;
    } else return confirm('Are you sure?');
  },
  
  // theme methods
  contentAdded: function(section){
    this.makeSortable();

    $(section).select('div.placeholder-block').invoke('remove');
    $(section).down('div.content-block').highlight({duration:0.5});
    SPICEWORKS.fire('portalv2:contentAdded');
  },
  themeChange: function(choice){
    choice = $(choice);
    // remember the originally active theme, but don't overwrite it a second time
    if (!this.originalTheme) this.originalTheme = this.activeTheme();
    this.switchTheme(choice.value);
    this.dirtyTheme = true;
  },
  undoThemeChange: function(){
    this.switchTheme(this.originalTheme);
  },
  activeTheme: function(){
    var theme = document.body.className.match(/theme-\w*/);
    return (theme ? theme[0] : null);
  },
  switchTheme: function(newTheme){
    if (this.activeTheme()) document.body.className = document.body.className.replace(/theme-\w*/, newTheme);
    else document.body.addClassName(newTheme);
    SPICEWORKS.fire('portalv2:themeChanged', newTheme);
  },
  cancelPreferenceChange: function(){
    var preferences = $('preferences');
    if (preferences) {
      preferences.hide();
      preferences.down('form').reset();
    }
    if (this.dirtyTheme) this.restoreState();
    return false;
  },
  cancelPageEdit: function(){
    $$('.darkbox, .lightbox').invoke('remove');
    return false;
  },
  themeBoxPreference: function(boxControl){
    if (!this.originalBoxPreference) this.originalBoxPreference = this.activeBoxPreference();
    this.switchBoxPreference(boxControl.value);
    this.dirtyTheme = true;
  },
  activeBoxPreference: function(){
    var box = document.body.className.match(/box-\w*/);
    return (box ? box[0] : null);
  },
  switchBoxPreference: function(newBoxPref){
    if (this.activeBoxPreference()) document.body.className = document.body.className.replace(/box-\w*/, newBoxPref);
    else document.body.addClassName(newBoxPref);
    SPICEWORKS.fire('portalv2:boxPreferenceChanged', newBoxPref);
  },
  restoreState: function(){
    if (this.originalTheme) this.switchTheme(this.originalTheme);
    if (this.originalBoxPreference) this.switchBoxPreference(this.originalBoxPreference);
  },

  /* rearranging methods
  **********************************************************************************************************/
  makeSortable: function(){
    // setup the sortable to allow rearranging of blocks within a column or from one column to another
    // relies on several overridden methods from dragdrop.js that are overridden in this file
    // Sortable#findElements, Sortable#mark, Draggables#activate
    if ($$('#content.is-article').size() > 0) return;
    Sortable.create('content', {tagSelector:'div.content-block', handle:'move', constraint:'', containment:['main', 'sidebar'], ghosting:true, onChange: this.renderPlaceholder.bind(this), onUpdate: this.reordered.bind(this)});
  },
  renderPlaceholder: function(element){
    // called whenever the content blocks are rearranged while in the middle of a drag
    // relies on the overridden Sortable#mark (runs before this callback is invoked) method setting two properties on the Sortable object

    // fetch the sortable object, the element passed in is the block that is being dragged
    var sortable = Sortable.options(element);
    
    // before we draw a placeholder, remove the existing placeholder (if applicable)
    $('content').select('.placeholder-block').invoke('remove');

    // the element that will be the new placeholder
    var placeholder = new Element('div', {'class':'content-block placeholder-block'}).update('<h3>Block will appear here</h3>');

    // the markedPosition & markedElement properties are set in our overridden Sortable#mark method
    if (sortable.markedPosition == 'before'){
      // most common case, draw the placeholder before the marked element
      element.parentNode.insertBefore(placeholder, sortable.markedElement);
    } else {
      // draw the placeholder after the marked element, if there is no next element then insert the placeholder at the end of the element
      if (sortable.markedElement.nextSibling) element.parentNode.insertBefore(placeholder, sortable.markedElement.nextSibling);
      else element.parentNode.appendChild(placeholder);
    }
  },
  reordered: function(sortableElement){
    // called after a dragon is complete

    // remove the placeholder element
    sortableElement.select('.placeholder-block').invoke('remove');
    
    // fetch the blocks in the main and sidebar areas, so we can do different things based on the state of those columns
    var sidebarContent = sortableElement.select('div#sidebar div.content-block'), mainContent = sortableElement.select('div#main div.content-block');
    if (sidebarContent.size() > 1 && sortableElement.hasClassName('no-sidebar-content')){
      // an element has been dragged into an empty sidebar, need to remove a few meta attributes on the sidebar now that it has real content
      sortableElement.removeClassName('no-sidebar-content');
      sortableElement.down('div.sidebar-placeholder').remove();
    } else if (sidebarContent.size() == 0){
      // the last element in the sidebar was dragged out, now we need to add a class to the container and render a placeholder
      // this placeholder element is a hack to allow dragging into the seemingly empty sidebar
      sortableElement.addClassName('no-sidebar-content');
      sortableElement.down('#sidebar').insert('<div class="content-block sidebar-placeholder">Drag content here to create a sidebar on this page</div>');
      // since we added a new element to the page, we need to renew our sortable
      this.makeSortable();
    }

    // if something was dragged out of the main area, we will need to do a few extra things if there is one element remaining (which cannot be moved)
    if (mainContent.size() == 1){
      // there is only one element in the main area, it cannot be moved until more than one element exists in the main area
      var mover = mainContent.first().down('a.move');
      // save off the title of the move control so we can add it back later
      mover.setAttribute('old_title', mover.title);
      mover.title = 'Cannot move the only main block';
      mover.up('li').addClassName('disabled');
    } else {
      // some other dragon event occurred, see if we need to enable a disabled move control
      var disabledMover = mainContent.first().up().select('div.edit-block li.disabled a');
      if (disabledMover.size() > 0){
        // there will only be one of these controls that is disabled
        disabledMover = disabledMover.first();
        // restore the saved title
        disabledMover.setAttribute('title', disabledMover.getAttribute('old_title'));
        disabledMover.up('li').removeClassName('disabled');
      }
    }
    
    // save the page state to the server
    this.saveReordering();
  },
  saveReordering: function(){
    SPICEWORKS.fire('portalv2:pageReordered');
    
    // do not try to save state if this is a new page (no page attribute present) or the user has removed all the main content
    if (!$('main').down('div.content-block')) return;

    new Ajax.Request('/portal/save_state', {parameters:this.serializePage()});
  },
  

  /* utility methods
  **********************************************************************************************************/
  dismissNotice: function(){
    $$('#notice').invoke('remove');
  },
  blockIndex: function(blockID){
    // for a given ID, which is assumed to be an immediate descendent of a primary wrapper (main or sidebar), then this will return the index of that item
    return $(blockID).up().select('div.content-block').pluck('id').indexOf(blockID);
  },
  
  
  // given a container, will reset its contents to the innerHTML of the container, useful for resetting parts of a form such as an file input control which cannot be accessed via JavaScript
  resetHTML: function(container){
    // need to use a delay here because the link that called into this method is going to overwritten .. IE doesn't like that
    (function(container){ container.update(container.innerHTML); }).delay(0.1, container);
    return false;
  },
  serializePage: function(location){
    // have to use a custom serializer here because the built-in one that comes with Sortable does not meet our needs
    var body = 'page=' + this.options.page + '&' + this._serializeSection('main') + '&' + this._serializeSection('sidebar') + '&authenticity_token=' + encodeURIComponent(this.options.authenticityToken);
    if (typeof this.newPage != 'undefined'){
      body += '&page_name=' + escape(this.options.pageName);
      if (this.options.isProtected) body += '&protected=true';
    }
    return body;
  },
  
  /* internal use methods
  **********************************************************************************************************/
  _serializeSection: function(section){
    // custom serializer for sortable
    // yields the value of the block attribute for each valid block
    // placeholder blocks are not valid, we will just reject them from the collection
    return section + '[]=' + $(section).select('.content-block').reject(function(el){
      return el.className.match(/placeholder/);
    }).collect(function(block){
      return escape(block.getAttribute('block'));
    }).join('&' + section + '[]=');
  }
};

var Block = {
  cancelEdit: function(activator){
    activator = $(activator);
    var editBlock = activator.up('.content-block');
    var block = $(editBlock.id.replace('edit-', ''));
    editBlock.remove();
    block.show();
    return false;
  },
  cancelNew: function(activator){
    activator = $(activator);
    var block = activator.up('.content-block'), parent = block.up('div');
    if (block) block.remove();
    return false;
  },
  moveNew: function(block){
    block = $(block);
    if (block) block.remove();
  },
  submit: function(form){
    form = $(form);
    if (typeof Portal.newPage != 'undefined'){
      form.down('input.new-page-name').setValue(Portal.options.pageName);
      form.down('input.new-page-protected').setValue(Portal.options.isProtected);
    }
    var editor = form.down('textarea.wysiwyg');
    if (editor) RichTextEditor.save(editor);
  }
};

// override a few methods that are defined in dragdrop.js for our special case usage
// this object only exists when dragdrop.js is included on the page and that only occurs when an admin is logged in
if (typeof Sortable != 'undefined'){

  // the Sortable#findElements method is a lot handier when it works with a selector
  Sortable.findElements = function(element, options){
    if (options.tagSelector) return element.select(options.tagSelector);
    else return Element.findChildren(element, options.only, options.tree ? true : false, options.tag);
  };
  
  // need to wrap Sortable#mark method so that we can setup a few properties on the sortable object since these values are not passed to the onChange callback like we want them to be
  Sortable.mark = Sortable.mark.wrap(function(){
    var args = $A(arguments), proceed = args.shift();
    proceed.apply(this, args);

    // fetch the sortable object
    var sortable = Sortable.options(args[0]);
    // in scriptaculous 1.8.2, this method is passed an element and a position where the dragged element will be inserted, we want to save those properties
    sortable.markedElement = args[0];
    sortable.markedPosition = args[1];
  });
  
  // need to wrap the Draggable#activate method so that if our placeholder element (for an empty sidebar) is dragged
  // we are using that placeholder element in the sidebar to allow dragging objects into the sidebar without having to do anything terribly tricky
  Draggables.activate = Draggables.activate.wrap(function(){
    var args = $A(arguments), proceed = args.shift(), draggable = args[0], parent = draggable.element.parentNode;
    
    // before allowing the dragon activation to occur, make sure the dragged element isn't a placeholder or isn't the last object in the main area
    if (draggable.element.hasClassName('sidebar-placeholder')) return;
    else if (parent.id == 'main' && parent.select('div.content-block').size() == 1) alert('Must leave at least one block in the main area');
    else proceed.apply(this, args);
  });
}

var AdminBar = {
  initialize: function(){
    this.bar = $('admin-bar');
    $('container').setStyle({paddingTop:this.bar.getHeight() + 'px'});

    if (document.location.search.indexOf('designer=true') > -1) this.designMode();
    else if (document.location.search.indexOf('preview=true') > -1) this.endUserMode();
    else if (Cookie.get('portal-admin-view') == 'end-user') this.endUserMode();
    
    SUI.simplemenu($("add-page-content-button"), $("add-content"), { activateOn: 'click', closeButton: ['input.close', 'a.button-add-new-content'], offsetTop:-17});
    SUI.simplemenu($("jump-to-button"), $("jump-to"), { activateOn: 'click', closeButton: ['input.close', 'a'], offsetTop:-19});
    SUI.simplemenu($("portal-preferences-button"), $("preferences"), { activateOn: 'click', closeButton: 'input.close', offsetTop:-19});
    SUI.simplemenu($("page-settings-button"), $("page-settings"), { activateOn: 'click', closeButton: 'input.close', offsetTop:-17});
    SUI.simplemenu($("new-tab-button"), $("new-tab"), { alignment:'left', activateOn: 'click', closeButton: 'input.close', offsetTop:-19});

    SPICEWORKS.fire('portalv2:adminBarInit');
  },
  addContent: function(blockToAdd){
    if (blockToAdd != ''){
      var postBody = '';
      if (Portal.options.page){
        postBody += '&page=' + Portal.options.page;
      } else if (typeof Portal.newPage != 'undefined') {
        postBody += '&new_page[name]=' + escape(Portal.options.pageName);
        if (Portal.options.isProtected) postBody += '&new_page[protected]=true';
      }
      if (blockToAdd != '__createnew__') postBody += '&block=' + blockToAdd;
      
      // don't let a new content block be added if there already is one unsaved block on the page
      if ($('new_user_portal_block')) return;
      
      new Ajax.Request('/portal/new_content', {parameters:postBody});
    }
  },
  designMode: function(){
    // note the $() call on body is for IE7 only, IE8 in IE7 compatibility mode even gets it right ARRGGHH
    $(document.body).removeClassName('end-user-mode');
    Cookie.set('portal-admin-view', 'admin-user');
    SPICEWORKS.fire('portalv2:designMode');
  },
  endUserMode: function(){
    // note the $() call on body is for IE7 only, IE8 in IE7 compatibility mode even gets it right ARRGGHH
    $(document.body).addClassName('end-user-mode');
    Cookie.set('portal-admin-view', 'end-user');
    SPICEWORKS.fire('portalv2:endUserMode');
  },
  toggleNewTab: function(clicked){
    document.location.href = '/portal/new_page';
  },
  toggleJumpTo: function(clicked){
    clicked = $(clicked);
    var jumpTo = $('jump-to');
    if (jumpTo && jumpTo.visible()) jumpTo.hide();
    else if (jumpTo && jumpTo.hasClassName('positioned')) this.showSimpleMenu(jumpTo);
    else if (jumpTo) this.showSimpleMenuFirstTime(jumpTo, clicked);
  },
  togglePreferences: function(clicked){
    clicked = $(clicked);
    var preferences = $('preferences');
    if (preferences && preferences.visible()) preferences.hide();
    else if (preferences) this.showSimpleMenu(preferences);
    else new Ajax.Request('/portal/preferences', {onComplete:this.showSimpleMenuFirstTime.bind(this, 'preferences', clicked)});
  },
  togglePageSettings: function(clicked){
    clicked = $(clicked);
    var settings = $('page-settings');
    if (settings && settings.visible()) settings.hide();
    else if (settings) this.showSimpleMenu(settings);    
    else if (settings && settings.hasClassName('positioned')) this.showSimpleMenu(settings);
    else if (settings) this.showSimpleMenuFirstTime(settings, clicked);
  },
  toggleAddContent: function(clicked){
    clicked = $(clicked);
    var addContent = $('add-content');
    if (addContent && addContent.visible()) addContent.hide();
    else if (addContent && addContent.hasClassName('positioned')) this.showSimpleMenu(addContent);
    else if (addContent) this.showSimpleMenuFirstTime(addContent, clicked);
  },
  showSimpleMenu: function(menu){
    $$('div.simple-menu').select(function(e){ return e.visible(); }).invoke('hide');
    menu.show();
  },
  showSimpleMenuFirstTime: function(menu, clickedFrom){
    menu = $(menu);
    $$('div.simple-menu').select(function(e){ return e.visible(); }).invoke('hide');
    var clickedDims = clickedFrom.getDimensions(), clickedOffsets = clickedFrom.cumulativeOffset();
    var right = (document.body.getWidth() - (clickedDims.width + clickedOffsets.left)) - 4;
    menu.setStyle({top: (clickedOffsets.top - 3) + 'px', right: right + 'px', marginRight: clickedFrom.getStyle('padding-right')}).show().addClassName('positioned');
  }
};

var Lightbox = {
  setup: function(){
    var viewportHeight = document.viewport.getHeight(), bodyHeight = $(document.body).getHeight();
    $$('.darkbox').invoke('setStyle', {height:(viewportHeight > bodyHeight ? viewportHeight : bodyHeight) + 'px'});
  },
  remove: function(){
    $$('.darkbox, .lightbox').invoke('remove');
  }
};

var Flyover = {destroy:Lightbox.remove};

var LoadingMessage = {
  set: function(el, message) {  
    if (!message) { message = "Loading&hellip;"; }
    StatusMessage.set(el, message, {'className': 'loading'});
  }
};

var StatusMessage = {
  set: function(el, message, options) {
    this.options = Object.extend({ className: '' }, options || {});    
     
    el = $$$(el);
    
    if (!el) { return; };    
    var loadingMessage = new Element('div', {'class': "sui-status-message" + " " + this.options['className'] }).update(new Element('h3').update(new Element("span").update(message)));
    
    el.update(loadingMessage);
  }
};

var CalendarPopup = { 
  setup:function(textFieldID, triggerID, additionalOptions) {
    var calendar;
    additionalOptions = additionalOptions || {};
    // silently fail if the nodes to attach the calendar to cannot be found
    if( $(textFieldID) && (!triggerID || $(triggerID)) ){
      calendar = Calendar.setup({ 
        inputField : textFieldID, // ID of the input field 
        ifFormat : additionalOptions.dateFormat, // the date format 
        button : triggerID, // ID of the button
        align : 'Bl',
        single_click : true,
        step : 1, // show every year in menu
        cache : true, // reuse the div is calendar is reopened
        showOthers : true,
        weekNumbers : false,
        onUpdate: additionalOptions.onUpdate,
        getDateStatus: additionalOptions.getDateStatus
      }); 
    }
    return calendar;
  }
};

var Cookie = {
  get: function( name ){
    var nameEQ = escape(name) + "=", ca = document.cookie.split(';');
    for (var i = 0, c; i < ca.length; i++) {
      c = ca[i];
      while (c.charAt(0) == ' ') c = c.substring(1, c.length);
      if (c.indexOf(nameEQ) == 0) return c.substring(nameEQ.length, c.length);
    }
    return null;
  },
  set: function( name, value, options ){
    options = (options || {});
    if ( options.expiresInOneYear ){
      var today = new Date();
      today.setFullYear(today.getFullYear()+1, today.getMonth, today.getDay());
      options.expires = today;
    }
    var curCookie = escape(name) + "=" + escape(value) + 
      ((options.expires) ? "; expires=" + options.expires.toGMTString() : "") + 
      ((options.path)    ? "; path="    + options.path : "") + 
      ((options.domain)  ? "; domain="  + options.domain : "") + 
      ((options.secure)  ? "; secure" : "");
    document.cookie = curCookie;
  },
  removeCookie: function (key) {
    var date = new Date();
    date.setTime(date.getTime()-(1*24*60*60*1000));
    this.set(key, '', {expires: date});
  },
  hasCookie: function( name ){
    return document.cookie.indexOf( escape(name) ) > -1;
  }
};

Ajax.Responders.register({
  onCreate: function(request){
    document.fire('ajax:started', request); // fire a custom event when an ajax request is started

    // This will ensure that all AJAX posts have an authenticity token so we won't
    // cause rails to throw an ActionController::InvalidAuthenticityToken exception.
    if (request.method == 'post' && Portal.options.authenticityToken) {
      // If we don't have a postBody, force one.  This is our only chance
      // to change what gets posted, because Ajax.Request will always use
      // the postBody if present and it's too late to add to request.options.parameters.
      if (!request.options.postBody)
        request.options.postBody = Object.toQueryString(request.parameters);
      
      var encodedToken = encodeURIComponent(Portal.options.authenticityToken);
      var regex = new RegExp(encodedToken);
      if (!regex.match(request.options.postBody)) request.options.postBody += "&authenticity_token=" + encodedToken;
    }
  },
  onComplete: function(request){
    document.fire('ajax:completed', request); // fire a custom event when an ajax request is completed for observers
  }
});

function $$$(selector) {
    return ($(selector) || $$(selector).first() || null);
}