// EgoDesc - 1.0
//A javascript application to colourize and edit collection descriptions
// // http://selfdescription.org/EgoDesc
// written (hacked together) by Tim Hodson
// tim@timhodson.com
// http://informationtakesover.co.uk

// This script requires
// prototype.js v. 1.5.0
// scriptaculous.js v. 1.6.2
// will NOT work with ANY browser other than Mozilla Firefox.

// provided without any warranty, at risk to your own life and that of your cat etc...
// released under a Creative Commons Attribution-ShareAlike 2.0 License
// http://creativecommons.org/licenses/by-sa/2.0/uk/

// NOT MSIE
if(/MSIE/.test(navigator.userAgent)){
	alert("sorry, this will only work in Firefox at the moment.  Not because we are exluding you, but because of our programing skills.  If you would like to donate your time and expertise, please get in touch.");
}
 
run();

function run()
{
	var oldOnload = window.onload;

	if (typeof(window.onload) != "function")
	{
		window.onload = SDInit;
	}
	else
	{
		window.onload = function()
		{
			oldOnload();
			SDInit();
		}
	}
}
 
function SDInit(){
	// build the key
	buildKey(theRules);
	// put something in the editor
	sample = "<div class='collection'> <h1 class='name'>The Collection of Things</h1> <p class='description'> This collection of things was <span class='custodial-history'>given to the library in 1834 by Thomas Inglebrook Morgan</span>. Consisting of <span class='extent'>1,300 items</span>, ranging from <span class='format'>Pamphlets</span>, to <span class='format'>manuscripts </span>and <span class='format'>books</span>, the collection continues to grow as <span class='accrual-policy'>new works are acquired</span>. </p><p class='description'> The collection is especially strong in the <span class='subject'>history of invention</span> with an emphasis on the use of <span class='subject'>puppetry</span>. </p> <p class='access-policy'>The collection is not on general view, please ask at the enquiry desk for access.</p> <p class='location vcard'><span class='fn n'>Lugg Library</span><br><span class='adr'><span class='street-address'> Main Road</span>,<br><span class='locality'> Hereford</span>,<br><span class='region'> Herefordshire</span>,<br><span class='postal-code'> HR28 5TG<br /></span></span> Tel: <span class='tel'><span class='work'>01432 123456</span></span><br> fax: <span class='fax'><span class='work'>01432 123654</span></span><br> email: <span class='email'><span class='work'>librarian@example.com</span></span><br> <p class='contributor vcard'><span class='fn n'> Tim Hodson</span> <span class='email'>tim@timhodson.com</span> </p> </div> ";
	editor = $('editme');
	tbox = $('scratchpad');
	editor.innerHTML = sample;
	tbox.value = sample;
	
	// init the accordian effect
	accInit();
	
	// using prototype.js to add event listeners
	// capture mousup on editor (may be a selection)
	Event.observe('editme','mouseup', selectionInit);
	
	// think this is handled in accordian - so may not be needed here?

	Event.observe('key','mousedown', mySelection.showSelection);
		
	Event.observe('scratchpad','keyup', toEditMe);
	Event.observe('scratchpad','mouseup', toEditMe);

}
//"<abbr class='{classname}' title='{title}'>{selection text}</abbr>"
var theTemplates =  [
			{"name":"div", "template":"<div class='{classname}' id='{title}'>{selection text}</div>"},
			{"name":"p", "template":"<p class='{clasname}' id='{id}'>{selection text}</p>"},
			{"name":"table", "template":"<table class='{clasname}' id='{id}'>{selection text}</table>"},
			{"name":"td", "template":"<td class='{classname}' id='{id}'>{selection text}</td>"},
			{"name":"tr", "template":"<tr class='{classname}' id='{id}'>{selection text}</tr>"},
			{"name":"h1", "template":"<h1 class='{classname}'>{selection text}</h1>"},
			{"name":"h2", "template":"<h2 class='{classname}'>{selection text}</h2>"},
			{"name":"h3", "template":"<h3 class='{classname}'>{selection text}</h3>"},
			{"name":"h4", "template":"<h4 class='{classname}'>{selection text}</h4>"},
			{"name":"h5", "template":"<h5 class='{classname}'>{selection text}</h5>"},
			{"name":"h6", "template":"<h6 class='{classname}'>{selection text}</h6>"},
			{"name":"strong", "template":"<strong class='{classname}'>{selection text}</strong>"},
			{"name":"b", "template":"<b class='{classname}'>{selection text}</b>"},
			{"name":"i", "template":"<i class='{classname}'>{selection text}</i>"},
			{"name":"em", "template":"<em class='{classname}'>{selection text}</em>"},
			{"name":"span", "template":"<span class='{classname}'>{selection text}</span>"}
			
];
var theRules = [
			{"group":"help","name":"help","classname": "help" , "allowTags": "", "attribute":0, "description":"<ol><li>Paste your html into the Scratchpad. It will then appear - with formatting -  in the EgoDesc Editor.</li><li>Use this expanding menu to select the metadata and the html element to wrap around your selection</li><li>Make your selection of text in the editor</li><li>It does not mater which way round you complete steps 2 and 3</li><li>When you are ready click the apply button.  This will apply the formatting.</li><li>Updates are immediately saved to the Scratchpad.</li><li>Updates to the Scratchpad are immediately reflected in the Editor.</li>"},
			{"group":"basic","name":"collection","classname": "collection" , "allowTags": "div,p,td,tr", "attribute":0, "description": "Identitfies a collection. Normally applied to a block elelement"},
			{"group":"basic","name":"name","classname": "name" , "allowTags": "h1,h2,h3,h4,h5,h6,b,strong,em,i", "attribute":0, "description":"The name of the collection"},
			{"group":"basic","name":"description", "classname": "description" , "allowTags": "div,p", "attribute":0, "description":"A block element containin a description of the collection"},
			{"group":"basic","name":"format","classname": "format" , "allowTags": "span", "attribute":0, "description":"The format of a significant part of the collection.  e.g. books, pamphlets, digital images."},
			{"group":"basic","name":"location","classname": "location" , "allowTags": "div,p", "attribute":0, "description":"This will describe the location of the collection, it may be used with the vcard rules"},
			{"group":"basic","name":"interface","classname": "interface" , "allowTags": "a", "attribute":0, "description":"The interface is a link to a website that provides searching or browsing of the website for human users."},
			{"group":"basic","name":"homepage","classname": "homepage" , "allowTags": "a", "attribute":0, "description":"The homepage is a web page with more information about the collection, but no means of access to search or browse functions"},
			{"group":"basic","name":"contributor", "classname": "contributor" , "allowTags": "div,p", "attribute":0, "description":"This is where you tell us about yourself, providing this self description with some authority."},
			{"group":"desired","name":"subject","classname": "subject" , "allowTags": "span", "attribute":0, "description":"A subject covered by the collection"},
			{"group":"desired","name":"language","classname": "language" , "allowTags": "span", "attribute":0, "description":"A language covered by the collection.  use xml:lang to highlight an element with a non english translation."},
			{"group":"desired","name":"spatial","classname": "spatial" , "allowTags": "span", "attribute":0, "description":"Geographic areas covered by the collections"},
			{"group":"desired","name":"temporal","classname": "temporal" , "allowTags": "span", "attribute":0, "description":"periods in time covered by the collection - not the accumulation of the collection"},
			{"group":"desired","name":"extent","classname": "extent" , "allowTags": "span", "attribute":0, "description":"Quantities of items, or space that a collection physically inhabits.  e.g. <span class='extent'>1450 books</span> or <span class='extent'>2GB of digital images</span>"},
			{"group":"desired","name":"access-policy","classname": "access-policy" , "allowTags": "span", "attribute":0, "description":"Any restrictions, or necessary requirements for access to a collection by a human user."},
			{"group":"desired","name":"accrual-policy","classname": "accrual-policy" , "allowTags": "span", "attribute":0, "description":"Periodicity, method, and status of aquiring new items for the collection"},
			{"group":"desired","name":"custodial-history","classname": "custodial-history" , "allowTags": "span", "attribute":0, "description":"For example, did someone bequeeth the collection, or was it bought by the institution?"},
			{"group":"optional","name":"id","classname": "id" , "allowTags": "any", "attribute":1, "msg":"Please enter the id for this selection.", "description":"This will add (or replace) an id attribute in the parent element of the selected text.  Elements with a hidden id attribute will be displayed with a purple dotted border."},
			{"group":"optional","name":"xml:lang","classname": "" , "allowTags": "any", "attribute":1, "msg":"Please enter the language code for this selection.", "description":"This will add (or replace) an xml:lang attribute in the parent element of the selected text."},
			{"group":"optional","name":"subtitle","classname": "subtitle" , "allowTags": "h2,h3,h4,h5,h6,b,strong,em,i", "attribute":0, "description":"An alternative title for the collection - if this title is not english, please remember to add the xml:lang attribute."},
			{"group":"vcard","name":"vcard - main declaration","classname": "vcard" , "allowTags": "div", "attribute":0, "description":"vcard"},
			{"group":"vcard","name":"full name - vcard","classname": "fn n" , "allowTags": "span", "attribute":0, "description":"Your name"},
			{"group":"vcard","name":"organisation name - vcard","classname": "org fn" , "allowTags": "span", "attribute":0, "description":"Your organisation's name"},
			{"group":"vcard","name":"organisation unit - vcard","classname": "organization-unit" , "allowTags": "span", "attribute":0, "description":"Your organisational unit's name"},
			{"group":"vcard","name":"address - vcard","classname": "adr" , "allowTags": "div,p", "attribute":0, "description":"To enclose an address"},
			{"group":"vcard","name":"street-address - vcard","classname": "street-address" , "allowTags": "span", "attribute":0, "description": "identify the street address. e.g. 45 Rue de Blanchard"},
			{"group":"vcard","name":"locality - vcard","classname": "locality" , "allowTags": "span", "attribute":0, "description":"the locality with a region"},
			{"group":"vcard","name":"region - vcard","classname": "region" , "allowTags": "span", "attribute":0, "description":"a county or other non-national region"},
			{"group":"vcard","name":"country-name - vcard","classname": "country-name" , "allowTags": "span", "attribute":0, "description":"eg UK, France, USA"},
			{"group":"vcard","name":"postal-code - vcard","classname": "postal-code" , "allowTags": "span", "attribute":0, "description":"Your postal code"},
			{"group":"vcard","name":"tel - vcard","classname": "tel" , "allowTags": "span", "attribute":0, "description":"A telephone number. If you specify that the telephone number is a work phone, or the preferred phone, or a voice phone, you will need to make sure that 'tel' is the outer element.  (this is a <abbr title='Performance Enhancement Request'>PER</abbr>!)"},
			{"group":"vcard","name":"email - vcard","classname": "email" , "allowTags": "span", "attribute":0, "description":"An email address that you can be contacted on. If you specify that this is a work, home or preferred address, you will need to make sure that 'email' is the outer element.  (this is a <abbr title='Performance Enhancement Request'>PER</abbr>"},
			{"group":"vcard","name":"work - vcard","classname": "work" , "allowTags": "span", "attribute":0, "description":"is the email or telephone number for work purposes?"},
			{"group":"vcard","name":"home - vcard","classname": "home" , "allowTags": "span", "attribute":0, "description":"is the email or telephone number for home purposes?"},
			{"group":"vcard","name":"pref - vcard","classname": "pref" , "allowTags": "span", "attribute":0, "description":"the preferred contact means, can be used with both tel and fax"},
			{"group":"vcard","name":"voice - vcard","classname": "voice" , "allowTags": "span", "attribute":0, "description":"a voice number"},
			{"group":"vcard","name":"fax - vcard","classname": "fax" , "allowTags": "span", "attribute":0, "description":"A fax number"},
			{"group":"vcard","name":"role - vcard","classname": "role" , "allowTags": "span", "attribute":0, "description":"Your role within the organisation"},
			{"group":"vcard","name":"title - vcard","classname": "title" , "allowTags": "span", "attribute":0, "description":"Your job title"}

			//{"group":"","name":"","classname": "" , "allowTags": "", "attribute":0, "msg":"","description":""}
];


