var MootoolsDynamicFormset = new Class({
	Implements: [Options, Events],
	options: {
		prefix: 'form',
		formClass: 'form',
		initialFields: [],
		add: {
			element: null,
			delegate: null,
			message: 'Formset has reached the maximum number of forms can be created.'
		},
		removable: true,
		remove: {
    		field: 'DELETE',
    		text: 'remove',
    		'class': 'remove',
    		confirm: true,
			delegate: null
		},
		sortable: true,
		sort: {
    		field: 'order'
		},
		sortOptions: {
		    clone: false,
			revert: false,
			opacity: 1
		}
	},
	container: null,
	forms: [],
	formTemplate: null,
	sortables: null,
	managementForms: {
		TOTAL_FORMS  : null,
		INITIAL_FORMS: null,
		MAX_NUM_FORMS: null
	},
	initialize: function(container, options) {
		this.setOptions(options);
		
        this.container = container;
        this.extractManagementForm();
        this.extractForms();
        this.resetFormCount();

		var add = this.options.add.element;
		if (add) {
			add = $(add);
			add.addEvent('click', this.addButtonDidClick.bind(this));
			this.options.add.element = add;
		}
		
        if (this.options.removable) this.addRemoveLink();
        if (this.options.sortable) {
            this.hideOrderField();
            this.sortForms();

			this.sortables = new MySortables(null, this.options.sortOptions);
			this.sortables.addItems(this.forms);
			this.sortables.addEvent('onComplete', this.sortDidComplete.bind(this));
        }
        
        this.createTemplateForm();
        container.store('formset', this);
	},
	getTotalFormCount: function() {
		return this.managementForms.TOTAL_FORMS.get('value').toInt();
	},
	getInitialFormCount: function() {
		return this.managementForms.INITIAL_FORMS.get('value').toInt();
	},
	getMaxNumFormCount: function() {
		var value = this.managementForms.MAX_NUM_FORMS.get('value');
		return value ? value.toInt() : null;
	},
	getActiveFormCount: function() {
		var count = 0;
		for (var i = 0; i < this.forms.length; i++) {
			if (!this.forms[i].retrieve('DDFS:manager').isRemoved()) count++;
		}
		return count;
	},
	
// ====================================================================
// Delegates
// ====================================================================
	add: function() {
		var newForm = this.createNewForm();

		var selector = '.' + this.options.formClass + ':nth-last-of-type(1)';
		var atLast = this.container.getElement(selector)
		newForm.inject(atLast, 'after');
		
		this.forms.push(newForm);
		if (this.options.sortable)
		    this.sortables.addItems(newForm);
		this.resetFormCount();
	
		return newForm;
	},
	remove: function(form) {
		var manager = form.retrieve('DDFS:manager');
		manager.remove();
		
		return this;
	},
	
// ====================================================================
// Event handlers
// ====================================================================
	addButtonDidClick: function(event) {
		event.stop();
		var max = this.getMaxNumFormCount();
		var count = this.getActiveFormCount();
		
		if (max && max <= count) {
			alert(this.options.add.message);
			return false;
		}
		
		var delegate = this.options.add.delegate;
		if (delegate && typeOf(delegate) == 'function') {
			delegate();
		} else {
			this.add();
		}
	},
	removeButtonDidClick: function(event) {
		event.stop();

		var index = event.target.retrieve('DDFS:index', index);
    	var form = this.forms[index];

		if (this.options.remove.confirm && !form.retrieve('DDFS:manager').isEmpty()) {
			var message = typeOf(this.options.remove.confirm) == 'string' ?
						      this.options.remove.confirm :
							  'Do you remove it?';
		    if (!confirm(message)) return false;
		}

		var delegate = this.options.remove.delegate;
		if (delegate && typeOf(delegate) == 'function') {
			delegate(form);
		} else {
			this.remove(form);
		}
	},
	sortDidComplete: function(element) {
		this.reindexForms();
	},

// ====================================================================
// Private methods
// ====================================================================
	extractManagementForm: function() {
		var container = this.container;
		var id_prefix = 'id_' + this.options.prefix;
		this.managementForms.TOTAL_FORMS   = container.getElementById(id_prefix + '-TOTAL_FORMS'  );
		this.managementForms.INITIAL_FORMS = container.getElementById(id_prefix + '-INITIAL_FORMS');
		this.managementForms.MAX_NUM_FORMS = container.getElementById(id_prefix + '-MAX_NUM_FORMS');
		
		return this.managementForms;
	},
	extractForms: function() {
		var initial = this.getInitialFormCount();
		
		this.forms = this.container.getElements('.' + this.options.formClass);
		$$(this.forms).each(function(form, index) {
			var manager = new FormManager(form, this);

			form.store('DDFS:index', index);
			form.store('DDFS:isInitial', index < initial);
			
			var isRemoved = manager.isRemoved();
		    if (isRemoved) form.setStyle('display', 'none');
		}, this);
		return this.forms;
	},
	addRemoveLink: function() {
		this.forms.each(function(form, index) {
			var del = form.retrieve('DDFS:manager').getFieldElement(this.options.remove.field);
			if (!del) return;
			
            var hidden = new Element('input', {
                type   : 'hidden',
                id     : del.get('id'   ),
                name   : del.get('name' ),
                'class': del.get('class'),
                value  : del.get('checked') ? 'on' : null
            }).replaces(del);
            del.destroy();

			var link = new Element('a', {
				href: '#remove',
				text: this.options.remove.text,
				'class': this.options.remove['class'],
				events: {
					click: this.removeButtonDidClick.bind(this)
				}
			});
			link.store('DDFS:index', index);
			link.inject(hidden, 'before');
		}, this);
	},
	hideOrderField: function() {
		this.forms.each(function(form, index) {
			var order = form.retrieve('DDFS:manager').getFieldElement(this.options.sort.field);
			if (!order) return;
			
            var hidden = new Element('input', {
                type   : 'hidden',
                id     : order.get('id'   ),
                name   : order.get('name' ),
                'class': order.get('class'),
                value  : order.get('value')
            }).replaces(order);
            order.destroy();
		}, this);
	},
	sortForms: function() {
		var forms = this.container.getElements('.' + this.options.formClass);
		
		forms.sort(function(a, b) {
			a = a.retrieve('DDFS:manager').getFieldElement(this.options.sort.field);
			if (!a) return 1;
			var aOrder = a.get('value').toInt();
			if (isNaN(aOrder)) return 1;

			b = b.retrieve('DDFS:manager').getFieldElement(this.options.sort.field);
			if (!b) return -1;
			var bOrder = b.get('value').toInt();
			if (isNaN(bOrder)) return -1;
			
			return (aOrder - bOrder);
		}.bind(this));
		
		forms.each(function(form) { form.dispose() });
		
		var contaienr = (this.container.get('tag') == 'table') ? this.container.getElement('tbody') : this.container;
		contaienr.adopt(forms);
	},
	createTemplateForm: function() {
        var origin = this.forms.getLast();
		var form = origin.retrieve('DDFS:manager').clone();
		var manager = form.retrieve('DDFS:manager');
		
		manager.changeIndex(-1);
		manager.clear();

		this.formTemplate = form;
		return this;
	},
	createNewForm: function() {
		var index = this.getTotalFormCount();
		var form = this.formTemplate.retrieve('DDFS:manager').clone();
	
		form.store('DDFS:index', index);
		form.store('DDFS:isInitial', false);
		form.retrieve('DDFS:manager').changeIndex(index);
		
		return form;
	},
	reindexForms: function() {
		var maxOrderingPosition = this.getInitialFormCount();
		
		var setPosition = function(form, position) {
			var order = form.retrieve('DDFS:manager').getFieldElement(this.options.sort.field);
			order.set('value', position);
		}.bind(this);

		var forms = this.container.getElements('.' + this.options.formClass);
		forms.each(function(form, position) {
			var index = this.forms.indexOf(form);
			var isInitial = form.retrieve('DDFS:isInitial');

			if (isInitial) {
				setPosition(form, position + 1);
			} else if (position < maxOrderingPosition) {
				setPosition(form, position + 1);
				maxOrderingPosition++;
			} else {
				setPosition(form, '');
			}
		}, this);
		
		$$(this.forms).each(function(form, index) {
			form.retrieve('DDFS:manager').changeIndex(index);
		}, this);
		
		return this;
	},
	resetFormCount: function() {
		this.managementForms.TOTAL_FORMS.set('value', this.forms.length);
		return this;
	}
});

