/*
 * Copyright 2006, John Drinkwater <john@nextraweb.com> http://johndrinkwater.name/
 * Distributed under the terms of the MIT License.
 * Please note, this script isn't as DOM friendly as I would like, 
 * but IE, as always, has problems with <select> and <option>s
 * Version 1N, from http://ezri.nextraweb.com/examples/js/haiku/rev1j/componentselect.js
 * Thanks to wkornew & tic
 */

// From Prototype
function $( ) {
    var elements = [];
    for ( var i = 0; i < arguments.length; i++ ) {
        var element = arguments[ i ];
        if ( typeof element == 'string' )
            element = document.getElementById( element );
        if ( arguments.length == 1 )
            return element;
        elements.push( element );
    }
    return elements;
}

/*
  http://simon.incutio.com/js/getElementsBySelector.js
  That useful function for easy css selector => element array
 */
function getAllChildren(e) { return e.all ? e.all : e.getElementsByTagName('*'); }

document.getElementsBySelector = function(selector) {
  if (!document.getElementsByTagName) {
    return [];
  }
  var tokens = selector.split(' ');
  var currentContext = [ document ];
  for (var i = 0; i < tokens.length; i++) {
    token = tokens[i].replace(/^\s+/,'').replace(/\s+$/,'');;
    if (token.indexOf('#') > -1) {
      var bits = token.split('#'), tagName = bits[0], id = bits[1];
      var element = document.getElementById(id);
      if (tagName && element.nodeName.toLowerCase() != tagName) { return []; }
      currentContext = [ element ];
      continue; 
    }
    if (token.indexOf('.') > -1) {
      var bits = token.split('.'), tagName = bits[0], className = bits[1];
      if (!tagName) { tagName = '*'; }
      var found = [], foundCount = 0;
      for (var h = 0; h < currentContext.length; h++) {
        var elements;
        if (tagName == '*') { elements = getAllChildren(currentContext[h]);
        } else { elements = currentContext[h].getElementsByTagName(tagName); }
        for (var j = 0; j < elements.length; j++) { found[foundCount++] = elements[j]; }
      }
      currentContext = [];
      var currentContextIndex = 0;
      for (var k = 0; k < found.length; k++) {
        if (found[k].className && found[k].className.match(new RegExp('\\b'+className+'\\b'))) {
          currentContext[currentContextIndex++] = found[k];
        }
      }
      continue; 
    }
    if (token.match(/^(\w*)\[(\w+)([=~\|\^\$\*]?)=?"?([^\]"]*)"?\]$/)) {
      var tagName = RegExp.$1, attr = RegExp.$2, attrOperator = RegExp.$3, attrValue = RegExp.$4;
      if (!tagName) { tagName = '*'; }
      var found = [], foundCount = 0;
      for (var h = 0; h < currentContext.length; h++) {
        var elements;
        if (tagName == '*') { elements = getAllChildren(currentContext[h]);
        } else { elements = currentContext[h].getElementsByTagName(tagName); }
        for (var j = 0; j < elements.length; j++) { found[foundCount++] = elements[j]; }
      }
      currentContext = [];
      var currentContextIndex = 0, checkFunction; 
      switch (attrOperator) {
        case '=': checkFunction = function(e) { return (e.getAttribute(attr) == attrValue); }; break;
        case '~': checkFunction = function(e) { return (e.getAttribute(attr).match(new RegExp('\\b'+attrValue+'\\b'))); }; break;
        case '|': checkFunction = function(e) { return (e.getAttribute(attr).match(new RegExp('^'+attrValue+'-?'))); }; break;
        case '^': checkFunction = function(e) { return (e.getAttribute(attr).indexOf(attrValue) == 0); }; break;
		case '$': checkFunction = function(e) { return (e.getAttribute(attr).lastIndexOf(attrValue) == e.getAttribute(attr).length - attrValue.length); }; break;
        case '*': checkFunction = function(e) { return (e.getAttribute(attr).indexOf(attrValue) > -1); }; break;
        default : checkFunction = function(e) { return e.getAttribute(attr); };
      }
      currentContext = [];
      var currentContextIndex = 0;
      for (var k = 0; k < found.length; k++) {
        if (checkFunction(found[k])) {
          currentContext[currentContextIndex++] = found[k];
        }
      }
      continue; // Skip to next token
    }
    tagName = token;
    var found = [];
    var foundCount = 0;
    for (var h = 0; h < currentContext.length; h++) {
      var elements = currentContext[h].getElementsByTagName(tagName);
      for (var j = 0; j < elements.length; j++) {
        found[foundCount++] = elements[j];
      }
    }
    currentContext = found;
  }
  return currentContext;
}