// editor - on mouse up.
function selectionInit(){
// prepare an object that represents the selected text
	mySelection.init();
	// does it have a current selection?
	// what is the parent element?
	// 
}

var mySelection = {
	init:function(){
		this.sel = window.getSelection();
		
		if(!this.sel.isCollapsed){
			this.rng = this.sel.getRangeAt(0);
			//remember startElement is likely to be a text node more often than not
			this.startElement = this.rng.startContainer;
			this.endElement = this.rng.endContainer;
			this.parentElement = this.rng.commonAncestorContainer;
			
			// selection text (for template use)
			// this DOES NOT HAVE OTHER TAGS!!
			this.selectionText = this.sel.toString();
		}
	},
	showSelection:function(){
	// func called as an event so DON'T use 'this.'
	// want to show the selection until we deselect -
		try{
			mySelection.sel.addRange(mySelection.rng);
		}
		catch(e){
		// don't catch any errors!
		}
		
	},
	deselect:function(){
	// func called as an event so DON'T use 'this.'
		// dispose of the selection
		mySelection.sel.removeAllRanges();
		mySelection.lastRng = '';
		// mySelection.flushed = true;
	}
}

//this roles all values we need for an insert into one handy object.
var myInsert = {
	init:function(event){
	// 'this.' in this context is a reference to the DOM node that triggered the event NOT this object!
		myInsert.radio = event.originalTarget;
		myInsert.parseID();
		myInsert.getValues();
		myInsert.lookUpTemplate();
	},
	parseID:function(){
		id = this.radio.id.split(/-/);
		for(i=0; i<id.length; i++){
			if (!isNaN(parseInt(id[i]))){
				this.id = id[i]
			}
		}
	},
	getValues:function(){
	//internal func
		this.group		= theRules[this.id].group;
		this.name		= theRules[this.id].name;
		this.classname	= theRules[this.id].classname;
		this.allowedTags= theRules[this.id].allowTags;
		this.tag 		= this.radio.value;
		this.attribute	= theRules[this.id].attribute;
		this.msg		= theRules[this.id].msg;
		this.description= theRules[this.id].description;
	},
	reset:function(){
	//internal func
		this.group		= '';
		this.name		= '';
		this.classname	= '';
		this.allowedTags= '';
		this.attribute	= '';
		this.msg		= '';
		this.description= '';
		this.template 	= '';
		if(this.radio){
			this.radio.checked = false;
		}
	},
	lookUpTemplate:function(){
	// internal function
		// get the template for the allowed tags chosen
		for(i=0; i<theTemplates.length; i++){
			if (theTemplates[i].name == this.tag){
				this.template = theTemplates[i].template;
				break;
			}
		}
		
		//alert(this.template);
	}
}
var egoPopUpDiv=function(content, click){
	this.div = document.createElement('div');
	this.div.onclick = click;
	this.div.appendChild(content);
	
}

