/* Model Functions
 *
 * Perform various and sundry operations on the edit-page. 
 */

var target_url = '/plugins/ktcore/admin/ajaxComplexConditionals.php';

function getTargetUrl() {
    var base = getElement('kt-core-baseurl');
    return base.value + target_url;
}

// returns the td element representing the row, for use as Parent.
function getColumnForField(field_id) {
    return getElement('md_'+field_id);
}

// returns a 2d array ("instances" => instance_id, "names" => names)
function getActiveLookupsForField(field_id, clear) {
    var names = Array();
    var instances = Array();
    var column = getColumnForField(field_id);

    var sf_source = getElementsByTagAndClassName('select','item_list',column);
    if (sf_source.length == 0) { 
        simpleLog('ERROR','no item_list found in field '+field_id);
        return false;
    }
    var selectfield = sf_source[0];

    for (var i=0; i<selectfield.options.length; i++) {
        var opt = selectfield.options[i];
        if (opt.selected) {
            names.push(scrapeText(opt));
            instances.push(opt.value);
            if (clear == true) {
                opt.selected = false;
            }
        }
    }
    return {"instances":instances, "names":names};
}

function setMessageForField(field_id, message_type, message_str) {
    var column = getColumnForField(field_id);
    var message_opts = getElementsByTagAndClassName('p',message_type,column);
    if (message_opts.length == 0) {
        simpleLog('ERROR','no message with identifier '+message_type+' found in field '+field_id);
        return false;
    }

    var message_obj = message_opts[0];
    replaceChildNodes(message_obj, message_str);
}

/* undo stack management. */
var fixed_field_stack = Array();
var fixed_value = null;

// GAH - stupid ie.
function addEvent(obj, event, func) {
    if (obj.attachEvent) { obj.attachEvent('on'+event, func); }
   else { obj.addEventListener(event, func, false); }
}

function removeEvent(obj, event, func) {
   if (obj.detachEvent) { obj.detachEvent('on'+event, func); }
   else { obj.removeEventListener(event, func, false); }
}

// called after push or pop, makes sure that there is a "done" button on the right column.
function placeFinishButton() {
    // remove the current button no matter what.
    var current_button = getElement('global-stack-popper-button');
    if (current_button != null) {
        current_button.parentNode.removeChild(current_button);
        simpleLog('DEBUG','removing stack-popper.');
    }
    // find the current "fixed" field.
    var active_field = null;
    if (fixed_field_stack.length == 0) { 
        return false;           // nothing "free" - remove it.
    } else {
        active_field = fixed_field_stack[fixed_field_stack.length - 1];
    }
    var column = getColumnForField(active_field);

    var finish_button = INPUT({'type':'button', 'value':_('Finish with this column\'s behaviours.'),'id':'global-stack-popper-button'});
    simpleLog('DEBUG','placing stack-popper with function popFixedStack in field '+active_field);
    addEvent(finish_button, 'click', popFixedStack);
    simpleLog('DEBUG','added listener.');
    column.appendChild(finish_button);
}

function pushFixedStack(field_id, value) {
    var current_last_field = null;
    if (fixed_field_stack.length != 0) {
        current_last_field = fixed_field_stack[fixed_field_stack.length-1];
    }
    if ((current_last_field == null) || (current_last_field != field_id)) {
	simpleLog('DEBUG','pushing onto stack. '+field_id)
        fixed_field_stack.push(field_id);
    }
    fixed_value = value;
    placeFinishButton();
    updateActiveFields();
}

// pop the fixed stack, and set fixed_value to the appropriate one.
function popFixedStack() {
    var fs_length = fixed_field_stack.length;
    if (fs_length == 0) {
        // nothing, ensure that we are clean.
        fixed_value = null;
        return false;
    } else if (fs_length == 1) {
        // empty the fixed_value, and pop.
        fixed_value = null;
        var current_field = fixed_field_stack.pop();
    } else {
        var current_field = fixed_field_stack.pop();
        var last_item = fixed_field_stack[fs_length - 2];   // we've popped one subsequently.
        fixed_value = getFixedValueForField(last_item);        
    }
    // make sure the old field gets the appropriate classes.
    removeElementClass(getColumnForField(current_field), 'fixed');
    clearFixedValueForField(current_field);
    placeFinishButton();
    updateActiveFields();
}

