
var treeSelectFields = [];

var TreeSelectFieldController = Class.create({
  
  initialize:function()
  {
    // loop through tree select fields
    treeSelectFields.each(function(fieldRecord) {
      
      // find all nodes needed to render selects for the initial value
      var nodes = this.findNodeAndAncestorsForValue($(fieldRecord.name + '_field').getValue(), fieldRecord);
      
      // create selectors
      var treeDepth = 0;
      nodes.each(function(node) {
        if (treeDepth == 0) {
          
          // create root node selector, and insert
          var selectorEl = this.createTreeNodeSelector(fieldRecord, treeDepth, node.options);
          $(fieldRecord.name+'_selector').appendChild(selectorEl);
          
        } else { // normal node
          
          // assign value to the parent node's selector
          var parentSelectEl = $(fieldRecord.name + '_' + (treeDepth-1) + '_select');
          parentSelectEl.value = node.value;
          
          // if this node has any children, create a selector for it
          if (node.children) {
            var selectorEl = this.createTreeNodeSelector(fieldRecord, treeDepth, node.children);
            $(fieldRecord.name + '_' + (treeDepth-1) + '_descendants').appendChild(selectorEl);
          }
          
        }
        treeDepth++;
      }.bind(this));
            
    }.bind(this));
  },
  
  createTreeNodeSelector:function(fieldRecord, treeDepth, childNodes)
  {
    // create wrapper div
    var wrapperEl = new Element('div', {'id': fieldRecord.name + '_' + treeDepth + '_wrapper'});
    
    // create select div, and observe it's changes
    var selectEl = new Element('select', {'id':fieldRecord.name + '_' + treeDepth + '_select'});
    selectEl.observe('change', this.treeSelectElValuedidChange.bind(this));
    wrapperEl.appendChild(selectEl);
    
    // create blank option value
    var optionEl = new Element('option', {'value': ''});
    optionEl.text = (treeDepth == 0) ? '(none)' : ' ';
    selectEl.options.add(optionEl);
    
    // add option values to this select
    childNodes.each(function(option) {
      optionEl = new Element('option', {'value': option.value});
      optionEl.text = option.label;
      if (this.isBlockedValue(fieldRecord, option.value))
        optionEl.disabled = true;
      
      selectEl.options.add(optionEl);
    }.bind(this));
    
    // create an empty div, which can be populated later with this selector's descendants
    var descendantsWrapperEl = new Element('div', {'id': fieldRecord.name + '_' + treeDepth + '_descendants', 'class': 'treeselect_descendants'});
    wrapperEl.appendChild(descendantsWrapperEl);
    
    return wrapperEl;
  },
  
  treeSelectElValuedidChange:function(event)
  {
    // determine the field and tree depth from the select's id
    var selectEl = event.element();
    var matches = selectEl.id.match(/^(.+)_([0-9]+)_select$/);
    var fieldName = matches[1];
    var treeDepth = parseInt(matches[2]);
    
    // if the value is null, and it's not the root node, use the *parent* select element instead (so we grab it's value)
    if (selectEl.getValue() == '' && treeDepth > 0) {
      treeDepth--;
      selectEl = $(fieldName + '_' + treeDepth + '_select');
    }
    
    // find the field record for this field
    var fieldRecord;
    treeSelectFields.each(function(treeSelectFieldsRecord) {
      if (treeSelectFieldsRecord.name == fieldName) {
        fieldRecord = treeSelectFieldsRecord;
      }
    }.bind(this));
    
    // find the option record for the new value.
    var selectedNode = this.findNodeForValue(selectEl.getValue(), fieldRecord.options);
    
    // apply the new selection to the hidden element
    $(fieldRecord.name + '_field').value = selectEl.getValue();
    
    // destroy any descendants of this selector, since they are no-longer valid
    var descendantsWrapperEl = $(fieldRecord.name + '_' + treeDepth + '_descendants');
    descendantsWrapperEl.innerHTML = '';
    
    // if the new value has any children, create a selector for it
    if (selectedNode.children) {
      var selectorEl = this.createTreeNodeSelector(fieldRecord, parseInt(treeDepth) + 1, selectedNode.children);
      descendantsWrapperEl.appendChild(selectorEl);
    }
  },
  
  findNodeForValue:function(searchValue, searchChildren)
  {
    if (searchValue == '')
      return false;
    
    var searchResult = false;
    searchChildren.each(function(childNode) {
      // check if this child is the desired value
      if (childNode.value == searchValue) {
        searchResult = childNode;
        throw $break;
      }
      
      // check if any of this child's descendants are the desired value
      if (childNode.children) {
         searchResult = this.findNodeForValue(searchValue, childNode.children);
         if (searchResult)
           throw $break;
      }
    }.bind(this));
    
    return searchResult;
  },
  
  findNodeAndAncestorsForValue:function(searchValue, fieldRecord)
  {
    // are we searching for nothing? just return root node
    if (searchValue == 0 || searchValue == '')
      return [fieldRecord];
    
    // do search with our internal function
    var results = this._findNodeAndAncestorsForValue(searchValue, fieldRecord.options, [fieldRecord]);
    
    return results;
  },
  
  _findNodeAndAncestorsForValue:function(searchValue, searchChildren, results) // internal recursive function for findNodeAndAncestorsForValue()
  {
    var didFind = false;
    
    searchChildren.each(function(childNode) {
      
      // if this is the search value, add it to the results list and return
      if (childNode.value == searchValue) {
        results.push(childNode);
        didFind = true;
        throw $break;
      }
      
      // if this node has children, search them
      if (childNode.children) {
        
        // make a temporary copy of results, with ourselves in it
        var tmpResults = results.slice(0);
        tmpResults.push(childNode);
        
        // search descendants
        var tmpResults = this._findNodeAndAncestorsForValue(searchValue, childNode.children, tmpResults);
        
        // if search among descendants was successful, return them
        if (tmpResults) {
          results = tmpResults;
          didFind = true;
          throw $break;
        }
      }
      
    }.bind(this));
    
    if (didFind)
      return results;
    
    return false;
  },
  
  isBlockedValue:function(fieldRecord, value)
  {
    var isBlocked = false;
    
    fieldRecord.blocked_values.each(function(blockedValue) {
      if (blockedValue == value) {
        isBlocked = true;
        throw $break;
      }
    }.bind(this));
    
    return isBlocked;
  }
  
});

document.observe('dom:loaded', function() {
  new TreeSelectFieldController();
});

