/*
 * Gallery - a web based photo album viewer and editor
 * Copyright (C) 2000-2006 Bharat Mediratta
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or (at
 * your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street - Fifth Floor, Boston, MA  02110-1301, USA.
 */

/**
 * ImageAreas JavaScript UI
 *
 * Using the JavaScript Module Pattern.
 * @package ImageAreas
 * @subpackage UserInterface
 * @author Paul Hinze <paul.t.hinze@gmail.com>
 * @version $Id: imageareas.js 1640 2008-10-01 03:44:17Z phinze $
 */
ImageAreas = function () { 
    /* Message level constants */
    var BAD  = 0;
    var OK   = 1;
    var GOOD = 2;

    /* Message constants to be internationalized */
    /* @todo internationalize these messages */
    var ERR_POST_UNSUPPORTED = 'Lo sentimos, tu navegador no soporta este m\u00F3dulo.';
    var ERR_DISALLOWED       = 'Lo sentimos, esa acci\u00F3n no est\u00E1 permitida.\n\n' +
			       'Por favor accesa con tu usuario y contrase\xF1a.';
    var ERR_SAVE_FAIL        = '';
    var ERR_LOAD_FAIL        = 'Ha ocurrido un error, y el m\u00F3dulo no fue debidamente cargado.';
    var ERR_NO_IMAGE         = 'No se pudo encontrar la imagen en la p\u00E1gina, no se realiz\u00F3 el cargado.';
    var ERR_UI_LOAD          = 'Error mientras se cargaba la IU del servidor:\n';

    var MSG_DELETE_CONFIRM   = '\u00BFEst\u00E1s seguro de eliminar el tag?';
    var MSG_LOAD_WAIT        = 'Cargando m\u00F3dulo...';
    var MSG_SAVE_WAIT        = 'Etiquetando imagen..';
	var MSG_DEL_WAIT        = 'Etiquetando imagen..';
    var MSG_SAVE_SUCCESS     = 'Etiquetado listo!';
    var MOVED_LINK_TEXT      = 'Ver imagen';

    var LINK_ADDING          = 'Cancelar agregado';
    var LINK_EDITING         = 'Cancelar edici\u00F3n';
    var LINK_DELETING        = 'Cancelar borrado';
    var LINK_HIDE            = 'Ocultar tags';
    var LINK_SHOW            = 'Mostrar tags';
    

    /**
     * XMLHTTPRequest object to communicate with server.
     * @todo refactor this to use YUI.connect
     * @var object
     */
    var _xhr = null;
    if (window.ActiveXObject) {
	try {
	    _xhr = new ActiveXObject('Microsoft.XMLHTTP');
	}
	catch (e) { }
    }
    else if (window.XMLHttpRequest) {
	_xhr = new XMLHttpRequest();
    }

    /**
     * Permissions
     * @var object
     */
    var _perms = { add : false, edit : false, del : false }; /* 'delete' is JS keyword */

    /**
     * Stores timer for hiding notes after timeout.
     * @var timer
     */
    var _hideTimer     = null;

    /**
     * Data store during note editing process.
     * @var object
     */
    var _editingData   = null;
    
    /**
     * Reference to the active image in the DOM
     * @var DOMObject
     */
    var _image         = null;    

    /**
     * Sends a request off to the server to get UI for image; receiveUi called on completion.
     * @param DOMObject imgObj reference to <img> element in the DOM
     */
    var _getUi = function(imgObj) {
	/* Store reference to image for receiveUi to use */
	_image = imgObj;

	/* Compose our request data and send it. */
	var data = {
	    'src'    : escape(imgObj.src),
	    'itemId' : ImageAreas.itemId,
	    'width'  : imgObj.width,
	    'height' : imgObj.height,
	    'class'  : imgObj.className
	};

	_postJson(data);
    };

    /**
     * Called on click of control buttons to highlight/dim them.
     * @param string action  string representing action for button
     * @param object trigger DOM reference to button clicked 
     */
    var _action = function(action, trigger) {
	if (action) {
	    /* Set a new action. */
	    ImageAreas.actionVerb = action;
	    
	    if (trigger) {
		/* Store trigger for later */
		ImageAreas.activeTriggerContent = trigger.innerHTML;
		ImageAreas.activeTrigger = trigger;
	    }
	}
	else {
	    ImageAreas.actionVerb = '';
	    if (ImageAreas.activeTrigger) {
		ImageAreas.activeTrigger.innerHTML = ImageAreas.activeTriggerContent;
		ImageAreas.activeTriggerContent = ImageAreas.activeTrigger = null;
	    }
	}
    };

    /*
     * Either shows or hides the editing UI.
     * @param bool show true to show, false to hide
     */
    var _editUiSet = function(show) {
	if (!_editingData) { return; }
	var a = _editingData.area;
	var f = _editingData.form;

	/* Start or stop dragging the selected area. */
	if (show) { ImageAreas.dragresize.select(a); }
	else { ImageAreas.dragresize.deselect(true); }

	/* put the UI in the right place before we change its visibility */
	ImageAreas.editUiSwitch();
	ImageAreas.editUiUpdate();

	/* Fade the editing UI in/out, and toggle its classname so it stays that way. */
	ImageAreas.elementFade(f, show);
	_classSet(f, show);

	/* Set area className so its remains visible if editing, or reset it back otherwise. */
	a.className = show ? 'ia-area-editing' : 'ia-area';

	/* Toggle the container class (for other notes' visibility) */
	_classSet(_editingData.container, !show);
    };

    /**
     * Adds a new note when the specified button is clicked.
     * @param object container DOM reference to container div
     */
    var _addNote = function(container) {

	/* Create a new area in which the note will reside. */
	var newArea = document.createElement('div');
	newArea.className = 'ia-area';
	newArea.style.left = (container.offsetWidth/2 - 25) + 'px';
	newArea.style.top  = (container.offsetHeight/2 - 25) + 'px';
	newArea.style.width = '130px';
	newArea.style.height = '130px';
	newArea.className = newArea.className + ' ia-area-new';

	var newNote = document.createElement('div');
	newNote.className = 'ia-note';
	newNote.align = 'center';
	newArea.appendChild(newNote);

	/* Create note elements. */
	var newId = document.createElement('span');
	newId.className = 'ia-note-id';
	newNote.appendChild(newId);

	/* add in innerborders */
	var newInnerBorder = document.createElement('div');
	newInnerBorder.className = 'ia-area-innerborder-right';
	newArea.appendChild(newInnerBorder);
	
	newInnerBorder = document.createElement('div');
	newInnerBorder.className = 'ia-area-innerborder-left';
	newArea.appendChild(newInnerBorder);
	
	newInnerBorder = document.createElement('div');
	newInnerBorder.className = 'ia-area-innerborder-top';
	newArea.appendChild(newInnerBorder);
	
	newInnerBorder = document.createElement('div');
	newInnerBorder.className = 'ia-area-innerborder-bottom';
	newArea.appendChild(newInnerBorder);

	/* Add newArea to document */
	container.appendChild(newArea);

	/* Record this note as editing, and set the "add" action flag. */
	_editingData = {
	    area: newArea,
	    note: newNote
	};

	/* Hand over to the editing function. */
	_editNote();
    };

    /**
     * Edits a passed note reference.
     * @param object note note to edit, if existing
     */
    var _editNote = function(note) {
	var area = null;
	if (note) {
	    /* If we're editing an existing note, setup the data store. */
	    area = note.parentNode;
	    _editingData = {
		area: area,
		note: note
	    };
	}
	else {
	    /* New notes: pull the note and area out of the stored data. */
	    area = _editingData.area;
	    note = _editingData.note;
	}

	/* Find our container and form references. */
	var container = ImageAreas.getContainer(area);
	if (!container) { return; }
	var form = container.getElementsByTagName('form');
	if (!form) { return; }
	form = form.item(0);
	
	/* Pick up existing values for content from the note. */
	var noteData = new Object();
	var fields = area.getElementsByTagName('span');
	for (var n = 0; n < fields.length; n++) {
	    var field = fields.item(n);
	    if ((/^ia-note-/).test(field.className)) {
		var dataKey = field.className.substr(8);
		noteData[dataKey] = field.title;
	    }
	}

	/* Backup the original content, refs and position in our datastore. */
	/* It already has the .note and .area properties. */
	_editingData.container   = container;
	_editingData.form        = form;
	_editingData.oldNoteData = noteData;
	_editingData.oldLeft     = parseInt(area.style.left, 10);
	_editingData.oldTop      = parseInt(area.style.top, 10);
	_editingData.oldWidth    = area.offsetWidth;
	_editingData.oldHeight   = area.offsetHeight;

	/* Populate the editing UI with current note data. */
	var editFields = form.elements;
	for (var i = 0; i < editFields.length; i++) {
	    var editField = editFields[i];
	    if (noteData[editField.name]) {
		editField.value = noteData[editField.name];
	    }
	}

	/* Finally, show the editing UI for the recorded area. */
	_editUiSet(true);
    };


    /**
     * Deletes a note area
     * @param object area a whole area reference
     */
    var _delNote = function(area) {
	/* Find the ID of this note. */
	var areaID = _getAreaID(area);
	if (!areaID) { ImageAreas.showMsg(ERR_SAVE_FAIL, BAD); return; }

	area.className += ' ia-area-highlight';

	if (areaID && confirm(MSG_DELETE_CONFIRM)) {
	    /* Set up our data store to delete this area, and post to the server. */
	    _editingData = {
		area: area,
		note: null,
		container: ImageAreas.getContainer(area)
	    };
	    _postJson( { 'areaId' : areaID } );
	}
	else {
	    /* Reset control bar if cancelled. */
	    area.className = 'ia-area';
	    _classSet(ImageAreas.getContainer(area), ImageAreas.showing);
	    _action('', null);
	}
    };

    /**
     * @param object area DOM reference to area div
     * @return int the areaId data stored in the XHTML.
     */
    var _getAreaID = function(area) {
	fields = area.getElementsByTagName('span');
	for (var n = 0; n < fields.length; n++) {
	    if (fields.item(n).className == 'ia-note-id') {
		return fields.item(n).getAttribute('title');
	    }
	}
	return null;
    };
	
    /**
     * Sends some JSON off to the server and calls the specified on completion.
     * @param object   data  object to send
     */
    var _postJson = function(data) {
	/* When the action has not been set yet we are in first request asking for UI */
	var action      = (ImageAreas.actionVerb) ? ImageAreas.actionVerb : 'display';
	var postContent = '&g2_action=' + action + '&g2_itemId=' + ImageAreas.itemId + 
			    '&g2_data=' + JSON.stringify(data);
				
	_xhr.open('POST', ImageAreas.serverUrl, true);
	
	_xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded; charset=utf-8');
	_xhr.setRequestHeader('Content-length', postContent.length);
	if (action == 'display') {
	    _xhr.onreadystatechange = function() {
		if (_xhr.readyState == 4) {
		    var data = new Object();
		    try {
				//Debug panchosoft
				//alert(ImageAreas.serverUrl);
			//alert(_xhr.responseText);
			data = JSON.parse(_xhr.responseText);
			
		    }
		    catch (e) {
			data.status = 'parseErr';
			data.raw = _xhr.responseText;
			
		    }
		    if(data) {
			ImageAreas.receiveUi(data);
		    } 
		}
	    };
	} else {
	    _xhr.onreadystatechange = function() {
		if (_xhr.readyState == 4) {
		    var data = new Object();
		    try {
			data = JSON.parse(_xhr.responseText);
		    }
		    catch (e) {
			data.status = 'parseErr';
			data.raw = _xhr.responseText;
		    }
		    if(data) {
			ImageAreas.sendComplete(data);
		    } 
		}
	    };
		if(action == 'add' || action == 'edit'){
	    	ImageAreas.showMsg(MSG_SAVE_WAIT, OK);
		}else if (action == 'delete'){
			ImageAreas.showMsg(MSG_DEL_WAIT, OK);
		}
	}
	/* Show "please wait" dialog */
	_xhr.send(postContent);
    };

    /**
     * If the element is inside a link, remove it and place the link below the element.
     * @param object elm       DOM element to remove from link
     * @param string linkText  text to use for the moved link
     */
    var _moveLinkBelow = function(elm, linkText) {
	var cur = elm;
	while (cur.parentNode.tagName.toLowerCase() != 'html') {
	    if (cur.parentNode.tagName.toLowerCase() == 'a') {
		var linkNode = cur.parentNode;
		var origParent = linkNode.parentNode;
		origParent.replaceChild(cur, linkNode);
		linkNode.innerHTML = linkText;
		//origParent.appendChild(linkNode);
		return;
	    }
	    cur = cur.parentNode;
	}
    };

    /**
     * Utility function that toggles the "-active" and "-inactive" classnames.
     * @param object elm     DOM element to toggle
     * @param bool   active  true for active, false for inactive
     */
    var _classSet = function(elm, active) {
	elm.className = elm.className.replace((active ? (/-inactive/) : (/-active/)), 
					    (active ? '-active' : '-inactive'));
    };
	
	var _mostrarBorde = function (elm, bandera){
		var clasesm = ["ia-area-innerborder-leftmin","ia-area-innerborder-topmin","ia-area-innerborder-bottommin","ia-area-innerborder-rightmin"];
		var clases = ["ia-area-innerborder-left","ia-area-innerborder-top","ia-area-innerborder-bottom","ia-area-innerborder-right"];
		
		if(bandera){
			for(i = 0; i < clasesm.length;i++){
				var clase = clasesm[i];
				var elementos = _getElementsByClassName(clase,elm);
				//alert("clase: " + clase + " total: " + elementos.length);
				for(y = 0; y < elementos.length; y++){
					var elem = elementos[y];
					elem.className = elem.className.replace(clase, clases[i]);
				}
			}
		}else{
			for(i = 0; i < clases.length;i++){
				var clase = clases[i];
				var elementos = _getElementsByClassName(clase,elm);
				//alert("clase: " + clase + " total: " + elementos.length);
				for(y = 0; y < elementos.length; y++){
					var elem = elementos[y];
					elem.className = elem.className.replace(clase, clasesm[i]);
				}
			}
		}
	};



    /**
     * Utility function, does what it says.
     * @param str     classname  class name
     * @param object  node       root node to search from, null if all document
     */
    var _getElementsByClassName = function(classname, node) {
	if(!node) { node = document.getElementsByTagName("body")[0]; }
	var a = [];
	var re = new RegExp('\\b' + classname + '\\b');
	var els = node.getElementsByTagName("*");
	for(var i=0,j=els.length; i<j; i++) { if(re.test(els[i].className))a.push(els[i]); }
	return a;
    }

    return  { 

	/**
	 * ID of item on page (set by block template)
	 * @var int
	 */
	itemId : null,

        /**
	 * Address of server (set by block template)
	 * @var string
	 */
	serverUrl : '',

	/**
	 * Generated src attrib to search for in image (set by block template)
	 * @var str
	 */
	imgSrc : '',

        /**
	 * The UI's current action. Can be 'add', 'edit', or 'delete'.
	 * @var str
	 */
	actionVerb    : '',

	/**
	 * Stores the currently visible note for use in fade in/out.
	 * @var object DOMObject
	 */
	activeNote    : null,

	/**
	 * Stores the currently active trigger so we can reset the text.
	 * @var object DOMObject
	 */
	activeTrigger : null,

	/**
	 * Stores the original content of the active trigger.
	 * @var string
	 */
	activeTriggerContent : null,

	/**
	 * Stores the timer used for hiding the modal dialog
	 * @var object timer
	 */
	modalTimer    : null,

	/**
	 * Records whether or not notes are showing
	 * @var bool
	 */
	showing       : true,

	/**
	 * Tells when JS UI has loaded
	 * @var bool
	 */
	loaded        : false,

	/**
	 * Non-destructively add a load event
	 *
	 * @param function func function to run on load
	 */
	addLoadEvent : function(func) {
	    var oldonload = window.onload;
	    if (typeof window.onload != 'function') {
		window.onload = func;
	    } else {
		window.onload = function() {
		    oldonload();
		    func();
		};
	    }
	},

	/**
	 * Loads the ImageAreas JS.
	 */
	load : function() {
	    if (!document.getElementById) {
		return null;
	    }

	    /* Create a new DragResize() object, and set it up. */
	    /* We apply to the whole document to interoperate with blinds. */
	    ImageAreas.dragresize = new DragResize('dragresize', { allowBlur: false });
	    ImageAreas.dragresize.isElement = function(elm) {
		if (!(/(add|edit)/).test(ImageAreas.actionVerb)) return false;
		if ((/ia-area-editing/).test(elm.className)) {
		    var container = ImageAreas.getContainer(elm);
		    this.maxLeft = container.offsetWidth - 2;
		    this.maxTop = container.offsetHeight - 2;
		    return true;
		}
	    };
	    ImageAreas.dragresize.isHandle = function(elm) {
		if (!(/(add|edit)/).test(ImageAreas.actionVerb)) return false;
		if ((/ia-editbar/).test(elm.className)) return false;
		if ((/ia-area-editing/).test(elm.className)) return true;
	    };
	    ImageAreas.dragresize.ondragfocus = function() {
		this.element.style.cursor = 'move';
	    };
	    ImageAreas.dragresize.ondragblur = function() {
		this.element.style.cursor = '';
	    };
	    ImageAreas.dragresize.apply(document);

	    /* Add listener to keep modal dialog in position */
	    addEvent(window, 'scroll', ImageAreas.updateModalDialogPosition);

	    /* Add listener to allow closing of modal dialog */
	    var dialog = document.getElementById('ia-modaldialog');
	    addEvent(dialog, 'click', ImageAreas.modalDialogClickHandler);

	    /* Set show/hide link text properly */
	    var t = _getElementsByClassName('gbLink-imageareas_ToggleAreas');
	    var showHideLink = (t.length == 1) ? t[0] : null;
	    if (showHideLink) { showHideLink.innerHTML = (ImageAreas.showing) ? LINK_HIDE : LINK_SHOW; }

	    /* Since we do not know where the theme is going to put the image, we use the src
	     * attribute generated in the same way as it gets generated for the template */
	    for (i = 0; i < document.images.length; i++) {
		if(document.images[i].src == ImageAreas.imgSrc) {
		    var imgObj = document.images[i];
		    _moveLinkBelow(imgObj, (imgObj.alt) ? imgObj.alt : MOVED_LINK_TEXT);
		    _getUi(imgObj);
		    return null; /* Only apply UI for one image. */
		}
	    }
	    ImageAreas.showMsg(ERR_NO_IMAGE, BAD);
	    return null;
	},

	/**
	 * Called on response from the display request.
	 *
	 * @param object response all response data received from server
	 */
	receiveUi : function(response) {
	    /* Check for errors */
	    if ( response.status != 'success') {
		switch (response.status) {
		case 'deny':
		    ImageAreas.showMsg(ERR_DISALLOWED, BAD);
		    break;
		case 'parseErr':
		    ImageAreas.showMsg(ERR_UI_LOAD + response.raw, BAD);
		    break;
		case 'error':
		default: 
		    ImageAreas.showMsg(ERR_UI_LOAD + response.ret, BAD);
		    break;
		}
		return null;
	    }

	    if (!response.html) {
		/* Server decided UI was not appropriate... so we're done */
		return null;
	    }

	    /* Extract returned XHTML text from reply and update document with it */
	    var iaXhtml = response.html;
	    var iaDiv = document.createElement('div');
	    iaDiv.innerHTML = iaXhtml;
	    _image.parentNode.replaceChild(iaDiv, _image);
	    _image = null;

	    /* Now wire in everything that needs to happen with the newly inserted UI */
	    var container = document.getElementById('ia-' + ImageAreas.itemId);
	    if(!container) {
		ImageAreas.showMsg(ERR_LOAD_FAIL + "\n Contenedor no encontrado.", BAD);
		return null;
	    }

	    /* Set visibility based on default setting */
	    _classSet(container, ImageAreas.showing);

	    /* Note show/hide events. */
	    addEvent(container, 'mouseover',
		new Function('e', 'ImageAreas.mouseOverOutHandler(e, 1)'));
	    addEvent(container, 'mouseout',
		new Function('e', 'ImageAreas.mouseOverOutHandler(e, 0)'));

	    /* Creation/editing/deletion events. */
	    if (document.createElement && document.documentElement) {
		addEvent(container, 'click', ImageAreas.clickHandler);
	    }

	    /* Keeps editing UI near the area being edited */
	    addEvent(document, 'mousemove', ImageAreas.editUiUpdate);

	    /* Set up events to clear default text from inputs */
	    /* (adapted from code by Ross Shannon, http://www.yourhtmlsource.com/) */
	    var formInputs = container.getElementsByTagName('form').item(0).elements;
	    for (var i = 0; i < formInputs.length; i++) {
		var theInput = formInputs[i];
		
		if (theInput.type == 'text' || theInput.type == 'textarea') {  
		    /* Add event handlers */          
		    addEvent(theInput, 'focus',
			function(e) {
			    var target = 
				window.event ? window.event.srcElement : e ? e.target : null;
			    if (!target) return;
			    if (target.value == target.defaultText) {
				target.value = '';
			    }
			}, false);
		    addEvent(theInput, 'blur',
			function(e) {
			    var target =
				window.event ? window.event.srcElement : e ? e.target : null;
			    if (!target) return;
			    if (target.value === '' && target.defaultText) {
				target.value = target.defaultText;
			    }
			}, false);
		    
		    /* Save the current value */
		    if (theInput.value !== '') {
			theInput.defaultText = theInput.value;
		    }
		}
	    }

	    ImageAreas.loaded = true;
	    return null;
	},

	/**
	 * Used by template to set permissions.
	 *
	 * @param bool add  add permission
	 * @param bool edit edit permission
	 * @param bool del  delete permission
	 */
	setPerms : function(add, edit, del) { 
	    _perms.add = add;
	    _perms.edit = edit;
	    _perms.del = del;
	},

	/**
	* Called on document.onmouseover & onmouseout, manages tip visibility.
	* @param evt    JS event
	* @param isOver true if we have an onmouseover event
	*/
	mouseOverOutHandler : function(evt, isOver) {
		
	    var node = evt.target || evt.srcElement;
	    if (node.nodeType != 1) { node = node.parentNode; }

	    while (node && !((node.className||'').indexOf('ia-container') > -1)) {
		/* If the node has an CLASS of "ia-area", process it. */
		/* No mouseovers if ImageAreas.actionVerb is set (i.e. editing/deleting/adding). */
		if (node && (/ia-area/).test(node.className) && !ImageAreas.actionVerb) {
		    var area = node;
		    /* Find the first child element, which will be the note in question. */
		    var note = area.firstChild;
		    while (note && note.nodeType != 1) { note = note.nextSibling; }
		    if (!note) { return; }

		    /*
		     * Clear any hide timeout, and either show the note, or set a timeout for its
		     * hide.  We record the currently active note for the hide timer to work, and
		     * also elevate its parent area above any previously active area (which is
		     * lowered).
		     */
		    clearTimeout(_hideTimer);
		    if (isOver) {
				//if(isOver == 1) alert("dentro");
			if (ImageAreas.activeNote && (note != ImageAreas.activeNote)) {
			    ImageAreas.elementFade(ImageAreas.activeNote, false);
			}
			ImageAreas.elementFade(note, true);
			if (ImageAreas.activeNote) {
			    ImageAreas.activeNote.parentNode.style.zIndex = 1;
			}
			note.parentNode.style.zIndex = 2;
			ImageAreas.activeNote = note;
		    }
		    else {
			_hideTimer = setTimeout('if (ImageAreas.activeNote) { ' +
			    'ImageAreas.elementFade(ImageAreas.activeNote, false);' +
			    'ImageAreas.activeNote = null }', 200);
		    }
		}

		/* Loop up the DOM. */
		node = node.parentNode;
	    }
	},

	/**
	 * Processes clicks on the document within the container, and steps in to perform the
	 * correct action if necessary.  Must return true so that IE registers click.
	 * @param object evt event
	 */
	clickHandler : function(evt) {
	    var node = evt.target || evt.srcElement;
	    if (node.nodeType != 1) { node = node.parentNode; }
	    while (node && !((node.className||'').indexOf('ia-container') > -1)) {
		/* Check buttons within the Edit bar. */
		if ((/ia-editbar-ok/).test(node.className)) {
		    return ImageAreas.editButtonHandler(true);
		}
		if ((/ia-editbar-cancel/).test(node.className)) {
		    return ImageAreas.editButtonHandler(false);
		}

		/* Perform no other if we're currently editing a note. */
		if (_editingData) { return true; }

		/* If an existing area with a CLASS of the form "ia-area" */
		/* has been clicked, check if we're editing/deleting it. */
		if ((/ia-area/).test(node.className)) {
		    var area = node;
		    if (ImageAreas.actionVerb == 'delete') { _delNote(area); }
		    if (ImageAreas.actionVerb == 'edit') {
			var note = area.firstChild;
			while (note && note.nodeType != 1) { note = note.nextSibling; }
			if (note) { _editNote(note); }
		    }
		    return true;
		}

		/* Otherwise, loop up the hierarchy. */
		node = node.parentNode;
	    }
	    return true;
	},

	/**
	 * Button click handler from the editing UI.
	 * @param bool ok Pass a boolean value indicating if the OK button was clicked (so save should
	 *                proceed).
	 */
	editButtonHandler : function(ok) {
	    if (!_editingData) { return false; }
	    if (ok) {
		/* Get new note position */
		var newLeft   = parseInt(_editingData.area.style.left, 10);
		var newTop    = parseInt(_editingData.area.style.top, 10);
		var newWidth  = _editingData.area.offsetWidth;
		var newHeight = _editingData.area.offsetHeight;

		/* Get note data from edting UI */
		var noteData = Object();
		var editFields = _editingData.form.elements;
		for (var i = 0; i < editFields.length; i++) {
		    if (editFields[i].value == editFields[i].defaultText) {
			editFields[i].value = '';
		    }
		    noteData[editFields[i].name] = editFields[i].value;
		}
			
		/* Get the scalefactor from a hidden SPAN in the container. */
		var sFact = 1;
		var spans = _editingData.container.getElementsByTagName('span');
		for (var n = 0; n < spans.length; n++) {
		    if ((/ia-scalefactor/).test(spans.item(n).className)) {
			sFact = parseFloat(spans.item(n).getAttribute('title'));
		    }
		}
		
		/* Begin server save operation. */
		var data = {    'areaId'      : (ImageAreas.actionVerb == 'edit' ?
						    _editingData.oldNoteData.id : ''),
				'itemId'      : this.itemId,
				'noteData'    : noteData,
				'areaBounds'  : parseInt(newLeft/sFact, 10) + '|' +
						parseInt(newTop/sFact, 10) + '|' +
						parseInt((newLeft+newWidth)/sFact, 10) + '|' +
						parseInt((newTop+newHeight)/sFact, 10)
		};

		_postJson(data);
	    }
	    else {
		/* For "cancel" clicks: */
		_editUiSet(false);
		if (ImageAreas.actionVerb == 'add') {
		    /* Just delete new notes. */
		    _editingData.area.parentNode.removeChild(_editingData.area);
		}
		else {
		    /* Restore original note area position/size for edited notes. */
		    _editingData.area.style.left = _editingData.oldLeft + 'px';
		    _editingData.area.style.top = _editingData.oldTop + 'px';
		    _editingData.area.style.width = _editingData.oldWidth + 'px';
		    _editingData.area.style.height = _editingData.oldHeight + 'px';
		}

		/* Hide the editing UI, reset the control bar, clear the data store. */
		_classSet(_editingData.container, ImageAreas.showing);
		_action('', null);
		_editingData = null;
	    }
	    return true;
	},

	/**
	 * Updates the position of the editing UI.
	 * @param evt event unused
	 */
	editUiUpdate : function(evt) {
	    if (!_editingData || !_editingData.form) { return; }
	    var a = _editingData.area;
	    var f = _editingData.form;

	    /* Move the editing UI just below the area */
	    var t = parseInt( a.style.top, 10);
	    var l = parseInt( a.style.left, 10);
	    var h = parseInt( a.style.height, 10);
	    f.style.top = t + h + 10 + 'px';
	    f.style.left = l + 'px';
	},

	/**
	 * Switches the editing UI based on drop down.
	 */
	editUiSwitch : function() {
	    if (!_editingData) { return; }
	    var f = _editingData.form;
	    var re = new RegExp(f.type.value);
	    var divs = f.getElementsByTagName('div');

	    for(var i = 0; i < divs.length; i++) {
		if ((/ia-editbar-inputfield/).test(divs[i].className)) {
		    if (re.test(divs[i].className)) {
			divs[i].style.display = 'block';
		    } else {
			divs[i].style.display = 'none';
		    }
		}
	    }
	},

	/**
	 * Fader function that shows/hides an element.
	 * @param object elm   DOM element to show or hide
	 * @param bool   show  true to fade in, false to fade out
	 */
	elementFade : function(elm, show) {
	    var speed = show ? 20 : 10;
	    elm._f_count |= 0;
	    elm._f_timer |= null;
	    clearTimeout(elm._f_timer);

	    if (show && !elm._f_count) { elm.style.visibility = 'inherit'; }

	    elm._f_count = Math.max(0, Math.min(100, elm._f_count + speed*(show?1:-1)));

	    var f = elm.filters, done = (elm._f_count==100);
	    if (f) {
		if (!done && elm.style.filter.indexOf("alpha") == -1) {
		    elm.style.filter += ' alpha(opacity=' + elm._f_count + ')';
		}
		else if (f.length && f.alpha)  {
		    if (done) { f.alpha.enabled = false; }
		    else { opacity = elm._f_count; f.alpha.enabled = true; }
		}
	    }
	    else { 
		elm.style.opacity = elm.style.MozOpacity = elm._f_count/100.1;
	    }
	    
	    if (!show && !elm._f_count) { elm.style.visibility = 'hidden'; }

	    if (elm._f_count % 100) {
		elm._f_timer = setTimeout(function() { ImageAreas.elementFade(elm,show); }, 50);
	    }
	},

	/**
	 * Shows or hides the browser-wide modal dialog.
	 * @todo refactor to use YUI dialogs
	 * @param string message Pass a message to show, or an empty string to hide the dialog.
	 * @param string level   A constant to determine modal dialog class
	 */
	showMsg : function(message, level) {
	    var dialog = document.getElementById('ia-modaldialog');

	    /* For emergencies */
	    if (!dialog) {
		alert(message);
	    }
		if(dialog === undefined) return;
	    var content = dialog.getElementsByTagName('p')[0];

	    switch (level) {
	    case GOOD:
		dialog.className = 'ia-modaldialog-good';
		break;
	    case BAD:
		dialog.className = 'ia-modaldialog-bad';
		break;
	    case OK:
	    default:
		dialog.className = 'ia-modaldialog-ok';
		break;
	    }
		try{
			content.innerHTML = message;
			ImageAreas.updateModalDialogPosition();
			dialog.style.visibility = message ? 'visible' : 'hidden';
		}catch(Exception){}
	},

	/**
	 * Called on scroll to keep dialog on top of window 
	 */
	updateModalDialogPosition : function() {
	    var dialog = document.getElementById('ia-modaldialog');
		if(dialog === undefined || dialog == null) return;
	    dialog.style.top = document.getElementsByTagName("html")[0].scrollTop + 10 + "px";
	},

	/**
	 * Handles [X] click for modal dialog.
	 */
	modalDialogClickHandler : function(evt) {
	    var node = evt.target || evt.srcElement;
	    if (node.nodeType != 1) { node = node.parentNode; }
	    if (node.id == 'ia-modaldialog-close') {
		if (ImageAreas.modalTimer) {
		    clearTimeout(ImageAreas.modalTimer);
		    ImageAreas.modalTimer = null;
		}
		return ImageAreas.showMsg('');
	    }
	},

	/**
	 * Called once the server responds post-Save operation.
	 * @param object response object from server with response data
	 */
	sendComplete : function(response) {
	    if ( response.status != 'success') {
		switch (response.status) {
		case 'deny':
		    ImageAreas.showMsg(ERR_DISALLOWED, BAD);
		    break;
		case 'parseErr':
		    ImageAreas.showMsg(ERR_SAVE_FAIL + response.raw, BAD);
		    break;
		case 'error':
		default: 
		    ImageAreas.showMsg(ERR_SAVE_FAIL + response.ret, BAD);
		    break;
		}
		/* Failed deletes: reset the control bar and clear the data store. */
		/* (Failed edits/adds: UI and data persist). */
		if (ImageAreas.actionVerb == 'delete') {
		    _editingData.area.className = 'ia-area';
		    _editingData = null;
		    _action('', null);
		}
		return;
	    }

	    /* If this was just a session save for a show/hide toggle, we're don't care about the
	     * rest of the UI state. */
	    /* TODO: implement session save of show/hide imageareas */
	    /*
	    if ( 'key' in response && 'value' in response ) {
		if ( response.key == 'showImageAreas' ) {
		    ImageAreas.show = (response.value == 'true') ? true : false;
		    ImageAreas.showing = !(ImageAreas.showing);
		    trigger.innerHTML = (ImageAreas.showing) ? LINK_HIDE : LINK_SHOW;
		    _classSet(ia, ImageAreas.showing);
		}
		ImageAreas.showMsg(MSG_SAVE_SUCCESS, GOOD);
		ImageAreas.modalTimer = setTimeout('ImageAreas.showMsg("")', 300);
		
		return;
	    }
	    */


	    /* Depending on our action, commit the changes to the document. */
	    if (ImageAreas.actionVerb == 'add' || ImageAreas.actionVerb == 'edit') {
		/* Place new data in the note. It's already in the right position. */
		_editingData.note.innerHTML = response.output;
			
		/* Hide the editing UI. */
		_editUiSet(false);
		_mostrarBorde(document.getElementById('ia-' + ImageAreas.itemId),false);
		
		try{ 
			var campo = document.getElementById('subject');
			agregarActividad(myID,campo.value,ImageAreas.itemId); 
		}catch(Exception){}
	    }
	    else {
		/* Deleting notes? Just remove the area from the document. */
		_editingData.area.parentNode.removeChild(_editingData.area);
	    }

	    /* All successful actions: let the user know it's OK, and reset the UI state, */
	    /* and clear the editing data store. */
	    ImageAreas.showMsg(MSG_SAVE_SUCCESS, GOOD);
	    ImageAreas.modalTimer = setTimeout('ImageAreas.showMsg("")', 1000);
	    _action('', null);
	    var ia = document.getElementById('ia-' + ImageAreas.itemId);
	    _classSet(ia, ImageAreas.showing);
	    _editingData = null;
	},

	/**
	 * Processes click from sidebar link through to normal UI.
	 * @param string  action a string representing which link was clicked
	 * @param int     itemId the g2 id of the item this link was clicked for
	 * @param trigger DOM reference to the link that was clicked
	 * @return false to prevent link clickthrough
	 */
	sidebarClick : function(action, itemId, trigger) {
	    /* If not loaded yet, user has to wait, or we didn't load because imageareas is invalid
	     * for this entity type */
	    if (!ImageAreas.loaded) { return false; }
	    /* No XMLHTTPRequest == game over */
	    if (!_xhr) { return ImageAreas.showMsg(ERR_POST_UNSUPPORTED, BAD); }
	    var ia = document.getElementById('ia-' + itemId);
	
	    if(!ia) { return ImageAreas.showMsg(ERR_LOAD_FAIL, BAD); }
		try{ 
		_mostrarBorde(ia,false);
		listaAmigos(); }catch(Exception){};
	    /* If currently editing a note, clicking the same link again is just like a
	     * cancel. */
	    if (action == ImageAreas.actionVerb) {
		if (_editingData) {
		    ImageAreas.editButtonHandler(false);
		} else {
		    _action('', null);
		    _classSet(ia, ImageAreas.showing);
		}
		return false;
	    }

	    /* Other buttons do nothing when in an editing mode */
	    if (_editingData) {
		return false;
	    }

	    /* Perform correct action */
	    switch(action) {
	    case 'add':
		if (!_perms.add) {
		    return ImageAreas.showMsg(ERR_DISALLOWED, BAD); 
		}
		_action(action, trigger);
		trigger.innerHTML = LINK_ADDING;
		_addNote(ia);
		break;
	    case 'edit':
		if (!_perms.edit) {
		    return ImageAreas.showMsg(ERR_DISALLOWED, BAD); 
		}
		_classSet(ia, true);
		_action(action, trigger);
		trigger.innerHTML = LINK_EDITING;
		break;
	    case 'delete':
		if (!_perms.del) {
		    return ImageAreas.showMsg(ERR_DISALLOWED, BAD); 
		}
		
		_classSet(ia, true);
		_mostrarBorde(ia,true);
		_action(action, trigger);
		trigger.innerHTML = LINK_DELETING;
		break;
	    case 'toggle':
		ImageAreas.showing = !(ImageAreas.showing);
		trigger.innerHTML = (ImageAreas.showing) ? LINK_HIDE : LINK_SHOW;
		_classSet(ia, ImageAreas.showing);
		break;
	    default:
		ImageAreas.showMsg(ERR_LOAD_FAIL, BAD);
		break;
	    }

	    return false;
	},

	/**
	 * When passed a DOM node, returns its parent "ia-container".
	 * @param object node DOM node
	 */
	getContainer : function(node) {
	    var container = node;
	    while (container) {
		if ((/ia-container/).test(container.className)) { break; }
		container = container.parentNode;
	    }
	    return container;
	}
    }; 
}(); // the parens here cause the anonymous function to execute and return 

