/**
 * Custom jQuery for Custom Metaboxes and Fields
 */

/*jslint browser: true, devel: true, indent: 4, maxerr: 50, sub: true */
/*global jQuery, tb_show, tb_remove */

'use strict';

var CMB = {

	_initCallbacks: [],
	_clonedFieldCallbacks: [],
	_deletedFieldCallbacks: [],

	_sortStartCallbacks: [],
	_sortEndCallbacks: [],

	init : function() {

		jQuery( '.field.repeatable' ).each( function() {
			CMB.isMaxFields( jQuery( this ) );
		} );

		// Unbind & Re-bind all CMB events to prevent duplicates.
		jQuery( document ).unbind( 'click.CMB' );
		jQuery( document ).on( 'click.CMB', '.cmb-delete-field', CMB.deleteField );
		jQuery( document ).on( 'click.CMB', '.repeat-field', CMB.repeatField );

		// When toggling the display of the meta box container - reinitialize
		jQuery( document ).on( 'click.CMB', '.postbox h3, .postbox .handlediv', CMB.init );

		CMB.doneInit();

		jQuery( '.field.cmb-sortable' ).each( function() {
			CMB.sortableInit( jQuery( this ) );
		} );

	},

	repeatField : function( e ) {

		var templateField, newT, field, index, attr;

		field = jQuery( this ).closest( '.field' );

		e.preventDefault();
		jQuery( this ).blur();

		if ( CMB.isMaxFields( field, 1 ) ) {
			return;
		}

		templateField = field.children( '.field-item.hidden' );

		newT = templateField.clone();
		newT.removeClass( 'hidden' );

		var excludeInputTypes = '[type=submit],[type=button],[type=checkbox],[type=radio],[readonly]';
		newT.find( 'input' ).not( excludeInputTypes ).val( '' );

		newT.find( '.cmb_upload_status' ).html( '' );

		newT.insertBefore( templateField );

		 // Recalculate group ids & update the name fields..
		index = 0;
		attr  = ['id','name','for','data-id','data-name'];

		field.children( '.field-item' ).not( templateField ).each( function() {

			var search  = field.hasClass( 'CMB_Group_Field' ) ? /cmb-group-(\d|x)*/g : /cmb-field-(\d|x)*/g;
			var replace = field.hasClass( 'CMB_Group_Field' ) ? 'cmb-group-' + index : 'cmb-field-' + index;

			jQuery( this ).find( '[' + attr.join( '],[' ) + ']' ).each( function() {

				for ( var i = 0; i < attr.length; i++ ) {
					if ( typeof( jQuery( this ).attr( attr[i] ) ) !== 'undefined' ) {
						jQuery( this ).attr( attr[i], jQuery( this ).attr( attr[i] ).replace( search, replace ) );
					}
				}

			} );

			index += 1;

		} );

		CMB.clonedField( newT );

		if ( field.hasClass( 'cmb-sortable' ) ) {
			CMB.sortableInit( field );
		}

	},

	deleteField : function( e ) {

		var fieldItem, field;

		e.preventDefault();
		jQuery( this ).blur();

		fieldItem = jQuery( this ).closest( '.field-item' );
		field = fieldItem.closest( '.field' );

		if ( false !== field.data( 'confirm-delete' ) && ! confirm( CMBData.strings.confirmDeleteField ) ) {
			return;
		}

		fieldItem = jQuery( this ).closest( '.field-item' );
		field     = fieldItem.closest( '.field' );

		CMB.isMaxFields( field, -1 );
		CMB.deletedField( fieldItem );

		fieldItem.remove();

	},

	/**
	 * Prevent having more than the maximum number of repeatable fields.
	 * When called, if there is the maximum, disable .repeat-field button.
	 * Note: Information Passed using data-max attribute on the .field element.
	 *
	 * @param jQuery .field
	 * @param int modifier - adjust count by this ammount. 1 If adding a field, 0 if checking, -1 if removing a field... etc
	 * @return null
	 */
	isMaxFields: function( field, modifier ) {

		var count, addBtn, min, max, count;

		modifier = (modifier) ? parseInt( modifier, 10 ) : 0;

		addBtn = field.children( '.repeat-field' );
		count  = field.children( '.field-item' ).not( '.hidden' ).length + modifier; // Count after anticipated action (modifier)
		max    = field.attr( 'data-rep-max' );

		// Show all the remove field buttons.
		field.find( '> .field-item > .cmb-delete-field, > .field-item > .group > .cmb-delete-field' ).show();

		if ( typeof( max ) === 'undefined' ) {
			return false;
		}

		// Disable the add new field button?
		if ( count >= parseInt( max, 10 ) ) {
			addBtn.attr( 'disabled', 'disabled' );
		} else {
			addBtn.removeAttr( 'disabled' );
		}

		if ( count > parseInt( max, 10 ) ) {
			return true;
		}

	},

	addCallbackForInit: function( callback ) {

		this._initCallbacks.push( callback )

	},

	/**
	 * Fire init callbacks.
	 * Called when CMB has been set up.
	 */
	doneInit: function() {

		var _this = this,
			callbacks = CMB._initCallbacks;

		if ( callbacks ) {
			for ( var a = 0; a < callbacks.length; a++) {
				callbacks[a]();
			}
		}

	},

	addCallbackForClonedField: function( fieldName, callback ) {

		if ( jQuery.isArray( fieldName ) ) {
			for ( var i = 0; i < fieldName.length; i++ ) {
				CMB.addCallbackForClonedField( fieldName[i], callback );
			}
		}

		this._clonedFieldCallbacks[fieldName] = this._clonedFieldCallbacks[fieldName] ? this._clonedFieldCallbacks[fieldName] : []
		this._clonedFieldCallbacks[fieldName].push( callback )

	},

	/**
	 * Fire clonedField callbacks.
	 * Called when a field has been cloned.
	 */
	clonedField: function( el ) {

		// also check child elements
		el.add( el.find( 'div[data-class]' ) ).each( function( i, el ) {

			el = jQuery( el );
			var callbacks = CMB._clonedFieldCallbacks[ el.attr( 'data-class' ) ];

			if ( callbacks ) {
				for ( var a = 0; a < callbacks.length; a++ ) {
					callbacks[a]( el );
				}
			}

		})
	},

	addCallbackForDeletedField: function( fieldName, callback ) {

		if ( jQuery.isArray( fieldName ) ) {
			for ( var i = 0; i < fieldName.length; i++ ) {
				CMB._deletedFieldCallbacks( fieldName[i], callback );
			}
		}

		this._deletedFieldCallbacks[fieldName] = this._deletedFieldCallbacks[fieldName] ? this._deletedFieldCallbacks[fieldName] : []
		this._deletedFieldCallbacks[fieldName].push( callback )

	},

	/**
	 * Fire deletedField callbacks.
	 * Called when a field has been cloned.
	 */
	deletedField: function( el ) {

		// also check child elements
		el.add( el.find( 'div[data-class]' ) ).each( function( i, el ) {

			el = jQuery( el );
			var callbacks = CMB._deletedFieldCallbacks[ el.attr( 'data-class' ) ];

			if ( callbacks ) {
				for ( var a = 0; a < callbacks.length; a++ ) {
					callbacks[a]( el )
				}
			}

		})
	},

	sortableInit : function( field ) {

		var items = field.find( ' > .field-item' ).not( '.hidden' );

		field.find( '> .field-item > .cmb-handle' ).remove();

		items.each( function() {
			jQuery( this ).append( '<div class="cmb-handle"></div>' );
		} );

		field.sortable( {
			handle: "> .cmb-handle" ,
			cursor: "move",
			items: " > .field-item",
			beforeStop: function( event, ui ) { CMB.sortStart( jQuery( ui.item[0] ) ); },
			deactivate: function( event, ui ) { CMB.sortEnd( jQuery( ui.item[0] ) ); },
		} );

	},

	sortStart : function ( el ) {

		// also check child elements
		el.add( el.find( 'div[data-class]' ) ).each( function( i, el ) {

			el = jQuery( el );
			var callbacks = CMB._sortStartCallbacks[ el.attr( 'data-class' ) ];

			if ( callbacks ) {
				for ( var a = 0; a < callbacks.length; a++ ) {
					callbacks[a]( el );
				}
			}

		});

	},

	addCallbackForSortStart: function( fieldName, callback ) {

		if ( jQuery.isArray( fieldName ) ) {
			for ( var i = 0; i < fieldName.length; i++ ) {
				CMB.addCallbackForSortStart( fieldName[i], callback );
			}
		}

		this._sortStartCallbacks[fieldName] = this._sortStartCallbacks[fieldName] ? this._sortStartCallbacks[fieldName] : []
		this._sortStartCallbacks[fieldName].push( callback )

	},

	sortEnd : function ( el ) {

		// also check child elements
		el.add( el.find( 'div[data-class]' ) ).each( function(i, el) {

			el = jQuery( el );
			var callbacks = CMB._sortEndCallbacks[ el.attr( 'data-class' ) ];

			if ( callbacks ) {
				for ( var a = 0; a < callbacks.length; a++ ) {
					callbacks[a]( el );
				}
			}

		})

	},

	addCallbackForSortEnd: function( fieldName, callback ) {

		if ( jQuery.isArray( fieldName ) ) {
			for ( var i = 0; i < fieldName.length; i++ ) {
				CMB.addCallbackForSortEnd( fieldName[i], callback );
			}
		}

		this._sortEndCallbacks[fieldName] = this._sortEndCallbacks[fieldName] ? this._sortEndCallbacks[fieldName] : [];
		this._sortEndCallbacks[fieldName].push( callback );

	},

	/**
	 * Simple debouncing function.
	 *
	 * @param func func function to run after wait period.
	 * @param wait int Wait interval.
	 * @param immediate bool Whether to run immediately or not.
	 * @returns {Function}
	 */
	debounce: function debounce( func, wait, immediate ) {
		var timeout;
		return function() {
			var context = this,
				args = arguments;
			var later = function() {
				timeout = null;
				if ( ! immediate ) {
					func.apply( context, args );
				}
			};
			var callNow = immediate && ! timeout;
			clearTimeout( timeout );
			timeout = setTimeout( later, wait );
			if ( callNow ) {
				func.apply( context, args );
			}
		};
	}

};

jQuery( document ).ready( function() {

	CMB.init();

});
