/**
 * AM UI
 * @version 1.2
 * @author Anton Matviichuk
 * @requires AM_ext 1.0
 * @copyright Copyright &copy; 2007—2008, Anton Matviichuk
 * @license http://www.gnu.org/licenses/gpl-3.0.txt GPL 3.0
 * 
 * 
 * Init:
 * 		No init required
 * 
 * Public methods:
 * 
 * 		onScreen(string text) — On-screen LED-like hint
 * 
 * 		box(object controls) — Dialog box
 * 			controls = {
 * 				name: {type: value or label, 'value': value, 'checked': true/false, events: {event: handler function, …}}
 * 				…
 * 			}
 * 
 * 		dump(object obj) — Dialog box with a object dump (very good for debugging)
 * 
 * 		menu(object controls, object options, object event) — Drop-down menu
 * 			controls = {
 * 				section: {label: handler function, …}}
 * 				…
 * 			}
 * 			options = {} 
 * 
 * 		setDrag(object options) — Make element draggable
 * 			options = {
 * 				element: DOM Element,
 *				type: type of dragged element,
 *				pass: parameter (i.e. object) to pass to handler,
 *				hint: will appear when dragging,
 *				onDrop: when drop is successfull,
 *				onCancel: when drop fails or element dropeed out of area
 * 			}
 * 
 * 		setDrop(object options) — Make element possible to handle drop
 * 			options = {
 * 				element: DOM Element,
 * 				accept: {
 * 					type: {hint, pass, onDrop, 'edges':{edge:{size, hint, pass, onDrop}, …}}
 * 					…
 * 				}
 * 			}
 */
ax

/**
 * Basic amui class. Can be used both as a singleton or as a prototype.
 */
