/**
 * BACK-OFFICE main script for Bullseye.
 *
 * Requires Prototype 1.6.
 *
 * @author Christophe Porteneuve <christophe@ciblo.net>
 * @legals © 2008 Ciblo SA.
 */

var gMCESelector = 'mceEditor';  // tinyMCE
var gRequiredField = /Req/;
var gAuthenticityToken = '';

var gDefaultThumbPath = '/images/back/default_thumb.gif';

/**
 * Grabs calendar decorations for date input fields and make them popup the calendar.
 * This assumes the image is on the same parent element as the input field.
 */
function bindDatePickers() {
  document.observe('click', function(e) {
    var activator = e.findElement('img.datePicker');
    if (!activator) return;
    var editor = activator.previous('input.date');
    if (editor.disabled) return;
    editor.writeAttribute('readonly', true);
    new CalendarDateSelect(editor, { buttons: false, popup: 'force' });
  });
} // bindDatePickers

/**
 * Make sure all forms check for required fields, valid prices, etc.
 *
 * @see #checkFormRequirements
 */
function bindFormSubmissions() {
	$$('form').invoke('observe', 'submit', checkFormRequirements);
};

/**
 * Grabs all main-level model listings that do not forego Ajaxified deletion
 */
function bindMainListDeleters() {
  var lists = $$('.mainList:not(.customDeletion)');
  if (!lists) return;
  lists.invoke('observe', 'click', function(e) {
    var activator = e.findElement('a.delete');
    if (!activator) return;
    if (activator.hasClassName('linkRemove')) alert('k thx bye!');
    e.stop();
    activator.blur();
    var li = activator.up('li');
    var name = li && li.down('.name'), kindLabel = li && li.up().readAttribute('kind_label') || 'l’élément';
    name = (name ? name.innerHTML.stripTags().unescapeHTML() : '').strip();
    var msg = 'Supprimer ' + kindLabel + (name == '' ? '' : (' « ' + name + ' »')) + ' ?';
    if (!confirm(msg)) return;
    var form = new Element('form', { method: 'post', action: activator.href, style: 'display: none;' });
    document.body.appendChild(form);
    form.insert('<input type="hidden" name="_method" value="delete" />\
    <input type="hidden" name="authenticity_token" value="' + gAuthenticityToken + '" />');
    form.submit();
  });
} // bindMainListDeleters


function bindSpecTogglers() {
	var links = $('submenu'), cookie = new Cookie();

	if (!links) return cookie.remove('submenu');
	
	var activeLink = links.select('li.active a').first(),
		activeItem = activeLink && activeLink.up(), submenu = cookie.get('submenu');
	
	if (!activeItem) return cookie.remove('submenu');
	
	function handleSpecEditorClick(e) {
		e.stop();
		var link = e.findElement('a'), item = link.up(), flash = $('flashes');
		link.blur();
		if (link == activeLink) return;
		if (flash) flash.update('');
		activeItem.removeClassName('active');
		(activeItem = item).addClassName('active');
		// IE 6 sucks... no effects !
		$(activeLink.readAttribute('href').substring(1)).hide();
		$((activeLink = link).readAttribute('href').substring(1)).show();
		cookie.set('submenu', activeLink.hash);
	} // handleSpecEditorClick

	links.select('a').invoke('observe', 'click', handleSpecEditorClick);
} // bindSpecTogglers

// DEV NOTE: whitespace is going to be stripped before matching those.
var REGEX_PRICE  = /^-?\d+(?:[.,]\d+)?$/;
var REGEX_EMAIL = /^[\w.-]+@([\w-]+\.)+[a-zA-Z]{2,6}$/;
var ERROR_CLASS = 'missing';

/**
 * Check for a form's required fields when it's submitted.  Dynamically
 * adds/removes "missing" CSS classes to labels and fields that are required,
 * depending on whether they have a non-blank value or not.  If there is at least
 * one missing field, the first such one is focused after the message appears.
 * (There is only one message listing all the fields, not multiple messages.)
 */
