// Copyright (c) 2006, Andrew Witte (except noted portions)
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without modification,
// are permitted provided that the following conditions are met:
//
//    * Redistributions of source code must retain the above copyright notice, this
//      list of conditions and the following disclaimer.
//    * Redistributions in binary form must reproduce the above copyright notice,
//      this list of conditions and the following disclaimer in the documentation
//      and/or other materials provided with the distribution.
//    * The names of its contributors may not be used to endorse or promote products
//      derived from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
// IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA,
// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY
// OF SUCH DAMAGE.


// Behavior for the Case Start page.
// Requires prototype.js and script.aculo.us to be loaded.

// GLOBALS
var widgets;
var columns;
var customizing = false; // Are we in "customize mode"?
// last thing we posted, so we can check to make sure we do not post it twice in a row:
var postString;

// Attach UI handlers and do other setup upon page load
function onLoad() {
  // setup globals
  widgets = document.getElementsByClassName('widget');
  columns = document.getElementsByClassName('col');

  // Some styles need realigning to look best
  // We need to do this whenever the window is resized
  setActiveStyleSheet(getPreferredStyleSheet());
  realignWidgets();
  Event.observe(window, 'resize', realignWidgets);
  
  // Put login UI into correct state
  if( loginUIState == 'form') {
    $('login-box').style.display = '';
    activateUserOrPasswordField();
  } else if( loginUIState == 'link' ) {
    $('login-link').style.display = ''
  }

  // Set up UI event handlers
  Event.observe('login-link', 'click', showLoginForm);
  $('login-link').onclick = function() { return false; }
  Event.observe('login-form', 'submit', submitLogin);
  $('login-form').onsubmit = function() { return false; }
  Event.observe('customize-link', 'click', toggleCustomizeMode);
  $('customize-link').onclick = function() { return false; }
  Event.observe('large-icons-button', 'click', function() {
      setActiveStyleSheet('Large icons');
      realignWidgets();
    });
  Event.observe('small-icons-button', 'click', function() {
      setActiveStyleSheet('Small icons');
      realignWidgets();
    });
  Event.observe('no-icons-button', 'click', function() {
      setActiveStyleSheet('No icons');
      realignWidgets();
    });
    
  setUpAddWidgetLinkHandlers();
  setUpWidgetControlHandlers();
}

// Apply the stylesheet with the specified title and turn off all other
// titled stylesheets. (From the ALA styleswitcher by Paul Sowden.)
// When skipServerCall is true, the new preference won't be submitted to the server.
function setActiveStyleSheet(title, skipServerCall) {
  var i, a, main;
  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
    if(a.getAttribute("rel").indexOf("style") != -1
       && a.getAttribute("title")) {
      a.disabled = true;
      if(a.getAttribute("title") == title) a.disabled = false;
    }
  }
  // post the new preference to the server
  if( ! skipServerCall ) {
    var post = 'postType=style&style=' + escape(title);
    new Ajax.Request('setPreferences.php', {postBody: post});
  }
}

// Return the title of the active style sheet. (From the ALA styleswitcher by Paul Sowden.)
function getActiveStyleSheet() {
  var i, a;
  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
    if(a.getAttribute("rel").indexOf("style") != -1
      && a.getAttribute("title")
      && !a.disabled) return a.getAttribute("title");
  }
  return null;
}

// Return the title of the preferred (non-alternate) stle sheet. (ALA again).
function getPreferredStyleSheet() {
  var i, a;
  for(i=0; (a = document.getElementsByTagName("link")[i]); i++) {
    if(a.getAttribute("rel").indexOf("style") != -1
       && a.getAttribute("rel").indexOf("alt") == -1
       && a.getAttribute("title")
       ) return a.getAttribute("title");
  }
  return null;
}

// Find out the maximum height of all the divs in the passed array,
// and add bottom padding so they all take up the same height.
// Based on column.js by Paul@YellowPencil.com and Scott@YellowPencil.com
function makeSameHeight(divs) {
  var maxHeight = 0;
  for (var i = 0; i < divs.length; i++) {
    if( navigator.appName != "Microsoft Internet Explorer" ) {
      divs[i].style.height = '';
    }
    divs[i].style.paddingBottom = '';
    if (divs[i].offsetHeight > maxHeight) {
      maxHeight = divs[i].offsetHeight;
    }
  }
  for (var i = 0; i < divs.length; i++) {
    divs[i].style.paddingBottom = (maxHeight - divs[i].offsetHeight) + 'px';
  }
}

// Undo the effects of makeSameHeight().
function makeNaturalHeight(divs) {
  for (var i = 0; i < divs.length; i++) {
    divs[i].style.paddingBottom = '0px';
  }
}