/*
	take the current selection, and make a haiku/component
 */
function updateSelection( set, level ) {
	
	var text, i, component = '';
	for ( i = 1; i <= level + 1 ; i++ ) { 
		if ( componentSelector[ set ][ i ] && componentSelector[ set ][ i ].selectedIndex != -1 ) {
			text = componentSelector[ set ][ i ].options[ componentSelector[ set ][ i ].selectedIndex ].text;
			if ( text != '' )
				component += text + '/';
		}
	}
	hiddenComponent[ set ].value = component.substring( 0, component.length - 1 );
}

/*
	The user has changed the selected component
 */
function selectComponent( e ) {

	// because browsers never make it easy (IE)..
	var caller, remover;
	if ( !e ) e = window.event;
	if ( e.target ) caller = e.target; else if ( e.srcElement ) caller = e.srcElement;
	if ( caller.nodeType == 3 ) // defeat Safari bug
		caller = caller.parentNode;
	var level = caller.stage, set = caller.set, onlyLeafs = caller.allowleafs;

	// bomb out if this is a last choice
	if ( level >= maxBranches ) {
		updateSelection( set, level ); 
		return;
	}

	// remove any <select>s to the right of us
	for ( i = maxBranches; i > level; i-- ) {
		if ( null != ( remover = componentSelector[ set ][ i ] ) ) {
			remover.selectedIndex = -1;
			if ( remover.parentNode )
				 remover.parentNode.removeChild( remover );
		}
	}
	// check if the user back–pedalled to a non option ( we no longer have '' choices )
	var parentChoice = componentSelector[ set ][ level ].options[ componentSelector[ set ][ level ].selectedIndex ].text;
	if ( parentChoice == '' ) {
		updateSelection( set, level ); 
		return;
	}

	var choice, superChoice, shortLeaf = false, previous = 's33d', p = 0, recursive = false, currentSelector = componentSelector[ set ][ level + 1 ];
	// empty the choice
	currentSelector.options.length = 0;
	previous = currentSelector.options[ p++ ] = new Option( );
 
	// Populate the choice 
	for ( i = 0; i < componentList.length ; i++ ) {
		choice = componentList[ i ][ level ]; 
		superChoice = componentList[ i ][ level - 1 ]; 
		if ( superChoice == parentChoice ) {
			if ( previous != choice && choice != null ) {
			
				previous = choice;
				currentSelector.options[ p++ ] = new Option( choice, choice );
			}
			if ( componentList[ i ][ level + 1 ] != null ) 
				recursive = true;
			if ( choice == null )
				shortLeaf = true;
		}
	}
	currentSelector.selectedIndex = 0; // Konq. has fits without

	// shortLeaf is true when we have a stump with leafs further on
	if ( currentSelector.options.length > 0 ) {
		if ( !( shortLeaf || !onlyLeafs) || previous == currentSelector.options[ 0 ]  ) {
			currentSelector.options[ 0 ] = null;
		}
	}

	// avoid cases where there are no options, ie, we have selected a leaf
	if ( currentSelector.options.length < ( onlyLeafs ? 1 : 2 ) ) {
		updateSelection( set, level ); 
		return;
	}

	// We're done, add it back to the page
	var parent = caller.parentNode;
	var sibling = caller.nextSibling;
	if ( sibling != null )
		parent.insertBefore( currentSelector, sibling );
	else
		parent.appendChild( currentSelector );

	// does this branch continue?
	if ( recursive ) {
		var event = { target : currentSelector };
		selectComponent( event ); // My Konq was being funny with anon {}
	} else 
		updateSelection( set, level ); 
}


/*
	This function populates the pages view of the component tree.
	It does nothing more.
 */
function generateTree( flatsource ) {

	// we receive a <select> here
	if ( componentList == null ) {
		componentList = {};
		var tmp, ref;

		for ( var i = 0; i < flatsource.length; i++ ) {
			tmp = flatsource[ i ].text.split( '/' );
			ref = "";
			for ( var j = 0; j < tmp.length; j++ ) {
				ref += "[ '" + tmp[ j ] + "' ]";
				eval( "if ( componentList" + ref + " == null ) componentList" + ref + " = { };" );
			}
			eval( "componentList" + ref + ".option = true;" );
			maxBranches = ( maxBranches < tmp.length ? tmp.length : maxBranches );
		}
	}
}


/*
	This function deletes the <select> box, replaces it with a hidden input box, 
	and as many <select>s as the first/selected component had parts: this new element has an 
	event that triggers on change, to add or remove <select> boxes
	original: the <select> field that is to be replaced
	set: support multiple component fields, can leave null for autoincrement
	onlyLeafs: add leafs that contain '', so users can pick super components
 */