function getFixedValueForField(field_id) {
    var column = getColumnForField(field_id);
    var fixed_value = null;
    var fixed_source = getElementsByTagAndClassName('input','fixed_value',column);
    if (fixed_source.length != 0) {
        for (var i=0; i<fixed_source.length; i++) {
            fixed_value = fixed_source[i].value;
        }
    }
    simpleLog('DEBUG','got fixed value for field '+field_id+':   '+fixed_value);    
    return fixed_value;
}

// also needs to pop this field's value from the stack.
function clearFixedValueForField(field_id) {
    var column = getColumnForField(field_id);
    simpleLog('DEBUG','clearing fixed value for field '+field_id);
    var fixed_source = getElementsByTagAndClassName('input','fixed_value',column);
    if (fixed_source.length != 0) {
        for (var i=0; i<fixed_source.length; i++) {
            fixed_source[i].parentNode.removeChild(fixed_source[i]);
        }
    }
    removeElementClass(column, 'fixed');
}

// setup a field to be "fixed" - that is, ensure that the correct hidden inputs are in place.
function setFixedValueForField(field_id, value, label) {
    var column = getColumnForField(field_id);
    simpleLog('DEBUG','fixing value for field '+field_id+' to '+label+' ('+value+')');
    addElementClass(column, 'fixed');
    setMessageForField(field_id,'fixed_message', _('Assuming this field has behaviour "')+label+'".');

    // now we need to check if this field _is_ fixed.
    var fixed_source = getElementsByTagAndClassName('input','fixed_value',column);
    if (fixed_source.length != 0) {
        for (var i=0; i<fixed_source.length; i++) {
            fixed_source[i].parentNode.removeChild(fixed_source[i]);
        }
    }
    appendChildNodes(column, INPUT({'class':'fixed_value', 'value':value, 'name':'fixed_value','type':'hidden'}));
    pushFixedStack(field_id, value);
    simpleLog('DEBUG','got to end of setFixed.');
}

// psuedo static.
function getFieldsetId() { 
   return getElement('global-fieldset-id').value;
}

/*
 * XMLHttpRequest Workers
 *
 */

function handleError(err) {
    simpleLog('ERROR','failed on xmlhttpreq.  exception: \n'+repr(err));    
}

// quick helper to send a POST to a url.
function getPOSTRequest(fullurl) {
    var req = getXMLHttpRequest();
    req.open('POST',fullurl,true);
    req.setRequestHeader('Content-Type','application/x-www-form-urlencoded')
    return req;
}

function removeFromBehaviour(field_id) {
    var action = 'removeFromBehaviour';    

    simpleLog('DEBUG','initiating item list update on field '+field_id);
    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);

	// get the assigned items that are selected.
	var column = getColumnForField(field_id);
	var assigned_sources = getElementsByTagAndClassName('SELECT','assigned_item_list', column);
	if (assigned_sources.length == 0) {
	   simpleLog('ERROR','Unable to locate assigned items.');
	   return;
	} 
	var assigned_select = assigned_sources[0];
	for (var i=0; i<assigned_select.options.length; i++) {
	    var opt = assigned_select.options[i];
		if (opt.selected) {
		    formKeys.push('fieldsToRemove[]');
			formValues.push(opt.value);
		}
	}


    // boilerplate.
    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request (to '+getTargetUrl()+'): \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(partial(do_removeItems, field_id));
    deferred.addErrback(handleError);
}

function do_removeItems(field_id, req) {
    updateItemListForField(field_id);
}

// updates the item list for a given field to the items which are "free".
function updateItemListForField(field_id) { 
    var action = 'getItemList';
    

    simpleLog('DEBUG','initiating item list update on field '+field_id);
    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');
    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);


    // boilerplate.
    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request (to '+getTargetUrl()+'): \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(partial(do_updateItemList, field_id));
    deferred.addErrback(handleError);
	
	
	
	
	action = 'getAssignedList';
	
	formKeys = Array();
	formValues = Array();
	
	formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');
    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);


    // boilerplate.
    POSTval = queryString(formKeys, formValues);
    req2 = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request (to '+getTargetUrl()+'): \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred2 = sendXMLHttpRequest(req2, POSTval);
    deferred2.addCallback(partial(do_updateAssignedItemList, field_id));
    deferred2.addErrback(handleError);
 }