// ====================================================================
// FormManager
// ====================================================================
var FormManager = new Class({
	form: null,
	formset: null,
	initialize: function(form, formset) {
		this.form = form;
		this.formset = formset;
		form.store('DDFS:manager', this);
	},
	getFieldElement: function(name) {
		var form = this.form;
		var tags = ['input', 'select', 'textarea'];
		
		for (var i = 0; i < tags.length; i++) {
			var field = form.getElement(tags[i] + '[name$=-' + name +']');
			if (field) return field
		}
		
		return false;
	},
	isRemoved: function() {
		var form = this.form;
		var options = this.formset.options;
		var del = this.getFieldElement(options.remove.field);

		if (!del) return false;
		if (del.get('type') == 'hidden'   && del.get('value'  ) == 'on') return true;
		if (del.get('type') == 'checkbox' && del.get('checked') == true) return true;

		return false;
	},
	remove: function() {
		var form = this.form;
		var formset = this.formset;
		var options = formset.options;
		var index = form.retrieve('DDFS:index');
		var isInitial = form.retrieve('DDFS:isInitial');

		if (isInitial) {
			var del = this.getFieldElement(options.remove.field);
			del.set('value', 'on');
			form.setStyle('display', 'none');
		} else {
			form.destroy();
			formset.forms.splice(index, 1);
			formset.resetFormCount();
		}
		
		formset.reindexForms();

		return this;
	},
	changeIndex: function(index) {
		var form = this.form;
		var formset = this.formset;
		var options = formset.options;
		var prefix = options.prefix;
		var regexp = new RegExp(prefix + '-(-?\\d+)-(.+)')
		var tags = 'input, select, textarea, label';

		form.store('DDFS:index', index);
		form.getElements(tags).each(function(element) {
			var name = element.get('name');
			if (name) {
				name = name.replace(regexp, prefix + '-' + index + '-$2');
				element.set('name', name);
			}

			var id = element.get('id');
			if (id) {
				id = id.replace(regexp, prefix + '-' + index + '-$2');
				element.set('id', id);
			}
			
			var labelFor = element.get('for');
			if (labelFor) {
				labelFor = labelFor.replace(regexp, prefix + '-' + index + '-$2');
				element.set('for', labelFor);
			}
		});

		var link = form.getElement('a.' + options.remove['class']);
		if (link) link.store('DDFS:index', index);

		return this;
	},
	clear: function() {
		var prefix = this.formset.options.prefix;
		var fields = this.formset.options.initialFields;

		this.form.getElements('input, select, textarea').each(function(element) {
		    var tag = element.get('tag');
			var name = element.get('name');
		
			for (var i = 0; i < fields.length; i++) {
				var regexp = new RegExp(prefix + '-(?:-?\\d)-' + fields[i]);
				if (!name.test(regexp)) continue;
				
				if (['radio', 'checkbox'].contains(element.get('type')) && element.get('checked')) {
                    element.removeProperty('checked');
                    element.set('checked', true);
                    element.defaultChecked = true;
    			}
    			return;
			}
		
		    if (tag == 'select') {
                element.getSelected().each(function(option) {
                    option.removeProperty('selected');
                    option.set('selected', true);
                    option.defaultSelected = false;
		        })
		    } else {
    		    element.set('checked', false);
    		    element.set('value', '');
		    }
		}, this);
	    
		return this;
	},
	isEmpty: function() {
		var form = this.form;
		var formset = this.formset;
		var options = formset.options;
		var prefix = options.prefix;

		var fields = options.initialFields;
		fields.push(options.remove.field);
		fields.push(options.sort.field);

		var elements = form.getElements('input, select, textarea');
		for (var i = 0; i < elements.length; i++) {
			var element = elements[i];
			var name = element.get('name');
			
			var found = false;
			for (var j = 0; j < fields.length; j++) {
				var regexp = new RegExp(prefix + '-\\d+-' + fields[j]);
				if (name.test(regexp)) {
					found = true;
					break;
				}
			}
			if (found) continue;
			
			if (['radio', 'checkbox'].contains(element.get('type')) && element.get('checked') == true) return false;
			if (element.get('value') != '') return false;
		}
		
		return true;
	},
	clone: function() {
		var formset = this.formset;
		var form = this.form;
		var clone = form.clone(true, true);
		new FormManager(clone, this.formset);

		var remove = clone.getElement('a.' + formset.options.remove['class']);
		if (remove)
			remove.addEvent('click', formset.removeButtonDidClick.bind(formset));
		
		return clone;
	}
});

