/* 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';
  },

  /* 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
  **********************************************************************************************************/
  pageReordered: 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()});
  },
  
  makeSortable: function(){
    if (this.draggables) this.draggables.each(function(d){ d.destroy(); });
    this.draggables = $$('div#content div.content-block').inject($A(), function(self, memo, element){
      if (element.down('.admin-actions .move')) memo.push(new Draggable(element, {handle:'move', ghosting:true, revert:self.reordering.revert.curry(self), onDrag:self.reordering.dragged.curry(self)}));
      return memo;
    }.curry(this));
    if (this.mainDroppable) Droppables.remove('main');
    if (this.sideDroppable) Droppables.remove('sidebar');
    if ($('main')) this.mainDroppable = Droppables.add('main', {onDrop:this.reordering.dropped.curry(this)});
    if ($('sidebar')) this.sideDroppable = Droppables.add('sidebar', {onDrop:this.reordering.dropped.curry(this)});
  },
  reordering: {
    dragged: function(self, draggable, mouseEvent){
      var main = $('main'), sidebar = $('sidebar'),  placeholderSelector = '';

      if (self.reordering.within(mouseEvent, main)){
        self.reordering.insertPlaceholder(self, main, mouseEvent);
      } else if (self.reordering.within(mouseEvent, sidebar) && !$('content').hasClassName('no-sidebar-content')){
        self.reordering.insertPlaceholder(self, sidebar, mouseEvent);
      } else {
        $$('div.draggable-placeholder').invoke('remove');
      }
    },
    revert: function(self, draggable){
      // all the revert logic is handled in the dropped method, which will set this flag if needed
      return self.doRevert;
    },
    dropped: function(self, element, droppedLocation, mouseEvent){
      droppedLocation.select('div.draggable-placeholder').invoke('remove');

      // don't allow the dropped events to continue if the dropped element was the last child to be removed from main
      if (element.parentNode.id == 'main' && droppedLocation.id != 'main' && element.parentNode.childNodes.length == 1) {
        droppedLocation.select('div.draggable-placeholder').invoke('remove');
        alert("You must have at least one content item in the main area at all times");
        self.doRevert = true;
        return;
      }
      
      if (element.hasClassName('locked-block') && droppedLocation.id != 'main'){
        droppedLocation.select('div.draggable-placeholder').invoke('remove');
        alert("This content item is locked to the main area and cannot be moved to the sidebar");
        self.doRevert = true;
        return;
      }
      
      droppedLocation = $(droppedLocation);
      element.remove().relativize().setStyle({top:0,left:0});
      var insertBefore = self.reordering.detectContainer(self, droppedLocation, mouseEvent);
      if (insertBefore) {
        // check for the super hack instance variable
        if (this.insertBelow) insertBefore.insert({before:element});
        else insertBefore.insert({before:element});
      }
      else droppedLocation.insert({top:element});
      
      if (droppedLocation.id == 'sidebar'){
        $('content').removeClassName('no-sidebar-content');
        $$('#sidebar > h2').invoke('remove');
      } else if (!$('sidebar').down('.content-block')) {
        $('content').addClassName('no-sidebar-content');
        $('sidebar').update('<h2>Drag content here to create a sidebar on this page</h2>');
      }

      self.pageReordered();
    },
    detectContainer: function(self, wrapperContainer, position){
      return wrapperContainer.select('div.content-block').detect(function(self, element){
        return self.reordering.within(position, element);
      }.curry(self));
    },
    insertPlaceholder: function(self, wrapperContainer, position){
      var draggingOver = self.reordering.detectContainer(self, wrapperContainer, position);
      if (draggingOver && !draggingOver.hasClassName('draggable-placeholder')) {
        $$('div.draggable-placeholder').invoke('remove');
        // check for the super hack instance variable
        if (this.insertBelow) draggingOver.insert({'after':'<div class="draggable-placeholder content-block placeholder-block"><h2>Block will appear here</h2></div>'});
        else draggingOver.insert({before:'<div class="draggable-placeholder content-block placeholder-block"><h2>Block will appear here</h2></div>'});
      }
    },
    within: function(testPosition, container, options){
      // given an x/y position on the screen, determine if that position is within a given element
      
      options = options || {};
      // allow these two important x/y coordinates to be passed in because they are expensive
      options.topLeft = options.topLeft || container.cumulativeOffset();
      options.dimensions = options.dimensions || container.getDimensions();
      
      // transpose the client x/y attributes to just plain ol x/y for readability and add in the scroll offsets
      var scrollOffset = document.body.cumulativeScrollOffset();
      testPosition = {x:testPosition.clientX + scrollOffset.left, y:testPosition.clientY + scrollOffset.top};
      
      // transpose the top/left position to x/y coordinates for readability
      options.topLeft = { x:options.topLeft.left, y:options.topLeft.top };
      // calculate the x/y of the bottom left corner
      options.bottomRight = { x:options.topLeft.x + options.dimensions.width, y:options.topLeft.y + options.dimensions.height };

      // warning, super hack
      // set an instance variable to track if we want to perform the insertion above or below the contained object
      // we are doing the superhack because this method just returns a true/false if you should do the insert
      // when really it should return the position of where it should be inserted, however that would require a total rewrite of this sorting logic
      this.insertBelow = false;
      var canInsert = false;
      if ((testPosition.x > options.topLeft.x && testPosition.x < options.bottomRight.x) && (testPosition.y > options.topLeft.y && testPosition.y < options.bottomRight.y)){
        // ok, our cursor position is within the tested element
        canInsert = true;
        // not let's check if it's in the top half of the element or the bottom half
        // if it's in the bottom half, set our super hack instance variable
        if (testPosition.y > (options.topLeft.y + (options.dimensions.height / 2))) this.insertBelow = true;
      }

      return canInsert;
    }
  },

  /* 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
    return section + '[]=' + $(section).select('.content-block').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);
  }
};

// hack sortable to allow passing a selector for the sortable tag instead just a tag
if (typeof Sortable != 'undefined'){
  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);
  };
}

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);
}