// updates the available behaviours for a given field.
function updateBehaviourListsForField(field_id) { 
    var action = 'getBehaviourList';

    simpleLog('DEBUG','initiating behaviour list update on field '+field_id);
    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');
    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);


    // boilerplate.
    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request (to '+getTargetUrl()+'): \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(partial(do_updateBehaviours, field_id));
    deferred.addErrback(handleError);
}

// updates the set of "active" columns.
// sends:
//      fixed field.
// gets:
//      active fields 
//          (everything else is inactive or fixed.)
function updateActiveFields() { 
    simpleLog('DEBUG','initiating active field update.');
    var action = 'getActiveFields';

    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');
    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());

    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request (to '+getTargetUrl()+' with action '+action+'): \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(do_updateActiveFields);
    deferred.addErrback(handleError);


}

// creates a new behaviour, and adds the appropriate metadata fields to it. 
function createBehaviourAndAssign(field_id, values, behaviour_name) { 
    var action = 'createBehaviourAndAssign';


    simpleLog('DEBUG','initiating behaviour creation on field '+field_id);
    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');
    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);
    // behaviour-name
    formKeys.push('behaviour_name');
    formValues.push(behaviour_name);
    // all the values.    
    for (var i=0; i<values.length; i++) {
        formKeys.push('lookups_to_assign[]');
        formValues.push(values[i]);
    }
    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request: \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(partial(do_createBehaviour, field_id));
    deferred.addErrback(handleError);
    // FIXME add an errback.
}

// variant of createBehaviourAndAssign that uses an existing behaviour.
function useBehaviourAndAssign(field_id, values, behaviour_id) { 
    var action='useBehaviourAndAssign';


    simpleLog('DEBUG','initiating behaviour creation on field '+field_id);
    
    var formKeys = Array();
    var formValues = Array();
    // action
    formKeys.push('action');
    formValues.push(action);
    // we need the fixed parent (or null - both are equivalent).
    formKeys.push('parent_behaviour');

    if (fixed_value == null) {
       formValues.push('');
    } else {
        formValues.push(fixed_value);
    }
    // fieldset-id
    formKeys.push('fieldset_id');
    formValues.push(getFieldsetId());
    // field_id
    formKeys.push('field_id');
    formValues.push(field_id);
    // behaviour-id
    formKeys.push('behaviour_id');
    formValues.push(behaviour_id);
    // all the values.    
    for (var i=0; i<values.length; i++) {
        formKeys.push('lookups_to_assign[]');
        formValues.push(values[i]);
    }
    var POSTval = queryString(formKeys, formValues);
    var req = getPOSTRequest(getTargetUrl());
    simpleLog('DEBUG','sending request: \n'+repr(map(null, formKeys, formValues))+'\nqueryString: '+POSTval);
    var deferred = sendXMLHttpRequest(req, POSTval);
    deferred.addCallback(partial(do_createBehaviour, field_id));
    deferred.addErrback(handleError);
}

/*
 * XMLHttpRequest Callbacks.
 *
 */