// Apply makeSameHeight and makeNaturalHeight in an appropriate manner
// based on the currently active stylesheet.
function realignWidgets() {
  if( getActiveStyleSheet() == 'Large icons' ) {
    makeSameHeight(widgets);
  } else {
    makeSameHeight(widgets); // Firefox problem fix
    makeNaturalHeight(widgets);
  }
  makeSameHeight(columns);
};

// Display the login form (animated)
function showLoginForm() {
  $('login-link').style.display = 'none';
  Effect.SlideDown('login-box', {duration: 0.6});
  setTimeout(activateUserOrPasswordField, 700);
}

// Activate appropriate field in login form
function activateUserOrPasswordField() {
  if( Field.present('case-id-field') ) {
    Field.activate('password-field');
  } else {
    Field.activate('case-id-field');
  }
}

// Submit login credentials
function submitLogin() {
  $('login-spinner').style.visibility = 'visible';
  new Ajax.Request(loginURL, {
      postBody: Form.serialize('login-form'),
      onSuccess: handleSuccessfulLogin,
      onFailure: handleFailedLogin,
      onException: handleFailedLogin
    });
}

function handleSuccessfulLogin(xhr) {
  $('login-spinner').style.visibility = 'hidden';
  // Display greeting
  Element.update('login-form', xhr.responseText);
  setTimeout(function() {
      Effect.SlideUp('login-box', {duration: 0.6});
    }, 1500);

  // Re-load widgets
  new Ajax.Request('widgets.php', {
      method: 'get',
      onSuccess: function(xhr) {
        Element.update('widgets', xhr.responseText);
        xhr.responseText.evalScripts();
        // need to fix up the global arrays
        widgets = document.getElementsByClassName('widget');
        columns = document.getElementsByClassName('col');
        // re-enable drag and drop if in customize mode
        if( customizing ) {
          enableDragAndDrop();
        }
        setUpWidgetControlHandlers();
        realignWidgets();
      }
    });
}

function handleFailedLogin() {
  $('login-spinner').style.visibility = 'hidden';
  Effect.Shake('login-box');
  activateUserOrPasswordField();
}

// Custom effect for sliding the customization panel. It's pretty specific to that purpose.
// fromPercent: initial left offset percentage
// toPercent: final left offset percentage
var SlideLeftPositionPercent = Class.create();
Object.extend(Object.extend(
    SlideLeftPositionPercent.prototype, Effect.Base.prototype),
{
  initialize: function(element, fromPercent, toPercent) {
    this.element = $(element);
    this.fromPercent = fromPercent;
    this.toPercent = toPercent;
    this.start(arguments[3] || {});
  },
  update: function(position) {
    var left = this.fromPercent + (this.toPercent-this.fromPercent) * position;
    this.element.style.left = Math.round(left) + '%';
  }
});

// Let the user customize the page (change style, drag elements)
function toggleCustomizeMode() {
  Event.stopObserving('customize-link', 'click', toggleCustomizeMode);
  var body = document.getElementsByTagName('body')[0];
  if( ! customizing )
  {
    customizing = true;
    Element.addClassName(body, 'customizing');
    Element.update('customize-link', '[done]');
    enableDragAndDrop();
    realignWidgets();
    
    new Effect.Parallel(
      [ new SlideLeftPositionPercent('customize', -21.0, 0.0, {sync: true}),
        new SlideLeftPositionPercent('main', 12.0, 22.0, {sync: true}) ], {
      duration: 0.6,
      afterFinish: function() {
        Event.observe('customize-link', 'click', toggleCustomizeMode);
        realignWidgets();
      }
    });
  }
  else
  {
    customizing = false;
    Element.removeClassName(body, 'customizing');
    Element.update('customize-link', '[customize]');
    disableDragAndDrop();
    realignWidgets();
    
    new Effect.Parallel(
      [ new SlideLeftPositionPercent('customize', 0.0, -21.0, {sync: true}),
        new SlideLeftPositionPercent('main', 22.0, 12.0, {sync: true}) ], {
      duration: 0.6,
      afterFinish: function() {
        Event.observe('customize-link', 'click', toggleCustomizeMode);
        realignWidgets();
      }
    });
  }
}

// Set up drag and drop widget reordering
function enableDragAndDrop() {
  var sortableOpts = {
    tag: 'div',
    only: 'widget',
    handle: 'handle',
    overlap: 'vertical',
    constraint: false,
    containment: ['col1', 'col2', 'col3'],
    dropOnEmpty: true,
    onChange: function() { makeSameHeight(columns); },
    onUpdate: handleSortOrderChanged
  }
  Sortable.create('col1', sortableOpts);
  Sortable.create('col2', sortableOpts);
  Sortable.create('col3', sortableOpts);
}