var myInspector ={
	init:function(e){
	// this will create a RIGHT CLICK popup div that is absolutely positioned above everything.
	// the div needs to:
		// know if the selected text is within any element with a currently set attribute?
		// we could show a html string of the selected text.
		// options to remove classnames, or attributes
		// for selected microformat name (selected in key - without), need to 
	// elem = new Event.element(e);
	}
	

}

var egoAdrForm={
	init:function(opt){
		// vCard Template
		this.adrFormTemplate = Array(
			"<div class='vcard {classname}'>",
			"	<span class='fn n'>{Name}</span><br />",
			"	<span class='fn org'>{Organisation}</span><br />",
			"	<span class='organization-unit'>{Organisational-unit}</span><br />",
			"	<span class='title'>{Title}</span><br />",
			"	<span class='role'>{Role}</span><br />",
			"	<span class='adr'>",
			"		<span class='street-address'>{Street-address}</span><br />",
			"		<span class='locality'>{Locality}</span><br />",
			"		<span class='Region'>{Region}</span><br />",
			"		<span class='country-name'>{Country-Name}</span><br />",
			"		<span class='postal-code'>{Postal-Code}</span>",
			"	</span>",
			"</div>"
		);
		
		this.line = Array();
		
		for (i=0; i<this.adrFormTemplate.length; i++){
			// this same parse is used when creating the output from the form
			this.parseLine(i, this.adrFormTemplate[i]);
		}
		// need a case statement here
		switch(opt){
		case "new":
			this.buildForm();
			break;
		case "vcard":
			this.rendervCard();
			break;
		default:
			this.buildForm();
			break;
		}
	},
	buildForm:function(){
		// the form is built using the this.line[i] parsed in the init.
		var frm = document.createElement('form');		
		
		// loop through this.line[i].match and build the input tag and label tags
		// while we are looping, add the first/match/second parts together
		for (i=0; i< this.line.length; i++){
			var line = this.line[i];
			// before part
			frm.innerHTML += line.before;
			if (line.after){
			// match part
			// start with classname!
			
			//now the inputs
			inp = this.createInput(line.match);
			lbl = this.createLabel(line.match);
			
			frm.appendChild(lbl);
			frm.appendChild(inp);
			// after part
			frm.innerHTML += line.after
			}
		}
		// testing - here is where we will call a popup div
		elem = $('inputwrapper');
		elem.appendChild(frm);
	},
	parseLine:function(i, templateLine){
		// only give one line of the template at a time as a string
		var r = /\{[A-Za-z0-9]*\}/;
		var result = r.exec(templateLine);
		if(result == null){
			this.line[i] = {'before':templateLine};
		}else{
			match = result[0];
			tmp = templateLine.split(match);
			left = tmp[0];
			right = tmp[1];
			this.line[i] = {'before':left, 'match':match, 'after':right};
		}
	},
	createInput:function(id){
		// id is a string in the form {text} (need to reove the brackets!)
		id = id.slice(1,-1);
		
		var input = document.createElement('input');
		input.type = 'text';
		input.id = id;
		input.width = 30;
		input.onfocus = function(){input.style.backgroundColor = '#ffe1b2'};
		input.onblur = function(){input.style.backgroundColor = '#ffffff'};
		
		return input;
	},
	createLabel:function(lblContent){
		lblContent = lblContent.slice(1,-1);
		lblFor = lblContent;
		// replace hyphen (-)
		lblContent = lblContent.replace(/-/," ");
		
		var lbl = document.createElement('label');
		lbl.htmlFor = lblFor
		txt = document.createTextNode(lblContent);
		lbl.appendChild(txt);
		
		return lbl;
	},
	addAddressBlock:function(){
		
	}
}