amui = {
	version: 1.2,
	boxQueue: [],
	
	onScreen: function(text) {
		if(!this.onScreenElement) this.onScreenElement = ax.dom.c('DIV',{id:'AMUI_onScreen'}).inside(document.body)

		if(typeof text != 'string') {
			this.onScreenElement.remove()
			delete this.onScreenElement
		} else {
			this.onScreenElement.innerHTML = text
			ax.event.listen(this.onScreenElement, 'click', this.onScreen.bind(this))
		}
	},
	
	dump: function(obj) {
		var dump = '<ul>';
		for(var i in obj) {
			type = typeof obj[i]
			dump += '<li class="AMUI_type_'+type+'"><b>' + i + '</b>: ' + obj[i] + '</li>'
		}
		dump += '</ul>';
		this.box({'header':{text:'amui dump'}, content:{text:dump}, cancel:{button:'close'}})
	},
	
	box: function(controls) {
		if(!controls) this.boxClose()

		// prevent box ovelapping
		if(this.boxElement) {
			this.boxQueue.push(controls)
			return
		}

		// draw basic elements (once)	
		this.dimmerElement = ax.dom.c('DIV',{id:'AMUI_dimmer'}).inside(document.body)
		this.boxElement = ax.dom.c('form',{id:'AMUI_askBox', className: controls.className}).inside(document.body)

		// draw box contents
		var html = ''
		var prev = ''
		if(controls.header) {
			ax.dom.c('h4').setText(controls.header.text).inside(this.boxElement)
			prev = 'header'
		}
		for(var c in controls) {
			if(prev == 'button' && !controls[c].button) this.buttonsP = null // close multibutton paragraph
			if(c == 'header' || c == 'className') {
				// nop
			} else if(controls[c].element) {
				ax.dom.$(controls[c].element).inside(this.boxElement)
			} else if(controls[c].code) {
				ax.dom.c('div').setHTML(controls[c].code).inside(this.boxElement)
				prev = 'block';
			} else if(controls[c].checkbox) {
				var p = ax.dom.c('p').inside(this.boxElement)
				ax.dom.c('input', {type:'checkbox', id:'amui.'+ c, checked: controls[c].checked}).inside(p)
				ax.dom.c('label').setHTML(' &mdash; '+ controls[c].checkbox.i18n()).inside(p)
//				html += "<p><input type='checkbox' id='amui."+ c +"' "+ checked +"> &mdash; <label for='amui."+ c +"'>"+ controls[c].checkbox.i18n() +"</p>"
				prev = 'string'
			} else if(controls[c].button) {
				if(prev != 'button') this.buttonsP = ax.dom.c('p', {className:'AMUI_input'}).inside(this.boxElement) // open multibutton paragraph
				var button = ax.dom.c('button', {type:'button', id:'amui.'+ c}).setText(controls[c].button).inside(this.buttonsP)
				if(controls[c].onClick) ax.event.listen(button, 'click', controls[c].onClick)
				prev = 'button'
			} else if(controls[c].input || controls[c].password) {
				html += "<p class='AMUI_input'><label>" + (controls[c].input || controls[c].password) + "</label>"
				html += "<input id='amui."+ c +"' type='" + (controls[c].password ? 'password' : 'text') + "' value='" + (controls[c].value||'') + "'/></p>"
				prev = 'block'
			} else if(controls[c].file) {
				html += "<form id='amui."+ c +".form' action='' method='post' type='' enctype='multipart/form-data'><p><label>" + controls[c].file + "</label>"
				html += "<input id='amui."+ c +".file' type='file' name='file_"+ c +"'/></p></form>"
				prev = 'block'
			} else if(controls[c].text) {
				ax.dom.c('p').setText(controls[c].text).inside(this.boxElement)
				prev = 'string'
			} else if(controls[c].h) {
				ax.dom.c('h5').setText(controls[c].h).inside(this.boxElement)
				prev = 'string'
			} else {
				prev = 'default'
			}
		}
		
		// event listeners and focus
		var toFocus = null
		for(var c in controls) {
			if(c == 'cancel') ax.event.listen(document.getElementById('amui.'+c), 'click', this.boxClose.bind(this))
			if(controls[c].events) for(var e in controls[c].events) {
				ax.event.listen(document.getElementById('amui.'+c), e, controls[c].events[e])
			}
			if(!toFocus || controls[c].focus) toFocus = document.getElementById('amui.'+c)
		}
		if(toFocus) toFocus.focus()
		
		// bind ESC key as CLOSE event
		this.boxCloseListener = ax.event.listen(window, 'keypress', function(event){
			if(event.keyCode == 27) {
				this.boxClose()
			}
		}.bind(this))
	},
	
	boxClose: function() {
		// close current
		ax.event.unlisten(this.boxCloseListener)
		this.dimmerElement.remove()
		delete this.dimmerElement		
		this.boxElement.remove()
		delete this.boxElement

		// open next
		if(this.boxQueue.length > 0) {
			this.box(this.boxQueue.shift())
		}
	},
	
	menuClose: function() {
		if(this.menuElement) this.menuElement.remove()
		delete this.menuElement
	},

	menu: function(content, o, event) {
		// draw menu
		this.menuClose()
		var menu = this.menuElement = ax.dom.c('div',{className:'AMUI_context_menu'})
		if(event) {
			ax.event.fix(event)
			menu.style.top = (event.pageY || ax.page.Y(event.target)) + 'px'
			menu.style.left = (event.pageX || ax.page.X(event.target)) + 'px'
		}

		// when close menu
		ax.event.listen(menu, 'mouseleave', function(event){this.menuClose()}.bind(this))
		ax.event.listen(menu, 'click', function(event){this.menuClose()}.bind(this))
		ax.event.listen(menu, 'keypress', function(event){if(event.keyCode == 27) this.menuClose()}.bind(this))

		// draw inner contents
		var toFocus = null;
		if(typeof content == 'string') {
			menu.innerHTML = content
		} else {
			ax.foreach(content, function(section, section_name) {
				if(ax.isEmptyObject(section)) return
				if(section_name=='header') {
					var h4 = ax.dom.c('h4').setText(content.header).inside(menu)
				} else {
					var h5 = ax.dom.c('h5').setText(section_name).inside(menu)
					var ul = ax.dom.c('ul').inside(menu)
//					for (var item in code[section]) if() {
					ax.foreach(section, function(item, item_name) {
						if(!item) return
						// simply function or complex object?
						switch(typeof item) {
							case 'function':
								var onClick = item
								var checked =  null
								break
							case 'object':
								var onClick = item.onClick
								var checked =  item.checked
								break
							default:
								var onClick = false
								break
						}

						// create LI
						var li = ax.dom.c('li').inside(ul)
						if(checked !== null) li.addClass('AMUI_checkbox ' + (checked ? 'checked' : ''))
						if(item.className) li.addClass(item.className)

						// create A
						if(onClick) {
							var a  = ax.dom.c('a', {href: '#js:menu-item'}).setText(item_name).inside(li)
							try {
								ax.event.listen(a, 'click', onClick, false);
							} catch(x) {}
							if(!toFocus) toFocus = a;
						} else {
							li.setText(item_name)
						}
					})
				}
			})
		}
		menu.inside(document.body)
		if(toFocus) toFocus.focus()
	},
	
	table: function(o) {
		// header
		var prev = []
		var table = ax.dom.c('table', {className: o.className})
		var thead = ax.dom.c('tr').inside(ax.dom.c('thead').inside(table))
		ax.foreach(o.head, function(value, key) {
			var th = ax.dom.c('th').inside(thead).setText(value)
			if(o.colWidth) th.style.width = o.colWidth[key]
		})

		// body
		var tbody = ax.dom.c('tbody').inside(table)
		// iterate rows
		ax.foreach(o.data, function(row, row_no) {
			var tr = ax.dom.c('tr').inside(tbody)
			// iterate cells
			ax.foreach(o.cells || row, function(cell, cell_no) {
				if(typeof cell == 'function') {
					var c = cell(row, prev)
					// cell() may return both object or string
					if(typeof c == 'string') {
						c = {value: c}
					}
					// prev[cell_no] is a same cell in a previous row, used to make rowSpan
					if(prev[cell_no] && c.value == prev[cell_no].value) {
						prev[cell_no].td.rowSpan = (prev[cell_no].td.rowSpan || 1) + 1
					} else {
						var td = ax.dom.c('td').inside(tr)
						if(c.element) {
							ax.dom.$(c.element).inside(td)
						} else {
							td.setText(c.value)
						}
						prev[cell_no] = {value: c.value, td: td}
					}
				} else {
					var c = o.cells ? row[cell] : cell
					if(prev[cell_no] && c == prev[cell_no].value) {
						prev[cell_no].td.rowSpan = (prev[cell_no].td.rowSpan || 1) + 1
					} else {
						var td = ax.dom.c('td').inside(tr)
						if(typeof c != 'string') c = c.toString()
						var str = o.truncate ? c.truncate(o.truncate, '…') : c
						if(str != c) {
							ax.dom.c('a').inside(td).setText(str)
							.listen('click', amui.box.bindListener(amui, [{header:'Full text',text:{text:c.nl2br()},cancel:{button:'Close'}}]))
						} else {
							td.setText(str.toString())
						}
						prev[cell_no] = {value: c, td: td}
					}
				}
			})
		})
		
		return table
	},

	setDrag: function(o) {
		if(typeof o.element == 'string') o.element = ax.dom.$(o.element).addClass('AMUI_drag')
		ax.event.listen(o.element, 'mousedown', amui_drag.prepare.bindListener(amui_drag, [o]))
	},

	setDrop: function(o) {
		if(typeof o.element == 'string') o.element = ax.dom.$(o.element).addClass('AMUI_drop')
		ax.event.listen(o.element, 'mouseup', amui_drag.drop.bindListener(amui_drag, [o]))
		ax.event.listen(o.element, 'mouseover', amui_drag.over.bindListener(amui_drag, [o]))
		ax.event.listen(o.element, 'mouseout', amui_drag.out.bindListener(amui_drag, [o]))
	}
}