function do_updateActiveFields (req) {
    simpleLog('DEBUG','callback for active field update received: \n'+req.responseText);
    // to handle this, you need to understand the model we're using to a degree.
    // at this point, fields have 3 states (modelled internally as classes, for various reasons):
    //      "active fixed"  - these are the items which are currently controlling what is availble.
    //      "active"  - NOT fixed.  potentially, some of these may need to be clobbered.
    //      "inactive" - could be made "active" by this change. 
    // so we grab all the fields, and then separate out those we are considering
    // from those we aren't.  we then toggle them on / off as required.

    xmldoc = req.responseXML;
    var inactive_fields = getElementsByTagAndClassName('td','inactive');
    var active_fields = getElementsByTagAndClassName('td','active');


    // create potential fields: all inactive, and non-fixed active fields are on the block.
    var potential_fields = Array();
    for (var i=0; i<inactive_fields.length; i++) {
        potential_fields[inactive_fields[i].id] = inactive_fields[i];
    }
    for (var i=0; i<active_fields.length; i++) {
        if (!hasElementClass(active_fields[i], 'fixed')) {
            potential_fields[active_fields[i].id] = active_fields[i];
        } else {
            simpleLog('DEBUG','discarded active fields '+active_fields[i]+' since its fixed.');
        }
    }
    simpleLog('DEBUG','identified potential fields as: '+potential_fields);
    // we use the fact that potential_fields["md_2"] is a reference, so delete is garbage collected (e.g. its still in the DOM).
    // so we delete items that match, and when we're done, we set everything else to "inactive".
    var response_active_list = xmldoc.getElementsByTagName('field');
    for (var i=0; i<response_active_list.length; i++) {
        var field_id = response_active_list[i].getAttribute('value');
        var td_id = "md_"+field_id;

        if (potential_fields[td_id]) {
            setElementClass(potential_fields[td_id], 'active');
            updateBehaviourListsForField(field_id);
            updateItemListForField(field_id);
            delete potential_fields[td_id];
            simpleLog('DEBUG','activating '+td_id);
        } else {
            simpleLog('ERROR','no field matching id specified in XML: '+td_id);            
        }
    }

    for (key in potential_fields) {
        setElementClass(potential_fields[key], 'inactive');
    }
}

function do_updateItemList(field_id, req) {
    simpleLog('DEBUG','entering callback for item-list update.');
    var items = req.responseXML.getElementsByTagName('item');
    var itemNodes = Array();
    for (var i=0; i<items.length; i++) {
        var item = items[i];
        itemNodes.push(createDOM('option',{'value':item.getAttribute('value')},item.getAttribute('label')));
    }
    // now, find the array and replaceChildNodes() it.
    var column = getColumnForField(field_id);
    var is_sources = getElementsByTagAndClassName('select','item_list',column);
    if (is_sources.length == 0) {
        simpleLog('ERROR','Could not find the item list in field '+field_id);
        return;
    }
    var item_select = is_sources[0];
    replaceChildNodes(item_select, itemNodes);
}


function do_updateAssignedItemList(field_id, req) {
    simpleLog('DEBUG','entering callback for assigned-item-list update.');
    var items = req.responseXML.getElementsByTagName('item');

    var itemNodes = Array();
    for (var i=0; i<items.length; i++) {
        var item = items[i];
        itemNodes.push(createDOM('option',{'value':item.getAttribute('value')},item.getAttribute('label')));

    }

    // now, find the array and replaceChildNodes() it.
    var column = getColumnForField(field_id);
	var assigned_wrapper = getElementsByTagAndClassName('DIV','assigned_items',column)[0];
	
	if (itemNodes.length == 0) {
		setElementClass(assigned_wrapper, 'assigned_items empty');
	} else { setElementClass(assigned_wrapper, 'assigned_items'); }
	
	
    var is_sources = getElementsByTagAndClassName('select','assigned_item_list',column);
    if (is_sources.length == 0) {
        simpleLog('ERROR','Could not find the assigned item list in field '+field_id);
        return;
    }
    var item_select = is_sources[0];
    replaceChildNodes(item_select, itemNodes);
}