//half thought undo idea - not implemented.... yet
var theContent = '';
var Undo = '';

function buildKey(theRules){
	 //function to build the list of key/classnames
	//<div id="accordion">
	elem = $('key');
	divAcc = document.createElement('div')
	divAcc.id ='accordion';
	
	elem.appendChild(divAcc)
	
	for(i=0; i<theRules.length; i++){

		divPan = document.createElement('div');
		divPan.id = 'panel-'+i;
		divAcc.appendChild(divPan);

		h3id = (i==0) ? 'visible' : '';
		divPan.innerHTML = '<h3 id="' + h3id + '" class="' + theRules[i].group +'" onclick="accordion(this)"><span class="' + theRules[i].classname + '">' + theRules[i].name + '</span></h3>';

		divPanBod = document.createElement('div');
		divPanBod.id = divPan.id + '-body';
		divPanBod.className = 'panel_body';
		divPan.appendChild(divPanBod);

		// This is the contents of this panel.
		divPanContent = document.createElement('div');
		divPanContent.id = theRules[i].name;
		divPanContent.className = theRules[i].classname
		divPanContent.onclick = mySelection.showSelection;
		divPanBod.appendChild(divPanContent);
		
		txtdesc = theRules[i].description;
		if (txtdesc){
			divPanContent.innerHTML += txtdesc;
		}

		tagul = document.createElement('ul');
		tags = theRules[i].allowTags;
		if(tags){
			tag = tags.split(/,/g);
			for(k=0; k<tag.length; k++){
				li = document.createElement('li');
				newr = document.createElement('input');
				txt = document.createTextNode(tag[k]);
				radioid = 'radio-'+i;
				
				newr.type = 'radio';
				newr.id = radioid;
				newr.name = theRules[i].classname;
				newr.value= tag[k];
				newr.onclick = myInsert.init;
				newr.onmouseup = mySelection.showSelection;
				
				li.appendChild(newr);
				li.appendChild(txt);
				tagul.appendChild(li);
				
			}
			
			// add a button to do the do.
			btn = document.createElement('input');
			btn.type = 'button';
			btn.onclick = addMeta;
			btn.value = "apply to selection";
			btn.className = "apply";
			btn.id = "btn-"+divPan.id;
			divPanContent.appendChild(tagul);
			divPanContent.appendChild(btn);
			
		}
				
	}
}

