// Name: Browse Folders
// Description: Adds "Browse Folders" menu option
// Author: kartu
//
// History:
//	2010-03-06 kartu - Added mount/umount feature
//	2010-03-07 kartu - Prepared for localization
//	2010-03-14 kartu - Refactored Utils -> Core
//	2010-03-14 kartu - Localized
//	2010-04-24 kartu - Prepared for merging into single JS
//	2010-04-25 kartu - Marked FolderNode and browseFoldersNode._myconstruct as constructors
//	2010-04-27 kravitz - Exported pathToBookNode() (fixing required)
//	2010-04-27 kravitz - Code of main menu forming is moved to MenuTuning.js
//	2010-04-29 kravitz - Refactored events handling
//	2010-05-15 kartu - Fixed: book list was initialized once and never updated
//	2010-05-24 kartu - Fixed weird bug "after deleting collection reader jumps to main menu, instead of collection" (fix by kravitz) that occured only if there was no SD card
//	2010-07-06 kartu - Rewritten from scratch, added "favourite folders" and picture,music,notes support
//	2010-09-29 kartu - Fixed: only existing roots will be shown in BrowseFolders (i.e. SD card won't be shown, if it is not inserted) 
//	2010-11-30 kartu - Refactoring Core.stirng => Core.text
//	2011-01-31 kartu - Removed comment field (fix for x50)
//	2011-02-04 kartu - Added trailing / to SD/MS/other roots check path
//	2011-02-06 kartu - Implemented #55 "'Jump to Folders' action"
//	2011-02-07 kartu - Implemented # sort by filename, showing filename as comment
//	2011-02-09 kartu - Fixed # BrowseFolders view not updated when settings change
//			Removed "mount" option
//	2011-03-03 kartu - Added support for converters/alternative viewers
//	2011-04-24 kartu - Added option to disable scanning without loading cache
//	2011-04-25 kartu - Added "via mount" option for SD/MS card access
//
tmp = function() {
	var log, L, startsWith, trim, BrowseFolders, TYPE_SORT_WEIGHTS, compare, sorter, folderConstruct, 
		createFolderNode, createMediaNode, favourites, loadFavFolders, folderRootConstruct,
		compareFields, supportedMIMEs, createLazyInitNode, constructLazyNode, ACTION_ICON,
		doCopyAndOpen, doCopy, doOpenHere, browseFoldersNode, ENABLED, DISABLED;
	
	ENABLED = "enabled";
	DISABLED = "disabled";
	log = Core.log.getLogger("BrowseFolders");
	L = Core.lang.getLocalizer("BrowseFolders");
	startsWith = Core.text.startsWith;
	trim = Core.text.trim;
	supportedMIMEs = {
		"application/rtf": true,
		"application/pdf": true,
		"application/epub+zip": true,
		"application/x-sony-bbeb": true,
		"text/plain": true
	};
	ACTION_ICON = "BACK";

	//-----------------------------------------------------------------------------------------------------------------------------
	// Sorting
	//-----------------------------------------------------------------------------------------------------------------------------
	TYPE_SORT_WEIGHTS = {
		folder: -100,
		book: -50,
		picture: -25,
		audio: -10,
		note: 0,
		file: 0
	};

	// Which fields to use for comparison
	compareFields = {
		"author": ["author", "titleSorter", "title"],
		"title": ["titleSorter", "title"],
		filename: ["path"],
		filenameAsComment: ["path"]
	};
	
	compare = Core.config.compat.compareStrings;
	sorter = function (a, b) {
		var compFields, i, n, result, field;
		try {
			if (a.type !== b.type) {
				// directories first, etc
				return TYPE_SORT_WEIGHTS[a.type] - TYPE_SORT_WEIGHTS[b.type];
			}
			if (a.type === "folder") {
				return compare(a.name, b.name);
			}
			if (a.hasOwnProperty("media") && b.hasOwnProperty("media")) {
				compFields = compareFields[BrowseFolders.options.sortMode];
				// compare fields until values don't match
				for (i = 0, n = compFields.length; i < n; i++) {
					field = compFields[i];
					result = compare(a.media[field], b.media[field]);
					if (result !== 0) {
						break;
					}
				}
				return result;
			}
			return compare(a.name, b.name);
		} catch (e) {
			log.error("in sortByTitle : " + e);
		}
	}; 

	//-----------------------------------------------------------------------------------------------------------------------------
	// Node creation
	//-----------------------------------------------------------------------------------------------------------------------------
	
	createFolderNode = function (path, title, parent, icon, needsMount) {
		try {
			if (icon === undefined) {
				icon = "FOLDER";
			}
			var node = Core.ui.createContainerNode({
					title: title,
					icon: icon,
					parent: parent,
					construct: folderConstruct
			});
			node.path = path + "/";
			node.type = "folder";
			node.needsMount = needsMount;
			return node;
		} catch (e) {
			log.error("in createFolderNode", e);
		}
	};
	
	createLazyInitNode = function (path, title, parent) {
		var node;
		if (BrowseFolders.options.cardScan === DISABLED && !Core.text.startsWith(path, "/Data")) {
			// if SD/MS scan is disabled and we are not in internal memory
			
			node = Core.ui.createContainerNode({
					title: title,
					icon: "BOOK",
					parent: parent,
					construct: constructLazyNode
			});				
		} else {
			node = Core.ui.createContainerNode({
					title: title,
					icon: "BOOK",
					parent: parent
			});
			node.enter = doOpenHere;
		}
		
		node.path = path;
		return node;
	};
	
	createMediaNode = function (path, title, parent, dummy, needsMount) {
		var node, mime;
		node = Core.media.createMediaNode(path, parent);
		if (node === null) {
			// Either file that is not a media, or unscanned
			mime = FileSystem.getMIMEType(path);
			if (supportedMIMEs[mime]) {
				node = createLazyInitNode(path, title, parent);
			} else {
				node = Core.convert.createMediaNode(path, title, parent);
			}
		} else if (BrowseFolders.options.sortMode === "filenameAsComment") {
			node._mycomment = function() {
				try {
					return Core.io.extractFileName(this.media.path);
				} catch (e) {
					return "error: " + e;
				}
			};
		}
		if (node) {
			node.needsMount = needsMount;
		}
		return node;
	};
	
	//-----------------------------------------------------------------------------------------------------------------------------
	// "Lazy" opening of media in place, for disabled scanning with loaded cache case
	//-----------------------------------------------------------------------------------------------------------------------------	
	doOpenHere = function() {
		var path, item, parent, mediaNode;
		
		// create media
		parent = this.parent;
		path = this.path;
		try {
			item = Core.media.loadMedia(path);
		} catch (ignore) {
			Core.ui.showMsg(L("MSG_ERROR_OPENING_BOOK"));
		}
		
		if (item) {
			// create node
			mediaNode = Core.media.createMediaNode(item, parent);
	
			// replace parent with media node
			Core.utils.replaceInArray(parent.nodes, this, mediaNode); 
			
			this.gotoNode(mediaNode, kbook.model);
		}
	};
	
	doCopy = function () {
		var fileName, path, needsMount;
		Core.ui.showMsg("MSG_COPYING_BOOK", 1);
		try {
			// mount, if needed
			needsMount = this.parent.needsMount;
			if (needsMount !== undefined) {
				Core.shell.mount(needsMount);
			}
			
			try {
				path = this.parent.path;
				fileName = Core.io.extractFileName(path);
				// FIXME: ask user first, if he wants to copy the book, if target exists
				fileName = Core.io.getUnusedPath("/Data" + BrowseFolders.options.imRoot + "/", fileName);
				Core.io.copyFile(path, fileName);
				return fileName;
			} finally {
				Core.shell.umount(needsMount);
			}
		} catch (ignore) {
			Core.ui.showMsg("MSG_ERROR_COPYING_BOOK");	
		}
	};
	
	doCopyAndOpen = function () {
		var path, foldersNode, nodes, targetFolder, imNode, i, n;
		// Copy the file and get new filename
		path = doCopy.call(this);
		targetFolder = Core.io.extractPath(path); 
		
		// find IM root node
		foldersNode = BrowseFolders.getAddonNode();
		nodes = foldersNode.nodes;
		imNode = null;
		for (i = 0, n = nodes.length; i < n; i++) {
			if (nodes[i].path === targetFolder) {
				imNode = nodes[i];
				break;
			}
		}
		
		// If IM root node not found, goto folders node
		if (imNode) {
			// goto IM node to allow it to construct children
			this.gotoNode(imNode, kbook.model);
			imNode.update();
			
			// Find element with matching path, if found, enter
			nodes = imNode.nodes;
			for (i = 0, n = nodes.length; i < n; i++) {
				if (path === nodes[i].path) {
					imNode.gotoNode(nodes[i], kbook.model);
					break;
				}
			}
		} else {
			this.gotoNode(foldersNode, kbook.model);
		}
	};
	
	//-----------------------------------------------------------------------------------------------------------------------------
	// Node constructors
	//-----------------------------------------------------------------------------------------------------------------------------
	// Construct "copy to IM", "copy to IM and open" node
	constructLazyNode = function() {
		var copyNode, copyAndOpenNode;
		
		// Node that copies to IM and opens book
		copyAndOpenNode = Core.ui.createContainerNode({
				title: L("NODE_COPY_AND_OPEN"),
				comment: L("NODE_COPY_AND_OPEN_COMMENT"),
				parent: this,
				icon: ACTION_ICON
		});
		copyAndOpenNode.enter = doCopyAndOpen;
		
		// Node that copies to IM without opening
		copyNode = Core.ui.createContainerNode({
				title: L("NODE_COPY_TO_INTERNAL_MEMORY"),
				comment: L("NODE_COPY_TO_INTERNAL_MEMORY_COMMENT"),
				parent: this,
				icon: ACTION_ICON
		});
		copyNode.enter = doCopy;
		
		this.nodes = [copyAndOpenNode, copyNode];		
	};
	
	// Constructs folder node
	folderConstruct = function() {
		var path, nodes, iterator, item, factory, node;
		path = this.path;
		nodes = [];
		try {
			// Mount card, if it's a "mount" node
			if (this.needsMount !== undefined) {
				Core.shell.mount(this.needsMount);
			}
			try {
				if (FileSystem.getFileInfo(path)) {
					// Iterate over item's content
					iterator = new FileSystem.Iterator(path);
					while (item = iterator.getNext()) {
						if (item.type === "directory") {
							factory = createFolderNode;
						} else {
							factory = createMediaNode;
						}
						
						node = factory(path + item.path, item.path, this, undefined, this.needsMount);
						if (node !== null) {
							nodes.push(node);
						}
					}
				}
			} finally {
				// Unmount card (power drain)
				Core.shell.umount(this.needsMount);
			}
				
			nodes.sort(sorter);
		} catch (e) {
			log.error("in folderConstruct " + e);
		}
		this.nodes = nodes;
	};
	
	// Loads favourite folders from fav_folders.config file if file exists 
	favourites = null;
	loadFavFolders = function (roots, rootTitles, rootIcons) {
		var filePath, content, line, name, path, i, n, icon;
		if (BrowseFolders.options.favFoldersFile === ENABLED) {
			// load fav file content
			if (favourites === null) {
				filePath = Core.config.publicPath + "folders.cfg";
				content = Core.io.getFileContent(filePath, null);
				if (content !== null) {
					favourites = content.split("\n");
				} else {
					favourites = [];
				}
			}
			
			// parse fav file
			for (i = 0, n = favourites.length; i < n; i++) {
				try {
					line = favourites[i].split("\t");
					if (line.length === 2 && FileSystem.getFileInfo(trim(line[1])) && !startsWith(line[0], "#")) {
						name = line[0];
						path = trim(line[1]);
						roots.push(path);
						rootTitles.push(name);
						if (startsWith(path, "/Data")) {
							icon = "INTERNAL_MEM";
						} else if (startsWith(path, "a:")) {
							icon = "MS";
						} else if (startsWith(path, "b:")) {
							icon = "SD";
						} else {
							icon = "FOLDER";
						}
						rootIcons.push(icon);
					}
				} catch (e) {
					log.error("Processing line " + i, e);
				}
			}
		}
	};
	
	// Constructs "Browse Folders" node
	folderRootConstruct = function() {
		try {
			var i,n, nodes, roots, rootTitles, rootIcons, nNodes, idx;
			nodes = [];
			roots = [];
			rootTitles = [];
			rootIcons = [];
			
			// Load favourites
			loadFavFolders(roots, rootTitles, rootIcons);
			
			if (roots.length === 0) {
				roots = ["/Data" + BrowseFolders.options.imRoot, "b:", "a:"];
				rootTitles = [
					L("NODE_INTERNAL_MEMORY"),
					L("NODE_SD_CARD"),
					L("NODE_MEMORY_STICK")
				];
				
				rootIcons = [
					"INTERNAL_MEM",
					"SD",
					"MS"
				];
			}
			
			// Number of nodes created
			nNodes = 0;
			for (i = 0, n = roots.length; i < n; i++) {
				if (FileSystem.getFileInfo(roots[i] + "/")) {
					idx = i;
					nNodes++;
				}
			}
			if (nNodes === 1) {
				// Only one node, ask folderConstruct to create nodes (effectively: jump to child)
				this.path = roots[idx] + "/";
				folderConstruct.call(this);
				return;
			} else {
				for (i = 0, n = roots.length; i < n; i++) {
					if (FileSystem.getFileInfo(roots[i] + "/")) {
						nodes.push(createFolderNode(roots[i], rootTitles[i], this, rootIcons[i]));
					}
				}
				
				// Add "via mount" nodes
				if (BrowseFolders.options.useMount === ENABLED) {
					if (FileSystem.getFileInfo("a:/")) {
						nodes.push(createFolderNode(
							Core.shell.MS_MOUNT_PATH, 
							L("NODE_MEMORY_STICK_MOUNT"), 
							this, 
							"MS",
							Core.shell.MS
						));
					}
					if (FileSystem.getFileInfo("b:/")) {
						nodes.push(createFolderNode(
							Core.shell.SD_MOUNT_PATH, 
							L("NODE_SD_CARD_MOUNT"), 
							this, 
							"SD",
							Core.shell.SD
						));
					}
				}
			}

			this.nodes = nodes;
		} catch (e) {
			log.error("in folderRootConstruct", e);
		}
	};
	
	//-----------------------------------------------------------------------------------------------------------------------------
	// Addon & Option definitions
	//-----------------------------------------------------------------------------------------------------------------------------
	BrowseFolders = {
		name: "BrowseFolders",
		title: L("TITLE"),
		icon: "FOLDER",				
		getAddonNode: function() {
			var oldUnlock;
			if (browseFoldersNode === undefined) {
				browseFoldersNode = Core.ui.createContainerNode({
						title: L("NODE_BROWSE_FOLDERS"),
						shortName: L("NODE_BROWSE_FOLDERS_SHORT"),
						icon: "FOLDER",
						comment: "",
						parent: kbook.root,
						construct: folderRootConstruct
				});
				
				// When BrowseFolders settings change, child nodes are detached and unlock is forced.
				// this could lead to lock value to become negative.
				oldUnlock = browseFoldersNode.unlock;
				browseFoldersNode.unlock = function() {
					oldUnlock.apply(this, arguments);
					if (this.locked < 0) {
						this.locked = 0;
					}
				};
			}
			return browseFoldersNode;
		},
		optionDefs: [
			{
				name: "sortMode",
				title: L("OPTION_SORTING_MODE"),
				icon: "LIST",
				defaultValue: "author",
				values: ["title", "author", "filename", "filenameAsComment"],
				valueTitles: {
					title: L("VALUE_BY_TITLE"),
					author: L("VALUE_BY_AUTHOR_THEN_TITLE"),
					filename: L("VALUE_BY_FILENAME"),
					filenameAsComment:  L("VALUE_BY_FILENAME_AS_COMMENT")
				}
			},
			{
				name: "imRoot",
				title: L("OPTION_IM_ROOT"),
				icon: "FOLDER",
				defaultValue: "",
				values: ["/database/media/books", "/database/media", "/media", "/books", ""],
				valueTitles: {
					"": "/" // yeah! :)
				}
			},
			{
				name: "favFoldersFile",
				title: L("OPTION_FAVOURITE_FOLDERS"),
				icon: "FOLDER",
				defaultValue: DISABLED,
				values: [ENABLED, DISABLED],
				valueTitles: {
					enabled: L("VALUE_ENABLED"),
					disabled: L("VALUE_DISABLED")
				}				
			}
		],
		
		onPreInit: function() {
			// These options make sense only if device has SD/MS card slots 
			if (Core.config.compat.hasCardSlots) {
				// Option to enable disable SD/MS card scanning
				BrowseFolders.optionDefs.push({
					name: "cardScan",
					title: L("OPTION_CARD_SCAN"),
					icon: "DB",
					defaultValue: ENABLED,
					values: [ENABLED, "disabledLoadCache", DISABLED],
					valueTitles: {
						enabled: L("VALUE_ENABLED"),
						disabledLoadCache: L("VALUE_DISABLED_LOAD_CACHE"),
						disabled: L("VALUE_DISABLED")
					}
				});

				BrowseFolders.optionDefs.push({
					name: "useMount",
					title: L("OPTION_MOUNT"),
					icon: "DB",
					defaultValue: ENABLED,
					values: [ENABLED, DISABLED],
					valueTitles: {
						enabled: L("VALUE_ENABLED"),
						disabled: L("VALUE_DISABLED")
					}
				});
			}
		},
		
		onInit: function() {
			// Bootstrap code knows only Core, not this addon
			Core.config.cardScanMode = BrowseFolders.options.cardScan;
		},
		
		actions: [{
			name: "BrowseFolders",
			title: L("TITLE"),
			group: "Utils",
			icon: "FOLDER",
			action: function () {
				var current = Core.ui.getCurrentNode();
				if (current) {
					current.gotoNode(browseFoldersNode, kbook.model);
				} else {
					Core.ui.doBlink();
				}
			}
		}],
		
		onSettingsChanged: function(propertyName, oldValue, newValue) {
			if (oldValue === newValue) {
				return;
			}
			
			// Bootstrap code knows only Core, not this addon
			if (propertyName === "cardScanMode") {
				Core.config.cardScanMode = newValue;
			}
			
			// Release the node so that it's content can be updated
			if (browseFoldersNode) {
				browseFoldersNode.unlockPath();
			}
		}
		
	};
	Core.addAddon(BrowseFolders);
};

try {
	tmp();
	tmp = undefined;
} catch (e) {
	// Core's log
	log.error("in BrowseFolders.js", e);
}