function reduceComponents( original, set, onlyLeafs ) {
	
	// check support, else block js method
	if ( !document.createElement || !original )
		return; 
	if ( set == null ) set = window.components++;
	if ( onlyLeafs == null ) onlyLeafs = true;

	var i, p, j, sibling = original, parent = original.parentNode;

	// only generates once
	generateTree( original.options );

	componentSelector[ set ] = [];

	// create some replacement dropdowns
	for ( i = 1; i <= maxBranches; i++ ) {
		componentSelector[ set ][ i ] = document.createElement( 'SELECT' );
		componentSelector[ set ][ i ].id = 'component-selector-' + set + '-' + i;
		componentSelector[ set ][ i ].className = 'haikucomponent';
		componentSelector[ set ][ i ].stage = i;
		componentSelector[ set ][ i ].set = set;
		componentSelector[ set ][ i ].allowleafs = onlyLeafs;
		addEvent( componentSelector[ set ][ i ], 'change', selectComponent );
	}

	// Setup the field to submit
	var currentSelector = componentSelector[ set ][ 1 ];
			hiddenComponent[ set ] = document.createElement( 'INPUT' );
			hiddenComponent[ set ].id = original.id;
			hiddenComponent[ set ].name = original.name;
			hiddenComponent[ set ].type = 'hidden';	

	// Take currently selected option, or the first
	if ( !original.options ) { alert( 'called' ); return; // because Opera is buggy }
	if ( original.selectedIndex == -1 ) original.selectedIndex = 0;
	// Query adds '' at the start
	if ( onlyLeafs && original.options[ original.selectedIndex ].text == '' && original.selectedIndex == 0 ) original.selectedIndex = 1;
	hiddenComponent[ set ].value = original.options[ original.selectedIndex ].text;
	subItems		  = hiddenComponent[ set ].value.split( '/' );
	var previous = 's33d', shortLeaf = false;
	
	// Populate choice(s)	
	for ( i = 0; i < subItems.length + 1; i++ ) {

		p = 0;
		currentSelector = componentSelector[ set ][ i + 1 ];
		if ( !currentSelector )
			continue;
		
		previous = currentSelector.options[ p++ ] = new Option( );
		for( j = 0; j < componentList.length ; j++ ) {

			choice = componentList[ j ][ i ];
			if ( componentList[ j ][ i - 1 ] == subItems[ i - 1 ] /*|| typeof subItems[ i - 1 ] == 'undefined'*/ ) {
				
				if ( previous != choice && choice != null && choice != '' ) {
					previous = choice;					
					currentSelector.options[ p++ ] = new Option( choice, choice );
					if ( choice == subItems[ i ] )
						currentSelector.selectedIndex = p - 1;
				}
				if ( choice == null )
					shortLeaf = true;
			}

		}
		// Special case(s) for supercomponents: we have children, but! we dont have a child ourselves
		if ( currentSelector.options.length > 0 ) {
			if ( !( subItems[ i ] == null || shortLeaf || !onlyLeafs ) || previous == currentSelector.options[ 0 ] ) {
				currentSelector.options[ 0 ] = null;
			}
		}

		if ( currentSelector.options.length < ( onlyLeafs ? 1 : 2 ) )
			continue;

		// insert into page
		if ( sibling != null )
			parent.insertBefore( currentSelector, sibling );
		else
			parent.appendChild( currentSelector );
	}
	parent.replaceChild( hiddenComponent[ set ], original );
}

window.componentList = null;
window.components = 0;
window.componentSelector = [];
window.hiddenComponent = [];
window.maxBranches = 0;

/*
  We hook into the query page with this attached to the filter <select>
  We should be called after the component has been created if the browser
  follows the DOM way of thinking
 */
function convertQueryComponent() {
	
	var comps = document.getElementsBySelector('tr.component td.filter select');
	if ( comps.length > 0 )
		for (var i = 0; i < comps.length; i++) 
			if ( comps[ i ].name == 'component' )
				reduceComponents( comps[i], null, true );		
}

function initialiseComponents() { 

	var comps = document.getElementsBySelector('tr.component td.filter select');
	if ( $( 'add_filter' ) )
		addEvent( $( 'add_filter' ), 'change', convertQueryComponent );

	if ( comps.length > 0 ) {
		// For the query page
		for (var i = 0; i < comps.length; i++)
			reduceComponents( comps[i], null, true );
	}
	
	// Interestingly, Opera picks up .names in getElementById(), hence it being at the end now
	if ( $( 'component' ) )
		reduceComponents( $( 'component' ), null, true ); // For the new ticket page
}

addEvent( window, 'load', initialiseComponents );

