AUI.add('aui-form-field', function(A) {
var Lang = A.Lang,

	getClassName = A.ClassNameManager.getClassName,

	NAME = 'field',

	getTypeClassName = A.cached(
		function(type, prefix) {
			var base = ['field'];

			if (prefix) {
				base.push(prefix);
			}

			base = base.join('-');

			var className = [getClassName(base, type)];

			if (type == 'password') {
				className.push(getClassName(base, 'text'));
			}

			return className.join(' ');
		}
	),

	CSS_FIELD = getClassName(NAME),
	CSS_FIELD_CONTENT = getClassName(NAME, 'content'),
	CSS_FIELD_INPUT = getClassName(NAME, 'input'),
	CSS_FIELD_HINT = getClassName(NAME, 'hint'),
	CSS_FIELD_INVALID = getClassName(NAME, 'invalid'),
	CSS_FIELD_LABEL = getClassName(NAME, 'label'),

	CSS_LABELS = getClassName(NAME, 'labels'),
	CSS_LABELS_INLINE = getClassName(NAME, 'labels', 'inline'),

	CSS_LABEL_ALIGN = {
		left: [CSS_LABELS, 'left'].join('-'),
		right: [CSS_LABELS, 'right'].join('-'),
		top: [CSS_LABELS, 'top'].join('-')
	},

	REGEX_INLINE_LABEL = /left|right/,

	TPL_BOUNDING_BOX = '<span class="' + CSS_FIELD + '"></span>',
	TPL_CONTENT_BOX = '<span class="' + CSS_FIELD_CONTENT + '"></span>',
	TPL_FIELD_HINT = '<span class="' + CSS_FIELD_HINT + '"></span>',
	TPL_INPUT = '<input autocomplete="off" class="{cssClass}" id="{id}" name="{name}" type="{type}" />',
	TPL_LABEL = '<label class="' + CSS_FIELD_LABEL + '"></label>',

	_FIELD_INSTANCES = {};

var Field = A.Component.create(
	{
		NAME: NAME,

		ATTRS: {
			readOnly: {
				value: false
			},

			name: {
				value: '',
				getter: function(value) {
					var instance = this;

					return value || instance.get('id');
				}
			},

			id: {
				getter: function(value) {
					var instance = this;

					var node = this.get('node');

					if (node) {
						value = node.get('id');
					}

					if (!value) {
						value = A.guid();
					}

					return value;
				}
			},

			type: {
				value: 'text',
				writeOnce: true
			},

			labelAlign: {
				value: ''
			},

			labelNode: {
				valueFn: function() {
					var instance = this;

					return A.Node.create(TPL_LABEL);
				}
			},

			labelText: {
				valueFn: function() {
					var instance = this;

					return instance.get('labelNode').get('innerHTML');
				},

				setter: function(value) {
					var instance = this;

					instance.get('labelNode').set('innerHTML', value);

					return value;
				}
			},

			node: {
				value: null,
				setter: function(value) {
					var instance = this;

					return A.one(value) || instance._createFieldNode();
				}
			},

			fieldHint: {
				value: ''
			},

			fieldHintNode: {
				value: null,
				setter: function(value) {
					var instance = this;

					return A.one(value) || instance._createFieldHint();
				}
			},

			prevVal: {
				value: ''
			},

			valid: {
				value: true,
				getter: function(value) {
					var instance = this;

					var validator = instance.get('validator');

					var valid = instance.get('disabled') || validator(instance.get('value'));

					return valid;
				}
			},

			dirty: {
				value: false,
				getter: function(value) {
					var instance = this;

					if (instance.get('disabled')) {
						value = false;
					}
					else {
						var currentVal = String(instance.get('value'));
						var prevVal = String(instance.get('prevVal'));

						value = (currentVal !== prevVal);
					}

					return value;
				}
			},

			size: {},

			validator: {
				valueFn: function() {
					var instance = this;

					return instance.fieldValidator;
				},
				validator: Lang.isFunction
			},

			value: {
				getter: '_getNodeValue',
				setter: '_setNodeValue',
				validator: 'fieldValidator'
			}
		},

		HTML_PARSER: {
			labelNode: 'label',
			node: 'input, textarea, select'
		},

		BIND_UI_ATTRS: [
			'id',
			'readOnly',
			'name',
			'size',
			'tabIndex',
			'type',
			'value'
		],

		getTypeClassName: getTypeClassName,

		getField: function(field) {
			var fieldWidget = null;

			if (field instanceof A.Field) {
				fieldWidget = field;
			}
			else if (field && (Lang.isString(field) || field instanceof A.Node || field.nodeName)) {
				var fieldId = A.one(field).get('id');

				fieldWidget = _FIELD_INSTANCES[fieldId];

				if (!fieldWidget) {
					var boundingBox = field.ancestor('.aui-field');
					var contentBox = field.ancestor('.aui-field-content');

					fieldWidget = new Field(
						{
							boundingBox: boundingBox,
							contentBox: contentBox,
							node: field
						}
					);
				}
			}
			else if (Lang.isObject(field)) {
				fieldWidget = new Field(field);
			}

			return fieldWidget;
		},

		prototype: {
			BOUNDING_TEMPLATE: TPL_BOUNDING_BOX,
			CONTENT_TEMPLATE: TPL_CONTENT_BOX,
			FIELD_TEMPLATE: TPL_INPUT,
			FIELD_TYPE: 'text',

			initializer: function() {
				var instance = this;

				var id = instance.get('node').guid();

				_FIELD_INSTANCES[id] = instance;
			},

			renderUI: function() {
				var instance = this;

				instance._renderField();
				instance._renderLabel();
				instance._renderFieldHint();
			},

			bindUI: function() {
				var instance = this;

				instance.after('labelAlignChange', instance._afterLabelAlignChange);
				instance.after('fieldHintChange', instance._afterFieldHintChange);
			},

			syncUI: function() {
				var instance = this;

				instance.set('prevVal', instance.get('value'));
			},

			fieldValidator: function(value) {
				var instance = this;

				return true;
			},

			isValid: function() {
				var instance = this;

				return instance.get('valid');
			},

			isDirty: function() {
				var instance = this;

				return instance.get('dirty');
			},

			resetValue: function() {
				var instance = this;

				instance.set('value', instance.get('prevVal'));

				instance.clearInvalid();
			},

			markInvalid: function(message) {
				var instance = this;

				instance.set('fieldHint', message);
				instance.get('fieldHintNode').show();

				instance.get('boundingBox').addClass(CSS_FIELD_INVALID);
			},

			clearInvalid: function() {
				var instance = this;

				instance.reset('fieldHint');

				if (!instance.get('fieldHint')) {
					instance.get('fieldHintNode').hide();
				}

				instance.get('boundingBox').removeClass(CSS_FIELD_INVALID);
			},

			validate: function() {
				var instance = this;

				var valid = instance.get('valid');

				if (valid) {
					instance.clearInvalid();
				}

				return valid;
			},

			_afterFieldHintChange: function(event) {
				var instance = this;

				instance._uiSetFieldHint(event.newVal, event.prevVal);
			},

			_afterLabelAlignChange: function(event) {
				var instance = this;

				instance._uiSetLabelAlign(event.newVal, event.prevVal);
			},

			_createFieldHint: function() {
				var instance = this;

				var fieldHint = A.Node.create(TPL_FIELD_HINT);

				instance.get('contentBox').append(fieldHint);

				return fieldHint;
			},

			_createFieldNode: function() {
				var instance = this;

				var fieldTemplate = instance.FIELD_TEMPLATE;

				instance.FIELD_TEMPLATE = A.substitute(
					fieldTemplate,
					{
						cssClass: CSS_FIELD_INPUT,
						id: instance.get('id'),
						name: instance.get('name'),
						type: instance.get('type')
					}
				);

				return A.Node.create(instance.FIELD_TEMPLATE);
			},

			_getNodeValue: function() {
				var instance = this;

				return instance.get('node').val();
			},

			_renderField: function() {
				var instance = this;

				var node = instance.get('node');

				node.val(instance.get('value'));

				var boundingBox = instance.get('boundingBox');
				var contentBox = instance.get('contentBox');

				var type = instance.get('type');

				boundingBox.addClass(getTypeClassName(type));
				node.addClass(getTypeClassName(type, 'input'));

				if (!contentBox.contains(node)) {
					if (node.inDoc()) {
						node.placeBefore(boundingBox);

						contentBox.appendChild(node);
					}
					else {
						contentBox.appendChild(node);
					}
				}

				boundingBox.removeAttribute('tabIndex');
			},

			_renderFieldHint: function() {
				var instance = this;

				var fieldHint = instance.get('fieldHint');

				if (fieldHint) {
					instance._uiSetFieldHint(fieldHint);
				}
			},

			_renderLabel: function() {
				var instance = this;

				var labelText = instance.get('labelText');

				if (labelText !== false) {
					var node = instance.get('node');
					var id = node.guid();

					labelText = instance.get('labelText');

					var labelNode = instance.get('labelNode');

					labelNode.addClass(getClassName(instance.name, 'label'));
					labelNode.setAttribute('for', id);
					labelNode.set('innerHTML', labelText);

					instance._uiSetLabelAlign(instance.get('labelAlign'));

					var contentBox = instance.get('contentBox');

					contentBox.prepend(labelNode);
				}
			},

			_setNodeValue: function(value) {
				var instance = this;

				instance._uiSetValue(value);

				return value;
			},

			_uiSetFieldHint: function(newVal, prevVal) {
				var instance = this;

				instance.get('fieldHintNode').set('innerHTML', newVal);
			},

			_uiSetId: function(newVal, src) {
				var instance = this;

				instance.get('node').set('id', newVal);
			},

			_uiSetLabelAlign: function(newVal, prevVal) {
				var instance = this;

				var boundingBox = instance.get('boundingBox');

				boundingBox.replaceClass(CSS_LABEL_ALIGN[prevVal], CSS_LABEL_ALIGN[newVal]);

				var action = 'removeClass';

				if (REGEX_INLINE_LABEL.test(newVal)) {
					action = 'addClass';
				}

				boundingBox[action](CSS_LABELS_INLINE);
			},

			_uiSetName: function(newVal, src) {
				var instance = this;

				instance.get('node').setAttribute('name', newVal);
			},

			_uiSetReadOnly: function(newVal, src) {
				var instance = this;

				instance.get('node').setAttribute('readOnly', newVal);
			},

			_uiSetSize: function(newVal, src) {
				var instance = this;

				instance.get('node').setAttribute('size', newVal);
			},

			_uiSetTabIndex: function(newVal, src) {
				var instance = this;

				instance.get('node').setAttribute('tabIndex', newVal);
			},

			_uiSetValue: function(newVal, src) {
				var instance = this;

				instance.get('node').val(newVal);
			},

			_requireAddAttr: false
		}
	}
);

A.Field = Field;

}, '1.0.1' ,{requires:['aui-base','aui-component','substitute']});