function accordion(el) {
	mySelection.showSelection();
	if ($('visible') == el) {
        return;
    }
    if ($('visible')) {
        var eldown = el.parentNode.id+'-body';
        var elup = $('visible').parentNode.id+'-body';
        new Effect.Parallel(
        [
            new Effect.SlideUp(elup),
            new Effect.SlideDown(eldown)
        ], {
            duration: 0.1
        });
        $('visible').id = '';
    }
    el.id = 'visible';
	
	//reset all radio buttons
	//reset selection
	myInsert.reset();

}

// initialises the accordian.
function accInit() {
    // hide all elements apart from the one with id visible
    var acc = $('accordion');
    var apanels = acc.getElementsByTagName('div');
    for (i = 0; i < apanels.length; i++) {
        if (apanels[i].className == 'panel_body') {
            apanels[i].style.display = 'none';
        }
    }
    var avis = $('visible').parentNode.id+'-body';
    $(avis).style.display = 'block';
}

//the function that will add the chosen metadata  to the selected (captured) text.
function addMeta(){
	// only do this if our myInsert object has been filled up with stuff!
	if(myInsert.id){
		if(myInsert.attribute == 1){
			addAttribute(mySelection.startElement.parentNode, myInsert.name, myInsert.msg);
			fromEditMe();
		}else{
			wrapTag();
			fromEditMe();
			//parseTemplate();
		}
	}
	
}
function wrapTag(){
// do the surrounding of the selection

//future
// does the new tag have to be NESTED WITHIN a specific element?

	// moz only - might be able to use prototype to do this?
//	insertion object? esp if using templates as strings
// or position.clone(source, target)?
	try{
		if(mySelection.rng){
			newrng = mySelection.rng;
			newrng.surroundContents(createElement(myInsert.tag, myInsert.classname));
			//alert(rng);
			mySelection.deselect();
			// do not use the rng after removing it - as it will try to apply it again and cause visual mayhem in the browser.
		}
	}
	catch(err){
		alert(err);
	}
}
function addAttribute(elem, name, msg){
	// elem - the element to give the attribute to
	// name - the name of the attribute.
	// need some user response - a prompt for the value
	var value = prompt(msg);
	if (value != null){
		elem.setAttribute(name, value);	
	}	
}