/**
 * Drag'n'drop singleton
 * @param {number} snap Snap
 */
amui_drag = {
	snap: 12,
	phase: 0,
	
	disableSelection: function(sw) {
		if (sw) {
			document.onselectstart = function(){return false}	// IE
			document.body.onmousedown = function(){return false}	// O
		} else {
			document.onselectstart = ''
			document.body.onmousedown = ''
		}
	},

	/**
	 * When user presses the button
	 */
	prepare: function(o, event) {
		this.disableSelection(1)
		this.startX = event.clientX
		this.startY = event.clientY
		this.o = o
		this.element = ax.dom.$(this.o.element)
		
		this.listenerMove = ax.event.listen(document.body, 'mousemove', this.move.bind(this))
		this.listenerUp = ax.event.listen(document.body, 'mouseup', this.cancel.bind(this))
		this.listenerAway = ax.event.listen(document.body, 'mouseleave', this.cancel.bind(this))
	},
	
	/**
	 * Start dragging
	 */
	start: function(event) {
		this.phase = 1
		if(this.o.ghost) {
			this.element = ax.dom.$(this.element.cloneNode(true)).inside(document.body)
			this.elementIsGhost = true
		}
		this.element.addClass("AMUI_dragging")
		ax.dom.addClass("AMUI_dragmode", document.body)

		this.dragHint = ax.dom.c('SPAN', {className:'AMUI_drag_hint'}).setText(this.o.hint).inside(this.element)
	},
	
	/**
	 * When mouse moves
	 */
	move: function(event) {
		// check snap
		if(this.phase==0)
			if(Math.abs(event.clientX - this.startX) > this.snap || Math.abs(event.clientY - this.startY) > this.snap) {
				this.start(event);
			} else return;

		// move
		ax.event.fix(event)
		this.element.style.top = (event.pageY+3) + 'px'
		this.element.style.left = (event.pageX+3) + 'px'

		// check edges
		if(this.a && this.a.accept[this.o.type].edges) {
			var edges = this.a.accept[this.o.type].edges
			oLeft = event.pageX - ax.page.X(this.a.element)
			oRight = this.a.element.offsetWidth + ax.page.X(this.a.element) - event.pageX
			oTop = event.pageY - ax.page.Y(this.a.element)
			oBottom = this.a.element.offsetHeight + ax.page.Y(this.a.element) - event.pageY
			this.noEdge()
			if(edges.top && oTop < edges.top.size) this.overEdge('top')
			else if(edges.bottom && oBottom < edges.bottom.size) this.overEdge('bottom')
			else if(edges.left && oLeft < edges.left.size) this.overEdge('left')
			else if(edges.right && oRight < edges.right.size) this.overEdge('right')
		} else {
			this.edge = null
		}
	},

	/**
	 * When mouse with dragged element is over target area
	 */
	over: function(a, event) {
		var target = event.srcElement || event.target;
		if(this.phase && this.o && a.accept[this.o.type] && target == a.element) {
			this.a = a
			if(this.dragHint) {
				this.dragHint.innerHTML = a.accept[this.o.type].hint
			}
			if(a.accept[this.o.type].onDrop) ax.dom.addClass("AMUI_drop_possible", this.a.element)
		}
	},
	
	/**
	 * When mouse with dragged element is over target area edge
	 */
	overEdge: function(edge, hint) {
		this.edge = edge
		this.dragHint.innerHTML = this.a.accept[this.o.type].edges[edge].hint || this.a.accept[this.o.type].hint
		ax.dom.addClass("AMUI_drop_edge_"+edge, this.a.element)
	},

	/**
	 * Clear edge classes
	 */
	noEdge: function() {
		if(this.a && this.edge) {
			var edges = ['left', 'right', 'top', 'bottom']
			for(i=0; i<edges.length; i++) ax.dom.removeClass('AMUI_drop_edge_' + edges[i], this.a.element)
			this.dragHint.innerHTML = this.a.accept[this.o.type].hint
		}
		this.edge = null
	},
	
	/**
	 * When mouse leaves target area
	 */
	out: function(a, event) {
		if(this.phase && this.o && a.accept[this.o.type] && a.element) {
			ax.dom.removeClass("AMUI_drop_possible", a.element)
			this.noEdge()
			if(this.dragHint) this.dragHint.innerHTML = this.o.hint
			this.a = null
		}
	},

	/**
	 * When element has been dropeed ot uf target area
	 */
	cancel: function() {
		if(this.phase && this.o.onCancel) this.o.onCancel()
		this.finish()
	},
	
	/**
	 * Successfull drop
	 */
	drop: function(a, event) {
		var target = event.srcElement || event.target;
		if(this.phase && this.o && a.accept[this.o.type] && target == a.element) {
			var handle = a.accept[this.o.type]
			if(this.o.onDrop) this.o.onDrop(handle.pass) // dragged element onDrop
			if(this.edge && handle.edges[this.edge].onDrop) handle.edges[this.edge].onDrop(this.o.pass) // area edge onDrop
			else if(handle.onDrop) handle.onDrop(this.o.pass) // area global onDrop
			this.finish()
		}
	},
	
	/**
	 * Turn dragging mode off
	 */
	finish: function() {
		if(this.a && this.a.element) ax.dom.removeClass("AMUI_drop_possible", this.a.element)
		this.noEdge()
		this.disableSelection(0);
		ax.dom.removeClass("AMUI_dragmode", document.body)
		
		ax.event.unlisten(this.listenerMove)
		ax.event.unlisten(this.listenerUp)
		ax.event.unlisten(this.listenerAway)
		
		if(this.elementIsGhost) {
			this.element.remove()
			this.elementIsGhost = null
		} else {
			ax.dom.removeClass("AMUI_dragging", this.element)
			this.element.style.width = ''
			this.element.style.height = ''
		}
		if(this.dragHint) {
			this.dragHint.remove()
			delete this.dragHint
		}
		this.element = null;
		this.o = null;
		this.a = null;
		this.phase = 0
	}
}


