/*
 *  Copyright (c) Codiad & Kent Safranski (codiad.com),
 *  Isaac Brown ( telaaedifex.com ) distributed as-is and without 
 *  warranty under the MIT License. See [root]/license.txt for more.
 *  This information must remain intact.
 */
( function( global, $ ) {
	
	var EditSession = ace.require( 'ace/edit_session' )
	.EditSession;
	var UndoManager = ace.require( 'ace/undomanager' )
	.UndoManager;
	
	var codiad = global.codiad;
	
	$( function() {
		codiad.active.init();
	});
	
	//////////////////////////////////////////////////////////////////
	//
	// Active Files Component for Codiad
	// ---------------------------------
	// Track and manage EditSession instaces of files being edited.
	//
	//////////////////////////////////////////////////////////////////
	
	codiad.active = {
		
		controller: 'components/active/controller.php',
		
		// Path to EditSession instance mapping
		sessions: {},
		
		// History of opened files
		history: [],
		
		// List of active file positions
		positions: {},
		
		//////////////////////////////////////////////////////////////////
		//
		// Check if a file is open.
		//
		// Parameters:
		//   path - {String}
		//
		//////////////////////////////////////////////////////////////////
		
		isOpen: function( path ) {
			return !!this.sessions[path];
		},
		
		open: function( path, content, mtime, inBackground, focus, read_only=false ) {
			
			/* Notify listeners. */
			amplify.publish( 'active.onFileWillOpen', {
				path: path,
				content: content
			});
			
			if( focus === undefined ) {
				
				focus = true;
			}
			
			var _this = this;
			
			if( this.isOpen( path ) ) {
				
				if( focus ) this.focus( path );
				return;
			}
			var ext = codiad.filemanager.getExtension( path );
			var mode = codiad.editor.selectMode( path );
			
			var fn = function() {
				
				//var Mode = require('ace/mode/' + mode)
				//    .Mode;
				
				// TODO: Ask for user confirmation before recovering
				// And maybe show a diff
				var draft = _this.checkDraft( path );
				if( draft ) {
					content = draft;
					codiad.message.success( i18n( 'Recovered unsaved content for: ' ) + path );
				}
				
				//var session = new EditSession(content, new Mode());
				var session = new EditSession( content );
				session.setMode( mode.mode );
				session.setUndoManager( new UndoManager() );
				
				session.path = path;
				session.serverMTime = mtime;
				_this.sessions[path] = session;
				session.untainted = content.slice( 0 );
				session.read_only = read_only;
				
				if( !inBackground && focus ) {
					codiad.editor.setSession( session );
				}
				_this.add( path, session, focus );
				
				if( !( _this.positions[`${path}`] === undefined ) && focus ) {
					
					_this.setPosition( _this.positions[`${path}`] );
				}
				
				/* Notify listeners. */
				amplify.publish( 'active.onOpen', path );
			};
			
			// Assuming the mode file has no dependencies
			$.loadScript( 'components/editor/ace-editor/mode-' + mode.name + '.js', fn );
		},
		
		init: function() {
			
			var _this = this;
			
			_this.initTabDropdownMenu();
			_this.updateTabDropdownVisibility();
			
			// Focus from list.
			$( '#list-active-files a' )
			.live( 'click', function( e ) {
				e.stopPropagation();
				_this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) );
			});
			
			// Focus on left button click from dropdown.
			$( '#dropdown-list-active-files a' )
			.live( 'click', function( e ) {
				if( e.which == 1 ) {
					/* Do not stop propagation of the event,
					 * it will be catch by the dropdown menu
					 * and close it. */
					_this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) );
				}
			});
			
			// Focus on left button mousedown from tab.
			$( '#tab-list-active-files li.tab-item>a.label' )
			.live( 'mousedown', function( e ) {
				if( e.which == 1 ) {
					e.stopPropagation();
					_this.focus( $( this ).parent( 'li' ).attr( 'data-path' ) );
				}
			});
			
			// Remove from list.
			$( '#list-active-files a>span' )
			.live( 'click', function( e ) {
				e.stopPropagation();
				_this.remove( $( this )
				.parent( 'a' )
				.parent( 'li' )
				.attr( 'data-path' ) );
			});
			
			// Remove from dropdown.
			$( '#dropdown-list-active-files a>span' )
			.live( 'click', function( e ) {
				e.stopPropagation();
				/* Get the active editor before removing anything. Remove the
				 * tab, then put back the focus on the previously active
				 * editor if it was not removed. */
				var activePath = _this.getPath();
				var pathToRemove = $( this ).parents( 'li' ).attr( 'data-path' );
				_this.remove( pathToRemove );
				if( activePath !== null && activePath !== pathToRemove ) {
					_this.focus( activePath );
				}
				_this.updateTabDropdownVisibility();
			});
			
			// Remove from tab.
			$( '#tab-list-active-files a.close' )
			.live( 'click', function( e ) {
				e.stopPropagation();
				/* Get the active editor before removing anything. Remove the
				 * tab, then put back the focus on the previously active
				 * editor if it was not removed. */
				var activePath = _this.getPath();
				var pathToRemove = $( this ).parent( 'li' ).attr( 'data-path' );
				_this.remove( pathToRemove );
				if( activePath !== null && activePath !== pathToRemove ) {
					_this.focus( activePath );
				}
				_this.updateTabDropdownVisibility();
			});
			
			// Remove from middle button click on dropdown.
			$( '#dropdown-list-active-files li' )
			.live( 'mouseup', function( e ) {
				if( e.which == 2 ) {
					e.stopPropagation();
					/* Get the active editor before removing anything. Remove the
					 * tab, then put back the focus on the previously active
					 * editor if it was not removed. */
					var activePath = _this.getPath();
					var pathToRemove = $( this ).attr( 'data-path' );
					_this.remove( pathToRemove );
					if( activePath !== null && activePath !== pathToRemove ) {
						_this.focus( activePath );
					}
					_this.updateTabDropdownVisibility();
				}
			});
			
			// Remove from middle button click on tab.
			$( '.tab-item' )
			.live( 'mouseup', function( e ) {
				if( e.which == 2 ) {
					e.stopPropagation();
					/* Get the active editor before removing anything. Remove the
					 * tab, then put back the focus on the previously active
					 * editor if it was not removed. */
					var activePath = _this.getPath();
					var pathToRemove = $( this ).attr( 'data-path' );
					_this.remove( pathToRemove );
					if( activePath !== null && activePath !== pathToRemove ) {
						_this.focus( activePath );
					}
					_this.updateTabDropdownVisibility();
				}
			});
			
			// Make list sortable
			$( '#list-active-files' )
			.sortable( {
				placeholder: 'active-sort-placeholder',
				tolerance: 'intersect',
				start: function( e, ui ) {
					ui.placeholder.height( ui.item.height() );
				}
			});
			
			// Make dropdown sortable.
			$( '#dropdown-list-active-files' )
			.sortable( {
				axis: 'y',
				tolerance: 'pointer',
				start: function( e, ui ) {
					ui.placeholder.height( ui.item.height() );
				}
			});
			
			// Make tabs sortable.
			$( '#tab-list-active-files' )
			.sortable( {
				items: '> li',
				axis: 'x',
				tolerance: 'pointer',
				containment: 'parent',
				start: function( e, ui ) {
					ui.placeholder.css( 'background', 'transparent' );
					ui.helper.css( 'width', '200px' );
				},
				stop: function( e, ui ) {
					// Reset css
					ui.item.css( 'z-index', '' )
					ui.item.css( 'position', '' )
				}
			});
			/* Woaw, so tricky! At initialization, the tab-list is empty, so
			 * it is not marked as float so it is not detected as an horizontal
			 * list by the sortable plugin. Workaround is to mark it as
			 * floating at initialization time. See bug report
			 * http://bugs.jqueryui.com/ticket/6702. */
			$( '#tab-list-active-files' ).data( 'sortable' ).floating = true;
			
			// Open saved-state active files on load
			$.get( _this.controller + '?action=list', function( data ) {
				console.log( data );
				var listResponse = codiad.jsend.parse( data );
				if( listResponse !== null ) {
					$.each( listResponse, function( index, data ) {
						
						codiad.active.positions[`${data.path}`] = JSON.parse( data.position );
						codiad.filemanager.openFile( data.path, data.focused );
					});
				}
			});
			
			// Prompt if a user tries to close window without saving all filess
			window.onbeforeunload = function( e ) {
				
				codiad.active.uploadPositions();
				if( $( '#list-active-files li.changed' )
				.length > 0 ) {
					var e = e || window.event;
					var errMsg = i18n( 'You have unsaved files.' );
					
					// For IE and Firefox prior to version 4
					if( e ) {
						e.returnValue = errMsg;
					}
					
					// For rest
					return errMsg;
				}
			};
		},
		
		//////////////////////////////////////////////////////////////////
		// Drafts
		//////////////////////////////////////////////////////////////////
		
		checkDraft: function( path ) {
			var draft = localStorage.getItem( path );
			if( draft !== null ) {
				return draft;
			} else {
				return false;
			}
		},
		
		removeDraft: function( path ) {
			localStorage.removeItem( path );
		},
		
		//////////////////////////////////////////////////////////////////
		// Get active editor path
		//////////////////////////////////////////////////////////////////
		
		getPath: function() {
			try {
				return codiad.editor.getActive()
				.getSession()
				.path;
			} catch ( e ) {
				return null;
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Check if opened by another user
		//////////////////////////////////////////////////////////////////
		
		check: function( path ) {
			$.get( this.controller + '?action=check&path=' + encodeURIComponent( path ),
				
				function( data ) {
					var checkResponse = codiad.jsend.parse( data );
				});
		},
		
		//////////////////////////////////////////////////////////////////
		// Add newly opened file to list
		//////////////////////////////////////////////////////////////////
		
		add: function( path, session, focus ) {
			if( focus === undefined ) {
				focus = true;
			}
			
			var listThumb = this.createListThumb( path );
			session.listThumb = listThumb;
			$( '#list-active-files' ).append( listThumb );
			
			
			
			
			/* If the tab list would overflow with the new tab. Move the
			 * first tab to dropdown, then add a new tab. */
			if( this.isTabListOverflowed( true ) ) {
				var tab = $( '#tab-list-active-files li:first-child' );
				this.moveTabToDropdownMenu( tab );
			}
			
			var tabThumb = this.createTabThumb( path );
			$( '#tab-list-active-files' ).append( tabThumb );
			session.tabThumb = tabThumb;
			
			this.updateTabDropdownVisibility();
			
			$.get( this.controller + '?action=add&path=' + encodeURIComponent( path ), function( data ) {
				
				//console.log( data );
			});
			
			if( focus ) {
				this.focus( path );
			}
			
			// Mark draft as changed
			if( this.checkDraft( path ) ) {
				this.markChanged( path );
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Focus on opened file
		//////////////////////////////////////////////////////////////////
		
		focus: function( path, moveToTabList ) {
			if( moveToTabList === undefined ) {
				moveToTabList = true;
			}
			
			/* Notify listeners. */
			amplify.publish( 'active.onWillFocus', path );
			
			this.highlightEntry( path, moveToTabList );
			
			if( path != this.getPath() ) {
				
				let _this = this;
				codiad.editor.setSession( this.sessions[path] );
				this.history.push( path );
				$.get( this.controller, {
					'action': 'focused',
					'path': path
				}, function() {
					
					if( !( _this.positions[`${path}`] === undefined ) ) {
						
						_this.setPosition( _this.positions[`${path}`] );
					}
				});
			}
			
			/* Check for users registered on the file. */
			this.check( path );
			
			/* Notify listeners. */
			amplify.publish( 'active.onFocus', path );
		},
		
		highlightEntry: function( path, moveToTabList ) {
			if( moveToTabList === undefined ) {
				moveToTabList = true;
			}
			
			$( '#list-active-files li' )
			.removeClass( 'active' );
			
			$( '#tab-list-active-files li' )
			.removeClass( 'active' );
			
			$( '#dropdown-list-active-files li' )
			.removeClass( 'active' );
			
			var session = this.sessions[path];
			
			if( session && session.tabThumb && $( '#dropdown-list-active-files' ).has( session.tabThumb ).length > 0 ) {
				if( moveToTabList ) {
					/* Get the menu item as a tab, and put the last tab in
					 * dropdown. */
					var menuItem = session.tabThumb;
					this.moveDropdownMenuItemToTab( menuItem, true );
					
					var tab = $( '#tab-list-active-files li:last-child' );
					this.moveTabToDropdownMenu( tab );
				} else {
					/* Show the dropdown menu if needed */
					this.showTabDropdownMenu();
				}
			} else if( this.history.length > 0 ) {
				var prevPath = this.history[this.history.length - 1];
				var prevSession = this.sessions[prevPath];
				if( $( '#dropdown-list-active-files' ).has( prevSession.tabThumb ).length > 0 ) {
					/* Hide the dropdown menu if needed */
					this.hideTabDropdownMenu();
				}
			}
			
			session.tabThumb.addClass( 'active' );
			session.listThumb.addClass( 'active' );
		},
		
		//////////////////////////////////////////////////////////////////
		// Mark changed
		//////////////////////////////////////////////////////////////////
		
		markChanged: function( path ) {
			this.sessions[path].listThumb.addClass( 'changed' );
			this.sessions[path].tabThumb.addClass( 'changed' );
		},
		
		//////////////////////////////////////////////////////////////////
		// Save active editor
		//////////////////////////////////////////////////////////////////
		
		save: function( path, alerts = true ) {
			/* Notify listeners. */
			amplify.publish( 'active.onSave', path );
			
			var _this = this;
			if( ( path && !this.isOpen( path ) ) || ( !path && !codiad.editor.getActive() ) ) {
				codiad.message.error( i18n( 'No Open Files to save' ) );
				return;
			}
			var session;
			if( path ) session = this.sessions[path];
			else session = codiad.editor.getActive()
			.getSession();
			var content = session.getValue();
			var path = session.path;
			var handleSuccess = function( result ) {
				var session = codiad.active.sessions[path];
				if( typeof session != 'undefined' ) {
					session.untainted = newContent;
					session.serverMTime = result.data.mtime;
					if( session.listThumb ) session.listThumb.removeClass( 'changed' );
					if( session.tabThumb ) session.tabThumb.removeClass( 'changed' );
				}
				_this.removeDraft( path );
			}
			// Replicate the current content so as to avoid
			// discrepancies due to content changes during
			// computation of diff
			
			var newContent = content.slice( 0 );
			if( session.serverMTime && session.untainted ) {
				codiad.workerManager.addTask( {
					taskType: 'diff',
					id: path,
					original: session.untainted,
					changed: newContent
				}, function( success, patch ) {
					
					console.log( "diff test", success, patch );
					
					if( success ) {
						if( patch.length > 0 ) {
							
							codiad.filemanager.save_file( path, {
								patch: patch,
								mtime: session.serverMTime
							}, alerts )
							.then( handleSuccess );
						}
					} else {
						
						console.log( "calling save while failed diff", path, newContent, alerts );
						codiad.filemanager.save_file( path, {content: newContent}, alerts )
						.then( handleSuccess );
					}
				}, this );
			} else {
				
				console.log( "calling save without mtime and untainted", path, newContent, alerts );
				codiad.filemanager.save_file( path, {content: newContent}, alerts )
				.then( handleSuccess );
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Save all files
		//////////////////////////////////////////////////////////////////
		
		saveAll: function() {
			var _this = this;
			for( var session in _this.sessions ) {
				if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) {
					codiad.active.save( session );
				}
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Remove file
		//////////////////////////////////////////////////////////////////
		
		remove: function( path ) {
			
			console.log( "remove file", this.isOpen( path ) );
			if( !this.isOpen( path ) ) return;
			let session = this.sessions[path];
			let closeFile = true;
			if( session.listThumb.hasClass( 'changed' ) ) {
				codiad.modal.load( 450, 'components/active/dialog.php?action=confirm&path=' + encodeURIComponent( path ) );
				closeFile = false;
			}
			if( closeFile ) {
				this.close( path );
			}
		},
		
		removeAll: function( discard ) {
			discard = discard || false;
			/* Notify listeners. */
			amplify.publish( 'active.onRemoveAll' );
			
			var _this = this;
			var changed = false;
			
			var opentabs = new Array();
			for( var session in _this.sessions ) {
				opentabs[session] = session;
				if( _this.sessions[session].listThumb.hasClass( 'changed' ) ) {
					changed = true;
				}
			}
			if( changed && !discard ) {
				codiad.modal.load( 450, 'components/active/dialog.php?action=confirmAll' );
				return;
			}
			
			for( var tab in opentabs ) {
				var session = this.sessions[tab];
				
				session.tabThumb.remove();
				_this.updateTabDropdownVisibility();
				
				session.listThumb.remove();
				
				/* Remove closed path from history */
				var history = [];
				$.each( this.history, function( index ) {
					if( this != tab ) history.push( this );
				})
				this.history = history
				
				delete this.sessions[tab];
				this.removeDraft( tab );
			}
			codiad.editor.exterminate();
			$( '#list-active-files' ).html( '' );
			$.get( this.controller + '?action=removeall' );
		},
		
		close: function( path ) {
			/* Notify listeners. */
			amplify.publish( 'active.onClose', path );
			
			var _this = this;
			var session = this.sessions[path];
			
			/* Animate only if the tabThumb if a tab, not a dropdown item. */
			if( session.tabThumb.hasClass( 'tab-item' ) ) {
				session.tabThumb.css( {
					'z-index': 1
				});
				session.tabThumb.animate( {
					top: $( '#editor-top-bar' ).height() + 'px'
				}, 300, function() {
					session.tabThumb.remove();
					_this.updateTabDropdownVisibility();
				});
			} else {
				session.tabThumb.remove();
				_this.updateTabDropdownVisibility();
			}
			
			session.listThumb.remove();
			
			/* Remove closed path from history */
			var history = [];
			$.each( this.history, function( index ) {
				if( this != path ) history.push( this );
			})
			this.history = history
			
			/* Select all the tab tumbs except the one which is to be removed. */
			var tabThumbs = $( '#tab-list-active-files li[data-path!="' + path + '"]' );
			
			if( tabThumbs.length == 0 ) {
				codiad.editor.exterminate();
			} else {
				
				var nextPath = '';
				if( this.history.length > 0 ) {
					nextPath = this.history[this.history.length - 1];
				} else {
					nextPath = $( tabThumbs[0] ).attr( 'data-path' );
				}
				var nextSession = this.sessions[nextPath];
				codiad.editor.removeSession( session, nextSession );
				
				this.focus( nextPath );
			}
			delete this.sessions[path];
			$.get( this.controller + '?action=remove&path=' + encodeURIComponent( path ) );
			this.removeDraft( path );
		},
		
		//////////////////////////////////////////////////////////////////
		// Process rename
		//////////////////////////////////////////////////////////////////
		
		rename: function( oldPath, newPath ) {
			var switchSessions = function( oldPath, newPath ) {
				var tabThumb = this.sessions[oldPath].tabThumb;
				tabThumb.attr( 'data-path', newPath );
				var title = newPath;
				if( codiad.project.isAbsPath( newPath ) ) {
					title = newPath.substring( 1 );
				}
				tabThumb.find( '.label' )
				.text( title );
				this.sessions[newPath] = this.sessions[oldPath];
				this.sessions[newPath].path = newPath;
				delete this.sessions[oldPath];
				//Rename history
				for( var i = 0; i < this.history.length; i++ ) {
					if( this.history[i] === oldPath ) {
						this.history[i] = newPath;
					}
				}
			};
			if( this.sessions[oldPath] ) {
				// A file was renamed
				switchSessions.apply( this, [oldPath, newPath] );
				// pass new sessions instance to setactive
				for( var k = 0; k < codiad.editor.instances.length; k++ ) {
					if( codiad.editor.instances[k].getSession().path === newPath ) {
						codiad.editor.setActive( codiad.editor.instances[k] );
					}
				}
				
				var newSession = this.sessions[newPath];
				
				// Change Editor Mode
				var mode = codiad.editor.selectMode( newPath );
				
				// handle async mode change
				var fn = function() {
					codiad.editor.setModeDisplay( newSession );
					newSession.removeListener( 'changeMode', fn );
				}
				
				newSession.on( "changeMode", fn );
				newSession.setMode( mode.mode );
			} else {
				// A folder was renamed
				var newKey;
				for( var key in this.sessions ) {
					newKey = key.replace( oldPath, newPath );
					if( newKey !== key ) {
						switchSessions.apply( this, [key, newKey] );
					}
				}
			}
			$.get( this.controller + '?action=rename&old_path=' + encodeURIComponent( oldPath ) + '&new_path=' + encodeURIComponent( newPath ), function() {
				/* Notify listeners. */
				amplify.publish( 'active.onRename', {
					"oldPath": oldPath,
					"newPath": newPath
				});
			});
		},
		
		//////////////////////////////////////////////////////////////////
		// Open in Browser
		//////////////////////////////////////////////////////////////////
		
		openInBrowser: function() {
			var path = this.getPath();
			if( path ) {
				codiad.filemanager.openInBrowser( path );
			} else {
				codiad.message.error( 'No Open Files' );
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Get Selected Text
		//////////////////////////////////////////////////////////////////
		
		getSelectedText: function() {
			
			let path = this.getPath();
			let session = this.sessions[path];
			
			if( path && this.isOpen( path ) ) {
				
				console.log( "Session:", session );
				console.log( "selection: ", codiad.editor.getActive().getSelectionRange() )
				console.log( "text: ", session.getTextRange( codiad.editor.getActive().getSelectionRange() ) )
				return session.getTextRange( codiad.editor.getActive().getSelectionRange() );
			} else {
				
				codiad.message.error( i18n( 'No Open Files or Selected Text' ) );
			}
		},
		
		//////////////////////////////////////////////////////////////////
		// Insert Text
		//////////////////////////////////////////////////////////////////
		
		insertText: function( val ) {
			codiad.editor.getActive()
			.insert( val );
		},
		
		//////////////////////////////////////////////////////////////////
		// Goto Line
		//////////////////////////////////////////////////////////////////
		
		gotoLine: function( line ) {
			codiad.editor.getActive()
			.gotoLine( line, 0, true );
		},
		
		//////////////////////////////////////////////////////////////////
		// Move Up (Key Combo)
		//////////////////////////////////////////////////////////////////
		
		move: function( dir ) {
			
			var num = $( '#tab-list-active-files li' ).length;
			if( num === 0 ) return;
			
			var newActive = null;
			var active = null;
			
			if( dir == 'up' ) {
				
				// If active is in the tab list
				active = $( '#tab-list-active-files li.active' );
				if( active.length > 0 ) {
					// Previous or rotate to the end
					newActive = active.prev( 'li' );
					if( newActive.length === 0 ) {
						newActive = $( '#dropdown-list-active-files li:last-child' )
						if( newActive.length === 0 ) {
							newActive = $( '#tab-list-active-files li:last-child' )
						}
					}
				}
				
				// If active is in the dropdown list
				active = $( '#dropdown-list-active-files li.active' );
				if( active.length > 0 ) {
					// Previous
					newActive = active.prev( 'li' );
					if( newActive.length === 0 ) {
						newActive = $( '#tab-list-active-files li:last-child' )
					}
				}
				
			} else {
				
				// If active is in the tab list
				active = $( '#tab-list-active-files li.active' );
				if( active.length > 0 ) {
					// Next or rotate to the beginning
					newActive = active.next( 'li' );
					if( newActive.length === 0 ) {
						newActive = $( '#dropdown-list-active-files li:first-child' );
						if( newActive.length === 0 ) {
							newActive = $( '#tab-list-active-files li:first-child' )
						}
					}
				}
				
				// If active is in the dropdown list
				active = $( '#dropdown-list-active-files li.active' );
				if( active.length > 0 ) {
					// Next or rotate to the beginning
					newActive = active.next( 'li' );
					if( newActive.length === 0 ) {
						newActive = $( '#tab-list-active-files li:first-child' )
					}
				}
				
			}
			
			if( newActive ) this.focus( newActive.attr( 'data-path' ), false );
		},
		
		//////////////////////////////////////////////////////////////////
		// Dropdown Menu
		//////////////////////////////////////////////////////////////////
		
		initTabDropdownMenu: function() {
			var _this = this;
			
			var menu = $( '#dropdown-list-active-files' );
			var button = $( '#tab-dropdown-button' );
			var closebutton = $( '#tab-close-button' );
			
			menu.appendTo( $( 'body' ) );
			
			button.click( function( e ) {
				e.stopPropagation();
				_this.toggleTabDropdownMenu();
			});
			
			closebutton.click( function( e ) {
				e.stopPropagation();
				_this.removeAll();
			});
		},
		
		showTabDropdownMenu: function() {
			var menu = $( '#dropdown-list-active-files' );
			if( !menu.is( ':visible' ) ) this.toggleTabDropdownMenu();
		},
		
		hideTabDropdownMenu: function() {
			var menu = $( '#dropdown-list-active-files' );
			if( menu.is( ':visible' ) ) this.toggleTabDropdownMenu();
		},
		
		toggleTabDropdownMenu: function() {
			var _this = this;
			var menu = $( '#dropdown-list-active-files' );
			
			menu.css( {
				top: $( "#editor-top-bar" ).height() + 'px',
				right: '20px',
				width: '200px'
			});
			
			menu.slideToggle( 'fast' );
			
			if( menu.is( ':visible' ) ) {
				// handle click-out autoclosing
				var fn = function() {
					menu.hide();
					$( window ).off( 'click', fn )
				}
				$( window ).on( 'click', fn );
			}
		},
		
		moveTabToDropdownMenu: function( tab, prepend ) {
			if( prepend === undefined ) {
				prepend = false;
			}
			
			tab.remove();
			path = tab.attr( 'data-path' );
			
			var tabThumb = this.createMenuItemThumb( path );
			if( prepend ) $( '#dropdown-list-active-files' ).prepend( tabThumb );
			else $( '#dropdown-list-active-files' ).append( tabThumb );
			
			if( tab.hasClass( "changed" ) ) {
				tabThumb.addClass( "changed" );
			}
			
			if( tab.hasClass( "active" ) ) {
				tabThumb.addClass( "active" );
			}
			
			this.sessions[path].tabThumb = tabThumb;
		},
		
		moveDropdownMenuItemToTab: function( menuItem, prepend ) {
			if( prepend === undefined ) {
				prepend = false;
			}
			
			menuItem.remove();
			path = menuItem.attr( 'data-path' );
			
			var tabThumb = this.createTabThumb( path );
			if( prepend ) $( '#tab-list-active-files' ).prepend( tabThumb );
			else $( '#tab-list-active-files' ).append( tabThumb );
			
			if( menuItem.hasClass( "changed" ) ) {
				tabThumb.addClass( "changed" );
			}
			
			if( menuItem.hasClass( "active" ) ) {
				tabThumb.addClass( "active" );
			}
			
			this.sessions[path].tabThumb = tabThumb;
		},
		
		isTabListOverflowed: function( includeFictiveTab ) {
			if( includeFictiveTab === undefined ) {
				includeFictiveTab = false;
			}
			
			var tabs = $( '#tab-list-active-files li' );
			var count = tabs.length
			if( includeFictiveTab ) count += 1;
			if( count <= 1 ) return false;
			
			var width = 0;
			tabs.each( function( index ) {
				width += $( this ).outerWidth( true );
			})
			if( includeFictiveTab ) {
				width += $( tabs[tabs.length - 1] ).outerWidth( true );
			}
			
			/* If we subtract the width of the left side bar, of the right side
			 * bar handle and of the tab dropdown handle to the window width,
			 * do we have enough room for the tab list? Its kind of complicated
			 * to handle all the offsets, so afterwards we add a fixed offset
			 * just t be sure. */
			var lsbarWidth = $( ".sidebar-handle" ).width();
			if( codiad.sidebars.isLeftSidebarOpen ) {
				lsbarWidth = $( "#sb-left" ).width();
			}
			
			var rsbarWidth = $( ".sidebar-handle" ).width();
			if( codiad.sidebars.isRightSidebarOpen ) {
				rsbarWidth = $( "#sb-right" ).width();
			}
			
			var tabListWidth = $( "#tab-list-active-files" ).width();
			var dropdownWidth = $( '#tab-dropdown' ).width();
			var closeWidth = $( '#tab-close' ).width();
			var room = window.innerWidth - lsbarWidth - rsbarWidth - dropdownWidth - closeWidth - width - 30;
			return ( room < 0 );
		},
		
		updateTabDropdownVisibility: function() {
			while( this.isTabListOverflowed() ) {
				var tab = $( '#tab-list-active-files li:last-child' );
				if( tab.length == 1 ) this.moveTabToDropdownMenu( tab, true );
				else break;
			}
			
			while( !this.isTabListOverflowed( true ) ) {
				var menuItem = $( '#dropdown-list-active-files li:first-child' );
				if( menuItem.length == 1 ) this.moveDropdownMenuItemToTab( menuItem );
				else break;
			}
			
			if( $( '#dropdown-list-active-files li' ).length > 0 ) {
				$( '#tab-dropdown' ).show();
			} else {
				$( '#tab-dropdown' ).hide();
				// Be sure to hide the menu if it is opened.
				$( '#dropdown-list-active-files' ).hide();
			}
			if( $( '#tab-list-active-files li' ).length > 1 ) {
				$( '#tab-close' ).show();
			} else {
				$( '#tab-close' ).hide();
			}
		},
		
		uploadPositions: function() {
			
			if( Object.keys( codiad.active.positions ).length > 0 ) {
				
				$.ajax( {
					type: 'POST',
					url: codiad.active.controller + '?action=save_positions',
					data: {
						positions: ( JSON.stringify( codiad.active.positions ) )
					},
					success: function( data ) {},
				});
			}
		},
		
		savePosition: function() {
			
			let editor = codiad.editor.getActive();
			let session = editor.getSession();
			let path = session.path;
			let position = this.getPosition( path, true );
			
			this.positions[path] = position;
		},
		
		getPosition: function( path = null, live = false ) {
			
			if( path === null ) {
				
				path = this.getPath();
			}
			
			let editor = null;
			let position = null;
			let session = codiad.active.sessions[path];
			
			for( let i = codiad.editor.instances.length; i--; ) {
				
				if( codiad.editor.instances[i].getSession().path == path ) {
					
					editor = codiad.editor.instances[i];
				}
			}
			
			if( live ) {
				
				position = editor.getCursorPosition();
			} else {
				
				if( !this.positions[path] === undefined ) {
					
					position = this.positions[path].position;
				}
			}
			
			if( position == null ) {
				
				position = {
					
					column: 0,
					row: 0
				};
			}
			
			return position;
		},
		
		setPosition: function( cursor = null ) {
			
			let editor = codiad.editor.getActive();
			
			if( cursor == null ) {
				
				cursor = this.getPosition();
			}
			
			editor.scrollToLine( cursor.row, true, true, function() {});
			editor.moveCursorTo( cursor.row, cursor.column );
		},
		//////////////////////////////////////////////////////////////////
		// Factory
		//////////////////////////////////////////////////////////////////
		
		splitDirectoryAndFileName: function( path ) {
			var index = path.lastIndexOf( '/' );
			return {
				fileName: path.substring( index + 1 ),
				directory: ( path.indexOf( '/' ) == 0 ) ? path.substring( 1, index + 1 ) : path.substring( 0, index + 1 )
			}
		},
		
		createListThumb: function( path ) {
			return $( '<li data-path="' + path + '"><a title="' + path + '"><span></span><div>' + path + '</div></a></li>' );
		},
		
		createTabThumb: function( path ) {
			split = this.splitDirectoryAndFileName( path );
			return $( '<li class="tab-item" data-path="' + path + '"><a class="label" title="' + path + '">' +
				split.directory + '<span class="file-name">' + split.fileName + '</span>' +
				'</a><a class="close">x</a></li>' );
		},
		
		createMenuItemThumb: function( path ) {
			split = this.splitDirectoryAndFileName( path );
			return $( '<li data-path="' + path + '"><a title="' + path + '"><span class="label"></span><div class="label">' +
				split.directory + '<span class="file-name">' + split.fileName + '</span>' +
				'</div></a></li>' );
		},
		
	};
	
})( this, jQuery );