function createElement(el, className){
	var newEl = document.createElement(el);
	newEl.className = className;
	return newEl;
}

function toEditMe(){
	editor = $('editme');

	// get textbox pasted content and put it into our editor div
	tbox = $('scratchpad');
	var text = tbox.value;
	editor.innerHTML = text;

	//new Effect.Opacity(editor,{duration:1, transition: Effect.Transitions.linear, from: 0, to: 1, queue:{position: 'end', scope: 'editorscope'}});
	theContent = text;
	Undo = text
}

function fromEditMe(){
	var newtext = $('editme').innerHTML;
	theContent = newtext;
	
	var tbox = $('scratchpad');
	tbox.value = theContent;
}
function parseTemplate(temp){
	
	// we should
	// temp is a string
	template = "<abbr class='{classname}' title='{title}'>{selection text}</abbr>"
		
	var selText = myInsert.selectionText;
	//var selText = "a selection";
	var classname = myInsert.classname;
	//var classname = "subject";
	var title = myInsert.title;
	//var title = "the title";
	
	// replace parts of the template
	rc = /\{classname\}/;
	rt = /\{title\}/;
	rs = /\{selection text\}/;
	
	tmp1 =  (!selText) ? template : tmp1 = temp.replace(rc,classname);
	tmp2 = (!classname) ? tmp1 : tmp2 = tmp1.replace(rt,title);
	tmp3 = (!title) ? tmp2 : tmp3 = tmp2.replace(rs,selText);
	alert(tmp3);
}
//parseTemplate();