/**
 * Variable observer singleton
 * 
 * HTML: <tagname observe='variable or some script'></tagname>
 */

amui_observer = {
	/**
	 * Start an observer
	 * @param {int} Refresh interval or false for no auto-refresh
	 * @param {bool} Auto rescan DOM on each refresh
	 */
	start: function(interval, autoRescan) {
		this.autoRescan = autoRescan
		this.scanDOM()
		if(interval) {
			this.interval = interval
			this.stop()
			this._interval = setInterval(this.update.bind(this), interval)
		} else {
			this.update()
		}
	},

	/**
	 * Stop observer
	 */
	stop: function() {
		 if(this._interval) clearInterval(this._interval)
	},
	
	/**
	 * Update observing nodes
	 */
	update: function() {
		if(this.autoRescan) this.scanDOM()
		for(i in this.elements) if(this.elements[i].getAttribute) this.elements[i].innerHTML = eval(this.elements[i].getAttribute('observe'))
	},
	
	/**
	 * Update list of observing nodes
	 */
	scanDOM: function() {
		this.elements = []
		tmp = document.getElementsByTagName('*')
		for(i in tmp) if(tmp[i].getAttribute && tmp[i].getAttribute('observe')) this.elements.push(tmp[i])
	}
}

ax.event.listen(window, 'load', function() {amui_observer.start()})