// ====================================================================
// Sortables
// ====================================================================
var MySortables = new Class({
    Extends: Sortables,
    options: {
        cloneContainer: null,
		targetClass: 'sorting'
    },
    initialize: function(lists, options) {
        this.setOptions(options);
        this.parent(lists, options);
        
        this.createCloneContainer();
    },
    
    createCloneContainer: function() {
        var container = this.options.cloneContainer;
        
        if (container) {
            container = $(container);
        } else {
            container = $('sortables-clone-container');
            if (!container) {
                container = new Element('div', {id: 'sortables-clone-container'});
                container.adopt(new Element('span'));
                $(document.body).adopt(container);
            }
        }
        container.setStyles({
            display: 'none',
            position: 'absolute',
            top: 0,
            left: 0
        });
        
        this.options.cloneContainer = container;
        
        return this;
    },
    
    adoptedIntoCloneContainer: function(clone) {
        switch (clone.get('tag')) {
            case 'tr':
                clone = new Element('table').adopt(clone);
                break;
        }
		if (this.options.clone) {
	        this.options.cloneContainer.setStyle('display', null);
	        this.options.cloneContainer.adopt(clone);
			
		}
        return this;
    },
    
    emptyCloneContainer: function() {
        this.options.cloneContainer.setStyles({
            'display': 'none',
            'opacity': 1
        });
        this.options.cloneContainer.empty();
        return this;
    },
    
    getClone: function(event, element){
		if (!this.options.clone) return new Element(element.tagName, {styles:{display: 'none'}});
		if (typeOf(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
		
		var clone = element.clone(true);
		clone.setStyles({
			margin: 0,
            visibility: 'hidden',
			background: element.getStyle('background'),
			backgroundImage: element.getStyle('backgroundImage'),
			backgroundColor: element.getStyle('backgroundColor')
		});
		clone.addEvent('mousedown', function(event){
			element.fireEvent('mousedown', event);
		});
		
		//prevent the duplicated radio inputs from unchecking the real one
		if (clone.get('html').test('radio')){
			clone.getElements('input[type=radio]').each(function(input, i){
				input.set('name', 'clone_' + i);
				if (input.get('checked')) element.getElements('input[type=radio]')[i].set('checked', true);
			});
		}

		return clone;
	},
    
    start: function(event, element){
        var ignoreTags = ['button', 'input', 'a', 'textarea', 'select', 'option', 'label'];
		if (
			!this.idle ||
			event.rightClick ||
			ignoreTags.contains(event.target.get('tag'))
		) return;

		this.idle = false;
		this.element = element;
		this.opacity = element.getStyle('opacity');
		this.list = element.getParent();
		this.clone = this.getClone(event, element);
		
        this.adoptedIntoCloneContainer(this.clone);
        this.options.cloneContainer.setPosition(element.getPosition());

		this.drag = new Drag.Move(this.options.cloneContainer, Object.merge({
			droppables: this.getDroppables()
		}, this.options.dragOptions)).addEvents({
			onSnap: function(){
				event.stop();
				this.clone.setStyle('visibility', 'visible');
				this.element.addClass(this.options.targetClass);
				this.element.setStyle('opacity', this.options.opacity || 0);
				this.fireEvent('start', [this.element, this.clone]);
			}.bind(this),
			onEnter: this.insert.bind(this),
			onCancel: this.end.bind(this),
			onComplete: this.end.bind(this)
		});

		this.drag.start(event);
	},
	
	end: function(){
		this.drag.detach();
		this.element.removeClass(this.options.targetClass);
		this.element.setStyle('opacity', this.opacity);
		
		if (this.effect){
			var dim = this.element.getStyles('width', 'height'),
				clone = this.options.cloneContainer,
				pos = clone.computePosition(this.element.getPosition());

            var func = this.emptyCloneContainer.bind(this);
			var destroy = function(){
				this.removeEvent('cancel', destroy);
				func();
			}

			this.effect.element = clone;
			this.effect.start({
				top: pos.top,
				left: pos.left,
				opacity: 0.25
			}).addEvent('cancel', destroy).chain(destroy);
		} else {
			this.emptyCloneContainer();
		}
		this.reset();
	}
});
