/* global FusionPageBuilderApp, FusionApp, fusionBuilderText, FusionEvents */
var FusionPageBuilder = FusionPageBuilder || {};

( function() {

	// Builder Builder History
	FusionPageBuilder.BuilderHistory = window.wp.Backbone.View.extend( {

		template: FusionPageBuilder.template( jQuery( '#fusion-builder-front-end-history' ).html() ),
		className: 'fusion-builder-history-list submenu-trigger-target',
		tagName: 'ul',

		/**
		 * Init.
		 *
		 * @since 2.0.0
		 * @param {Object} data - The data.
		 * @return {void}
		 */
		initialize: function() {
			var data = FusionApp.data;

			this.fusionCommands       = new Array( '[]' );
			this.fusionCommandsStates = new Array( '[]' ); // History states
			this.maxSteps             = 25; // Maximum steps allowed/saved
			this.currStep             = 1; // Current Index of step
			this.allElements          = data.postDetails.post_content;
			this.fusionHistoryState   = '';
			this.tracking             = 'on';
			this.trackingPaused       = 'off';
			this.unsavedStep          = 1; // Unsaved steps.

			// Set initial history step
			this.fusionCommands[ this.currStep ]       = { allElements: data.postDetails.post_content };
			this.fusionCommandsStates[ this.currStep ] = fusionBuilderText.empty;

			this.listenTo( FusionEvents, 'fusion-history-pause-tracking', this.pauseTracking );
			this.listenTo( FusionEvents, 'fusion-history-resume-tracking', this.resumeTracking );
			this.listenTo( FusionEvents, 'fusion-history-save-step', this.saveHistoryStep );
			this.listenTo( FusionEvents, 'fusion-history-turn-on-tracking', this.turnOnTracking );
			this.listenTo( FusionEvents, 'fusion-history-turn-off-tracking', this.turnOffTracking );
			this.listenTo( FusionEvents, 'fusion-history-go-to-step', this.historyStep );
			this.listenTo( FusionEvents, 'fusion-history-clear', this.clearEditor );
			this.listenTo( FusionEvents, 'fusion-history-capture-editor', this.captureEditor );
			this.listenTo( FusionEvents, 'fusion-history-undo', this.doUndo );
			this.listenTo( FusionEvents, 'fusion-history-redo', this.doRedo );
			this.listenTo( FusionEvents, 'fusion-builder-reset', this.resetStates );
			this.listenTo( FusionEvents, 'fusion-element-removed', this.resetStates );
		},

		resetStates: function( cid ) {
			var self = this;

			if ( 'object' === typeof this.fusionCommands ) {
				_.each( this.fusionCommands, function( state, index ) {
					if ( 'undefined' === typeof cid || ! cid || ( 'param' === state.type && 'undefined' !== typeof state.cid && cid === state.cid ) ) {
						self.fusionCommands[ index ] = { allElements: state.allElements };
					}
				} );
			}
		},

		/**
		 * Renders the view.
		 *
		 * @since 2.0.0
		 * @return {Object} this
		 */
		render: function() {
			var self = this;

			this.$el.html( this.template( { steps: this.fusionCommandsStates, currentStep: this.currStep } ) );
			this.$el.attr( 'aria-expanded', false );
			this.$el.find( 'li' ).on( 'click', function( event ) {
				if ( event ) {
					event.preventDefault();
				}
				self.historyStep( event );
			} );

			this.updateUI();

			return this;
		},

		/**
		 * Saves a step in the history.
		 *
		 * @since 2.0.0
		 * @param {string} text - The text to be displayed in the history log.
		 * @return {void}
		 */
		saveHistoryStep: function( text, state ) {

			this.fusionHistoryState = text;

			this.turnOnTracking();
			this.captureEditor( state );
			this.turnOffTracking();
		},

		/**
		 * Captures the editor (used in front-end.js)
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		captureEditor: function( state ) {
			if ( 'object' !== typeof state ) {
				state = {};
			}

			if ( 'undefined' === typeof FusionPageBuilderApp ) {
				return;
			}

			FusionPageBuilderApp.builderToShortcodes();

			if ( this.isTrackingOn() && ! this.isTrackingPaused() ) {

				// If reached limit
				if ( this.currStep == this.maxSteps ) {

					// Remove first index
					this.fusionCommands.shift();
					this.fusionCommandsStates.shift();
				} else {

					// Else increment index
					this.currStep    += 1;
					this.unsavedStep += 1;
				}

				// If we are not at the end of the states, we need to wipe those ahead.
				if ( this.currStep !== this.fusionCommands.length ) {
					this.fusionCommandsStates.length = this.currStep;
					this.fusionCommands.length       = this.currStep;
				}

				// Get content
				this.allElements = FusionApp.data.postDetails.post_content;

				// Add all elements as fallback method.
				state.allElements = this.allElements;

				// Add editor data to Array
				this.fusionCommands[ this.currStep ] = state;

				// Add history state
				this.fusionCommandsStates[ this.currStep ] = this.fusionHistoryState;

				FusionApp.contentChange( 'page', 'builder-content' );

				// Update buttons
				this.fusionHistoryState = '';
				this.render();
			}
		},

		/**
		 * Turn history tracking ON.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		turnOnTracking: function() {
			this.tracking = 'on';
		},

		/**
		 * Turn history tracking OFF.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		turnOffTracking: function() {
			this.tracking = 'off';
		},

		/**
		 * Turn history tracking ON.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		pauseTracking: function() {
			this.trackingPaused = 'on';
		},

		/**
		 * Turn history tracking OFF.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		resumeTracking: function() {
			this.trackingPaused = 'off';
		},

		canApplyStep: function( historyStep ) {
			if ( 'object' !== typeof historyStep || 'undefined' === typeof historyStep.type ) {
				return false;
			}

			if ( 'param' === historyStep.type || 'price-param' === historyStep.type || 'pricefooter-param' === historyStep.type || 'pricefeatures-param' === historyStep.type ) {
				return true;
			}

			return false;
		},

		canApplySteps: function( stepIndex ) {
			var self     = this,
				redo     = stepIndex < this.currStep ? false : true,
				steps    = [],
				canApply = true;

			if ( ! redo ) {
				steps = this.fusionCommands.slice( stepIndex + 1, this.currStep + 1 );
			} else {
				steps = this.fusionCommands.slice( this.currStep + 1, stepIndex + 1 );
			}

			_.each( steps, function( step ) {
				if ( ! self.canApplyStep( step ) ) {
					canApply = false;
				}
			} );

			return canApply;
		},

		applySteps: function( stepIndex ) {
			var self  = this,
				redo  = stepIndex < this.currStep ? false : true,
				steps = [];

			if ( ! redo ) {
				steps     = this.fusionCommands.slice( stepIndex + 1, this.currStep + 1 ).reverse();
			} else {
				steps = this.fusionCommands.slice( this.currStep + 1, stepIndex + 1 );
			}

			_.each( steps, function( step ) {
				self.applyStep( step, redo );
			} );
		},

		applyStep: function( historyStep, redo ) {
			var elementView,
				params,  // eslint-disable-line no-unused-vars
				columnView;

			redo = 'undefined' === typeof redo ? false : redo;

			switch ( historyStep.type ) {

			case 'param':
				elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
				if ( elementView ) {
					params = elementView.model.get( 'params' ); // eslint-disable-line no-unused-vars

					// If undo, set new value to step so redo can use it.
					if ( ! redo ) {
						elementView.historyUpdateParam( historyStep.param, historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.oldValue );
					} else {
						elementView.historyUpdateParam( historyStep.param, historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.newValue );
					}
				}
				break;

			case 'price-param':
				elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
				if ( elementView ) {

					// If undo, set new value to step so redo can use it.
					if ( ! redo ) {
						elementView.updatePricingTablePrice( historyStep.param, historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.oldValue );
					} else {
						elementView.updatePricingTablePrice( historyStep.param, historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed', historyStep.param, historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, historyStep.param, historyStep.newValue );
					}
				}
				break;

			case 'pricefooter-param':
				elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
				if ( elementView ) {

					// If undo, set new value to step so redo can use it.
					if ( ! redo ) {
						elementView.updatePricingTableFooter( historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'footer_content', historyStep.oldValue );
					} else {
						elementView.updatePricingTableFooter( historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'footer_content', historyStep.newValue );
					}
				}
				break;

			case 'pricefeatures-param':
				elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );
				if ( elementView ) {

					// If undo, set new value to step so redo can use it.
					if ( ! redo ) {
						elementView.updatePricingTableFeatures( historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.oldValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'feature_rows', historyStep.oldValue );
					} else {
						elementView.updatePricingTableFeatures( historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed', 'footer_content', historyStep.newValue );
						FusionEvents.trigger( 'fusion-param-changed-' + historyStep.cid, 'feature_rows', historyStep.newValue );
					}
				}
				break;

			case 'add-element':
				if ( redo ) {
					FusionPageBuilderApp.collection.add( historyStep.model );
				} else {
					elementView = window.FusionPageBuilderViewManager.getView( historyStep.model.cid );
					if ( elementView ) {
						elementView.removeElement();
					}
				}
				break;

			case 'remove-element':
				if ( redo ) {
					elementView = window.FusionPageBuilderViewManager.getView( historyStep.model.cid );
					if ( elementView ) {
						elementView.removeElement();
					}
				} else {
					FusionPageBuilderApp.collection.add( historyStep.model );
				}
				break;

			case 'move-element':
				elementView = window.FusionPageBuilderViewManager.getView( historyStep.cid );

				// Need to ignore itself.
				elementView.$el.addClass( 'ignore-me' );

				if ( redo ) {
					columnView = window.FusionPageBuilderViewManager.getView( historyStep.newParent );
					if ( elementView && columnView ) {
						columnView.$el.find( '.fusion-builder-column-content' ).first().find( '> span, > div' ).not( '.ignore-me' ).eq( ( historyStep.newIndex - 1 ) ).after( elementView.$el );
						FusionPageBuilderApp.onDropCollectionUpdate( elementView.model, historyStep.newIndex, historyStep.newParent );
					}
				} else {
					columnView = window.FusionPageBuilderViewManager.getView( historyStep.oldParent );
					if ( elementView && columnView ) {
						columnView.$el.find( '.fusion-builder-column-content' ).first().find( '> span, > div' ).not( '.ignore-me' ).eq( ( historyStep.oldIndex - 1 ) ).after( elementView.$el );
						FusionPageBuilderApp.onDropCollectionUpdate( elementView.model, historyStep.oldIndex, historyStep.oldParent );
					}
				}

				elementView.$el.removeClass( 'ignore-me' );

				break;
			}
		},

		updateActiveStyling: function() {
			FusionApp.builderToolbarView.$el.find( '.fusion-builder-history-list li' ).removeClass( 'fusion-history-active-state' );
			FusionApp.builderToolbarView.$el.find( '.fusion-builder-history-list' ).find( '[data-state-id="' + this.currStep + '"]' ).addClass( 'fusion-history-active-state' );
		},

		fullContentReplace: function( data ) {
			this.resetStates();
			FusionPageBuilderApp.clearBuilderLayout();
			FusionPageBuilderApp.$el.find( '.fusion_builder_container' ).remove();

			// Try to make the shortcode if the content does not contain them.
			if ( ! FusionApp.data.is_fusion_element || 'mega_menus' === FusionApp.data.fusion_element_type ) {
				data = FusionPageBuilderApp.validateContent( data );
			}

			// Reset models with new elements
			FusionPageBuilderApp.createBuilderLayout( data );
		},

		/**
		 * Undo last step in history.
		 * Saves the undone step so that we may redo later if needed.
		 *
		 * @since 2.0.0
		 * @param {Object} event - The event.
		 * @return {void}
		 */
		doUndo: function( event ) {

			var undoData,
				historyStep = {};

			if ( event ) {
				event.preventDefault();
			}

			// Turn off tracking first, so these actions are not captured
			if ( this.hasUndo() ) { // If no data or end of stack and nothing to undo

				// Close opened nested cols to make sure UI works after history change.
				this.closeNestedCols();

				this.turnOffTracking();

				// Data to undo
				historyStep = this.fusionCommands[ this.currStep ];

				if ( this.canApplyStep( historyStep ) ) {
					this.applyStep( historyStep, false );
					this.currStep -= 1;
				} else {
					this.currStep -= 1;
					historyStep    = this.fusionCommands[ this.currStep ];
					undoData       = 'object' === typeof historyStep ? historyStep.allElements : false;
					if ( undoData && '[]' !== undoData ) {
						this.fullContentReplace( undoData );
					}
				}
				this.updateActiveStyling();
			}
		},

		/**
		 * Redo last step.
		 *
		 * @since 2.0.0
		 * @param {Object} event - The event.
		 * @return {void}
		 */
		doRedo: function( event ) {

			var redoData;

			if ( event ) {
				event.preventDefault();
			}

			if ( this.hasRedo() ) { // If not at end and nothing to redo

				// Close opened nested cols to make sure UI works after history change.
				this.closeNestedCols();

				// Turn off tracking, so these actions are not tracked
				this.turnOffTracking();

				// Move index
				this.currStep += 1;

				window.historyStep = this.fusionCommands[ this.currStep ];
				redoData           = 'object' === typeof window.historyStep ? window.historyStep.allElements : false;

				if ( this.canApplyStep( window.historyStep ) ) {
					this.applyStep( window.historyStep, true );
				} else if ( redoData && '[]' !== redoData ) {
					this.fullContentReplace( redoData );
				}

				this.updateActiveStyling();
			}
		},

		/**
		 * Go to a step.
		 *
		 * @since 2.0.0
		 * @param {string|number} step - The step.
		 * @param {Object}     event - The event.
		 * @return {void}
		 */
		historyStep: function( event ) {
			var step,
				stepData;

			if ( event ) {
				event.preventDefault();
			}

			// Close opened nested cols to make sure UI works after history change.
			this.closeNestedCols();

			step = jQuery( event.currentTarget ).data( 'state-id' );

			// Turn off tracking, so these actions are not tracked
			this.turnOffTracking();

			if ( this.canApplySteps( step ) ) {
				this.applySteps( step );
				this.currStep = step;
			} else {
				this.currStep = step;
				stepData      = 'object' === typeof this.fusionCommands[ this.currStep ] ? this.fusionCommands[ this.currStep ].allElements : false;
				if ( stepData && '[]' !== stepData ) {
					this.fullContentReplace( stepData );
				}
			}
			this.updateActiveStyling();
		},

		/**
		 * Are we currently tracking history?
		 *
		 * @since 2.0.0
		 * @return {boolean}
		 */
		isTrackingOn: function() {
			return 'on' === this.tracking;
		},

		/**
		 * Is tracking paused currently?
		 *
		 * @since 2.0.0
		 * @return {boolean}
		 */
		isTrackingPaused: function() {
			return 'on' === this.trackingPaused;
		},

		/**
		 * Log commands in the console as JSON.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		logStacks: function() {
			console.log( JSON.parse( this.fusionCommands ) );
		},

		/**
		 * Clear the editor.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		clearEditor: function() {
			this.fusionCommands       = new Array( '[]' );
			this.fusionCommandsStates = new Array( '[]' );
			this.currStep             = 1;
			this.unsavedStep          = 1;
			this.fusionHistoryState   = '';

			this.fusionCommands[ this.currStep ]       = { allElements: FusionApp.data.postDetails.post_content };
			this.fusionCommandsStates[ this.currStep ] = fusionBuilderText.empty;
			this.render();
		},

		/**
		 * Do we have an undo? Checks if the current step is the 1st one.
		 *
		 * @since 2.0.0
		 * @return {boolean}
		 */
		hasUndo: function() {
			return 1 !== this.currStep;
		},

		/**
		 * Do we have a redo? Checks if a step greater than current one exists.
		 *
		 * @since 2.0.0
		 * @return {boolean}
		 */
		hasRedo: function() {
			return this.currStep < ( this.fusionCommands.length - 1 );
		},

		/**
		 * Get the array of steps/fusionCommands.
		 *
		 * @since 2.0.0
		 * @return {Array}
		 */
		getCommands: function() {
			return this.fusionCommands;
		},

		/**
		 * Update the undo/redo/history buttons.
		 *
		 * @since 2.0.0
		 * @return {void}
		 */
		updateUI: function() {
			if ( 1 < this.unsavedStep ) {
				FusionApp.builderToolbarView.$el.find( '#fusion-builder-toolbar-history-menu' ).attr( 'data-has-unsaved', true );
			} else {
				FusionApp.builderToolbarView.$el.find( '#fusion-builder-toolbar-history-menu' ).attr( 'data-has-unsaved', false );
			}
			this.updateActiveStyling();
		},

		/**
		 * Close nested cols.
		 *
		 * @since 2.2
		 * @return {void}
		 */
		closeNestedCols: function() {
			var activeNestedCols = FusionPageBuilderApp.$el.find( '.fusion-nested-columns.editing' );

			if ( activeNestedCols.length ) {
				activeNestedCols.find( '.fusion-builder-cancel-row' ).trigger( 'click' );
			}
		}
	} );
}( jQuery ) );