function checkFormRequirements(e) {
	var msg = '', firstOffender = null, that = this, form = e.target;
	function checkError(fieldOK, field, message) {
		var label = form.select('label[for="' + field.id + '"]').first();
		if (fieldOK) {
			field.removeClassName(ERROR_CLASS);
			label && label.removeClassName(ERROR_CLASS);
		} else {
			that._hasValidationErrors = true;
			field.addClassName(ERROR_CLASS);
			label && label.addClassName(ERROR_CLASS);
			firstOffender = firstOffender || field;
			var labelText = getFieldLabelText(field, label);
			msg += '<li><label for="' + field.id + '">' + labelText +
				'</label> ' + message + '</li>';
		}
	}
	function getFieldLabelText(field, label) {
		label = label || $$('label[for="' + field.id + '"]').first();
		if (label) {
			var text = (field.hasAttribute('title')
			? field.readAttribute('title')
			: label.innerHTML.stripTags().unescapeHTML());
		  var par = label.up('p');
		  var bar = par && par.previous('.languageBar');
		  if (bar && !bar.hasClassName('default'))
		    text += ' (' + bar.down('span').innerHTML.stripTags().unescapeHTML() + ')';
		  return text.gsub(/(?:&nbsp;| )?:|\*/, '');
		}
		return field.hasAttribute('title') ? field.readAttribute('title') : field.id;
	}
	this._hasValidationErrors = false;
	// Required fields
	this.select('*[id*=Req]:not(.tree)').each(function(field) {
		checkError(field.present(), field, 'est manquant(e)');
	});
	// Tiny MCE required fields
	this.select('.' + gMCESelector + '.required').each(function(field) {
		var content = tinyMCE.get(field.id).getContent(), present = 
			'' != content.stripTags().replace(/(&nbsp;)|(&#160;)/, '').strip();
		field.next().down('table')[(present ? 'remove' : 'add') + 'ClassName'](ERROR_CLASS);
		checkError(present, field, 'est manquant(e)');
		
	});
	// Tree required item
	this.select('*[id*=Req].tree').each(function(tree) {
		var present = tree.select('input[type=checkbox]').pluck('checked').include(true);
		checkError(present, tree, 'est manquant(e)');
	});
	// Integer fields
	this.select('input[id*=Integer]').each(function(field) {
	  if (!field.present()) return;
	  var text = field.getValue().gsub(/[,.  -]+/, ''), intConversion = parseInt(text, 10).toString();
	  field.setValue(text);
	  checkError(text == intConversion, field, 'n’est pas un nombre entier');
	});
	// Email fields
	this.select('input[id*=Email]').each(function(field) {
	  if (!field.present()) return;
	  checkError(REGEX_EMAIL.test(field.getValue()), field, 'n’est pas une adresse e-mail valide');
	});
	// Price fields
	var priceMsg = '', firstNullPrice, nullPriceCount = 0;
	this.select('input.price').each(function(field) {
		if (!field.present()) return;
		var priceText = field.getValue().gsub(/\s+/, '');
		checkError(REGEX_PRICE.test(priceText), field, 'n\'est pas un prix valide');
		if (REGEX_PRICE.test(priceText) && parseFloat(priceText.replace(',', '.'), 10) == 0) {
		  firstNullPrice = firstNullPrice || field;
		  priceMsg += '- ' + getFieldLabelText(field) + "\n";
		  ++nullPriceCount;
		}
	});
	if (firstOffender) {
		e.stop();
		if (firstOffender.activate) firstOffender.activate();
		if (firstOffender.tinyMCE) tinyMCE.execCommand('mceFocus', false, firstOffender.id);
		var container = form.up('#mainContent'), flashes = container.down('div.flashes');
		if (!flashes) {
			flashes = new Element('div', { className: 'flashes error' });
			container.insert({ top: flashes });
		}
		flashes.update('<p>Les champs suivants posent problème :</p>' +
			'<ul>' + msg + '</ul>');
	} else if (firstNullPrice) {
	  priceMsg = (1 == nullPriceCount ? 'Le prix suivant est à zéro :' : 'Les prix suivants sont à zéro :') +
	    "\n" + priceMsg + 'Souhaitez-vous tout de même continuer ?';
	  if (!confirm(priceMsg)) {
	    e.stop();
	    firstNullPrice.activate();
	  }
	}
} // checkFormRequirements

/**
 * Decorates the accesskey character in a label: its text is then searched
 * for the first occurrence of that character (case-insensitively), which is wrapped in
 * a <span class="accessKey">...</span> for styling.
 */
function decorateLabel(node, key) {
	if (Object.isElement(node)) {
		var children = node.childNodes;
		for (var index = 0, n = children[index], l = children.length; index < l; ++index)
			if (decorateLabel(n, key))
				return true;
	} else if (node.nodeType == Node.TEXT_NODE) {
		var text = node.nodeValue.toLowerCase(), key = key.toLowerCase();
		var pos = text.indexOf(key);
		if (-1 != pos) {
			if (pos > 0)
				node.parentNode.insertBefore(new Element('span').update(node.nodeValue.substring(0, pos)), node);
			node.parentNode.insertBefore(new Element('span', { className: 'accessKey' }).update(node.nodeValue.charAt(pos)), node);
			node.nodeValue = node.nodeValue.substring(pos + 1);
			return true;
		}
	}
	return false;
} // decorateLabel

/**
 * Sweeps <label> elements to tag them with the 'required' CSS class if they're
 * associated to a required field (their for= attribute points to a field whose id=
 * contains the 'Req' string), for styling.  Every label also gets decorated for its
 * potential accesskey using decorateLabel.
 *
 * If at least one label got the 'required' CSS class, an extra paragraph with both
 * 'required' and 'notice' CSS classes gets inserted at the bottom of the form, bearing
 * the text 'Champ obligatoire'.
 */
function decorateLabels() {
	var decoratedForms = [];
	$$('label').each(function(l) {
		var field = $(l.readAttribute('for'));
		if (!field) return;
		if (field.hasClassName(gMCESelector) && field.hasClassName('required')) { // tinyMCE
			l.addClassName('required');
			l.insert('*');
		}
		if (gRequiredField.test(field.id)) {
			l.addClassName('required');
			l.insert('*');
			var form = l.up('form');
			if (!decoratedForms.include(form)) {
				decoratedForms.push(form);
				form.insert({ bottom: new Element('p', { className: 'notice required'}).update('* Champ obligatoire') });
			}
		}
		var ak = l.getAttribute('accesskey');
		if (ak) decorateLabel(l, ak);
	});
} // decorateLabels

/**
 * Fades out and removes any flash message (as in Rails flash).
 */
function fadeFlashMessages() {
	$$('p.flash:not(.noAutoFade)').each(Effect.Fade);
} // fadeFlashMessages


/**
 * Focuses the first eligible form field in the page, if any.
 */
function focusFirstField() {
	var f = $$('form').first();
	if (f && f.findFirstElement())
		f.focusFirstElement();
} // focusFirstField


document.observe('dom:loaded', function() {
	bindDatePickers();
	// Entity-index-level lists
	bindMainListDeleters();
	// Generic right-hand systems on entity editor views
	bindSpecTogglers();
	// Auto-fade flash messages
	fadeFlashMessages.delay(3);
	// Generic form management
	decorateLabels();
	bindFormSubmissions();
	focusFirstField();
});
