/*
    author: ApmeM (artem.votincev@gmail.com)
    date: 9-June-2010
    version: 1.4
    download: http://code.google.com/p/jq-serverbrowse/
 */

(function($) {
	$.fn.serverBrowser = function(settings) {
		this.each(function() {

			var config = {
				// Event function
				// Appear when user click 'Ok' button, or doubleclick on file
				onSelect : function(file) {
					alert('You select: ' + file);
				},
				onLoad : function() {
					return config.basePath;
				},
				multiselect : false,
				// Image parameters
				// System images (loading.gif, unknown.png, folder.png and
				// images from knownPaths) will be referenced to systemImageUrl
				// if systemImageUrl is empty or not specified - imageUrl will
				// be taken
				// All other images (like images for extension) will be taken
				// from imageUrl
				imageUrl : 'img/',
				systemImageUrl : '',
				showUpInList : false,
				// Path properties
				// Base path, that links should start from.
				// If opened path is not under this path, alert will be shown
				// and nothing will be opened
				// Path separator, that will be used to split specified paths
				// and join paths to a string
				basePath : 'C:',
				separatorPath : '/',
				// Paths, that will be displayed on the left side of the dialog
				// This is a link to specified paths on the server
				useKnownPaths : true,
				knownPaths : [ {
					text : 'Desktop',
					image : 'desktop.png',
					path : 'C:/Users/All Users/Desktop'
				}, {
					text : 'Documents',
					image : 'documents.png',
					path : 'C:/Users/All Users/Documents'
				} ],
				// Images for known extension (like 'png', 'exe', 'zip'), that
				// will be displayed with its real names
				// Images, that is not in this list will be referenced to
				// 'unknown.png' image
				// If list is empty - all images is known.
				knownExt : [],
				// Server path to this plugin handler
				handlerUrl : 'browserDlg.txt',
				// JQuery-ui dialog settings
				title : 'Browse',
				width : 300,
				height : 300,
				position : [ 'center', 'center' ],

				// Administrative parameters used to
				// help programmer or system administrator
				requestMethod : 'POST'
			};

			if (settings)
				$.extend(config, settings);
			// Required configuration elements
			// We need to set some configuration elements without user
			// For example there should be 2 buttons on the bottom,
			// And dialog should be opened after button is pressed, not when it
			// created
			// Also we need to know about dialog resizing
			$.extend(config, {
				autoOpen : false,
				modal : true,
				buttons : {
					"Open" : function() {
						doneOk();
					},
					"Cancel" : function() {
						browserDlg.dialog("close");
					}
				},
				resize : function(event, ui) {
					recalculateSize(event, ui);
				}
			});

			function systemImageUrl() {
				if (config.systemImageUrl.length == 0) {
					return config.imageUrl;
				} else {
					return config.systemImageUrl;
				}
			}

			var privateConfig = {
				// This stack array will store history navigation data
				// When user open new directory, old directory will be added to
				// this list
				// If user want, he will be able to move back by this history
				browserHistory : [],

				// This array contains all currently selected items
				// When user select element, it will add associated path into
				// this array
				// When user deselect element - associated path will be removed
				// Exception: if 'config.multiselect' is false, only one element
				// will be stored in this array.
				selectedItems : []
			};

			// Main dialog div
			// It will be converted into jQuery-ui dialog box using my
			// configuration parameters
			// It contains 3 divs
			var browserDlg = $('<div title="' + config.title + '"></div>').css( {
				'overflow' : 'hidden'
			}).appendTo(document.body);
			browserDlg.dialog(config);

			// First div on the top
			// It contains textbox field and buttons
			// User can enter any paths he want to open in this textbox and
			// press enter
			// There is 3 buttons on the panel:
			var enterPathDiv = $('<div></div>').addClass('ui-widget-content').appendTo(browserDlg).css( {
				'height' : '30px',
				'width' : '100%',
				'padding-top' : '7px'
			});

			var enterButton = $('<div></div>').css( {
				'float' : 'left',
				'vertical-align' : 'middle',
				'margin-left' : '6px'
			}).addClass('ui-corner-all').hover(function() {
				$(this).addClass('ui-state-hover');
			}, function() {
				$(this).removeClass('ui-state-hover');
			});

			var enterLabel = $('<span></span>').text('Look in: ').appendTo(enterButton.clone(false).appendTo(enterPathDiv));

			var enterText = $('<input type="text">').keypress(function(e) {
				if (e.keyCode == '13') {
					e.preventDefault();
					loadPath(enterText.val());
				}
			}).css('width', '200px').appendTo(enterButton.clone(false).appendTo(enterPathDiv));

			// Back button.
			// When user click on it, 2 last elements of the history pop from
			// the list, and reload second of them.
			var enterBack = $('<div></div>').addClass('ui-corner-all ui-icon ui-icon-circle-arrow-w').click(function() {
				privateConfig.browserHistory.pop(); // Remove current element.
													// It is not required now.
				var backPath = config.basePath;
				if (privateConfig.browserHistory.length > 0) {
					backPath = privateConfig.browserHistory.pop();
				}
				loadPath(backPath);
			}).appendTo(enterButton.clone(true).appendTo(enterPathDiv));

			// Level Up Button
			// When user click on it, last element of the history will be taken,
			// and '..' will be applied to the end of the array.
			var enterUp = $('<div></div>').addClass('ui-corner-all ui-icon ui-icon-arrowreturnthick-1-n').click(function() {
				backPath = privateConfig.browserHistory[privateConfig.browserHistory.length - 1];
				if (backPath != config.basePath) {
					loadPath(backPath + config.separatorPath + '..');
				}
			}).appendTo(enterButton.clone(true).appendTo(enterPathDiv));

			// Second div is on the left
			// It contains images and texts for pre-defined paths
			// User just click on them and it will open pre-defined path
			var knownPathDiv = $('<div></div>').addClass('ui-widget-content').css( {
				'text-align' : 'center',
				'overflow' : 'auto',
				'float' : 'left',
				'width' : '100px'
			});
			if (config.useKnownPaths) {
				knownPathDiv.appendTo(browserDlg);
				$.each(config.knownPaths, function(index, path) {
					var knownDiv = $('<div></div>').css( {
						'margin' : '10px'
					}).hover(function() {
						$(this).addClass('ui-state-hover');
					}, function() {
						$(this).removeClass('ui-state-hover');
					}).click(function() {
						loadPath(path.path);
					}).appendTo(knownPathDiv);

					$('<img />').attr( {
						src : systemImageUrl() + config.separatorPath + path.image
					}).css( {
						width : '32px',
						margin : '5px 10px 5px 5px'
					}).appendTo(knownDiv);
					$('<br/>').appendTo(knownDiv);
					$('<span></span>').text(path.text).appendTo(knownDiv);
				});
			}

			// Third div is everywhere :)
			// It show files and folders in the current path
			// User can click on path to select or deselect it
			// Doubleclick on path will open it
			// Also doubleclick on file will select this file and close dialog
			var browserPathDiv = $('<div></div>').addClass('ui-widget-content').css( {
				'float' : 'right',
				'overflow' : 'auto'
			}).appendTo(browserDlg);

			// Now everything is done
			// When user will be ready - he just click on the area you select
			// for this plugin and dialog will appear
			$(this).click(function() {
				privateConfig.browserHistory = [];
				var startpath = removeBackPath(config.onLoad());

				startpath = startpath.split(config.separatorPath);
				startpath.pop();
				startpath = startpath.join(config.separatorPath);

				if (!checkBasePath(startpath)) {
					startpath = config.basePath;
				}
				loadPath(startpath);
				browserDlg.dialog('open');
				recalculateSize();
			});

			// Function check if specified path is a child path of a
			// 'config.basePath'
			// If it is not - user should see message, that path invalid, or
			// path should be changed to valid.
			function checkBasePath(path) {
				if (config.basePath == '')
					return true;
				var confPath = config.basePath.split(config.separatorPath);
				var curPath = path.split(config.separatorPath);
				if (confPath.length > curPath.length)
					return false;
				var result = true;
				$.each(confPath, function(index, partConfPath) {
					if (partConfPath != curPath[index]) {
						result = false;
					}
				});
				return result;
			}

			// Function remove '..' parts of the path
			// Process depend on config.separatorPath option
			// On the server side you need to check / or \ separators
			function removeBackPath(path) {
				var confPath = config.basePath.split(config.separatorPath);
				var curPath = path.split(config.separatorPath);
				var newcurPath = [];
				$.each(curPath, function(index, partCurPath) {
					if (partCurPath == "..") {
						newcurPath.pop();
					} else {
						newcurPath.push(partCurPath);
					}
				});
				return newcurPath.join(config.separatorPath);
			}

			// This function will be called when user click 'Open'
			// It check if any path is selected, and call config.onSelect
			// function with path list
			function doneOk() {
				var newCurPath = [];
				$.each(privateConfig.selectedItems, function(index, item) {
					newCurPath.push($.data(item, 'path'));
				});
				if (newCurPath.length == 0) {
					newCurPath.push(privateConfig.browserHistory.pop());
				}

				if (config.multiselect)
					config.onSelect(newCurPath);
				else {
					if (newCurPath.length == 1) {
						config.onSelect(newCurPath[0]);
					} else if (newCurPath.length > 1) {
						alert('Plugin work incorrectly. If error repeat, please add issue into http://code.google.com/p/jq-serverbrowse/issues/list with steps to reproduce.');
						return;
					}
				}
				browserDlg.dialog("close");
			}

			// Function recalculate and set new width and height for left and
			// right div elements
			// height have '-2' because of the borders
			// width have '-4' because of a border an 2 pixels space between
			// divs
			function recalculateSize(event, ui) {
				knownPathDiv.css( {
					'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2
				});
				browserPathDiv.css( {
					'height' : browserDlg.height() - enterPathDiv.outerHeight(true) - 2,
					'width' : browserDlg.width() - knownPathDiv.outerWidth(true) - 4
				});
			}

			// Function adds new element into browserPathDiv element depends on
			// file parameters
			// If file.isError is set, error message will be displayed instead
			// of clickable area
			// Clickable div contain image from extension and text from file
			// parameter
			function addElement(file) {
				var itemDiv = $('<div></div>').css( {
					margin : '2px'
				}).appendTo(browserPathDiv);
				if (file.isError) {
					itemDiv.addClass('ui-state-error ui-corner-all').css( {
						padding : '0pt 0.7em'
					});
					var p = $('<p></p>').appendTo(itemDiv);
					$('<span></span>').addClass('ui-icon ui-icon-alert').css( {
						'float' : 'left',
						'margin-right' : '0.3em'
					}).appendTo(p);
					$('<span></span>').text(file.name).appendTo(p);
				} else {
					var fullPath = file.path + config.separatorPath + file.name;
					itemDiv.hover(function() {
						$(this).addClass('ui-state-hover');
					}, function() {
						$(this).removeClass('ui-state-hover');
					});
					var itemImage = $('<img />').css( {
						width : '16px',
						margin : '0 5px 0 0'
					}).appendTo(itemDiv);
					var itemText = $('<span></span>').text(file.name).appendTo(itemDiv);
					if (file.isFolder)
						itemImage.attr( {
							src : systemImageUrl() + 'folder.png'
						});
					else {
						ext = file.name.split('.').pop();
						var res = '';
						if (ext == '' || ext == file.name || (config.knownExt.length > 0 && $.inArray(ext, config.knownExt) < 0))
							itemImage.attr( {
								src : systemImageUrl() + 'unknown.png'
							});
						else
							itemImage.attr( {
								src : config.imageUrl + ext + '.png'
							});
					}
					$.data(itemDiv, 'path', fullPath);
					itemDiv.unbind('click').bind('click', function(e) {
						if (!$(this).hasClass('ui-state-active')) {
							if (!config.multiselect && privateConfig.selectedItems.length > 0) {
								$(privateConfig.selectedItems[0]).click();
							}
							privateConfig.selectedItems.push(itemDiv);
						} else {
							var newCurPath = [];
							$.each(privateConfig.selectedItems, function(index, item) {
								if ($.data(item, 'path') != fullPath)
									newCurPath.push(item);
							});
							privateConfig.selectedItems = newCurPath;
						}
						$(this).toggleClass('ui-state-active');
					});

					itemDiv.unbind('dblclick').bind('dblclick', function(e) {
						if (file.isFolder) {
							loadPath(fullPath);
						} else {
							privateConfig.selectedItems = [ itemDiv ];
							doneOk();
						}
					});
				}
			}

			// Main plugin function
			// When user enter path manually, select it from pre-defined path,
			// or doubleclick in browser this function will call
			// It send a request on the server to retrieve child directories and
			// files of the specified path
			// If path is not under 'config.basePath', alert will be shown and
			// nothing will be opened
			function loadPath(path) {
				privateConfig.selectedItems = [];

				// First we need to remove all '..' parts of the path
				path = removeBackPath(path);

				// Then we need to check, if path based on 'config.basePath'
				if (!checkBasePath(path)) {
					alert('Path should be based from ' + config.basePath);
					return;
				}

				// Then we can put this path into history
				privateConfig.browserHistory.push(path);

				// Show it to user
				enterText.val(path);

				// And load
				$.ajax( {
					url : config.handlerUrl,
					type : config.requestMethod,
					data : {
						action : 'browse',
						path : path,
						time : new Date().getTime()
					},
					beforeSend : function() {
						browserPathDiv.empty().css( {
							'text-align' : 'center'
						});
						$('<img />').attr( {
							src : systemImageUrl() + 'loading.gif'
						}).css( {
							width : '32px'
						}).appendTo(browserPathDiv);
					},
					success : function(files) {
						browserPathDiv.empty().css( {
							'text-align' : 'left'
						});
						if (path != config.basePath && config.showUpInList) {
							addElement( {
								name : '..',
								isFolder : true,
								isError : false,
								path : path
							});
						}
						$.each(files, function(index, file) {
							addElement($.extend(file, {
								path : path
							}));
						});
					},
					dataType : 'json'
				});
			}
		});
		return this;
	};
})(jQuery);