function do_updateBehaviours(field_id, req) {
    simpleLog('DEBUG','entering callback for behaviour-lists update.');
    // we handle this slightly differently to updateItemList.
    // particularly, we have a number of different items that 
    // are behaviour lists in a given dropdown.
    var behaviours = req.responseXML.getElementsByTagName('behaviour');
    var behaviourVals = Array();
    for (var i=0; i<behaviours.length; i++) {
        var behaviour = behaviours[i];
        behaviourVals.push({'value':behaviour.getAttribute('value'), 'label':behaviour.getAttribute('label')});
    }

    // now, find the selects(!) and replaceChildNodes() them.
    var column = getColumnForField(field_id);
    var ab_sources = getElementsByTagAndClassName('select','available_behaviours',column);
    var eb_sources = getElementsByTagAndClassName('select','edit_behaviours',column);



    if ((ab_sources.length == 0) || (eb_sources.length == 0)) {
        simpleLog('ERROR','Could not find all behaviourlists in field '+field_id);
        return;
    }
    var available_select = ab_sources[0];
    var edit_select = eb_sources[0];

    // we can't use the same set of DOM nodes, since DOM is a tree.
    var behaviourOptions = Array();
    behaviourOptions.push(createDOM('option',null,'Select a behaviour'));
    for (var i=0; i<behaviourVals.length; i++) {
        var entry = behaviourVals[i];
        behaviourOptions.push(createDOM('option',{'value':entry.value}, entry.label));
    }
    replaceChildNodes(available_select, behaviourOptions);

    // we can't use the same set of DOM nodes, since DOM is a tree.
    var behaviourOptions = Array();
    behaviourOptions.push(createDOM('option',null,'Select a behaviour'));
    for (var i=0; i<behaviourVals.length; i++) {
        var entry = behaviourVals[i];
        behaviourOptions.push(createDOM('option',{'value':entry.value}, entry.label));
    }
    replaceChildNodes(edit_select, behaviourOptions);
}

// trigger a itemlist and behaviourlist update.
function do_createBehaviour(field_id, req) { 
    simpleLog('DEBUG','callback for create behaviour (field: '+field_id+') received: \n'+req.responseText);
    updateItemListForField(field_id);
    updateBehaviourListsForField(field_id);
}


// doesn't act on local state directly, since that could clobber any number of things.
// rather, purge and go.
function do_assignBehaviour(field_id, req) { 
    simpleLog('DEBUG','callback for assign behaviour (field: '+field_id+') received: \n'+req.responseText);
    updateItemListForField(field_id);
}



/*
 * HTML Interface functions.
 *
 * contain just-enough-logic, and interfaces with the DOM (hopefully) only through model-functions (see above)
 */

function changeAssignments(field_id) {
    simpleLog('DEBUG','Change assignments called for field '+field_id);
}

function assignToBehaviour(select, field_id) {
    simpleLog('DEBUG','assignToBehaviour called for field '+field_id+'.');
    behaviourId=select.value;
    behaviourName=scrapeText(select.options[select.selectedIndex]);
    // clear this to allow another lot to be assigned here.
    select.options[select.selectedIndex].selected = false;
    var activeValues = getActiveLookupsForField(field_id, true);
    if (activeValues.instances.length == 0) {
        simpleLog('DEBUG','no values selected, passing through without acting.');
        return ;        
    } 
   var log_message = 'assigning  to '+behaviourName+' ('+behaviourId+').\nnames: ' + activeValues["names"].join(',')+'\nvalues: '+activeValues["instances"].join(',');
   simpleLog('DEBUG',log_message);
   useBehaviourAndAssign(field_id, activeValues.instances, behaviourId);
}

function assignToNewBehaviour(field_id) {
    simpleLog('DEBUG','assignToNewBehavriour called for field '+field_id+'.');

    // get the 
    var column = getColumnForField(field_id);
    var tf_source = getElementsByTagAndClassName('input','new_behaviour',column)
    if (tf_source.length == 0) {
        simpleLog('ERROR','no new_behaviour fields found!');   
        return ;
    } else {
        var textfield = tf_source[0];
    }
    var behaviourName = textfield.value;
    // clear this to prevent confusion.
    textfield.value = '';
    var activeValues = getActiveLookupsForField(field_id, true);
    if (activeValues.instances.length == 0) {
        simpleLog('DEBUG','no values selected, passing through without acting.');
        return ;        
    } 
    var log_message = 'assigning  to '+behaviourName+'.\nnames: ' + activeValues["names"].join(',')+'\nvalues: '+activeValues["instances"].join(',');
    simpleLog('DEBUG',log_message);    
    createBehaviourAndAssign(field_id, activeValues.instances, behaviourName);
}


function editBehaviour(selectfield, field_id) {
    var optObj = selectfield.options[selectfield.selectedIndex];
    var behaviourName = scrapeText(optObj);
    var behaviourValue = optObj.value;
    simpleLog('DEBUG','editBehaviour called for field '+field_id+' with "'+behaviourName+'" ('+behaviourValue+').');
    optObj.selected=false;
    setFixedValueForField(field_id, behaviourValue, behaviourName);
}