// Event handler for widget ordering changed
// This is where we post the new order to the server
function handleSortOrderChanged() {
  makeSameHeight(columns);
  var newPostString = 'postType=layout'
  for( var i = 1; i <= 3; i++ ) {
    var chunk = Sortable.serialize('col' + i);
    if( chunk.length > 0 ) {
      newPostString += '&' + chunk;
    }
  }
  if( newPostString != postString ) {
    postString = newPostString;
    setTimeout(function() {
        new Ajax.Request('setPreferences.php', {postBody: postString});
      }, 500);
  }
}

// Turn off drag and drop on widgets
function disableDragAndDrop() {
  Sortable.destroy('col1');
  Sortable.destroy('col2');
  Sortable.destroy('col3');
}

// Set up handlers for the widget close boxes, edit links, etc.
function setUpWidgetControlHandlers() {
  // Close box handlers
  var links = document.getElementsByClassName('close-box');
  for(var i = 0; i < links.length; i++) {
    Event.observe(links[i], 'click', closeWidget);
    links[i].onclick = function() { return false; }
  }
  // Edit link handlers
  links = document.getElementsByClassName('edit-link');
  for(var i = 0; i < links.length; i++) {
    Event.observe(links[i], 'click', editWidget);
    links[i].onclick = function() { return false; }
  }
  // Edit form handlers
  var forms = document.getElementsByClassName('widget-edit-form');
  for(var i = 0; i < links.length; i++) {
    Event.observe(forms[i], 'submit', saveWidgetPreferences);
    forms[i].onsubmit = function() { return false; }  
  }
}

// Handle clicking on a widget's close box (event comes from the close link).
function closeWidget(event) {
  var widget = firstAncestorWithClassName(event.target || event.srcElement, 'widget');
  widget.parentNode.removeChild(widget);
  realignWidgets();
  handleSortOrderChanged();
}

// Handle clicking on a widget's edit link, by giving it the "editing" class
function editWidget(event) {
  var widget = firstAncestorWithClassName(event.target || event.srcElement, 'widget');
  Element.addClassName(widget, 'editing');
}

// Handle submitting a widget edit form
function saveWidgetPreferences(event) {
  var form = firstAncestorWithClassName(event.target || event.srcElement, 'widget-edit-form');
  var widget = firstAncestorWithClassName(event.target || event.srcElement, 'widget');
  new Ajax.Request(form.action, {
      postBody: Form.serialize(form),
      onSuccess: function(xhr) {
          Element.update(widget, xhr.responseText);
          Element.removeClassName(widget, 'editing');
          setUpWidgetControlHandlers();
          enableDragAndDrop();
        },
      onFailure: function(xhr) {
          var errZone = Element.childrenWithClassName(form, 'widget-edit-error')[0];
          if( errZone.firstChild ) {
            Effect.Pulsate(errZone);
          }
          Element.update(errZone, xhr.responseText);
        }
    });
}

// Set up handlers for the 'add widget' links
function setUpAddWidgetLinkHandlers() {
  var links = document.getElementsByClassName('add-widget');
  for(var i = 0; i < links.length; i++) {
    Event.observe(links[i], 'click', addWidget);
    links[i].onclick = function() { return false; }
  }
}

// Add a widget to the page. We are passed the event from clicking the add link.
function addWidget(event) {
  var url = firstAncestorWithClassName(event.target || event.srcElement, 'add-widget').getAttribute('href');
  new Ajax.Request(url, {
      method: 'get',
      onSuccess: function(xhr) {
          // Decide what column to place the widget in
          // (the least full one, favoring the center)
          var col = columns[0]
          var col0l = columns[0].childNodes.length;
          var col1l = columns[1].childNodes.length;
          var col2l = columns[2].childNodes.length;
          if( col2l < col0l && col2l < col1l ) {
            col = columns[2];
          } else if( col1l < col0l ) {
            col = columns[1];
          }
          // Insert the widget and take care of details
          new Insertion.Top(col, xhr.responseText);
          col.style.paddingBottom = '1px'; // fixes weird flashy widget bug
          widgets = document.getElementsByClassName('widget');
          col.firstChild.style.display = 'none';
          Effect.BlindDown(col.firstChild, {
              duration: 0.5,
              afterFinish: function() {
                  handleSortOrderChanged();
                  setUpWidgetControlHandlers();
                  enableDragAndDrop();
                }
            });
        }
    });
}

// Utility to search outward from a node until we read a node
// with the specified class, and return the found node.
function firstAncestorWithClassName(node, classname) {
  while( node != null ) {
    if( node.className && Element.hasClassName(node, classname) ) {
      return node;
    }
    node = node.parentNode;
  }
  return null;
}
