API Docs for:
Show:

File: application.js

/*******************************************************************************
 * Copyright (c) 2017 Genialist Software Ltd.
 * All rights reserved.
 ******************************************************************************/

const max_post_body = 524000;
const max_add_pictures = 8196;

const pImageVersions  = new (function() {
	var that = this, listeners = new pMap(), map = (window.localStorage)? {
		getValue: function(n, d) {
			if (window.sessionStorage && window.localStorage) {
				//var v = sessionStorage.getItem(n) || localStorage.getItem(n);
				
				var p = n.split('_'), t = p[1]/*, l = p[2], lid = p[3], id = p[4];*/, v = sessionStorage.getItem(t);
				if (v)
					v = pObject.query(JSON.parse(v), p.slice(2));//l, lid, id ]);
				if (!v)
					v = localStorage.getItem(n);
				
				if (v) return v;
			}
			return d;
		},
		put: function(n, v) {
			if (v) {
				localStorage.setItem(n, v);
				localStorage.removeItem(n);
				
				var p = n.split('_'), t = p[1];//, l = p[2], lid = p[3], id = p[4];
				//var vv = pObject.replace(JSON.parse(sessionStorage.getItem(t) || '{}'), p.slice(2), v);//l, lid, id ], v);
				//sessionStorage.setItem(t, JSON.stringify(vv));
				sessionStorage.setItem(t, JSON.stringify(pObject.replace(JSON.parse(sessionStorage.getItem(t) || '{}'), p.slice(2), v)));
			}
		}
	} : new pMap();
	
	this.version = function(t, lid, id) {
		if (t == 'artwork' || t == 'thumbnail') return that.artworkVersion(lid, id); 
		if (t == 'backdrop') return that.backdropVersion(lid, id); 
		if (t == 'poster') return that.posterVersion(lid, id);
		if (t == 'banner') return that.bannerVersion(lid, id);
		return 0;
	};
	
	this.URL = function(t, lid, id) {
		if (t == 'artwork' || t == 'thumbnail') return that.artworkURL(lid, id); 
		//if (t == 'backdrop') return this.backdropVersion(lid, id); 
		//if (t == 'poster') return this.posterVersion(lid, id);
		//if (t == 'banner') return this.bannerVersion(lid, id);
	};
	
	this.versionListenerLibraryID = function(t, lib) {
		if (t == 'artwork' || t == 'thumbnail') return 'session_t_'+lib
		if (t == 'backdrop') return 'session_b_'+lib; 
		if (t == 'poster') return 'session_p_'+lib;
		if (t == 'banner') return 'session_n_'+lib;
		//return null;
	};
	
	this.versionListenerID = function(t, lid, id) {
		var lib = pMediaLibrary.getLibraryIDfromURL();
		
		if (t == 'artwork' || t == 'thumbnail') return 'session_t_'+lib+((lid && lid!='library')? '_'+lid+'_'+id : ''); 
		if (t == 'backdrop') return 'session_b_'+lib+((lid && lid!='library')? '_'+lid+'_'+id : ''); 
		if (t == 'poster') return 'session_p_'+lib+((lid && lid!='library')? '_'+lid+'_'+id : '');
		if (t == 'banner') return 'session_n_'+lib+((lid && lid!='library')? '_'+lid+'_'+id : '');
		//return null;
	};
	
	this.addListener = function(eid, f) {
		var ff = function(e) {
			if (e.newValue) {
				var p = e.key.split('_'), lid = p[3], id = p[4];
				this.f.call(this, lid, id, e.newValue);
				
				if (window.sessionStorage) {
					var t = p[1], vv = pObject.replace(JSON.parse(sessionStorage.getItem(t) || '{}'), p.slice(2), e.newValue);
					sessionStorage.setItem(t, JSON.stringify(vv));
				}
			}
		}.bind({ f: f });
		
		listeners.put(f, ff);
		pLocalStorage.addListener(eid, ff);
	};
		
	this.removeListener = function(eid, f) {
		var ff = listeners.getValue(f);
		if (ff)
			pLocalStorage.removeListener(eid, ff);
	};
	
	this.update = function(t, lid, id, v) {
		map.put(that.versionListenerID(t, lid, id), v);
	};
		
	this.f_getArtworkVersion = function(p_doc_url) {
		return that.artworkVersion(pMediaLibrary.getLIDfromURL(p_doc_url), pMediaLibrary.getIDfromURL(p_doc_url));
	};
	this.artworkVersion = function(lid, id) {
		return map.getValue(that.versionListenerID('artwork', lid, id), 'null').split('|')[0];
	};
	this.artworkURL = function(lid, id) {
		var v = map.getValue(that.versionListenerID('artwork', lid, id), 'null').split('|');
		if (v.length>1 && pString.v(v[1]))
			return v[1];
	};
	
	this.artworkLibraryVersion = function(lib) {
		return map.getValue(that.versionListenerLibraryID('artwork', lib)).split('|')[0];
	};
	
	this.f_getBackdropVersion = function(p_doc_url) {
		return that.backdropVersion(pMediaLibrary.getLIDfromURL(p_doc_url), pMediaLibrary.getIDfromURL(p_doc_url));
	};
	this.backdropVersion = function(lid, id) {
		return map.getValue(that.versionListenerID('backdrop', lid, id));
	};
	this.f_getDefaultBackdropVersion = function() {
		var i_result = map.getValue('session_b_0');
		return (pString.v(i_result))? i_result : pCookieManager.xgetCookie("SID");
	};
	this.f_getBannerVersion = function(p_doc_url) {
		return that.bannerVersion(pMediaLibrary.getLIDfromURL(p_doc_url), pMediaLibrary.getIDfromURL(p_doc_url));
	};
	this.bannerVersion = function(lid, id) {
		return map.getValue(that.versionListenerID('banner', lid, id));
	};
	this.f_getPosterVersion = function(p_doc_url) {
		return that.posterVersion(pMediaLibrary.getLIDfromURL(p_doc_url), pMediaLibrary.getIDfromURL(p_doc_url));
	};
	this.posterVersion = function(lid, id) {
		return map.getValue(that.versionListenerID('poster', lid, id));
	};
	this.f_getThumbnailVersion = function(p_doc_url) {
		return that.artworkVersion(pMediaLibrary.getLIDfromURL(p_doc_url), pMediaLibrary.getIDfromURL(p_doc_url));
	};
	this.f_getImageVersion = function(p_doc_url) {
		return map.getValue('session_i_'+pMediaLibrary.getLIDfromURL(p_doc_url)+'_'+pMediaLibrary.getIDfromURL(p_doc_url));
	};
	
	this.addVersion = function(u, v) {
		var uv = pLocation.param(u, 'v');
		if (uv) {
			uv = parseInt(uv);
			v = parseInt(v);
			return (uv < v)? pURL.replaceParam(u, 'v', v) : u;
		}
		return v? pURL.addParam(u, 'v', v) : u;
	};
	
	var f_put = map.put;
	map.put = function(n, v) {
		pDocument.setHistoryState(n, v);
		f_put(n, v);
	};
});
	
window.addEventListener('storage', function(e) {
	if (e.key == 'session_b_'+pMediaLibrary.getLIDfromURL()+'_'+pMediaLibrary.getIDfromURL())
		pMediaLibrary.showBackdrop();
	if (e.key == 'session_p_'+pMediaLibrary.getLIDfromURL()+'_'+pMediaLibrary.getIDfromURL())
		pMediaLibrary.showPoster();
	if (e.key == 'session_t_'+pMediaLibrary.getLIDfromURL()+'_'+pMediaLibrary.getIDfromURL()) {
		
		var v = this.map.getValue(this.versionListenerID('artwork', lid, id)).split('|');
		if (v.length>1 && pString.v(v[1]))		
			pPage.metadata_artwork_url = url_prefix+pMediaLibrary.getLibraryIDfromURL()+'/resources/'+v[1];
		
		pMediaLibrary.showArtwork();	
	}
});

window.h_offset = 12;
window.f_main_offset = 0;
window.getInnerHeight = function() {
	return window.innerHeight;// - ((pDevice.isIOS() && !window.navigator.standalone)? 50:0);
};
window.resizeVideoImpl = function(x) {
	if (x = pElement.x(x)) {
		/*if (f_main_offset == 0) {
			h = pDocument.innerHeight('div-vlc-main');
			if (h > 0) {
				//console.log([ i_h, window.innerHeight ].join(' - '));
				f_main_offset = window.innerHeight - h_offset - h;
			}
		}
		else
			h = window.innerHeight - h_offset - f_main_offset;
			*/	
		
		var m = pElement.x('menu'), w = pElement.x('window-play');
		var top = pDocument.height('div-vlc-header');//view-item-close'), pDocument.height('view-item-title'));
		var h = getInnerHeight() - pDocument.height(m) - top - (new pStyle(w)).paddingHeight();
		
		//var v = pElement.x('window-play');
		//var top = pDocument.height('div-vlc-header');//view-item-close'), pDocument.height('view-item-title'));
		
		//var h = window.innerHeight - pDocument.top(v) - new pStyle(v).surroundHeight() - top;
		
		pConsole.debug(null, x.id+".height -> " + h);
		
		//console.log(x.id + 'height: ' + h)
		x.height = h;
		x.style.height = h+'px';
		
		
		
		h = pDocument.innerWidth('div-vlc-main');
		
		pConsole.debug(null, x.id+".width -> " + h);
		
		x.width = h;
		x.style.width = h+'px';
	}
};
window.f_resize_video = function() {
	
	var m = pElement.x('div-vlc-main');
	if (m) {
		var top = pDocument.height('div-vlc-header');
		var h = pDocument.innerHeight('window-play') - top;
		
		m.style.height = h + 'px';
	}
				
	[ 'video0-video', 'video0-youtube' ].forEach(window.resizeVideoImpl);
};
window.f_resize_window_play = function() {
	if (pDocument.componentIsShown('search-results'))
		return;
	
	var x = pElement.x('window-play');
	if (x) {
		pConsole.debug(null, "Resizing window-play...");
		pConsole.debug(null, "Window Inner Height: " + window.innerHeight);
		pConsole.debug(null, "Document Scroll Height: " + pDocument.scrollHeight());
		
		var m = pElement.x('menu');
		var h = getInnerHeight() - pDocument.height(m) - (new pStyle(x)).marginHeight();
		//console.log('h :' + h);
		//i_e.style.height = h + 'px';
		//i_e.style.maxHeight = h + 'px';
		x.style.minHeight = h + 'px';
		x.style.maxHeight = h + 'px';
		x.style.height = h + 'px';
	}	
	f_resize_video();//, 200);
};
window.f_resize_play = function() {
	pConsole.debug(null, "Resizing video and images...");
	pConsole.debug(null, "Window Inner Height: " + window.innerHeight);
	//pConsole.info(this, "main.top: " + pDocument.top(document.body.main));
	
	//var h = window.innerHeight - 156;
	var x = pElement.x('view-item');
	if (x) {
		//pConsole.info(this, x.id+".top: " + pDocument.top(x));
		
		//console.log('y: '+pElement.posY(i_e));
		//var h = window.innerHeight - (h_offset + 148);
		var m = pElement.x('menu');
		var h = window.innerHeight - pDocument.height(m) - (new pStyle(x)).marginHeight();
		//console.log('h :' + h);
		x.style.maxHeight = h + 'px';
		x.style.minHeight = h + 'px';
		x.style.height = h + 'px';
		//i_e.style.height = h + 'px';
		//pConsole.info(this, x.id + ".style.maxHeight -> " + x.style.maxHeight)
	}

	window.f_resize_window_play();
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pGoogleDrive = new (function() {
	var that = this, developerKey = 'AIzaSyCL4wuwpmDVsDzxYpdW7a8zdaudgmFG5Bc', clientId = "132272967633-gvqjr3jjoujq8v4dd7152n8ud30e11nc.apps.googleusercontent.com",
		appId = "genialist-media-server",
		scope = ['https://www.googleapis.com/auth/drive'],
		pickerApiLoaded = false,
		oauthToken;

    // Use the Google API Loader script to load the google.picker script.
    this.loadPicker = function() {
      gapi.load('auth', {'callback': that.onAuthApiLoad});
      gapi.load('picker', {'callback': that.onPickerApiLoad});
    };

    this.onAuthApiLoad = function() {
       window.gapi.auth.authorize({ 'client_id': clientId, 'scope': scope, 'immediate': false }, that.handleAuthResult);
    };

    this.onPickerApiLoad = function() {
    	pickerApiLoaded = true;
    	that.createPicker();
    };

    this.handleAuthResult = function(authResult) {
      if (authResult && !authResult.error) {
        oauthToken = authResult.access_token;
        that.createPicker();
      }
    };

    // Create and render a Picker object for searching images.
    this.createPicker = function(f_callback) {
      if (pickerApiLoaded && oauthToken) {
    	  var picker = new google.picker.PickerBuilder()
          	.addView(google.picker.ViewId.DOCS_VIDEOS)
          	.setOAuthToken(oauthToken)
          	.setDeveloperKey(developerKey)
          	.setCallback(f_callback || that.pickerCallback)
          	.build();
    	  picker.setVisible(true);
      }
    };

    // A simple callback implementation.
    this.pickerCallback = function(data) {
      if (data.action == google.picker.Action.PICKED) {
        var fileId = data.docs[0].id;
        pDialog.setInputValue('add-url', 'url', 'https://drive.google.com/open?id='+fileId);
      }
    };
});
    
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pDescription = new (function() {
	var that = this;
	var htmlTags = [ '<br', '<p' ];
	
	function formatImpl(h) {
		var i_pos = h.indexOf('&lt;a href="http://www.last.fm/');
		if (i_pos<0) i_pos = h.indexOf('&lt;a href=&quot;http://www.last.fm/');
		if (i_pos>0) {
			var i_pos2 = h.indexOf('&lt;/a&gt;', i_pos);
			if (i_pos2>0)
				return h.substring(0, i_pos) + '<a target="_blank" ' + h.substring(i_pos+2, i_pos2).replace('&lt;','<').replace('&gt;', '>').replace('&quot;', '"') + '</a>' + h.substring(i_pos2 + '&lt;/a&gt;'.length);
		}
		return h;
	}
	
	this.format = function(h) {
		//console.log(h.replace(/\&lt;p\/\&gt;/g, 'x'));
		//return h.replace(/\&lt;p\/\&gt;/g, String.fromCharCode(13, 10, 13, 10));
		
		if (h.trim()=='<!---->') 
			return '';
		h = h.replace(/<!---->/, '');
		
		if (htmlTags.find(function(x) { return h.indexOf(x)>=0; }))
			return formatImpl(h);
		
		h = h.split('\n').filter(pString.v);
		if (h.length==1) return h[0];
		
		return h.map(formatImpl).join('<br>');
	};
});

/******************************************************************************/

/**
 * The pTitle object exposes functions to manage media title information.
 * @class pTitle
 */
const pTitle = new (function() {
	var that = this;
	
	/** 
	 * Formats a title string by putting proper spaces after commas and & separator.
	 * @method format
	 * @param {String} t The title to format. 
	 */ 
	this.format = function(t) {
		//console.log('f: ' + t);
		if (t.indexOf('&')>=0)
			return t.replace(re_dhtml_amp, '&').split('&').map(that.format).join(' & ');
		return t.split(',').map(pString.trim).join(', ');
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

/**
 * The pTag object exposes functions to manage media tag information.
 * @class pTag
 */
const pTag = new (function() {
	var that = this;
	
	function f_split_and_format(t, s) { // s: separator
		return t.split(s).map(pTag.format).join(s);
	}
	
	/** 
	 * Formats a tag string by capitalizing words.
	 * @method format
	 * @param {String} tag The tag to format. 
	 * @param {Int} p The parsing position within the whole tag (when the function is called recursively internally).
	 * @param {String[]} t The array of words within the whole tag (when the function is called recursively internally).
	 */ 
	this.format = function(tag, p, t) { //p: position within whole tag, t: array of words within whole tag
		
		if (re_surround1.test(tag) || re_surround2.test(tag) || re_surround3.test(tag))
			return tag.charAt(0)+that.format(tag.substring(1, tag.length-1))+tag.charAt(tag.length-1);
		
		if (tag.indexOf(' ')>=0)
			return f_split_and_format(tag, ' ');
		if (tag.indexOf('&')>=0)
			return f_split_and_format(tag, '&');
		if (tag.indexOf('-')>=0)
			return f_split_and_format(tag, '-');
		
		if (tag.length==2 && !pString.isVowel(tag.charAt(0)) && !pString.isVowel(tag.charAt(1))) {
			var r = tag.toUpperCase();
			if (t && t.length>1 && p!=null && p<=t.length-1 && r=='ST') return 'St';
			return tag.toUpperCase();
		}
		if (tag.length==3 && !pString.isVowel(tag.charAt(0)) && !pString.isVowel(tag.charAt(1)) && !pString.isVowel(tag.charAt(2)))
			return tag.toUpperCase();
		
		return tag.charAt(0).toUpperCase() + tag.substring(1).toLowerCase();
	};
	
	/**
	 * Parses a list of tag names separated by an optional separator.
	 * @method parse
	 * @param {String} tags The string to parse.
	 * @param {String} [sep='|'] The separator.
	 * @return An array of tag names, all in lowercase, with each name trimmed (only non-empty tag names are returned).
	 */
	this.parse = function(tags, sep) {
		return pString.split((tags || '').toLowerCase()/*.replace('&amp;', '&')*/, sep || '|').sort();
	};
});

//console.log("pTag.format('tag') = " + pTag.format('tag'));
//console.log("pTag.format('[tag]') = " + pTag.format('[tag]'));

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pTVContentRating = new (function() {
	this.sortValue = function(item) { 
		if (item.tv_rating && item.tv_rating!='')
			return pString.padLeft(''+item.tv_rating_score || 0/*pContentRating.score(i_item.tv_rating)*/, 6, '0'); 
		return '000'; 
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pMetadata = new (function() {
	var that = this, 
		description_ids = [ "description", "description_long", "async_description", "async_description_long" ],
		title_ids = [ "title", "async_title" ];
	
	this.data_ids = [
			       { id: "actors", cid: 'container-actors', sep: '|' }, 
			       { id: "author", cid: 'container-author', sep: '|' }, 
			       { id: "directors", cid: 'container-directors', sep: '|' }, 
			       { id: "countries", cid: 'container-country', sep: '|' },
			       { id: "genre", cid: 'container-genre', sep: '|' },
			       { id: "lang", cid: 'container-lang', sep: '|', parse: function(text, sep) {
			    	   var r = pString.split(text, sep || ',')
			    	   if (r.length==1 && r[0].toLowerCase() == pROSE.getMainLongLang().toLowerCase())
			    		   return [];
			    	   return pApplicationUI.OPTION_SHOW_NATIVE_LANGS? r.map(pLang.display) : r;
			       }}, 
			       { id: "models", cid: 'container-models', sep: '|' }, 
			       { id: "subtitles", cid: 'container-subtitles', sep: '|', parse: function(text, sep) {
			    	   var r = pString.split(text, sep || ',')
			    	   return pApplicationUI.OPTION_SHOW_NATIVE_LANGS? r.map(pLang.display) : r;
			       }}, 
			       { id: "tags", cid: 'container-tags', sep: '|', format: pTag.format, parse: pTag.parse }, 
			       { id: "members", cid: 'container-members', sep: '|', format: 
			    	   function(t, d) {
			    	   		if (t == d.texts[0].name) {
				    	   		var title = pElement.getAttribute('name', 'org');
				    	   		if (title.startsWith(d.texts[0].name) && title.endsWith(d.texts[d.texts.length-1].name)) {
				    	   			var pi = 0;
				    	   			d.texts.forEach(function(tt) {
				    	   				var p = title.indexOf(tt.name, pi);
				    	   				if (p>=0) {
				    	   					var r = '<a class="title link" href="' + d.getURL(tt) + '">' + pString.encodeHTML(tt.name) + '</a>';
				    	   					title = title.substring(0, p) + r + title.substring(p+tt.name.length);
				    	   					pi = r.length;
				    	   				}
				    	   			});
				    	   			pElement.setInnerHTML('name', title);
				    	   		}
			    	   		}
			    	   		return t;
			       	   }}, 
			       { id: "groups", cid: 'container-groups', sep: '|' }
			   ];
	
	this.getDescription = function(p_item) {
		var i = description_ids.find(function(x) { return pString.v(p_item[x]); });
		return i? pDescription.format(p_item[i]) : '';
	};

	this.getTitle = function(p_item) {
		var i = title_ids.find(function(x) { return pString.v(p_item[x]); });
		return i? p_item[i] : '';
	};
	
	this.f_filter = function(g) {
		return pString.v(g) && g.toLowerCase() != '(unknown)';
	};
	
	this.rate = function(s) {
		var n = parseInt(s);
		if (n != 'NaN') {
			if (n>1000) return (n/1000) + ' Mbps';
			return n+' Kbps';
		}
		return s.replace(/kb[\/p]s/i, 'Kbps').replace(/mb[\/p]s/i, 'Mbps').replace(/gb[\/p]s/i, 'Gbps');
	};
	
	//this.TVContentRating
	
	this.columns = new pMap([ 
	   'rating', 
	   { 
		   th:'Rating', 
		   content: function(item) { 
			   return '<span class="rating-normal">'+(item.rating || '')+'</span>'; 
		   },
		   compare: function(x,y) { return pString.compareNotNull(x.rating, y.rating); }
	   }, 
	   'tv-content-rating', 
	   { 
		   th:(pDevice.isSmallScreen())? 'Cert':'Certificate',
		   sort: pTVContentRating.sortValue,
		   content: function(item, library_id, title, url, tooltip, i) {
			   if (item.tv_rating && item.tv_rating!='') {
					var h = '';
	
					var i_ricon = url_prefix+library_id+'/resources/html/images/ratings/'+item.tv_rating_icon;
					var d = pString.encodeHTML(item.tv_rating_description);
	
					h += '<img id="rating-td-icon-'+i+'" class="rating-td-icon" src="'+i_ricon+'" ';
					h += ' onError="pDocument.show(\'rating-td-text-'+i+'\', \'inline\'); pDocument.hide(\'rating-td-icon-'+i+'\');"';
					h += ' alt="'+d+'" ';
					if (pString.v(d)) h += ' title="'+d+'"';
					h += '/>';
					h += '<span id="rating-td-text-'+i+'" class="tv-content-rating-text" ' + (pString.v(d)? ' title="'+d+'"':'') + ' style="display: none;">'+pString.encodeHTML(item.tv_rating)+'</span>';
					//h += '<="link" href="'+url_prefix+library_id+'/music.artists/name:x?name='+pString.encodeURI(pString.decodeHTML(i_item.artist))+'">'+i_item.artist+'</a></td>';
					return h;
				}
				return '';
		   },
		   compare: function(x,y) { 
			   return pObject.compare(pTVContentRating.sortValue(x), pTVContentRating.sortValue(y)); 
		   }
	   }, 
	   'artist', { th:'Artist', content: function(item, library_id) { 
		   			return '<a class="link" href="'+url_prefix+library_id+'/music.artists/name:x?name='+pString.encodeURI(item.artist)+'">'+pString.encodeHTML(item.artist)+'</a>'; } }, 
		'album', { th:'Album', content: function(item, library_id, title, url, tooltip) {
			
			var h = '<a class="link tableview-text" href="'+url+'" title="'+pString.encodeHTMLLines(tooltip)+'">' + pString.encodeHTML(title) + '</a>';
	
			if ((''+item.adult) == "true")
				h += '<span class="td-text-adult advisory advisory-adult decorated" decorated="st-adult">Adult</span>';
	
			if (item.advisory) {
				var a = pROSE.canonicalizeAdvisory(''+item.advisory);
				if (a != '') {
					h += '<a class="td-text-advisory advisory advisory-'+a.toLocaleLowerCase()+' decorated" decorated="st-advisory-'+a.toLocaleLowerCase()+' st-advisory" ';
					h += ' target="_blank" tabindex="-1">'+pString.encodeHTML(a)+'</a>';
				}
			}
			return h;
		} }, 
		'title', { th:'Title', content: function(item, library_id, title, url, tooltip) {
			var h = '<a id="a.id.item.'+item.lid+'.'+item.id+'" class="link tableview-text" href="'+url+'" title="'+pString.encodeHTMLLines(tooltip)+'">' + pString.encodeHTML(title) + '</a>';
			h += '<span class="playlist-title-icons">';
			h += pMetadata.renderTablePC(item);
			h += '</span>';
			return h;
		} }, 
		'year', { th:'Year', content: function(item, library_id) { return item.year || ''; }, compare: function(x,y) { return pString.compareNotNull(x.year, y.year); } }, 
		'length', { th:'Runtime', content: function(item, library_id) { return item.duration || '0'; }, compare: function(x,y) { return pString.compareNotNull(x.duration || '0', y.duration || '0'); } },
		'lang', { th:'Lang', content: function(i) { return pLang.display(i.lang); }, compare: function(x,y) { return pString.compareNotNull(x.lang, y.lang); } },
		'countries', { th:'Countries', content: function(i) { return i.countries || ''; }, compare: function(x,y) { return pString.compareNotNull(x.countries, y.countries); } },
	], true);
	
	this.renderTablePC = function(i_item) {
		var h = '';
		
		if ((''+i_item.adult) == "true")
			h += '<span class="advisory advisory-adult">Adult</span>';

		if (i_item.advisory) {
			var a = pROSE.canonicalizeAdvisory(''+i_item.advisory);
			if (a != '') {
				h += '<a class="advisory advisory-'+a.toLocaleLowerCase()+'" ';
				h += ' href="http://en.wikipedia.org/wiki/Parental_Advisory" title="Click to learn more about Parental Advisory warning label..." target="_blank">'+a+'</a>';
			}
		}
		return h;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pMetadataDialog = new (function() {
	var that = this;
	
	this.c_dropImage = function(f, u) {
		if (f) {
			if (f[0] && pApplicationUI.acceptImageFile(f[0]) && pApplicationUI.acceptImageFileSize(f[0]))
				pFile.readAsDataURL(f[0], addImageDataURL);
		}
		else if (u)
			that.addImageURL(null, u, true);
	};
	
	function addImageDataURL(f, u) {
		that.addImageURL(null, u, true, f? f.name : null, f? f.lastModified : null);
	}

	function removeImageURL(p_dialog, p_url) {
		if (!p_url)
			return;

		var i_dialog = pDialog.getValue('properties');
		if (i_dialog && i_dialog.request)
			i_dialog = i_dialog.request;

		if (i_dialog.artworks)
			i_dialog.artworks.remove(p_url);

		pDialog.removeDialogOption(i_dialog.id, p_url);
		i_dialog.artworkChanged = true;
	}
	
	this.addImageURL = function(p_dialog, p_url, p_user, p_name, p_lastModified) {
		//if (p_file && p_url)
		//	p_url = p_file;
		//if (p_file &&
		if (!p_url)
			return;

		var i_dialog = pDialog.getValue('properties');
		if (i_dialog && i_dialog.request)
			i_dialog = i_dialog.request;

		var i_img = pImages.createImage(p_url, { image: { name: p_name, lastModified: p_lastModified }}), i_op = {
			id: ''+Math.random(),
			value: p_url,
			image: i_img,
			disabled: true
		};
		i_op.dcall = function() { removeImageURL(null, this.op_id); }.bind({ op_id: i_op.id })

		if (!i_dialog.artworks)
			i_dialog.artworks = new pMap();
		i_dialog.artworks.put(i_op.id, i_img.uri);

		i_op.image.onerror = function(p_img) {
			pConsole.error(that, 'Failed to load image: ' + p_img.src);

			this.dialog.artworks.remove(this.op.id);

			pImages.download(this.doc_url, p_img.src).then(function(p_new_url) {
			
				this.op.value = p_new_url;
				this.dialog.artworks.put(this.op.id, p_new_url);
				this.img.onerror = null;
				this.img.src = p_new_url;

			}.bind({ dialog: this.dialog, img: p_img, op: this.op }));

		}.bind({ doc_url: i_dialog.doc_url, dialog: i_dialog, op: i_op });

		pDialog.addDialogOption('properties', 'tab-artworks', i_op);//, 'select-artwork-image-1');
		if (p_user)
			i_dialog.artworkChanged = true;
	}

	this.submit = function(p_dialog, i_data) {
			//pDocument.wait();
			var i_body = pURL.toFormSubmitBody(p_dialog.inputs, that.transformToServer), i_artwork = false;
			if (p_dialog.artworks) {
				if (p_dialog.artworkChanged)
					for(var i=0 ; i<p_dialog.artworks.length ; i++) {
						//console.log('artwork: ' + p_dialog.artworks[i]);
						if (!p_dialog.artworks.valueAt(i)) continue;
	
						i_body = pURL.addBodyParameter(i_body, 'metadata_artwork', p_dialog.artworks.valueAt(i));
						i_artwork = true;
						break;
					}
				if (/*i_data.org_artworks.length>0 ||*/ p_dialog.artworks.length<1 && i_artwork === false && p_dialog.artworkChanged)
					i_body = pURL.addBodyParameter(i_body, 'remove_artwork', 1);
			}

			//if (p_dialog.inputs.getValue('rename_file', false) === true)
			//	i_body = pURL.addBodyParameter(i_body, 'filename_new', p_dialog.inputs.getValue('filename_new', ''));

			if (i_data.doc_url.indexOf('/pictures.groups/')>0 || i_data.doc_url.indexOf('/books.groups/')>0)
				i_body = pURL.addBodyParameter(i_body, 'children', 0);
			else if (i_data.children === true)
				i_body = pURL.addBodyParameter(i_body, 'children', 1);
			
			var i_msg_start = 'Updating file properties...';
			if (i_data.doc_url.indexOf('/tvshows.seasons/')>0)
				i_msg_start = 'Updating properties...';
			
			pTransaction.simpleTransaction({
				trace: that, 
				url: pURL.addQueryParameter(i_data.doc_url, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(), 'children', i_data.children ]), 
				method_name: "SetMetadata",
				msg_start: i_msg_start,
				msg_end: [ i_msg_start+' Done.', i_data.c_deleted, that.c_completed ],//,createDeletedCheck(i_data.doc_url)/*pMediaLibrary.onDocCreated,*/  ],
				headers: pHTTPRequest.headers_post,
				body: i_body,
			});
	};

	var m_transform_types = [
			'metadata_genre', 'metadata_actors', 'metadata_directors', 'metadata_author', 'metadata_producers',
			'metadata_models', 'metadata_tags', 'metadata_remixer', 'metadata_studios', 'metadata_countries', 'metadata_members', 'metadata_groups',
			'http_nonProxyHosts'
		];

	var m_season = /^Season [0-9]{1,3}$/;
		
	this.transformFromServer = function(p_id, v) {
			if (!v)
				return v;

			if (v == '(Unknown)')
				return '';

			if (m_transform_types.indexOf(p_id) >= 0)
				return pString.concat(pString.split(v, '|'), ', ');
			
			if (p_id == 'metadata_season')
				if (m_season.test(v))
					return v.substring('Season '.length);
			
			return v;
	};

	this.transformToServer = function(p_id, p_value) {
		if (null==p_value)
			return '';

		if (m_transform_types.includes(p_id)) {
			var v = pString.split(p_value, ',');
			return pString.concat(v, '|', v.length > 1);
		}
		return p_value;
	};

	//m_ids: [ "actors", "author", "directors", "genre", "lang", "models", "subtitles", "tags", "members", "groups" ],
		
	function c_completed_processItem(p_item, xid_prefix) {
		pMetadata.data_ids.forEach(function(i_id) {
				var i_value = p_item[i_id.id];
				if (i_value) {
					if (!xid_prefix)
						pPage.change("metadata_"+i_id.id, i_value);
	
					var x = pElement.x(xid_prefix+i_id.id);
					if (x) {
						if (i_value!='') {
							if (i_value != pElement.getAttribute(x, 'data-text'))
								if (pElement.setAttribute(x, 'data-text', i_value)) {
									pDocument.renderEllipsisText(i_id, null, null, true);//x, 'container-'+i_id, '|', true);
								}
						}
						else {
							i_value = p_item["async_"+i_id.id];
							if (i_value) {
								pPage["async_metadata_"+i_id.id] = i_value;
								if (pElement.setAttribute(i_id.id, 'data-text', i_value)) {
									pDocument.renderEllipsisText(i_id);
									pDocument.m_renderEllipsisTextImpl();
								}
							}
						}
					}
				}
			});

			var i_value = pMetadata.getDescription(p_item);
			if (i_value != '') {
				pElement.setInnerHTML(xid_prefix+"container-description", i_value);//pString.encodeHTML(i_value));
				pElement.setInnerHTML(xid_prefix+"container-description2", i_value);//pString.encodeHTML(i_value));
			}

			if (this && this.option_title) {
				i_value = pMetadata.getTitle(p_item);
				if (i_value != '') {
					i_value = pTitle.format(pString.encodeHTML(i_value));
					pElement.setTextContent(xid_prefix+"container-title-a", i_value);
					pElement.setTextContent(xid_prefix+"container-title-unavailable", i_value);
				}
			}

			if (!xid_prefix)
				pPage.change("async_metadata_image_uris", p_item["async_metadata_image_uris"]);
	}

	var c_completedImpl = new pSharedCallback('pMetadataDialogInstance.c_completedImpl', function(i_obj, options){

			options = options || {};
			
			//0.9.18
			if (i_obj.actions)
				(i_obj.others || []).forEach(function(o) {
					var a = i_obj.actions.findIndex(function(a) { return a.object.lid == o.lid && a.object.id == o.id });
					if (a>=0)
						i_obj.actions[a].object = o;
				});
			
			var xid_prefix = (options.xid_prefix)? options.xid_prefix+'-' : '';
			var i_done = false;
			var i_path = options.path || window.location.pathname;
			if (!i_path.endsWith('/')) i_path += '/';

			if (!i_done && i_obj.actions && i_obj.actions.length>0) {
				var i_action = i_obj.actions.find(function(a) {
					if (options.mlid) {
						return a.object.lid+'/'+a.object.id == options.mlid;
					}
					else {
						return pMediaLibrary.getDocURL(null, a.object.lid, a.object.id) == i_path;
					}
				});
				if (i_action) {
					c_completed_processItem(i_action.object, xid_prefix, options);
					i_done = true;
				}
			}

			if (!options.mlid && !i_done && i_obj.others && i_obj.others.length>0) {
				var i_action = i_obj.others.find(function(o) {
					return pMediaLibrary.getDocURL(null, o.lid, o.id) == i_path;
				});
				if (i_action) {
					c_completed_processItem(i_action, xid_prefix, options);
					i_done = true;
				}
			}

			if (options.mlid && !i_done && i_obj.others && i_obj.others.length>0) {
				var mlid = options.mlid, i_action = i_obj.others.find(function(i_obj) {
					return i_obj.lid + '/' + i_obj.id == mlid;
				});
				if (i_action) {
					c_completed_processItem(i_action, xid_prefix, options);
					i_done = true;
				}
			}
			
			var i_other = null;
			if (i_obj.others && i_obj.others.length>0)
				i_other = i_obj.others.find(function(o) {
					return pMediaLibrary.getDocURL(null, o.lid, o.id) == i_path;
				});
			
			if (options.option_showArtwork) {
				if (i_other && i_other.thumbnail_url) 
					pImageVersions.update('artwork', i_other.lid, i_other.id, pLocation.param(i_other.thumbnail_url, 'v'));
				pMediaLibrary.showArtwork();
			}
			if (options.option_showBackdrop) {
				if (i_other && i_other.backdrop_url) 
					pImageVersions.update('backdrop', i_other.lid, i_other.id, pLocation.param(i_other.backdrop_url, 'v'));
				pMediaLibrary.showBackdrop();
			}
			if (options.option_showPoster) {
				if (i_other && i_other.poster_url) 
					pImageVersions.update('poster', i_other.lid, i_other.id, pLocation.param(i_other.poster_url, 'v'));
				pMediaLibrary.showPoster();
			}
			if (options.option_showBanner) {
				if (i_other && i_other.banner_url) 
					pImageVersions.update('banner', i_other.lid, i_other.id, pLocation.param(i_other.banner_url, 'v'));
			//	pMediaLibrary.showBanner();
			}
			
			if (options.option_showImage) {
				var u = pElement.getSrc(xid_prefix+'image');
				if (null!=u)
					pElement.setSrc(xid_prefix+'image', pURL.addQueryParameter(u, 'v', Math.random()));

				u = pElement.getSrc(xid_prefix+'image2');
				if (null!=u)
					pElement.setSrc(xid_prefix+'image2', pURL.addQueryParameter(u, 'v', Math.random()));
			}
			
			if (i_other) {
				var i = [ 'imdb', 'tmdb', 'ttvdb', 'discogs' ];
				var u = [ 'http://www.imdb.com/title/', 'http://www.themoviedb.org/movie/', 'http://www.thetvdb.com/?tab=series&id=', 'http://www.discogs.com/artist/' ];
				var ids = pString.split(i_other.metadata_ids + ' imdb:'+(i_other.metadata_imdb_id || '') + ' tmdb:'+(i_other.metadata_tmdb_id || ''), ' '); 
				var aids = pString.split(i_other.async_metadata_ids + ' imdb:'+(i_other.async_metadata_imdb_id || '') + ' tmdb:'+(i_other.async_metadata_tmdb_id || ''), ' ');
				
				i.forEach(function(id, i) {
					var f = ids.find(function(f) { return f.startsWith(id+':') && f.length > id.length+1 });
					if (f) {
						var url = u[i]+f.substring(id.length+1);
						var x = pElement.x('toolbar-'+id);
						if (x) { 
							x.onclick = function() { window.open(url, '_blank') };
							pDocument.show(x, 'inline-block');
						}
					}
					else {
						f = aids.find(function(f) { return f.startsWith(id+':') && f.length > id.length+1 });
						if (f) {
							var url = u[i]+f.substring(id.length+1);
							var x = pElement.x('toolbar-'+id);
							if (x) { 
								x.onclick = function() { window.open(url, '_blank') };
								pDocument.show(x, 'inline-block');
							}
						}
						else
							pDocument.hide('toolbar-'+id);
					}
				});
			}
		})
	;
 
	this.c_completed = function(p_request, p_options){
		var i_obj = pJSON.parse(p_request.responseText);
		c_completedImpl.call(that, i_obj, p_options);
	};

	this.list_01_to_99 = [ { text: '', value: ''}];
	for(var i=1 ; i<1000; i++)
		this.list_01_to_99.push({ text: ((i<10)? '0':'')+i, value: ((i<10)? '0':'')+i });

	this.list_0_to_99 = [ { text: '', value: ''}];
	for(var i=0 ; i<100; i++)
		this.list_0_to_99.push({ text: ''+i+((i==0)? ' (Specials, OAVs, OVAs, ...)':''), value: ''+i });
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pMediaLibrary = new (function() {
	var that = this;
	this.tracePrefix = 'MediaLibrary';

	function notifyDeletion() {
		pListManager.notify(pListManager.EVENT_REMOVED_ITEM);
	}
	
	/*this.vprivate = {
		
		removeImage: function(p_opid) {
			var i_data = this;
			pDialog.removeDialogOption(i_data.dialog_id, p_opid);
		},
		addImage: function(p_files, p_url) {
			var i_data = this;

			if (p_files)
				for(var i=0 ; i<p_files.length ; i++)
					if (p_files[i])
						pFile.readAsDataURL(p_files[i], function(p_file, p_bin) {
							var i_data = this;

							//TODO: check if mime type is image
							pDialog.insertDialogOption(i_data.dialog_id, 'content', {
								id: p_bin,
								value: p_bin,
								image: { uri: p_bin },
								//title: ''+((i_item.width)? i_item.width + ' x ' + i_item.height : '') + ((i_item.lang)? '&lt;br&gt;'+i_item.lang:''),
								fcall: i_data.fcall
							}, 'select-image-1');
						}.bind(i_data));
			else if (p_url) {
				pDialog.insertDialogOption(i_data.dialog_id, 'content', {
					id: p_url,
					value: p_url,
					image: { uri: p_url },
					//title: ''+((i_item.width)? i_item.width + ' x ' + i_item.height : '') + ((i_item.lang)? '&lt;br&gt;'+i_item.lang:''),
					fcall: i_data.fcall
				}, 'select-image-1');
			}
		},
		addSelectImage: function(p_files, p_url) {
			var i_data = this;

			if (p_files)
				p_files.forEach(function(f) {
					if (f)
						pFile.readAsDataURL(f, function(f, p_bin) {
							var i_data = this;

							//TODO: check if mime type is image
							pDialog.addDialogOption(i_data.dialog_id, 'tab-more', {
								id: p_bin,
								value: p_bin,
								image: { uri: p_bin },
								//title: ''+((i_item.width)? i_item.width + ' x ' + i_item.height : '') + ((i_item.lang)? '&lt;br&gt;'+i_item.lang:''),
								fcall: i_data.fcall
							}, 'select-image-1');
						}.bind(i_data));
				});
			else if (p_url) {
				pDialog.addDialogOption(i_data.dialog_id, 'tab-more', {
					id: p_url,
					value: p_url,
					image: { uri: p_url },
					//title: ''+((i_item.width)? i_item.width + ' x ' + i_item.height : '') + ((i_item.lang)? '&lt;br&gt;'+i_item.lang:''),
					fcall: i_data.fcall
				}, 'select-image-1');
			}
		}
	};*/
	
	function createDeletedCheck(p_url) {
		return function(i_request) {
		
			if (pLocation.path(this.url) == window.location.pathname) {
				pHTTPRequest.head({ url: this.url, f404: [ pApplicationUI.askBackAndReload, notifyDeletion ] });
				return;
			}
			pHTTPRequest.head({ url: window.location.pathname, f404: [ pApplicationUI.askBackAndReload, notifyDeletion ] });
		}.bind({ url: p_url });
	}
	
	this.addRadioToLibrary = function(p_library_url, p_url, title) {
		if (p_url && title)
			pTransaction.simpleTransaction({
				trace: that, 
				url: pURL.addQueryParameter(this.library_url || that.getLibraryURL(), [ 'action', 'add-url', 'url', p_url, 'v', Math.random(), 'wait', 1 ]), 
				method_name: "AddURLToLibrary",
				msg_start: 'Adding Radio '+title+' to library...',
				msg_end: [ 'Adding Radio '+title+' to library... Done.', that.onDocCreated ],
				headers: pHTTPRequest.headers_post,
				body: pURL.toFormSubmitBody([ 'metadata_type', 'Radio', 'metadata_title', title ]),
			});
		else
			pDialog.dialogQuestion({
				id: 'add-radio',
				options: [
				    //{ id: 'google-drive', text: 'Google Drive', scall: pGoogleDrive.loadPicker },
				    'Add an URL poiting to a Web Radio flux URL...<p>',
				    { id: 'title', input: { required: true, spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false }, text: 'Radio Name' },
					{ id: 'url', input: { 
						required: true, spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false,
						/*icons:[ {
							className: 'dialog-question-button-google-drive',
							title: 'Click here to select a video from Google Drive...',
							onclick: function(op_id){
								pGoogleDrive.loadPicker(function(data) {
									if (data.action == google.picker.Action.PICKED) 
										pDialog.setInputValue('add-url', 'url', 'https://drive.google.com/open?id='+data.docs[0].id);
								});
							}
						}]*/
					}, text: 'URL' },
					'ok',
					'cancel'
				],
				fcall: function(p_dialog, p_value) {
					that.addRadioToLibrary(this.library_url, p_dialog.inputs.getValue('url', ''), p_dialog.inputs.getValue('title', ''));
				}.bind({ library_url: p_library_url })
			});
	};

	this.addURLToLibrary = function(p_library_url, p_url, p_doc_lid, p_doc_id) {
		if (p_url) {
			var n = pNotificationUI.addNotification('Adding URL .../'+pString.basename(p_url)+' to library...', 'start');
			pPageParser.parse(p_url).then(function(p_values) {
				var t = p_values.getValue('metadata_title') || '.../'+pString.basename(p_url), y = p_values.getValue('metadata_year');
				if (y) t += ' ('+y+')';
				pNotificationUI.removeNotification(n);
				pTransaction.simpleTransaction({
					trace: that, 
					url: pURL.addQueryParameter(this.library_url || that.getLibraryURL(), [ 'action', 'add-url', 'doc_id', p_doc_id, 'doc_lid', p_doc_lid, 'url', this.url, 'v', Math.random(), 'wait', 1 ]), 
					method_name: "AddURLToLibrary",
					msg_start: 'Adding URL '+t+' to library...',
					msg_end: [ 'Adding URL '+t+' to library... Done.', that.onDocCreated ],
					headers: pHTTPRequest.headers_post,
					body: pURL.toFormSubmitBody(p_values),
				});
			}.bind({ url: p_url, library_url: p_library_url }));
		}
		else
			pDialog.dialogQuestion({
				id: 'add-url',
				options: [
				    //{ id: 'google-drive', text: 'Google Drive', scall: pGoogleDrive.loadPicker },
				    'Add an URL poiting to a web-site containing videos or movies, such as YouTube, Amazon Video, etc...<p>',
				    'The web-site will be loaded inside your web-browser for analysis before beging added into the libary...<p>',
					{ id: 'url', input: { 
						required: true, spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false,
						/*icons:[ {
							className: 'dialog-question-button-google-drive',
							title: 'Click here to select a video from Google Drive...',
							onclick: function(op_id){
								pGoogleDrive.loadPicker(function(data) {
									if (data.action == google.picker.Action.PICKED) 
										pDialog.setInputValue('add-url', 'url', 'https://drive.google.com/open?id='+data.docs[0].id);
								});
							}
						}]*/
					}, text: 'URL' },
					'ok',
					'cancel'
				],
				fcall: function(p_dialog, p_value) {
					that.addURLToLibrary(this.library_url, p_dialog.inputs.getValue('url', ''));
				}.bind({ library_url: p_library_url })
			});
	};

	var indexLids = [ { lid: "music.albums", x: "latest-albums" },
						 { lid: "movies.movies", x: "latest-movies" },
						 { lid: "tvshows.seasons", x: "latest-tvshows" },
						 { lid: "books.medias", x: "latest-books" },
						 { lid: "pictures.medias", x: "latest-pictures" }];
	
	this.onDocCreated = function(p_request) {
		var i_obj = pJSON.parse(p_request.responseText);
		if (i_obj.actions && i_obj.actions.length>0) {

			//0.9.8
			var lids = pArray.clone(indexLids);
			i_obj.actions.forEach(function(a) {
				lids.forEach(function(l) {
					if (!l.done)
						if (a.object.lid == l.lid)
							if (pElement.x(l.x)) {
								loadLatest(l.x, false, true);
								l.done = true;
							}
				});
			});
			/*
			var lids = indexLids.map(function(i) { return { lid: i.lid, x: i.x };});
			i_obj.actions.forEach(function(i_action) {
				lids.forEach(function(i_lid) {
					if (!i_lid.done)
						if (i_action.object.lid == i_lid.lid)
							if (pElement.x(i_lid.x)) {
								loadLatest(i_lid.x);
								i_lid.done = true;
							}
				});
			});*/
		}
	};

	this.refreshIndex = function(p_request) {

		indexLids.forEach(function(i_lid) {
			if (pElement.x(i_lid.x))
				loadLatest(i_lid.x);
		});
	};

	this.addFileToLibrary = function(p_library_url, p, p_options) {
		if (p) {
			pTransaction.simpleTransaction({
				trace: that, url: pURL.addQueryParameter(p_library_url || that.getLibraryURL(), [ 'action', 'add-file', 'path', p, 'v', Math.random(), 'wait', 1, 'log', 1, 'log_actions', 1, 'template', 'object-min.json' ]), method_name: "AddFileToLibrary",
				msg_start: 'Adding File .../'+pString.basename(p)+' to library...',
				msg_end: [ 'Adding File .../'+pString.basename(p)+' to library... Done.', that.onDocCreated ],
				interval: [ { ms: 5000, func: that.refreshIndex } ],
				headers: pHTTPRequest.headers_post,
				body: p_options,
			});
		}
		else {
			var i_library_url = p_library_url;
			
			pOpenFileDialog.selectFile({
				title: 'Add File into Library...',
				icon: { src: '/resources/html/images/32x32/page_add.png' },
				callback: function(p) {
					pUserJobManager.getImportOptions(function(p_options) {
						that.addFileToLibrary(i_library_url, p, p_options);
					});
				}
			});
		}
	};

	this.addFolderToLibrary = function(p_library_url, p, p_options) {
		if (p) {
			pTransaction.simpleTransaction({
				trace: that, 
				url: pURL.addQueryParameter(p_library_url || that.getLibraryURL(), [ 'action', 'add-file', 'path', p, 'v', Math.random(), 'wait', 1, 'log', 1, 'log_actions', 1, 'template', 'object-min.json' ]), 
				method_name: "AddFolderToLibrary",
				msg_start: 'Adding Folder .../'+pString.basename(p)+' to library...',
				msg_end: [ 'Adding Folder .../'+pString.basename(p)+' to library... Done.', that.onDocCreated ],
				interval: [ { ms: 5000, func: that.refreshIndex } ],
				headers: pHTTPRequest.headers_post,
				body: p_options,
			});
		}
		else {
			var i_library_url = p_library_url;
		
			pOpenFileDialog.selectFolder({
				title: 'Add Folder into Library...',
				icon: { src: '/resources/html/images/32x32/folder_add.png' },
				callback: function(p) {
					pUserJobManager.getImportOptions(function(p_options) {
						that.addFolderToLibrary(i_library_url, p, p_options);
					});
				},
				ok_title: "Import this Folder"
			});
		}
	};

	this.createPlaylistDialogOption = function(i_data, i_item) {
		return {
			id: i_item.id,
			value: i_item.id,
			className: 'select-playlist-item',
			text: pString.basename(i_item.name) + ((i_item.owner_fullname)? " ("+i_item.owner_fullname+")" : ""),
			fcall: function(p_dialog, p_value) {
				var i_data = this.data;
				that.addToPlaylist(i_data.doc_url, p_value, this.name);
			}.bind({ data: i_data, name: pString.basename(i_item.name) })
		}
	};
	
	this.addToLibraryPlaylist = function(p_doc_url) {

		pDocument.wait();

		var i_data = {
			library_id: this.getLibraryIDfromURL(p_doc_url),
			doc_url: p_doc_url || window.location.pathname
		};

		var i_url = url_prefix+i_data.library_id+'/playlists.medias/?format=json&sort=name&v=' + Math.random();
		pHTTPRequest.get(i_url, function(i_request, i_data) {

			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;

			//try {
				var i_obj = pJSON.parse(i_request.responseText);

				var i_dialog = { id: 'add-to-playlist', title: "Add to playlist...", options: ['cancel'] };
				i_obj.reply.forEach(function(i_item) {
					if (i_data.doc_url != that.getDocURL(i_data.library_id, i_item.lid, i_item.id))
						if (i_item.owner == pResources.get('login'))
							i_dialog.options.push(that.createPlaylistDialogOption(i_data, i_item));
				});
				i_dialog.footer = [
					{ id: 'dialog-new', text: (pDevice.isSmallScreen())? '+':'<div class="toolbar-create-playlist">New...</div>', className: 'dialog-footer-button-new dialog-footer-button noselect', scall: function(p_dialog, value) {
						var i_data = this;
						
						that.createPlaylist().then(function() {

							pDialog.clearDialogOptions('add-to-playlist', 'content');

							var i_url = url_prefix+i_data.library_id+'/playlists.medias/?format=json&sort=name&v=' + Math.random();
							pHTTPRequest.get(i_url, function(i_request, i_data) {

								//handle errors
								if (pROSE.handleHTTPResponse(i_request) === false)
									return false;

								//try {
									var i_obj = pJSON.parse(i_request.responseText);

									i_obj.reply.forEach(function(i_item) {
										if (i_data.doc_url != that.getDocURL(i_data.library_id, i_item.lid, i_item.id))
											if (i_item.owner == pResources.get('login'))
												pDialog.addDialogOption('add-to-playlist', 'content', that.createPlaylistDialogOption(i_data, i_item));
									});
								//}
							}, false, i_data);
						});
						
					}.bind(i_data) }];

				pDocument.stopwait();
				pDialog.dialogQuestion(i_dialog);
			//}
		}, false, i_data);
	};

	this.addToPlaylist = function(p_doc_url, p_playlist_id, p_playlist_name, callback) {
		var i_playlist_url = that.getDocURL(that.getLibraryIDfromURL(pLocation.path(p_doc_url)), 'playlists.medias', p_playlist_id);
		pTransaction.simpleTransaction({
			trace: this, url: pURL.addQueryParameter(p_doc_url, [ 'action', 'add-to-playlist', 'playlist-id', p_playlist_id, 'v', Math.random(), 'wait', 1 ]), method_name: "AddToPlaylist",
			msg_start: 'Adding selected media(s) to playlist <a href="'+i_playlist_url+'">'+p_playlist_name+'</a>...',
			msg_end: callback
		});
	};

	this.updateMetadataDialogField = function(p_id) {
		pElement.updateValue('dialog-input-'+p_id, ''+tmp_properties.async.getValue(p_id, ''));

		//*** UPDATE FILENAME
		that.suggestFilename();
	};

	var tmp_properties;
	this.getMetadata = function(p_children, p_doc_url, p_artwork_url, p_other) {

		/*[ '2017-08-23T22:41:27.838+0100', '2017-08-23T22:41:27.838', '2017-08-23T22:41:27+0100', '2017-08-23T22:41:27.838+01:00' ].forEach(function(d) {
		try {
			var dd =new Date(d);
			alert('dd: '+d+' -> ' + dd);
		}
		catch (ex) {
			alert('Invalid: ' + d);
		}});*/
		
		if (!pApplicationUI.canShowDialog())
			return;

		pDocument.wait();
		try {
			var i_data = {
				children: p_children || false,
				url: p_doc_url || window.location.pathname,
				org_artworks: [ p_artwork_url ],
				other: p_other || {}
			};
			i_data.doc_url = i_data.url;
			i_data = that.prepareRefreshRequest(i_data);

			var i_url = i_data.url+'?action=get-metadata&format=json&contents=values&v='+Math.random()+'&children='+p_children;

			pConsole.info(this, "GetMetadata: " + i_url);
			pHTTPRequest.get(i_url, function(i_request, i_data) {

				try {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				var i_lid = that.getLIDfromURL(i_data.doc_url), i_obj = pJSON.parse(i_request.responseText);
				//alert(pJSON.pretty(i_request.responseText));
				pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));

				var i_values = new pMap(i_obj.values);
				i_obj.values.forEach(function(v, i) { if (v.see) i_values.put(i_obj.values[i-1], i_values.getValue(v.see)); });

				var i_ctype = i_values.getValue('metadata_type', ''), i_types = null;
				if (i_obj.types) {
					i_types = { type: 'select', options: [], onchange: that.suggestFilename };
					i_types.options = i_obj.types.map(function(i_type) { return { text: i_type, value: i_type, selected: i_ctype == i_type }});
				}

				var i_clang = i_values.getValue('metadata_lang', ''), i_langs = null;
				if (i_obj.langs) {
					i_langs = { type: 'select', options: [], onchange: that.suggestFilename };
					i_langs.options = i_obj.langs.map(function(l) { return { text: pLang.display(pLang.getISOName(l)), value: l, selected: i_clang == l }});
				}

				var i_slang = i_values.getValue('metadata_lang_subtitles', ''), i_slangs = null;
				if (i_obj.langs) {
					i_slangs = { type: 'select', options: [], onchange: that.suggestFilename };
					i_slangs.options = i_obj.langs.map(function(l) { return { text: pLang.display(pLang.getISOName(l)), value: l, selected: i_slang == l }});
				}
				
				var i_readonly = i_obj.file && i_obj.file.exists === false, i_tags_icons = [];
				if (!i_readonly && (i_data.children || [ 'movies.medias', 'movies.movies', 'homevideos.movies', 'homevideos.medias', 'pictures.medias', 'books.medias' ].includes(i_lid)))
					i_tags_icons.push({
						className: 'dialog-question-button-select',
						title: 'Click here to select the value from the library...',
						onclick: function(op_id){
							that.selectTags(i_data.doc_url, pElement.getValue('dialog-input-'+op_id).split(',').join('|'), 'auto', function(p_dialog, p_value){
								pElement.updateValue('dialog-input-'+this.opid, p_value.map(pTag.format).join(', '));
							}.bind({ opid: op_id }));
						}
					});
				
				var i_dialog = {
					id: 'properties',
					title: i_data.other.title || 'Properties',
					icon: i_data.other.icon,
					artworks: i_data.artworks,
					doc_url: i_data.doc_url,
					select_input: i_data.children,
					//TODO: for properties(all) readonly if one of the file is unavailable...
					readonly: i_readonly,
					//destroy: false,
					//submitChangedInputsOnly: true, NO!!!
					options: [
						(i_readonly)? null : { id: 'ok', text: 'Save Changes' },
						{ id: 'cancel', text: (i_readonly)? 'Close':'Cancel' }
					],
					fcall: function(p_dialog) { pMetadataDialog.submit(p_dialog, i_data); }
				};
					
				//*** FILE
				if (i_obj.file) {
					var fn = i_obj.file.name;
					pArray.append(i_dialog.options, [
	
						{ id: 'file', tab: true, text: 'File', className: (i_obj.file.exists === false)? 'dialog-tab-title-file-not-found':'' },
						(i_obj.file.exists === false)? { id: 'file-not-found', sep: true, text: 'File not found...', className: 'dialog-file-not-found' } : null,
						{ id: 'file-name', input: { readonly: true }, text: 'Name:', value: fn.endsWith(i_obj.file.ext)? fn : (i_obj.file.ext? fn+'.'+i_obj.file.ext : fn) },
						'<hr>',
						(i_obj.file.os_type)? { id: 'file-type', input: { readonly: true }, text: 'Type of File:', value: i_obj.file.os_type } : null,
						(i_obj.file.os_type)? '<hr>' : null,
						{ id: 'file-location', input: { readonly: true }, text: 'Location:', value: pString.dirname(i_obj.file.path) }
					]);
					
					if (i_obj.file.exists!=false)
						pArray.append(i_dialog.options, [
							{ id: 'file-size', input: { readonly: true }, text: 'Size:', value: pHumanText.toByteSize(i_obj.file.size) },
							'<hr>',
							(i_obj.file.lastCreated)? { id: 'file-lastCreated', input: { type: 'datetime-local', readonly: true }, text: 'Created:', value: pDate.create(i_obj.file.lastCreated) } : null,
							(i_obj.file.lastModified)? { id: 'file-lastModified', input: { type: 'datetime-local', readonly: true }, text: 'Modified:', value: pDate.create(i_obj.file.lastModified) } : null,
							(i_obj.file.lastAccessed)? { id: 'file-lastAccessed', input: { type: 'datetime-local', readonly: true }, text: 'Accessed:', value: pDate.create(i_obj.file.lastAccessed) } : null
						]);
					
					pArray.append(i_dialog.options, [
						'<hr>',
						{ id: 'metadata_provider', input: { readonly: true }, text: 'Provider:', value: i_obj.metadata_provider },
						{ id: 'url', input: { type: 'link', readonly: true }, text: 'URL:', value: i_obj.url, style: 'padding-bottom: 10px;' },
	
						(i_data.children === false)? { id: 'metadata_publisher', input: { readonly: true }, text: 'Publisher:', value: i_obj.metadata_publisher } : null,
						(i_data.children === false)? { id: 'metadata_copyright', input: { readonly: true }, text: 'Copyright:', value: i_obj.metadata_copyright } : null,
						(i_data.children === false && i_values.getValue('metadata_right_usage_terms'))? { id: 'metadata_right_usage_terms', input: { readonly: true }, text: 'Right Usage Terms:', value: i_obj.metadata_right_usage_terms } : null,
										
						'<hr>',
						{ id: 'metadata_purchase_account', input: { readonly: true }, text: 'Purchase Account:', value: i_obj.metadata_purchase_account },
						{ id: 'metadata_purchase_account_type', input: { readonly: true }, text: 'Purchase Account Type:', value: i_obj.metadata_purchase_account_type },
						{ id: 'metadata_purchase_country', input: { readonly: true }, text: 'Purchase Country:', value: i_obj.metadata_purchase_country },
						{ id: 'metadata_purchase_date', input: { type: 'datetime-local', readonly: true }, text: 'Purchase Date:', value: pDate.create(i_obj.metadata_purchase_date) }
					]);
				} //if (i_obj.file)
				
				//*** LIBRARY
				if (!i_data.children) {
					pArray.append(i_dialog.options, [
						{ id: 'tab-library', tab: true, text: 'Library' },
						{ id: 'library-id', input: { readonly: true }, text: 'ID:', value: i_data.doc_url },
						((i_values.getValue('name', null))? { id: 'name', input: { readonly: true }, text: 'Name:', value: i_values.getValue('name', null) } : null),
						'<hr>',
						{ id: 'library-added', input: { type: 'datetime-local', readonly: true }, text: 'Added:', value: pDate.create(i_obj.added) },
						{ id: 'library-lastModified', input: { type: 'datetime-local', readonly: true }, text: 'Modified:', value: pDate.create(i_obj.lastModified) }
					]);
					
					if (i_values.getValue('metadata_library_owner'))
						pArray.append(i_dialog.options, [
							'<hr>',
							{ id: 'metadata_library_private', input: { type: 'checkbox', readonly: true }, text: 'Private:', value: i_obj.metadata_library_private },
							
							{ id: 'metadata_library_owner', input: { readonly: true }, text: 'Owner:', value: i_obj.metadata_library_owner }
						]);
				}
						
				//*** GENERAL
				if (i_data.children || ![ 'tvshows.seasons' ].includes(i_lid)) {
					pArray.append(i_dialog.options, [
						{ id: 'tab-general', tab: true, text: 'General', selected: true },
						(i_types)? { id: 'metadata_type', input: i_types, text: 'Type:', value: i_obj.metadata_type } : null,
						(i_types)? '<hr>' : null
					]);
					if (i_data.children || (!i_lid.startsWith('playlists.') && !i_lid.endsWith('.tags')))
						pArray.append(i_dialog.options, [
							{ id: 'metadata_title', input: { oninput: function() { 
								that.suggestFilename(); 
								that.suggestAlbum('dialog-input-metadata_title'); 
							} }, text: 'Title:', value: i_obj.metadata_title },
							{ id: 'metadata_artist', input: { oninput: function() { 
								that.suggestFilename(); 
								that.suggestArtist('dialog-input-metadata_artist');
							} }, text: 'Artist:', value: i_obj.metadata_artist },
							{ id: 'metadata_album', input: { oninput: function() { 
								that.suggestFilename();
								that.suggestAlbum('dialog-input-metadata_album');
							} }, text: 'Album:', value: i_obj.metadata_album },
							'<hr>',
							{ id: 'metadata_year', input: { oninput: that.suggestFilename }, text: 'Year:', value: i_obj.metadata_year },
							(!i_lid.startsWith('homevideos'))? { id: 'metadata_genre', input: { oninput: that.suggestFilename, icons: [
								{
									className: 'dialog-question-button-select',
									title: 'Click here to select the value from the library...',
									onclick: function(op_id){
										that.selectGenres({ 
											doc_url: i_data.doc_url, 
											value: pElement.getValue('dialog-input-'+op_id).replace(/,/g, '|'), 
											list_url: 'auto', 
											fcall: function(p_dialog, p_value){
												pElement.updateValue('dialog-input-'+this.opid, p_value);
											}.bind({ opid: op_id }) 
										});
									}
								}
							] }, text: 'Genre:', value: i_obj.metadata_genre } : null,
							{ id: 'metadata_track', className: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Track:', value: i_obj.metadata_track },
							{ id: 'metadata_disc', className: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Disc:', value: i_obj.metadata_disc },
							{ id: 'metadata_compilation', className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text: 'Part of Compilation', value: i_obj.metadata_compilation },
							'<hr>'
						]);
					
					//*** GENERAL TAB FOR TAGs
					if (i_lid.endsWith('.tags'))
						pArray.append(i_dialog.options, [
						    { id: 'library-x', input: { readonly: true }, text: 'Type:', value: 'Tag' },
							'<hr>',
 							{ id: 'metadata_title', input: { readonly: true }, text: 'Name:', value: i_obj.metadata_title },
 							'<hr>'
 						]);
					
					//*** GENERAL TAB FOR PLAYLISTs
					if (!i_data.children && i_lid.startsWith('playlists.'))
						pArray.append(i_dialog.options, [
						    { id: 'library-x', input: { readonly: true }, text: 'Type:', value: 'Playlist' },
							'<hr>',
 							{ id: 'fixed-name', input: { readonly: true }, text: 'Name:', value: pString.basename(pPageData.getValue('metadata_name')) },
 							'<hr>'
 						]);
					
					pArray.append(i_dialog.options, [
						{ id: 'metadata_description', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ] }, text: 'Description:', value: i_obj.metadata_description },
						{ id: 'metadata_description_long', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ] }, text: 'Description (long):', value: i_obj.metadata_description_long }
					]);	
					
					//*** RENAME FILE
					if (i_obj.file && !i_data.children)
						pArray.append(i_dialog.options, [
							'<hr>',
							{ id: 'rename_file', input: { type: 'checkbox' }, text: 'Rename/Move File' },
							{ id: 'rename-replace', input: { type: 'checkbox' }, text: 'Replace Existing File' },
							{
								id: 'filename_new', input: {
									icons: [{
										className: 'dialog-question-button-fileserver',
										title: 'Click here to change the folder of the renamed file...',
										onclick: function(op_id){
											var i_path = pElement.x('dialog-input-' + op_id).value;
											pOpenFileDialog.selectFolder({ 
												callback: function(p_path) {
													var i_path = pElement.x('dialog-input-' + this.id).value;
													pElement.updateValue('dialog-input-' + this.id, p_path + '/' + pString.basename(i_path));
												}.bind({ id: op_id }), 
												title: 'Change Folder...', 
												current_path: (i_path.indexOf('/')>=0 || i_path.indexOf(pString.backslash)>=0)? pString.dirname(i_path) : null,
												cookie: 'save-file-folder',
												ok_title: 'Select this Folder' 
											}); //TODO: check current_path
										}
									}]
								},
								text: 'New Pathname:', value: '' }
						]);
				} //if (![ 'tvshows.seasons' ].includes(i_lid)) {
				
				//*** OTHER
				if (i_data.children || (![ 'tvshows.seasons' ].includes(i_lid) && !i_lid.endsWith('.tags') && !i_lid.startsWith('playlists.')))
					pArray.append(i_dialog.options, [
						{ id: 'tab-other', tab: true, text: 'Other' },
						{ id: 'metadata_title_alt', input: { oninput: that.suggestFilename }, text: 'Title (Alternative):', value: i_obj.metadata_title_alt },
						'<hr>',
						{ id: 'metadata_albumArtist', input: { oninput: function() { 
							that.suggestFilename(); 
							that.suggestArtist('dialog-input-metadata_albumArtist');
						} }, text: 'Album Artist:', value: i_obj.metadata_albumArtist },
						{ id: 'metadata_actors', input: { oninput: that.suggestFilename }, text: 'Actors:', value: pString.concat(pString.split(i_obj.metadata_actors, '|'), ', ') },
						
						(pResources.get('library.list.(pictures.models).enabled') != 'false')? { id: 'metadata_models', input: { oninput: that.suggestFilename }, text: 'Models:', value: pString.concat(pString.split(i_obj.metadata_models, '|'), ', ') } : null,
						
						{ id: 'metadata_directors', input: { oninput: that.suggestFilename }, text: 'Directors:', value: pString.concat(pString.split(i_obj.metadata_directors, '|'), ', ') },
						{ id: 'metadata_author', input: { oninput: that.suggestFilename }, text: 'Authors:', value: pString.concat(pString.split(i_obj.metadata_author, '|'), ', ') },
						{ id: 'metadata_producers', input: { oninput: that.suggestFilename }, text: 'Producers:', value: pString.concat(pString.split(i_obj.metadata_producers, '|'), ', ') },
						{ id: 'metadata_remixer', input: { oninput: function() { 
							//that.suggestFilename(); 
							that.suggestArtist('dialog-input-metadata_remixer');
						} }, text: 'Remixers:', value: pString.concat(pString.split(i_obj.metadata_remixer, '|'), ', ') },
						{ id: 'metadata_studios', input: { }, text: 'Studios:', value: pString.concat(pString.split(i_obj.metadata_studios, '|'), ', ') },
						{ id: 'metadata_countries', input: { }, text: 'Countries:', value: pString.concat(pString.split(i_obj.metadata_countries, '|'), ', ') },
						'<hr>',
						{ id: 'metadata_release_date', input: { oninput: that.suggestFilename }, text: 'Release Date:', value: i_obj.metadata_release_date },
						'<hr>',
						{ id: 'metadata_tags', input: { oninput: that.suggestFilename, icons: i_tags_icons }, text: 'Tags:', value: pString.concat(pString.split(i_obj.metadata_tags, '|'), ', ') },
						{ id: 'metadata_tagline', input: { oninput: that.suggestFilename }, text: 'Tagline:', value: i_obj.metadata_tagline },
						'<hr>',
						{ id: 'metadata_comment', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ] }, text: 'Comments:', value: i_obj.metadata_comment },
						'<hr>',
						(i_langs)?  { id: 'metadata_lang', input: i_langs, text: 'Language', value: i_obj.metadata_lang } : null,
						(i_slangs)? { id: 'metadata_lang_subtitles', input: i_slangs, text: 'SubTitles Language:', value: i_obj.metadata_lang_subtitles } : null,
						(i_slangs)? { id: 'metadata_hard_subtitles', input: { type: 'checkbox' }, text: 'Hard Subtitles:', value: i_obj.metadata_hard_subtitles } : null
					]);
				
				//*** TV SHOW
				if (i_data.children || (![ 'tvshows.seasons' ].includes(i_lid) && !i_lid.endsWith('.tags') && !i_lid.startsWith('playlists.')))
					pArray.append(i_dialog.options, [
						{ id: 'tab-tvshow', tab: true, text: 'TV Show' },
						{ id: 'metadata_show', input: { oninput: that.suggestFilename }, text: 'TV Show:', value: i_obj.metadata_show },
						{ id: 'metadata_show_alt', input: { oninput: that.suggestFilename }, text: 'TV Show (Alternative names):', value: i_obj.metadata_show_alt },
						'<hr>',
						{ id: 'metadata_tvshow_description', input: { type: 'textarea', rows: 3 }, text: 'TV Show Description:', value: i_obj.metadata_tvshow_description },
						'<hr>',
						{ id: 'metadata_season', input: { type: 'select', options: pMetadataDialog.list_0_to_99, onchange: that.suggestFilename }, text: 'Season:', value: i_obj.metadata_season },
						{ id: 'metadata_tv_season_name', input: { oninput: that.suggestFilename }, text: 'Season Name:', value: i_obj.metadata_tv_season_name },
						'<hr>',
						{ id: 'metadata_tv_episode', xclassName: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Episode:', value: i_obj.metadata_tv_episode },
						{ id: 'metadata_tv_episode_id', xclassName: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Episode ID:', value: i_obj.metadata_tv_episode_id }
					]);

				//*** TV SHOW
				if (!i_data.children && i_lid == 'tvshows.seasons')
					pArray.append(i_dialog.options, [
						{ id: 'tab-tvshow', tab: true, text: 'TV Show' },
						{ id: 'metadata_show', input: { readonly: true }, text: 'TV Show:', value: i_obj.metadata_show },
						{ id: 'metadata_show_alt', input: { readonly: true }, text: 'TV Show (Alternative names):', value: i_obj.metadata_show_alt },
						'<hr>',
						{ id: 'metadata_tvshow_description', input: { readonly: true, type: 'textarea', rows: 3 }, text: 'TV Show Description:', value: i_obj.metadata_tvshow_description },
						'<hr>',
						{ id: 'metadata_season', input: { readonly: true, type: 'select', options: pMetadataDialog.list_0_to_99 }, text: 'Season:', value: i_obj.metadata_season },
						{ id: 'metadata_tv_season_name', input: { readonly: true }, text: 'Season Name:', value: i_obj.metadata_tv_season_name }
					]);
				
				//*** SORTING TAB
				if (i_data.children || (![ 'tvshows.seasons' ].includes(i_lid) && !i_lid.endsWith('.tags') && !i_lid.startsWith('playlists.')))
					pArray.append(i_dialog.options, [
						{ id: 'tab-sorting', tab: true, text: 'Sorting' },
						{ id: 'metadata_title_sort', input: { oninput: function() { 
							that.suggestFilename(); 
							that.suggestAlbum('dialog-input-metadata_title_sort');
						} }, text: 'Title (Sort):', value: i_obj.metadata_title_sort },
						{ id: 'metadata_library_title_sort', input: { readonly: true }, text: 'Library Title (Sort):', value: i_obj.metadata_library_title_sort },
						{ id: 'metadata_artist_sort', input: { oninput: function() { 
							that.suggestFilename(); 
							that.suggestArtist('dialog-input-metadata_artist_sort');
						} }, text: 'Artist (Sort):', value: i_obj.metadata_artist_sort },
						{ id: 'metadata_album_sort', input: { oninput: function() { 
							that.suggestFilename(); 
							that.suggestAlbum('dialog-input-metadata_album_sort');
						} }, text: 'Album (Sort):', value: i_obj.metadata_album_sort },
						{ id: 'metadata_author_sort', input: { oninput: that.suggestFilename }, text: 'Author (Sort):', value: i_obj.metadata_author_sort },
						{ id: 'metadata_show_sort', input: { oninput: that.suggestFilename }, text: 'TV Show (Sort):', value: i_obj.metadata_show_sort },
						{ id: 'metadata_show_org_sort', input: { oninput: that.suggestFilename }, text: 'TV Show Original (Sort):', value: i_obj.metadata_show_org_sort },
						'<hr>',
						{ id: 'create_metadata_group', input: { readonly: true }, text: 'Group:', value: i_obj.create_metadata_group }
					]);
				
				//*** SORTING TAB FOR TAGs
				if (i_lid.endsWith('.tags'))
					pArray.append(i_dialog.options, [
	                    { id: 'tab-sorting', tab: true, text: 'Sorting' },
					    { id: 'metadata_genre', input: { icons: [
							{
								className: 'dialog-question-button-select',
								title: 'Click here to select the value from the library...',
								onclick: function(op_id){
									that.selectGenres({
										doc_url: i_data.doc_url, 
										value: pElement.getValue('dialog-input-'+op_id).replace(/,/g, '|'), 
										list_url: that.getTagsURL(i_data.doc_url), 
										fcall: function(p_dialog, p_value){
											pElement.updateValue('dialog-input-'+this.opid, p_value);
										}.bind({ opid: op_id }),
										title: 'Select Group(s)...'
									});
								}
							}
							] }, text: 'Groups:', title: 'Choose one or more group names to group tags inside \'Select Tags(s)...\' dialog boxes...', value: i_obj.metadata_genre },
						]);
				
				//*** PARENTAL CONTROLS
				if (i_data.children || (![ 'tvshows.seasons' ].includes(i_lid) && !i_lid.endsWith('.tags') && !i_lid.startsWith('playlists.')))
					if (pResources.get('admin') === "true")
						pArray.append(i_dialog.options, [
							{ id: 'tab-parents', tab: true, text: 'Parental Controls' },
							{ id: 'metadata_advisory', input: { type: 'select', options: [ { text: 'None', value: 'none'}, { text: 'Explicit', value: 'explicit'}, { text: 'Clean', value: 'clean'} ] }, text: 'Advisory:', value: i_obj.metadata_advisory },
							{ id: 'metadata_adult', input: { type: 'checkbox' }, text: 'Adult:', value: i_obj.metadata_adult },
							{ id: 'metadata_tv_content_rating', input: { readonly_value: true }, text: 'Content Rating:', value: i_obj.metadata_tv_content_rating,
								title: (i_obj.content_rating)? i_obj.content_rating.description : null }
							//{ id: 'select', text: 'select', scall: function() { that.selectContentRating(this); }.bind(i_obj) },
						]);

				//*** THUMBNAIL
				if (i_data.children || ![ 'tvshows.seasons', 'playlists.default', 'group.playlists.default' ].includes(i_lid)) {
					pArray.append(i_dialog.options, [
						{ id: 'tab-artworks', tab: true, text: 'Thumbnails' }
					]);
					
					if (!i_readonly)
						pArray.append(i_dialog.options, [
							//*** TYPE AN URL
							{ id: 'img-url', input: { icons: [ pDialog.resources.icon_copy, pDialog.resources.icon_paste, pDialog.resources.icon_delete,
								{
									className: 'dq-bt-download',
									title: 'Download image from this URL...',
									onclick: function(op_id){
										var i_input = pElement.x('dialog-input-'+op_id);
										var i_url = pElement.x('dialog-input-'+op_id).value;
										if (i_url!='')
											pMetadataDialog.c_dropImage.call(this, null, i_url);
									},
									ondisable: true
								}]
							}, text: 'Type an URL:' },
		
							pOpenImageDialog.opSelectImage,
							//{ id: 'select-image-drop', disabled: true, text: 'Drop some images here or '+'<label id="label-select-image-drop-browse" for="select-image-drop-browse">browse<input id="select-image-drop-browse" type="file"/></label> for an image file...' }
							//{ id: 'select-image-drop', disabled: true, text: 'Drop an image here', className: 'image-drop' },
							(i_data.doc_url.indexOf('/tvshows.seasons/')>0 || i_data.doc_url.indexOf('/tvshows.medias/')>0 || i_data.doc_url.indexOf('/movies.medias/')>0)?
								{ id: 'select-poster',
								  scall: function() {
									  var i_data = this;
									that.selectPosterImage(i_data.doc_url, null, function(p_dialog, p_url, img) {
										//0.9.12
										pMetadataDialog.addImageURL(p_dialog, img, true);
										//pMetadataDialog.addImageURL(p_dialog, p_url, true, img); 
									}, null, false);
								  }.bind(i_data), text: 'Select image from posters...',
								} : null,
							(i_data.doc_url.indexOf('/pictures.')>0 || i_data.doc_url.indexOf('/books.')>0)?
								{ id: 'select-image-folder', text: 'Select an image from folder content...',
								  scall: function() {
									var i_data = this;
		
									var i_url = pURL.addQueryParameter(i_data.doc_url, [ 'action', 'contents', 'v', Math.random() ]);
		
									pOpenImageDialog.select({
										doc_url: i_data.doc_url,
										fcall: function(p_dialog, p_url, img) {
											pMetadataDialog.addImageURL(p_dialog, img, true);
										},
										title: 'Select Image from Folder...',
										//more: i_more,
										images: i_url,
										//null, i_url, 'Select Image from Folder...',
										tab_more: false
									});
								  }.bind(i_data)
								} : null
						]);
				}
				
				//*** DISPLAY
				if (i_data.children === false && i_lid == 'tvshows.seasons')
					pArray.append(i_dialog.options, [
						{ id: 'tab-display', tab: true, text: 'Display' },
						{ id: 'pres_show_episode_id', input: { type: 'checkbox' }, text_after: 'Show Episode IDs insted of Episode Numbers', text_after_class: 'library-properties-checkbox' },
					]);
				if (i_data.children === false && i_lid == 'pictures.medias')
					pArray.append(i_dialog.options, [
						{ id: 'tab-display', tab: true, text: 'Display' },
						{ id: 'pres_overides_device', input: { type: 'checkbox', onchange: 
							function() {
								pDialog.resources.f_enableIfEnabled(i_dialog.id, 'pres_overides_device', [ 'pres_sort_date_taken' ]);				
							}
						}, text_after: 'Use the settings below instead of Device Settings', text_after_class: 'library-properties-checkbox' },
						{ id: 'pres_sort_date_taken', input: { type: 'checkbox' }, text_after: 'Sort Pictures by date taken', text_after_class: 'library-properties-checkbox' },
					]);
				
				tmp_properties = { dialog: i_dialog, async: new pMap(), obj: i_obj };

				//*** UPDATE VALUES
				i_dialog.options.forEach(function(op) {
					if (!op || !op.id || !op.input) return;
					if (op.id.indexOf('file-')==0) return;
					if (op.id.indexOf('library-')==0) return;
					if (op.id.indexOf('fixed-')==0) return;
					
					var i_value = i_values.getValue(op.id, '');
					if (op.id == 'create_metadata_group')
						if (!i_value)
							i_value = i_values.getValue('metadata_title_group', '');
					op.value = pMetadataDialog.transformFromServer(op.id, i_value);

					if (op.id == 'metadata_tv_content_rating') {
						op.input.data_value = op.value;
						if (i_obj.content_ratings) {
							var i_tokens = pString.split(op.value.toLowerCase(), '|').filter(function(token, i) { return i<3 });
							var i_value = pString.concat(i_tokens, '|', false);
							var i_rating = i_obj.content_ratings.find(function(i_rating) { return i_rating.code == i_value });
							if (i_rating) op.value = i_rating.description;
						}
					}
					
					if (op.id == 'metadata_tv_content_rating') {
						op.input.icons = op.input.icons || [];
						op.input.icons.push({
							className: 'dialog-question-button-contentratings',
							title: pDialog.getDefaultTitle('certification'),//Select the Certification...',
							onclick: function(op_id){ that.selectContentRating(tmp_properties.obj); }
						});
					}

					var i_async = i_values.getValue("async_"+op.id, null);
					if (i_async) {
						i_async = pMetadataDialog.transformFromServer(op.id, i_async);

						op.input.icons = op.input.icons || [];
						if (op.id == 'metadata_tv_content_rating') {
							/*op.input.icons.push({
								className: 'dialog-question-button-contentratings',
								title: pDialog.getDefaultTitle('certification'),//Select the Certification...',
								onclick: function(op_id){ that.selectContentRating(tmp_properties.obj); }
							});*/
						}
						else {
							op.input.icons.push({
								className: 'dialog-question-button-useinternet',
								title: 'Click here to change into the value found on the internet: \n\n' + i_async,
								onclick: function(op_id){ that.updateMetadataDialogField(op_id); }
							});
						}
						tmp_properties.async.put(op.id, i_async);
					}
				});

				pDocument.stopwait(); //TODO: inside dialogQuestion...
				pDialog.dialogQuestion(i_dialog).doc_url = i_data.doc_url;

				//*** UPDATE ARTWORKS
				i_data.org_artworks = (i_data.org_artworks || []).filter(pObject.isNotNull);
				//if (!i_data.org_artworks) i_data.org_artworks = []; else i_data.org_artworks = i_data.org_artworks.filter(pObject.isNotNull);
				i_data.org_artworks.forEach(function(i_artwork) { pMetadataDialog.addImageURL(null, i_artwork); });

				i_data.artworks = (i_obj.artworks || []).filter(pObject.isNotNull);
				//if (!i_obj.artworks) i_data.artworks = []; else i_data.artworks = i_obj.artworks.filter(pObject.isNotNull);
				i_data.artworks.forEach(function(i_artwork) { pMetadataDialog.addImageURL(null, i_artwork.uri); });

				//*** ENABLE IMAGE DROP
				pElement.setOnDrop('select-image-drop', pMetadataDialog.c_dropImage);
				pElement.setOnFileChange('select-image-drop-browse', pMetadataDialog.c_dropImage);

				//*** UPDATE FILENAME
				that.suggestFilename(i_readonly);
				
				/*fetch(new Request(
					that.getDocURL(null, 'tvshow.seasons')+'?names=1', 
					{ headers: new Headers({ "cookie": "SID="+pCookieManager.xgetCookie("SID"), "x-csrf-token": pCookieManager.xgetCookie("SID"), }) }
				)).then(function(r) {
					r.text().then(function(text) {
						var obj = pJSON.parse(text);
						var d = pElement.x('dlist-dialog-input-metadata_show');
						if (d)
							obj.reply.forEach(function(n) {
								var o = document.createElement('option');
								o.value = n;
								d.appendChild(o);
							});
					});
				});*/

				var n = i_obj.suggestions || {};
				if (pApplicationUI.OPTION_DATALISTS) {
					if (pApplicationUI.OPTION_DATALIST_METADATA_SHOW)
						pHTTPRequest.get(that.getDocURL(null, 'tvshows.seasons')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText);
							pElement.fillDatalist('dlist-dialog-input-metadata_show', obj.reply);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_TITLE)
						pHTTPRequest.get(that.getDocURL(null, 'movies.movies')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText), s = n.music || {};
							
							pElement.fillDatalist('dlist-dialog-input-metadata_title', obj.reply, s.album, s.title, s.artist, s.albumArtist);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_ALBUM)
						pHTTPRequest.get(that.getDocURL(null, 'music.albums')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText), s = n.music || {};
							
							pElement.fillDatalist('dlist-dialog-input-metadata_album', obj.reply, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_album_sort', obj.reply, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_title', obj.reply, s.album, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_title_sort', obj.reply, s.album, s.title, s.artist, s.albumArtist);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_ARTIST)
						pHTTPRequest.get(that.getDocURL(null, 'music.artists')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText), s = n.music || {};
							
							pElement.fillDatalist('dlist-dialog-input-metadata_artist', obj.reply, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_artist_sort', obj.reply, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_albumArtist', obj.reply, s.album, s.title, s.artist, s.albumArtist);
							pElement.fillDatalist('dlist-dialog-input-metadata_remixer', obj.reply, s.album, s.title, s.artist, s.albumArtist);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_ACTOR)
						pHTTPRequest.get(that.getDocURL(null, 'movies.actors')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText);
							pElement.fillDatalist('dlist-dialog-input-metadata_actors', obj.reply);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_AUTHOR)
						pHTTPRequest.get(that.getDocURL(null, 'books.authors')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText);
							pElement.fillDatalist('dlist-dialog-input-metadata_author', obj.reply);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_MODEL)
						pHTTPRequest.get(that.getDocURL(null, 'pictures.models')+'?names=1&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText);
							pElement.fillDatalist('dlist-dialog-input-metadata_models', obj.reply);
						});
					if (pApplicationUI.OPTION_DATALIST_METADATA_TITLE_SORT)
						pHTTPRequest.get(that.getDocURL(null, 'movies.medias')+'?names=1&f=metadata.title.sort&v='+Math.random(), function(request) { 
							var obj = pJSON.parse(request.responseText), s = n.music || {};
							
							pElement.fillDatalist('dlist-dialog-input-metadata_title_sort', obj.reply, s.album, s.title, s.artist, s.albumArtist);
						});
					}
				}
				catch (exception) {
					alert("exception " + exception + " - " + exception.stack);
				}

			}, false, i_data);
		}
		catch (exception) {
			alert("exception " + exception + " - " + exception.stack);
		}
	};

	this.createContentRatingOption = function(i_rating, p_value_as_score) {
		return {
			id: i_rating.code,
			value: (p_value_as_score)? i_rating.score : i_rating,
			text: '<div class="tv-content-rating-container"><span id="movie-rating-text" class="tv-content-rating-text" style="display: inline-block; margin-right: 10px;">'+i_rating.rating+'</span></div>' + i_rating.description
		}
		/*return {
			id: i_rating.code,
			text: '<img class="image" style="vertical-align: middle; padding-right: 10px;" src="/resources/html/images/ratings/'+i_rating.icon+'" onError="this.src=\'/resources/html/images/16x16/file_extension_txt.png\';">' + i_rating.description,
			value: i_rating
		};*/
	};
	this.selectContentRating = function(p_obj) {

		var i_dialog = {
			id: 'certification',
			options: ['cancel'],
			fcall: function(p_dialog, p_value) {
				pElement.setValue('dialog-input-'+'metadata_tv_content_rating', p_value.description); // not updateValue!!!
				pElement.setAttribute('dialog-input-'+'metadata_tv_content_rating', 'data_value', p_value.code);
				//alert(p_value);
			}
		};

		if (p_obj.content_rating) {
			i_dialog.options.push({ id: 'tab-recommended', tab: true, text: 'Recommended' });

			var i_rating = p_obj.content_rating;
			i_dialog.options.push(that.createContentRatingOption(i_rating));

			if (p_obj.async_content_ratings)
				if (p_obj.async_content_ratings.length>1 || (p_obj.async_content_ratings.length==1 && p_obj.async_content_ratings[0].code!=i_rating.code))
					p_obj.async_content_ratings.forEach(function(r) {
						i_dialog.options.push(that.createContentRatingOption(r));
					});
		}
		else if (p_obj.async_content_ratings) {
			i_dialog.options.push({ id: 'tab-recommended', tab: true, text: 'Recommended' });

			p_obj.async_content_ratings.forEach(function(r) {
				i_dialog.options.push(that.createContentRatingOption(r));
			});
		}

		if (p_obj.content_ratings) {

			var i_type = '', i_country = '';
			//i_dialog.options[i_dialog.options.length] = { id: 'tab-others', tab: true, text: 'Others' };
			p_obj.content_ratings.forEach(function(i_rating) {
				if (i_rating.country.code != p_obj.user_country) return;

				if (i_type!=i_rating.type || i_country!=i_rating.country.code)
					i_dialog.options[i_dialog.options.length] = { id: 'tab-'+i_rating.country.code+'-'+i_rating.type, tab: true, text: i_rating.country.name+' ('+i_rating.type.toUpperCase()+')' };

				i_dialog.options[i_dialog.options.length] = that.createContentRatingOption(i_rating);

				i_type = i_rating.type;
				i_country = i_rating.country.code;
			});

			i_dialog.options[i_dialog.options.length] = { id: 'tab-others', tab: true, text: 'Others' };
			p_obj.content_ratings.forEach(function(i_rating) {
				if (i_rating.country.code == p_obj.user_country) return;

				if (i_type!=i_rating.type || i_country!=i_rating.country.code)
					i_dialog.options[i_dialog.options.length] = { id: 'sep-'+i_rating.country.code+'-'+i_rating.type, sep: true, text: i_rating.country.name+' ('+i_rating.type.toUpperCase()+')', disabled: true };

				i_dialog.options[i_dialog.options.length] = that.createContentRatingOption(i_rating);

				i_type = i_rating.type;
				i_country = i_rating.country.code;
			});
		}

		pDialog.dialogQuestion(i_dialog);
	};

	var suggestFilename_url_request;
	this.suggestFilename = function(p_readonly) {
		if (p_readonly === true)
			return;
		
		var i_dialog = pDialog.getValue('properties');
		if (i_dialog) {
			var id = ''+Math.random(), u = i_dialog.doc_url + '?action=suggest-filename&v='+id;
			pConsole.info(that, "SuggestFilename: "+u);
	
			suggestFilename_url_request = id;
			pHTTPRequest.post({ id: id, url: u, headers: pHTTPRequest.headers_post, body: pURL.toFormSubmitBody(pDialog.getInputValues('properties')),
				onreadystatechange: function(i_request, data, p_request) {
	
					//handle errors
					if (pROSE.handleHTTPResponse(i_request) === false)
						return false;
	
					if (p_request.id === suggestFilename_url_request) {
						var i_obj = pJSON.parse(i_request.responseText);
						pElement.updateValue('dialog-input-filename_new', i_obj.filename);
					}
				}
			});
		}
	};
	
	this.suggestArtist = function(x) {
		var v = pElement.getValue(x);
		pElement.addToDatalist('dlist-dialog-input-metadata_artist', v);
		pElement.addToDatalist('dlist-dialog-input-metadata_artist_sort', v);
		pElement.addToDatalist('dlist-dialog-input-metadata_albumArtist', v); 
		pElement.addToDatalist('dlist-dialog-input-metadata_remixer', v); 
	};

	this.suggestAlbum = function(x) {
		var v = pElement.getValue(x);
		pElement.addToDatalist('dlist-dialog-input-metadata_album', v);
		pElement.addToDatalist('dlist-dialog-input-metadata_album_sort', v);
		pElement.addToDatalist('dlist-dialog-input-metadata_title', v); 
		pElement.addToDatalist('dlist-dialog-input-metadata_title_sort', v); 
	};
	
	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/
	
	this.getFileMetadata = function(p_doc_url, p_file) {
		
		if (!pApplicationUI.canShowDialog())
			return;

		pDocument.wait();
		try {
			var i_data = {
				url: p_doc_url || window.location.pathname,
				file: p_file,
				other: {}
			};
			i_data.doc_url = i_data.url;
			i_data = that.prepareRefreshRequest(i_data);

			var u = pURL.addParam(i_data.url, [ 'action', 'get-metadata', 'format', 'json', 'contents', 'values', 'v', Math.random(), 'file', i_data.file ]);

			pConsole.info(this, "GetMetadata: " + u);
			pHTTPRequest.get(u, function(i_request, i_data) {

				try {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				var i_lid = that.getLIDfromURL(i_data.doc_url), i_obj = pJSON.parse(i_request.responseText);
				//alert(pJSON.pretty(i_request.responseText));
				pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));

				var i_values = new pMap(i_obj.values);
				i_obj.values.forEach(function(v, i) { if (v.see) i_values.put(i_obj.values[i-1], i_values.getValue(v.see)); });

				var i_ctype = i_values.getValue('metadata_type', ''), i_types = null;
				if (i_obj.types) {
					i_types = { type: 'select', options: [], onchange: that.suggestFilename };
					i_types.options = i_obj.types.map(function(i_type) { return { text: i_type, value: i_type, selected: i_ctype == i_type }});
				}

				var i_clang = i_values.getValue('metadata_lang', ''), i_langs = null;
				if (i_obj.langs) {
					i_langs = { type: 'select', options: [], onchange: that.suggestFilename };
					i_langs.options = i_obj.langs.map(function(i_lang) { return { text: i_lang, value: i_lang, selected: i_clang == i_lang }});
				}

				var i_slang = i_values.getValue('metadata_lang_subtitles', ''), i_slangs = null;
				if (i_obj.langs) {
					i_slangs = { type: 'select', options: [], onchange: that.suggestFilename };
					i_slangs.options = i_obj.langs.map(function(i_lang) { return { text: i_lang, value: i_lang, selected: i_slang == i_lang }});
				}
				
				var i_readonly = true, i_tags_icons = [];
				if (!i_readonly && [ 'movies.medias', 'movies.movies', 'homevideos.movies', 'homevideos.medias', 'pictures.medias', 'books.medias' ].includes(i_lid))
					i_tags_icons.push({
						className: 'dialog-question-button-select',
						title: 'Click here to select the value from the library...',
						onclick: function(op_id){
							that.selectTags(i_data.doc_url, pElement.getValue('dialog-input-'+op_id).split(',').join('|'), 'auto', function(p_dialog, p_value){
								pElement.updateValue('dialog-input-'+this.opid, p_value.map(pTag.format).join(', '));
							}.bind({ opid: op_id }));
						}
					});
				
				var i_dialog = {
					id: 'properties',
					title: i_data.other.title || 'File Properties',
					icon:  i_data.other.icon,
					artworks: i_data.artworks,
					doc_url: i_data.doc_url,
					//TODO: for properties(all) readonly if one of the file is unavailable...
					readonly: i_readonly,
					//destroy: false,
					//submitChangedInputsOnly: true, NO!!!
					options: [
						(i_readonly)? null : { id: 'ok', text: 'Save Changes' },
						{ id: 'cancel', text: (i_readonly)? 'Close':'Cancel' }
					]
				};
					
				//*** FILE
				if (i_obj.file) {
					var fn = i_obj.file.name;
					pArray.append(i_dialog.options, [
	
						{ id: 'file', tab: true, text: 'File', className: (i_obj.file.exists === false)? 'dialog-tab-title-file-not-found':'' },
						(i_obj.file.exists === false)? { id: 'file-not-found', sep: true, text: 'File not found...', className: 'dialog-file-not-found' } : null,
						{ id: 'file-name', input: { readonly: true }, text: 'Name:', value: fn.endsWith(i_obj.file.ext)? fn : fn+'.'+i_obj.file.ext },
						'<hr>',
						(i_obj.file.os_type)? { id: 'file-type', input: { readonly: true }, text: 'Type of File:', value: i_obj.file.os_type } : null,
						(i_obj.file.os_type)? '<hr>' : null,
						{ id: 'file-location', input: { readonly: true }, text: 'Location:', value: pString.dirname(i_obj.file.path) }
					]);
					
					if (i_obj.file.exists!=false)
						pArray.append(i_dialog.options, [
							{ id: 'file-size', input: { readonly: true }, text: 'Size:', value: pHumanText.toByteSize(i_obj.file.size) },
							'<hr>',
							(i_obj.file.lastCreated)? { id: 'file-lastCreated', input: { type: 'datetime-local', readonly: true }, text: 'Created:', value: pDate.create(i_obj.file.lastCreated) } : null,
							(i_obj.file.lastModified)? { id: 'file-lastModified', input: { type: 'datetime-local', readonly: true }, text: 'Modified:', value: pDate.create(i_obj.file.lastModified) } : null,
							(i_obj.file.lastAccessed)? { id: 'file-lastAccessed', input: { type: 'datetime-local', readonly: true }, text: 'Accessed:', value: pDate.create(i_obj.file.lastAccessed) } : null
						]);
					
					pArray.append(i_dialog.options, [
 						'<hr>',
 						{ id: 'metadata_provider', input: { readonly: true }, text: 'Provider:', value: i_obj.metadata_provider },
 						{ id: 'url', input: { type: 'link', readonly: true }, text: 'URL:', value: i_obj.url, style: 'padding-bottom: 10px;' },
 	
 						{ id: 'metadata_publisher', input: { readonly: true }, text: 'Publisher:', value: i_obj.metadata_publisher },
 						{ id: 'metadata_copyright', input: { readonly: true }, text: 'Copyright:', value: i_obj.metadata_copyright },
 						i_values.getValue('metadata_right_usage_terms')? { id: 'metadata_right_usage_terms', input: { readonly: true }, text: 'Right Usage Terms:', value: i_obj.metadata_right_usage_terms } : null,
 						
 						'<hr>',
 						{ id: 'metadata_purchase_account', input: { readonly: true }, text: 'Purchase Account:', value: i_obj.metadata_purchase_account },
 						{ id: 'metadata_purchase_account_type', input: { readonly: true }, text: 'Purchase Account Type:', value: i_obj.metadata_purchase_account_type },
 						{ id: 'metadata_purchase_country', input: { readonly: true }, text: 'Purchase Country:', value: i_obj.metadata_purchase_country },
 						{ id: 'metadata_purchase_date', input: { type: 'datetime-local', readonly: true }, text: 'Purchase Date:', value: pDate.create(i_obj.metadata_purchase_date) }
 					]);
					
				} //if (i_obj.file)
				
				pArray.append(i_dialog.options, [
					{ id: 'tab-general', tab: true, text: 'General', selected: true },
					(i_types)? { id: 'metadata_type', input: i_types, text: 'Type:', value: i_obj.metadata_type } : null,
					(i_types)? '<hr>' : null
				]);
					
				pArray.append(i_dialog.options, [
					{ id: 'metadata_title', input: { oninput: that.suggestFilename }, text: 'Title:', value: i_obj.metadata_title },
					//{ id: 'metadata_artist', input: { oninput: that.suggestFilename }, text: 'Artist:', value: i_obj.metadata_artist },
					//{ id: 'metadata_album', input: { oninput: that.suggestFilename }, text: 'Album:', value: i_obj.metadata_album },
					'<hr>'//,
					//{ id: 'metadata_year', input: { oninput: that.suggestFilename }, text: 'Year:', value: i_obj.metadata_year },
					/*{ id: 'metadata_genre', input: { oninput: that.suggestFilename, icons: [
						{
							className: 'dialog-question-button-select',
							title: 'Click here to select the value from the library...',
							onclick: function(op_id){
								that.selectGenres({
									doc_url: i_data.doc_url, 
									value: pElement.getValue('dialog-input-'+op_id), 
									list_url: 'auto', 
									fcall: function(p_dialog, p_value){
										pElement.updateValue('dialog-input-'+this.opid, p_value);
									}.bind({ opid: op_id })
								});
							}
						}
					] }, text: 'Genre:', value: i_obj.metadata_genre },
					{ id: 'metadata_track', className: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Track:', value: i_obj.metadata_track },
					{ id: 'metadata_disc', className: 'dialog-question-input-inline', input: { type: 'select', options: pMetadataDialog.list_01_to_99, onchange: that.suggestFilename }, text: 'Disc:', value: i_obj.metadata_disc },
					{ id: 'metadata_compilation', className: 'dialog-question-input-inline', input: { type: 'checkbox' }, text: 'Part of Compilation', value: i_obj.metadata_compilation },
					'<hr>'*/
				]);
				
				pArray.append(i_dialog.options, [
				    //{ id: 'metadata_release_date', input: { type: 'datetime-local', readonly: true }, text: 'Date Taken:', value: pDate.create(i_obj.metadata_release_date) },
				    //'<hr>',
					{ id: 'metadata_description', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ] }, text: 'Description:', value: i_obj.metadata_description },
					{ id: 'metadata_description_long', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ] }, text: 'Description (long):', value: i_obj.metadata_description_long }
				]);						
				
				if (![ 'playlists.medias', 'tvshows.seasons' ].includes(i_lid))
					pArray.append(i_dialog.options, [
						{ id: 'tab-other', tab: true, text: 'Other' },
						
						{ id: 'metadata_author', input: { }, text: 'Authors:', value: pString.concat(pString.split(i_obj.metadata_author, '|'), ', ') },
						
	 				    '<hr>',
	 				    { id: 'metadata_tags', input: { icons: i_tags_icons }, text: 'Tags:', value: pString.split(i_obj.metadata_tags, '|').join(', ') },

						(i_langs)?  '<hr>' : null,
						(i_langs)?  { id: 'metadata_lang', input: i_langs, text: 'Language', value: i_obj.metadata_lang } : null
					]);
				
				if (pApplicationUI.acceptImageFileName(i_obj.file.name)) 
					pArray.append(i_dialog.options, [
					    { id: 'tab-picture', tab: true, text: 'Picture' },
					    
					    { id: 'dimension', input: { readonly: true }, text: 'Dimensions:' },
					    { id: 'metadata_orientation_exif', input: { readonly: true }, text: 'Orientation:' },
					    { id: 'dim', input: { readonly: true }, text: 'Dimensions (Image):' },
					    '<hr>',
	 				    { id: 'metadata_release_date', input: { type: 'datetime-local', readonly: true }, text: 'Date Taken:', value: pDate.create(i_obj.metadata_release_date) }
	 				]);
				
				tmp_properties = { dialog: i_dialog, async: new pMap(), obj: i_obj };

				//*** UPDATE VALUES
				i_dialog.options.forEach(function(op) {
					if (!op || !op.id || !op.input) return;
					if (op.id.indexOf('file-')==0) return;
					if (op.id.indexOf('library-')==0) return;

					var v = i_values.getValue(op.id, '');
					if (op.id == 'dimension')
						v = i_values.getValue("width") + ' x ' + i_values.getValue("height");
					if (op.id == 'metadata_orientation_exif') {
						var o = parseInt(i_values.getValue("metadata_orientation_exif"));
						if (o == 6) v = 'Rotation -90\u{B0}';
						else if (o == 3) v ='Rotation 180\u{B0}';
						else if (o == 8) v = 'Rotation 90\u{B0}';
						else
							v = 'Default';
					}
					if (op.id == 'dim') {
						var o = parseInt(i_values.getValue("metadata_orientation_exif"));
						if (o == 8 || o == 6)
							v = i_values.getValue("height") + ' x ' + i_values.getValue("width");
						else
							v = i_values.getValue("width") + ' x ' + i_values.getValue("height");
					}
					op.value = pMetadataDialog.transformFromServer(op.id, v);
				});

				pDocument.stopwait(); //TODO: inside dialogQuestion...
				pDialog.dialogQuestion(i_dialog).doc_url = i_data.doc_url;

				}
				catch (exception) {
					alert("exception " + exception + " - " + exception.stack);
				}

			}, false, i_data);
		}
		catch (exception) {
			alert("exception " + exception + " - " + exception.stack);
		}
	};	
	
	/*******************************************************************************/
	
	this.renamePlaylist = function(p_doc_url, p_exists) {
		var u = p_doc_url || window.location.pathname;
		
		pDialog.dialogQuestion({
			id: 'rename-playlist',
			options: [
			    (p_exists === true)? { id: 'error', text: 'A playlist already exists with this name, please try another name...', disabled: true, className: 'dialog-error' } : null,
				{ id: 'name', input: { type: 'text', required: true }, text: 'Playlist Name:', value: pString.basename(pPageData.getValue('metadata_name')) },
				'ok', 'cancel'
			],
			fcall: function(p_dialog, p_value) {
				var i_name = p_dialog.inputs.getValue('name', '').trim();
				if (i_name.length<1)
					return;

				pTransaction.simpleTransaction({
					trace: that, 
					url: pURL.addQueryParameter(u, [ 'action', 'rename', 'name', i_name, 'v', Math.random() ]), method_name: "RenamePlaylist",
					msg_start: 'Renaming Playlist...',
					msg_end: function(i_request) { 
						pListManager.notify(pListManager.EVENT_RENAMED_ITEM);
						pPageData.put('metadata_name', i_request.responseText);
					},
					msg_409: function(i_request) {
						that.renamePlaylist(u, true);
					},
					msg_500: function(i_request) {
						pApplicationUI.errorDialog({ text: 'Failed to rename playlist, the server returned an error:<br/>'+i_request.statusText, icon: { src: '/resources/html/images/32x32/folder_error.png' } });
					}
				});
			}.bind({ doc_url: p_doc_url || window.location.pathname })
		});
	};
	
	/*******************************************************************************/
	
	this.removeFromLibraryPlaylist = function(p_doc_url, p_playlist_id) {
		var rq = {
			id: 'remove-from-playlist',
			url: p_doc_url || window.location.pathname,
			pl_id: p_playlist_id
		};

		pDialog.question(rq).then(function() {	that.removeFromPlaylist(this.url, this.pl_id); }.bind(rq));
	};
	this.removeFromPlaylist = function(p_doc_url, p_playlist_id) {
		var url = pURL.addQueryParameter(p_doc_url, [ 'action', 'remove-from-playlist', 'playlist-id', p_playlist_id, 'v', Math.random() ]);

		pConsole.info(this, "RemoveFromPlaylist: " + url);
		pHTTPRequest.sendDELETE(url, function() { window.location.reload(); }, false); //TODO
	};

	/*******************************************************************************/
	
	this.optimize = function(p_doc_url) {
		var rq = {
			id: 'optimize',
			options: [
				pString.decodeHTML(pDocument.getStyleValue('dialog-optimize-question', 'content')) + '<p>',
				pDialog.newCheckbox('scale', 'Create scaled pictures as well'),
				'<hr>'
			],
			url: p_doc_url || window.location.pathname
		};
		
		pNoteUI.notesDialog('help-optimize-pictures').then(function() {
			pDialog.question(this).then(function () {
				pTransaction.simpleTransaction({
					trace: this, url: pURL.addQueryParameter(this.url, [ 'action', 'optimize', 'v', Math.random(), 'scale', this.inputs.getValue('scale') ]), method_name: "Optimize",
					msg_start: 'Optimizing...'
				});
			}.bind(this));
		}.bind(rq));
	};
	
	this.optimizeFile = function(i_data) {
		return new Promise(function(resolve, reject) {
			i_data = that.prepareRefreshRequest(i_data);
			pTransaction.simpleTransaction({
				trace: i_data, 
				url: pURL.addQueryParameter(i_data.url, [ 'action', 'optimize-file', 'v', Math.random(), 'wait', 1 ]), 
				method_name: "OptimizeFile",
				msg_start: 'Optimizing media(s)...',
				msg_end: [ 'Optimizing media(s)... Done.', i_data.c_deleted, resolve ],
				errorCodes: [ 500, 409, 404 ]
			});
		});
	};
	
	/*******************************************************************************/
	
	this.selectArtworkImage = function(p_doc_url, p_backdrop_url, p_more) {

		pDocument.wait();

		var i_data = {
			doc_url: p_doc_url || window.location.pathname,
			url: p_backdrop_url || pPage.metadata_artwork_url
		};
		i_data.scall = function(p_dialog, p_value) {
			var i_data = this;
			that.setArtworkImage(i_data.doc_url, p_value, i_data.url);
		}.bind(i_data);

		pOpenImageDialog.selectImage(
			i_data.doc_url,
			i_data.doc_url+'?action=allartworks&format=json&v='+Math.random(),
			//(that.getLIDfromURL(i_data.doc_url)=='movies.movies')? i_data.doc_url+'?action=posters&format=json&v='+Math.random() : i_data.doc_url+'?action=artworks&format=json&v='+Math.random(),
			pDocument.getStyleValue('artwork'),//pResources.resources.toolbar.selectArtwork,
			i_data.scall, p_more);
	};
	this.setArtworkImage = function(p_doc_url, p_image_uri, p_backdrop_url, p_nomain) {
		if (!p_image_uri) {
			pConsole.info(that, "SetArtworkImage: null");
			return;
		}

		pTransaction.simpleTransaction({
			trace: that, url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-artwork', 'v', Math.random(), 'wait', 1 ]), method_name: "SetArtworkImage",
			msg_start: 'Setting up thumbnail image...',
			errorCodes: [ 500 ],
			msg_end: function(p_request) { 
				var i_id = that.getIDfromURL(p_doc_url), i_lid = that.getLIDfromURL(p_doc_url), v = p_request.responseText.split('|');
				
				if (v.length>1 && pString.v(v[1]) && pPage && !p_nomain)
					pPage.metadata_artwork_url = url_prefix+that.getLibraryIDfromURL()+'/resources/'+v[1];
				pImageVersions.update('artwork', (i_lid=='library')? null : i_lid, i_id, p_request.responseText);
				
				if (!p_nomain)
					that.showArtwork();
				else {
					var u = url_prefix+that.getLibraryIDfromURL(p_doc_url)+'/resources/'+v[1];
					
					//u = pImageVersions.addVersion(u, pImageVersions.f_getArtworkVersion());
					pElement.setSrc('artwork-'+i_lid+'-'+i_id, u);
					pElement.setHref('artwork-link-'+i_lid+'-'+i_id, u);
					pConsole.info(that, "SetArtwork: " + u);
				}
				
			},//.bind({ u: (p_backdrop_url)? p_backdrop_url : '' }),
			headers: pHTTPRequest.headers_post,
			body: (p_image_uri.ms)? 'capture=1&ms='+parseInt(p_image_uri.ms) : 'image-uri='+pURL.fixedEncodeURIComponent(p_image_uri),
			responseType: 'text'
		});
	};
	this.showArtwork = function(u) {
		u = '' + (u || ((pPage.getValue)? pPage.getValue("metadata_artwork_url") : pPage.metadata_artwork_url));
		if (!pString.v(u))
			return;

		u = pImageVersions.addVersion(u, pImageVersions.f_getArtworkVersion());
		pElement.setSrc('artwork', u);
		pElement.setHref('artwork-link', u);
		pConsole.info(that, "SetArtwork: " + u);
	};
	this.autoRefreshArtwork = function() {
		if (window.pPage)
			that.showArtwork();
		
		pDocument.addOnPageReshow(function() { that.showArtwork(); });
	};

	this.selectBackdropImage = function(p_doc_url, p_backdrop_url, p_more) {

		pDocument.wait();

		var i_data = {
			doc_url: p_doc_url || window.location.pathname,
			url: (p_doc_url)? p_backdrop_url : pPage.metadata_backdrop_url
		};
		i_data.scall = function(p_dialog, p_value) {
			that.setBackdropImage(i_data.doc_url, p_value, i_data.url);
		};

		pOpenImageDialog.selectImage(
			i_data.doc_url,
			i_data.doc_url+'?action=backdrops&format=json&v='+Math.random(),
			"Select Backdrop Image...",
			i_data.scall, p_more);
	};
	this.setBackdropImage = function(p_doc_url, p_image_uri, p_backdrop_url) {

		if (!p_image_uri) {
			pConsole.info(this, "SetBackdropImage: null");
			return;
		}

		pTransaction.simpleTransaction({
			trace: this, url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-backdrop', 'v', Math.random(), 'wait', 1 ]), method_name: "SetBackdropImage",
			msg_start: 'Setting up backdrop image...',
			msg_end: function(p_request) {
				var i_id = that.getIDfromURL();
				var i_lid = that.getLIDfromURL();
				pImageVersions.update('backdrop', (i_lid=='library')? null : i_lid, i_id, p_request.responseText);
				that.showBackdrop(this.u); 
			}.bind({ u: p_backdrop_url }),
			headers: pHTTPRequest.headers_post,
			body: (p_image_uri.ms)? 'capture=1&ms='+p_image_uri.ms : 'image-uri='+pURL.fixedEncodeURIComponent(p_image_uri),
			responseType: 'text'
		});
		
		//TODO: copy v=id from response into pImageVersions....
	};

	this.setBackdrop = function(u) {
		document.body.style.backgroundImage = 'url('+pURL.addSizeParameters(u)+')';
		pConsole.info(that, "SetBackground: " + document.body.style.backgroundImage);
	};
	/** @param p_auto Indicates if call happens automatically (<code>true</code>) or is triggered by user interaction (<code>false</code>). Default is <code>true</code>. */
	this.showBackdrop = function(u, p_auto) {
		u = u || ((pPage.getValue)? pPage.getValue("metadata_backdrop_url") : pPage.metadata_backdrop_url);
		if (!pString.v(u) || pApplicationUI.OPTION_NO_BACKDROP) {
			this.showDefaultBackdrop();
			return;
			//TODO: if (p_auto === false) notify user...
		}

		u = pImageVersions.addVersion(u, pImageVersions.f_getBackdropVersion());
		that.setBackdrop(u);

		setTimeout(function() {
			pConsole.info(that, "CheckBackground: " + u);
			pHTTPRequest.head({ url: u, f404: that.showDefaultBackdrop });
		}, 10);
	};
	this.showDefaultBackdrop = function() {

		function showSystemBackdrop() {
			document.body.style.backgroundImage = null;
			pElement.removeClassName(document.body, "background");
			pElement.addClassName(document.body, "background-index");
		}
		
		var i_url = url_prefix + that.getLibraryIDfromURL()+'/resources/backdrop/0.jpg?v='+pImageVersions.backdropVersion();
		pConsole.info(that, "CheckBackground: " + i_url);
		pHTTPRequest.head({ 
			url: i_url, 
			f404: showSystemBackdrop, 
			onreadystatechange: function(i_request, i_library) { that.setBackdrop(i_url); }, 
			data: this 
		});
	};
	this.autoRefreshBackdrop = function() {
		if (window.pPage)
			that.showBackdrop();
		
		pDocument.addOnPageReshow(function() { that.showBackdrop(); });
		
		pImageVersions.addListener(pImageVersions.versionListenerID('backdrop'), function() { that.showBackdrop(); });
	};

	this.selectPosterImage = function(p_doc_url, p_backdrop_url, fcall, p_more, tab_more) {

		pDocument.wait();

		var i_data = {
			doc_url: (p_doc_url)? p_doc_url : window.location.pathname,
			url: (p_doc_url)? p_backdrop_url : pPage.metadata_poster_url,
			fcall: fcall
		};
		i_data.scall = function(p_dialog, p_value, op) {
			var i_data = this;
			if (i_data.fcall)
				i_data.fcall.call(this, p_dialog, p_value, op.image);
			else
				that.setPosterImage(i_data.doc_url, p_value, i_data.url);
		}.bind(i_data);
		pOpenImageDialog.select({
			doc_url: i_data.doc_url,
			images: i_data.doc_url+'?action=posters&format=json&v='+Math.random(),
			title: "Select Poster Image...",
			fcall: i_data.scall, 
			more: p_more,
			tab_more: tab_more
		});
	};
	this.setPosterImage = function(p_doc_url, p_image_uri, p_poster_url) {

		if (!p_image_uri) {
			pConsole.info(this, "SetPosterImage: null");
			return;
		}

		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-poster', 'v', Math.random(), 'wait', 1 ]), 
			method_name: "SetPosterImage",
			msg_start: 'Setting up poster image...',
			msg_end: function(p_request) { 
				if (this.url) {
					var i_id = that.getIDfromURL(this.doc_url);
					var i_lid = that.getLIDfromURL(this.doc_url);
					pImageVersions.update('poster', i_lid, i_id, p_request.responseText);					
					
					that.showPoster(this.doc_url, this.url); 
				}
			}.bind({ doc_url: p_doc_url, url: p_poster_url }),
			headers: pHTTPRequest.headers_post,
			body: (p_image_uri.ms)? 'capture=1&ms='+p_image_uri.ms : 'image-uri='+pURL.fixedEncodeURIComponent(p_image_uri)
		});
	};
	this.showPoster = function(p_doc_url, u) {
		u = '' + (u || ((pPage.getValue)? pPage.getValue("metadata_poster_url") : pPage.metadata_poster_url));
		if (!pString.v(u))
			return;

		u = pImageVersions.addVersion(u, pImageVersions.f_getPosterVersion());
		pElement.setSrc('poster', u);
		pElement.setHref('poster-link', u);
		pConsole.info(that, "SetPoster: " + u);
	};
	this.autoRefreshPoster = function() {
		if (window.pPage)
			that.showPoster();
		
		pDocument.addOnPageReshow(function() { that.showPoster(); });
	};

	this.selectScreenshotImage = function(p_doc_url, p_backdrop_url, fcall, p_more) {

		pDocument.wait();

		var i_data = {
			doc_url: (p_doc_url)? p_doc_url : window.location.pathname,
			url: p_backdrop_url,
			fcall: fcall
		};
		i_data.scall = function(p_dialog, p_value) {
			var i_data = this;
			if (i_data.fcall)
				i_data.fcall.call(this, p_dialog, p_value);
			else
				that.setScreenshotImage(i_data.doc_url, p_value, i_data.url);
		}.bind(i_data);
		pOpenImageDialog.selectImage(
			i_data.doc_url,
			{ images: [ { uri: p_backdrop_url } ]},
			//p_doc_url+'?action=posters&format=json&v='+Math.random(),
			"Select Screenshot Image...",
			i_data.scall, p_more);
	};
	this.setScreenshotImage = function(p_doc_url, p_image_uri, p_poster_url) {

		if (!p_image_uri) {
			pConsole.info(this, "SetScreenshotImage: null");
			return;
		}

		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-screenshot', 'v', Math.random(), 'wait', 1 ]), 
			method_name: "SetScreenshotImage",
			msg_start: 'Setting up screenshot image...',
			msg_end: function() {
				if (this) {
					var i_random = Math.random();
					pElement.setSrc('screenshot', pURL.addQueryParameter(this, 'v', i_random));
					pElement.setSrc('screenshot-'+this, pURL.addQueryParameter(this, 'v', i_random));
				}
			}.bind(p_poster_url),
			headers: pHTTPRequest.headers_post,
			body: (p_image_uri.ms)? 'capture=1&ms='+p_image_uri.ms : 'image-uri='+pURL.fixedEncodeURIComponent(p_image_uri)
		});
	};

	this.selectImageImage = function(p_doc_url, p_backdrop_url, x_ids) {

		pDocument.wait();

		var i_data = {
			doc_url: p_doc_url,
			img_url: p_backdrop_url,
			xids: x_ids
		};
		i_data.scall = function(p_dialog, p_value) {
			this.value = p_value;
			that.setImageImage(this);
		}.bind(i_data);
		pOpenImageDialog.selectImage(
			i_data.doc_url,
			{ images: [ { uri: i_data.img_url } ]},
			//p_doc_url+'?action=posters&format=json&v='+Math.random(),
			"Select Screenshot Image...",
			i_data.scall, null);
	};
	this.setImageImage = function(p_data) {

		if (!p_data.value) {
			pConsole.info(this, "SetImageImage: null");
			return;
		}

		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_data.doc_url, [ 'action', 'set-image', 'v', Math.random(), 'wait', 1 ]), 
			method_name: "SetImageImage",
			msg_start: 'Setting up image...',
			msg_end: function(p_request) {
				
				if (this.value) {
					var i_id = that.getIDfromURL(this.doc_url);
					var i_lid = that.getLIDfromURL(this.doc_url);
					pImageVersions.map.put('session_i_'+i_lid+'_'+i_id, p_request.responseText);					
					
					that.showImage(this); 
				}
			}.bind(p_data),
			headers: pHTTPRequest.headers_post,
			body: 'image-uri='+pURL.fixedEncodeURIComponent(p_data.value)
		});
	};
	this.showImage = function(p_data) {
		var i_img_url = pURL.addQueryParameter(pLocation.path(p_data.img_url), 'v', pImageVersions.f_getImageVersion(p_data.doc_url));
		
		if (p_data.xids)
			p_data.xids.forEach(function(x) { pElement.setSrc(x, i_img_url) });
	};

	this.selectSeasonBannerImage = function(p_doc_url, p_backdrop_url) {

		pDocument.wait();

		var i_data = {
			doc_url: (p_doc_url)? p_doc_url : window.location.pathname,
			url: (p_doc_url)? p_backdrop_url : pPage.metadata_season_banner_url
		};
		i_data.scall = function(p_dialog, p_value) {
			var i_data = this;
			that.setSeasonBannerImage(i_data.doc_url, p_value, i_data.url);
		}.bind(i_data);
		pOpenImageDialog.selectImage(
			i_data.doc_url,
			i_data.doc_url+'?action=season-banners&format=json&v='+Math.random(),
			"Select Banner Image...",
			i_data.scall);
	};
	this.setSeasonBannerImage = function(p_doc_url, p_image_uri, p_banner_url) {
		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-season-banner', 'v', Math.random(), 'wait', 1 ]), 
			method_name: "SetSeasonBannerImage",
			msg_start: 'Setting up season banner image...',
			msg_end: function() {
				if (this)
					pElement.setSrc('season-banner', pURL.addQueryParameter(this.u, 'v', Math.random()));
			}.bind({ u: p_banner_url }),
			headers: pHTTPRequest.headers_post,
			body: 'image-uri='+pURL.fixedEncodeURIComponent(p_image_uri)
		});
	};

	this.selectAddImage = function(p_doc_url, fcall, p_more, name) {

		pDocument.wait();

		var i_data = {
			doc_url: p_doc_url || window.location.pathname,
			fcall: fcall
		};
		i_data.scall = function(p_dialog, p_value) {
			var i_data = this;
			if (i_data.fcall)
				i_data.fcall.call(this, p_dialog, p_value);
			else
				that.addPicture(i_data.doc_url, p_dialog.artworks, name);
		}.bind(i_data);
		pOpenImageDialog.select({
			doc_url: i_data.doc_url,
			images: { images: []}, //null,//{ images: [ { uri: p_backdrop_url } ]},
			//p_doc_url+'?action=posters&format=json&v='+Math.random(),
			title: "Add Picture(s)...",
			fcall: i_data.scall, more: p_more, multiples: true, ok_text: 'Add Chosen Picture(s)' });
	};
	this.addPicture = function(p_doc_url, p_image_uris, name) {

		if (!p_image_uris) {
			pConsole.info(this, "AddImage: null");
			return;
		}

		var b = [], bb = '', ba = '', i = 0;
		p_image_uris.forEach(function(u) {
			if (typeof u == 'string') {
				bb = pURL.addBodyParameter(bb, 'image-uri'+i, u);
				if (bb.length > max_post_body) {
					if (ba.length>0) b.push(ba); i = 0;
					ba = bb = pURL.addBodyParameter('', 'image-uri'+i, u);
				}
				else
					ba = bb;
			}
			else {
				bb = pURL.addBodyParameter(bb, [ 'image-uri'+i, u.uri, 'image-name'+i, u.name, 'image-lastModified'+i, u.lastModified ]);
				if (bb.length > max_post_body) {
					if (ba.length>0) b.push(ba); i = 0;
					ba = bb = pURL.addBodyParameter('', [ 'image-uri'+i, u.uri, 'image-name'+i, u.name, 'image-lastModified'+i, u.lastModified ]);
				}
				else
					ba = bb;
			}
			i++;
		});
		if (ba.length>0)
			b.push(ba);
		
		/*var i_body = '';
		p_image_uris.forEach(function(u, i) {
			if (typeof u == 'string')
				i_body = pURL.addBodyParameter(i_body, 'image-uri'+i, u);
			else
				i_body = pURL.addBodyParameter(i_body, [ 'image-uri'+i, u.uri, 'image-name'+i, u.name, 'image-lastModified'+i, u.lastModified ]);
		});*/

		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_doc_url, [ 'action', 'add-picture', 'v', Math.random(), 'wait', 1 ]), 
			method_name: "AddPicture",
			msg_start: name? 'Adding pictures into '+pString.encodeHTML(name)+'...' : 'Adding pictures...',
			msg_end: [ name? 'Adding pictures into <a href="'+p_doc_url+'">'+pString.encodeHTML(name)+'</a>... Done.' : 'Adding pictures... Done.', window.pPictureGallery? pPictureGallery.update : null ],
			headers: pHTTPRequest.headers_post,
			body: b//[ i_body ]
		});
	};

	/*******************************************************************************/
	
	this.findMetadata = function(p_doc_url, p_xid_prefix, p_mlid) {
		pTransaction.simpleTransaction({
			trace: that, 
			url: pURL.addQueryParameter(p_doc_url || window.location.pathname, 
				[ 'action', 'find-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', that.getMLIDfromURL(p_doc_url) ]), 
			method_name: "FindMetadata",
			msg_start: 'Searching for information...',
			msg_end: function(req) {
				pMetadataDialog.c_completed.call(this, req, {
				option_showArtwork: true, option_showBackdrop: true, option_showPoster: true, option_showImage: true, xid_prefix: p_xid_prefix, mlid: p_mlid, option_title: (p_mlid)? true : null, path: p_xid_prefix? p_doc_url : null });
				
				document.dispatchEvent(new Event("xfindmetadata"))
			}
			//TODO: show "more on imdb, ttvdb.com..." tasks
		});
	};

	/*******************************************************************************/
	
	this.formats = {
		video: [
			{ text: 'AVI Windows Video File', value: 'avi' },
			{ value: 'flv', text: 'Flash Video Files' },
			{ value: 'mkv', text: 'Matroska Video Files' },
			{ value: 'mov', text: 'Quicktime Video Files' },
			{ value: 'mpeg', text: 'MPEG Video Files' },
			{ text: 'MP4 Video File', value: 'm4v', selected: true },
			{ value: 'qt', text: 'Quicktime Video Files' },
			{ value: 'webm', text: 'WebM Video Files' },
			{ text: 'WMV Windows Video File', value: 'wmv' }
		],
		audio: [ 
	       	{ value: 'aac', text: 'AAC Audio Files' },
	       	{ value: 'flac', text: 'FLAC Audio Files' },
	       	{ value: 'mka', text: 'Matroska Audio Files' },
	       	{ value: 'mp3', text: 'MP3 Audio File', selected: true },
	       	{ value: 'm4a', text: 'MP4 Audio Files' },
	       	{ value: 'wav', text: 'Waveform Audio Files (.wav)' },
	       	{ value: 'wma', text: 'Windows Audio Files (.wma)' }
       	],
       	image: [
		   { text: 'BMP Image File', value: 'bmp' }, 
		   { text: 'JPEG Image File', value: 'jpg', selected: true }, 
		   { text: 'GIF Image File', value: 'gif' }, 
		   { text: 'Portable Network Grpahics (PNG) Image File', value: 'png' }, 
		   { text: 'TIFF Image File', value: 'tiff' }, 
		   { text: 'Google WebP Image', value: 'webp' }
		]
	};
	this.formats.audio_video = pArray.sortByText(this.formats.audio.concat(this.formats.video));
	
	this.convertToMP4 = function(i_data) {
		return new Promise(function(resolve, reject) {
			
			pDocument.wait();

			i_data = i_data || {};
			var i_url = pURL.addParam(i_data.url, [ 'action', 'get-metadata',  
				'format', 'json', 'contents', 'values', 'v', ''+Math.random(), 'files', 1, 'file', i_data.play_url || '', 'artwork', i_data.play_url? 0 : 1,
				'content_ratings', 0, "user_country", 0, "langs", 0, "types", 0 ]);
			pConsole.info(this, "GetProperties: " + i_url);
			pHTTPRequest.get(i_url).then(function(i_request) {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//console.log(i_request.responseText);
				//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
				var i_obj = pJSON.parse(i_request.responseText);
				//pConsole.debug(that, "GetProperties: response: " + pJSON.pretty(i_request.responseText));

				//TODO: 3gp
				var i_values = new pMap(i_obj.values), i_types = (i_data.audio? that.formats.audio : (i_data.img? that.formats.image : that.formats.audio_video)),
					f = i_obj.file || (i_obj.files && i_obj.files.length>0? i_obj.files[0] : null), fn = (f? f.name : null) || 'output.m4v';
				
				if (f && f.ext) {
					fn = fn.substring(0, fn.length - f.ext.length - 1);
					//i_types = i_types.filter(function(t) { return t.value != f.ext; });
					
					var fnl = fn.toLowerCase(), exts = i_types.map(pObject.value);
					i_types.map(pObject.value).concat([ 'mpg', 'mpeg', 'mp4', 'bmp', 'jpg', 'jpeg', 'gif', 'png', 'tiff', 'webp' ]).forEach(function(t) {
						if (fnl.endsWith('-'+t) || fnl.endsWith('_'+t))
							if (fn.length - t.length - 1 > 0)
								fn = fn.substring(0, fn.length - t.length - 1);
					});
					if (i_data.img)
						fn += exts.includes('jpg')? '.jpg' : '.'+i_types[0].value;
					else if (i_data.audio)
						fn += exts.includes('mp3')? '.mp3' : '.'+i_types[0].value;
					else
						fn += exts.includes('m4v')? '.m4v' : '.'+i_types[0].value;
					
					//fn += (i_data.img && exts.includes('jpg'))? '.jpg': (i_data.audio && exts.include)? '.mp3' : '.m4v');
				}
				
				var i_dialog = { 
					id: 'convert', 
					title: 'Convert media(s) to another format...',
					options: [ { id: 'ok', text: 'Convert' }, 'cancel',
						{ id: 'tab-main', tab: true, text: 'Files' },
						
					    i_data.all===true? 'This will convert and merge all media files from this page into a single media file...<p>' : null,
					    i_data.batch===true? 'This will convert all media files from this page into the selected format...<p>' : null,
					    i_data.img? '<div class="warning">Metadata from the input file (title, description, date taken, etc...), will not be copied into the output file...<p></div>' : null,
					    !i_data.all && !i_data.batch && f.name? { id: 'from', input: { readonly: true }, text: 'Input File:', value: f.name } : null,
					    		'<p>',
					    { id: 'format', input: { type: 'select', options: i_types, onchange: function() {
					    	var t = pDialog.getInputValue('convert', 'format'), fn = pDialog.getInputValue('convert', 'output'), p = fn.lastIndexOf('.');
					    	pDialog.setInputValue('convert', 'output', p>0? fn.substring(0, p+1) + t : fn+'.'+t); 
					    } }, text: 'Output Format:' },
						i_data.batch===true? null : {
							id: 'output', input: {
								placeholder: 'Destination file...',
								icons: [{
									className: 'dialog-question-button-fileserver',
									title: 'Click here to change the folder of the output file...',
									onclick: function(op_id){
										var i_path = pElement.x('dialog-input-' + op_id).value;
										pOpenFileDialog.selectFolder({ 
											callback: function(p_path) {
												var i_path = pElement.x('dialog-input-' + this.id).value;
												pElement.updateValue('dialog-input-' + this.id, p_path + '/' + pString.basename(i_path));
											}.bind({ id: op_id }), 
											title: 'Change Folder...', 
											current_path: (i_path.indexOf('/')>=0 || i_path.indexOf(pString.backslash)>=0)? pString.dirname(i_path) : null, 
											ok_title: 'Select this Folder' 
										}); //TODO: check current_path
									}
								}]
							},
							text: 'Output file:', value: fn },
							
						'<p>',
						i_data.ximport===false? null : pDialog.newCheckbox('import', 'Import output file into library', null, true),
						pDialog.newCheckbox('replace', 'Replace existing output file (if it already exists)', null, true),
						
						/*{ id: 'tab-advanced', tab: true, text: 'Advanced' },
						{ id: 'time-all', input: { type: 'radio', name: 'time', value: 1, selected: true }, text_after: 'All', text_after_class: 'library-properties-checkbox', value: "1" },
						{ id: 'time-from', input: { type: 'radio', name: 'time', value: 0 }, text_after: 'From', text_after_class: 'library-properties-checkbox', value: "0" },
						{ id: 'start_ms', input: { type: 'time', min: 0, max: pDate.inputTimeToMS(i_values.getValue('metadata_length')) }, text: 'From:', className: 'dialog-question-input-inline', value: pDate.msToInputTime(0) },
						{ id: 'end_ms', input: { type: 'time', min: 0, max: pDate.inputTimeToMS(i_values.getValue('metadata_length'))  }, text: 'To:', className: 'dialog-question-input-inline', value: i_values.getValue('metadata_length') }//pDate.msToInputTime(0) }
						*/
					]
				};
						
				pDocument.stopwait(); //TODO: inside dialogQuestion...
				pDialog.question(i_dialog).then(function(inputs) {
					var t = inputs.getValue('format');
					
					i_data = that.prepareRefreshRequest(i_data);
					pTransaction.simpleTransaction({
						trace: i_data, 
						url: pURL.addQueryParameter(i_data.url, [ 'action', 'convert-to-mp4', 'v', Math.random(), 'wait', 1, 
	'name', i_data.batch===true? 'foo.'+t : inputs.getValue('output'), 
	'import', inputs.getValue('import'), 'replace', inputs.getValue('replace'), 'batch', i_data.batch, 'play_url', i_data.play_url || '',
	'all', inputs.getValue('time'),
	'start_ms', inputs.getValue('start_ms'),
	'end_ms', inputs.getValue('end_ms')
						]), 
						method_name: "ConvertToMP4",
						msg_start: 'Converting media(s)...',
						msg_end: [ 'Converting media(s)... Done.', i_data.c_deleted, resolve ],
						errorCodes: [ 500, 409, 404 ],
						headers: pHTTPRequest.headers_post,
						body: ''//p_options
					});
				});
			});
		});
	};
	
	this.reImport = function(i_data) {
		
		i_data = that.prepareRefreshRequest(i_data);
		i_data.fok = function() {
			pDocument.fire('gms-reimport');
		};
		
		var i_url = pURL.addQueryParameter(i_data.url || window.location.pathname, [ 'action', 'get-url', 'v', Math.random() ]);
		pHTTPRequest.invoke({ 
			url: i_url, 
			//method: 'GET', 
			//responseType: 'text',
			f404: function() { that.reImportImpl(this); }.bind(i_data), 
			onreadystatechange: function(p_request) {
				
				pPageParser.parse(p_request.responseText).then(function(p_values) {

					pTransaction.simpleTransaction({
						trace: that, url: pURL.addQueryParameter(this.url, [ 'action', 'reimport', 'v', Math.random(), 'wait', 1 ]), method_name: "ReImport",
						msg_start: 'Re-importing media(s)...',
						msg_end: [ 'Re-importing media(s)... Done.', this.c_deleted ],
						headers: pHTTPRequest.headers_post,
						body: pURL.toFormSubmitBody(p_values)
					});
					
				}.bind(this));
				
			}.bind(i_data) 
		});
	};
	this.reImportImpl = function(i_data) {
		
		i_data = that.prepareRefreshRequest(i_data);
		
		pUserJobManager.getImportOptions((function(p_options) {
			pTransaction.simpleTransaction({
				trace: this, url: pURL.addQueryParameter(this.url, [ 'action', 'reimport', 'v', Math.random(), 'wait', 1 ]), method_name: "ReImport",
				msg_start: 'Re-importing media(s)...',
				msg_end: [ 'Re-importing media(s)... Done.', this.c_deleted ],
				headers: pHTTPRequest.headers_post,
				body: p_options
			});
		}).bind(i_data), true);
	};

	this.reImportParentFolder = function(i_data) {

		i_data = that.prepareRefreshRequest(i_data);
		
		pUserJobManager.getImportOptions((function(p_options) {
			pTransaction.simpleTransaction({
				trace: this, url: pURL.addQueryParameter(this.url || window.location.pathname, [ 'action', 'reimport-parent-folder', 'v', Math.random(), 'wait', 1 ]), method_name: "ReImportParentFolder",
				msg_start: 'Re-importing media parent folder...',
				msg_end: [ 'Re-importing media parent folder... Done.', this.c_deleted ],
				headers: pHTTPRequest.headers_post,
				body: p_options
			});
		}).bind(i_data), false);
	};

	this.copy = function(p_doc_url, p_f_callback) {
		pOpenFileDialog.selectFolder({
			title: 'Copy... Select the Destination Folder...', ok_title: 'Select this folder',
			callback: function(p_path) {
				pTransaction.simpleTransaction({
					trace: this, url: pURL.addQueryParameter(this.url, 'to', p_path), method_name: "Copy",
					msg_start: 'Copying to other folder...',
					errorCodes: [ 500 ],
					responseType: "text",
					headers: pHTTPRequest.headers_post,
					body: this.body
				});
			}.bind({ url: pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'copy', 'v', Math.random() ]), callback: p_f_callback, body: '' })
		});
	};
	
	this.download = function(p_doc_url) {
		window.open(pURL.addQueryParameter(p_doc_url, [ 'action', 'download', 'v', Math.random() ]));
	};
	
	this.copyPicture = function(p_doc_url, p_play_url, p_f_callback) {
		if (p_play_url.forEach) {
			p_doc_url = pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'copy-item', 'v', Math.random() ]);
			var i_body = '';
			p_play_url.forEach(function(i_url, i) {
				i_body = pURL.addBodyParameter(i_body, 'play_url'+i, i_url);
			});

			pOpenFileDialog.selectFolder({
				title: 'Copy Picture File(s)... Select the Destination Folder...', ok_title: 'Select this folder',
				callback: function(p_path) {
					pTransaction.simpleTransaction({
						trace: this, url: pURL.addQueryParameter(this.url, 'to', p_path), method_name: "CopyItem",
						msg_start: 'Copying Picture(s) to other folder...',
						errorCodes: [ 500 ],
						responseType: "text",
						headers: pHTTPRequest.headers_post,
						body: this.body
					});
				}.bind({ url: p_doc_url, callback: p_f_callback, body: i_body })
			});
		}
		else
			pOpenFileDialog.selectFolder({
				title: 'Copy Picture File... Select the Destination Folder...', ok_title: 'Select this folder',
				callback: function(p_path) {
					pTransaction.simpleTransaction({
						trace: this, url: pURL.addQueryParameter(this.url, 'to', p_path), method_name: "CopyItem",
						msg_start: 'Copying Picture to other folder...',
						errorCodes: [ 500 ],
						responseType: "text",
						headers: pHTTPRequest.headers_post,
						body: this.body
					});
				}.bind({ url: pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'copy-item', 'v', Math.random() ]), callback: p_f_callback, body: pURL.addBodyParameter('', 'play_url', p_play_url) })
			});
	};
	this.movePicture = function(p_doc_url, p_play_url, p_f_callback, p_to_url) {
		if (p_play_url.forEach) {
			p_doc_url = pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'move-item', 'v', Math.random() ]);
			var i_body = '';
			p_play_url.forEach(function(i_url, i) {
				i_body = pURL.addBodyParameter(i_body, 'play_url'+i, i_url);
			});
			
			if (p_to_url) 
				pTransaction.simpleTransaction({
					trace: this, url: pURL.addQueryParameter(p_doc_url, 'to_url', p_to_url), method_name: "MoveItem",
					msg_start: 'Moving Picture(s) to this folder...',
					msg_end: p_f_callback,
					errorCodes: [ 500 ],
					headers: pHTTPRequest.headers_post,
					body: i_body
				});
			else
				pOpenFileDialog.selectFolder({
					title: 'Move Picture File(s)... Select the Destination Folder...', ok_title: 'Select this folder',
					callback: function(p_path) {
						pTransaction.simpleTransaction({
							trace: this, url: pURL.addQueryParameter(this.url, 'to', p_path), method_name: "MoveItem",
							msg_start: 'Moving Picture(s) to other folder...',
							msg_end: this.callback,
							errorCodes: [ 500 ],
							headers: pHTTPRequest.headers_post,
							body: this.body
						});
		
					}.bind({ url: p_doc_url, callback: p_f_callback, body: i_body })
				});
		}
		else {
			p_doc_url = pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'move-item', 'v', Math.random() ]);
			var i_body = pURL.addBodyParameter('', 'play_url', p_play_url);
			if (p_to_url)
				pTransaction.simpleTransaction({
					trace: this, url: pURL.addQueryParameter(p_doc_url, 'to_url', p_to_url), method_name: "MoveItem",
					msg_start: 'Moving Picture to this folder...',
					msg_end: p_f_callback,
					errorCodes: [ 500 ],
					headers: pHTTPRequest.headers_post,
					body: i_body
				});
			else
				pOpenFileDialog.selectFolder({
					title: 'Move Picture File... Select the Destination Folder...', ok_title: 'Select this folder',
					callback: function(p_path) {
						pTransaction.simpleTransaction({
							trace: this, url: pURL.addQueryParameter(this.url, 'to', p_path), method_name: "MoveItem",
							msg_start: 'Moving Picture to other folder...',
							msg_end: this.callback,
							errorCodes: [ 500 ],
							headers: pHTTPRequest.headers_post,
							body: this.body
						});
		
					}.bind({ url: p_doc_url, callback: p_f_callback, body: i_body })
				});
		}
	};
	this.deletePicture = function(p_doc_url, p_play_url, p_f_callback, p_ask, p_title, p_msg) {
		if (p_ask === false) {
			var i_body = '';
			p_play_url.forEach(function(u, i) {
				i_body = pURL.addBodyParameter(i_body, 'play_url'+i, u);
			});
			
			pTransaction.simpleTransaction({
				trace: that, 
				url: pURL.addQueryParameter(p_doc_url || window.location.pathname, [ 'action', 'delete-item', 'v', Math.random() ]), 
				method_name: "DeleteItem", 
				method: 'DELETE',
				msg_start: 'Deleting Picture(s)...',
				msg_end: p_f_callback,
				errorCodes: [ 500 ],
				responseType: "text",
				headers: pHTTPRequest.headers_post,
				body: i_body
			});
		}
		else if (p_play_url.forEach) {
			pDialog.dialogQuestion({
				id: 'that.delete-pictures',
				title: p_title || 'Delete Picture File(s)...',
				options: [
					p_msg || 'Are you sure to delete those file(s) from disk?',
					'<hr>', 'yes', 'no'
				],
				fcall: function(p_dialog) {
					  that.deletePicture(this.url, this.p_play_url, this.callback, false);
				}.bind({ url: p_doc_url, callback: p_f_callback, p_play_url: p_play_url })
			});
		}
		else
			pDialog.dialogQuestion({
				id: 'that.delete-picture',
				title: 'Delete Picture File...',
				options: [
					'Are you sure to delete this file from disk?',
					'<hr>', 'yes', 'no'
				],
				fcall: function(p_dialog) {
						  that.deletePicture(this.url, this.p_play_url, this.callback, false);
					  }.bind({ url: p_doc_url, callback: p_f_callback, p_play_url: [ p_play_url ] })
			});
	};
	this.renameItem = function(p_doc_url, p_play_url, p_f_callback, p_name, p_msg, p_icon, p_error) {
		var i_data = {
			p_doc_url: p_doc_url || window.location.pathname,
			play_url: p_play_url,
			callback: p_f_callback,
			p_name: p_name,
			p_msg: p_msg,
			p_icon: p_icon
		};

		var i_selection = p_name;
		if (p_name) {
			var i_pos = p_name.lastIndexOf('.');
			if (i_pos>0)
				i_selection = p_name.substring(0, i_pos);
		}
		var i_input = pDialog.resources.input_foldername();
		i_input.selection = i_selection;
		pDialog.dialogQuestion({
			id: 'that.rename-picture',
			title: 'Rename Picture File...',
			icon: p_icon,
			options: [
				(p_error)? { id: 'error', text: p_error.text, disabled: true, className: 'dialog-error' } : null,
				(p_error && p_error.img)? pImages.createDialogOption(p_error.img) : null,
				{ id: 'name', input: i_input, text: 'New name:', value: (p_name)? p_name : '' },
				'ok', 'cancel',
			],
		  fcall: function(p_dialog) {
			  var i_data = this;
			  var i_oname = i_data.p_name;
			  i_data.p_name = p_dialog.inputs.getValue('name', null);

			  var i_url = pURL.addQueryParameter(i_data.p_doc_url, [ 'action', 'rename-item', 'play_url', i_data.play_url, 'name', i_data.p_name, 'v', Math.random() ]);

			  pConsole.info(this, "RenameItem: " + i_url);
			  pHTTPRequest.post(i_url, function(request) {
				  var i_data = this;
				  if (request.status == 409)  {//login page is 200...
					  
					  var n = i_data.p_name.toLowerCase();
					  pConsole.info(this, 'Getting picture ('+n+')...');
					  var i_url = pURL.addQueryParameter(i_data.p_doc_url, [ 'action', 'contents', 'v', Math.random(), 'sort', 0, 'filter_name', n ]);
					  if (pApplicationUI.OPTION_PICTURES_GET_ORIENTATION) 
						  i_url = pURL.addQueryParameter(i_url, 'get_orientation', '1');

						pHTTPRequest.get(i_url).then(function(p_request) {
						
							if (pROSE.handleHTTPResponse(p_request)===false)
								return;
						
							var i_obj = pJSON.parse(p_request.responseText);
							if (!i_obj)
								that.renameItem(i_data.p_doc_url, i_data.play_url, i_data.callback, i_oname, i_data.p_msg, i_data.p_icon, { text: 'A file already exists with the same name...' });
							else {
								var i = i_obj.images.find(function(i) { return i.name.toLowerCase() == n });
								that.renameItem(i_data.p_doc_url, i_data.play_url, i_data.callback, i_oname, i_data.p_msg, i_data.p_icon, 
									i? { text: 'A file already exists with the same name:', img: i } : { text: 'A file already exists with the same name...' });
								}
							});
						  
							//that.renameItem(i_data.p_doc_url, i_data.play_url, i_data.callback, i_data.p_name, i_data.p_msg, i_data.p_icon, 'A file already exists with the same name...');
						return;
				  }

				  //handle errors
				  if (pROSE.handleHTTPResponse(request) === false)
					return false;

				if (i_data.callback)
					i_data.callback.call(i_data, i_data.p_name);

			  }.bind(i_data), false, null);
		  }.bind(i_data)
		});
	};

	/*******************************************************************************/
	/***  RENAME PARENT FOLDER  ****************************************************/
	/*******************************************************************************/

	this.renameParentFolder = function(r) {
		var T = pText.rename_parent_folder;
		
		r = that.prepareRefreshRequest(r);
		
		var i_dialog = {
			id: 'pMediaLibrary-renameParentFolder',
			icon: r.icon,
			options: [
				(r.error)? { id: 'error', text: r.error, disabled: true, className: 'dialog-error' } : null,
				{ id: 'name', input: pDialog.resources.input_foldername(), text: T.name, value: r.name },
				'ok',
				'cancel',
			],
			fcall: function(did) {
				  var r = this;
				  var n = r.name = did.inputs.getValue('name', null);

				  pTransaction.simpleTransaction({
					trace: that, 
					url: pURL.addQueryParameter(r.url, [ 'action', 'rename-parent-folder', 'name', n, 'v', Math.random(), 'wait', 1 ]), 
					method_name: "RenameParentFolder",
					msg_start: pText.safe(T.msg_start, n),
					msg_end: [ pText.safe(T.msg_end, n), this.c_deleted ],
					msg_409: function(req) {
						this.error = pText.safe(T.msg_409, n);
						that.renameParentFolder(this);
					}.bind(this),
					msg_500: function(req) {
						pApplicationUI.errorDialog({ text: pText.safe(T.failure, n, req.statusText), icon: { src: '/resources/html/images/32x32/folder_error.png' } });
					},
				});
			  }.bind(r)
		};
		
		r.error = null;
		pDialog.dialogQuestion(i_dialog);
	};

    /*******************************************************************************/
	/***  REMOVE FILE/DOC/GROUP FROM LIBRARY  **************************************/
	/*******************************************************************************/

	this.prepareRefreshRequest = function(i_data) {

		if (i_data && typeof i_data == "string") i_data = { url: i_data };
		i_data = i_data || {};
		i_data.url = i_data.url || window.location.path;

		i_data.f_fdeleted = function() {
			if (this.fdeleted || this.fok)
				pHTTPRequest.head({ url: this.url, f200: this.fok, f404: this.fdeleted });
		}.bind(i_data);
		
		i_data.c_deleted = function(i_request) {
			
			var o = pJSON.parse(i_request.responseText);
			if (o && o.actions) {
				var id = that.getIDfromURL(), lid = that.getLIDfromURL(), a;
				if (o.actions.find(function(a) { return a.action == 2 && a.object.id == id && a.object.lid == lid }))
					if (a = o.actions.find(function(a) { return a.action == 0 && a.object.lid == lid && a.object.id != id })) {
						var u = url_prefix + that.getLibraryIDfromURL()+'/'+a.object.lid+'/'+a.object.id;
						
						if (window.history.length<2)
							pNotificationUI.addNotification('This page has been recreated with a different location. Click <a href=\"'+u+'\">here</a> to visit it.');
						else
							pNotificationUI.addNotification('This page has been recreated with a different location. Click <a href=\"'+u+'\">here</a> to visit it or <a href=\"javascript:void(pLocation.backAndReload())\">here</a> to go back and refresh...');
						
						notifyDeletion();
						return;
					}
			}
				
			if (pLocation.path(this.url) == window.location.pathname) {
				pHTTPRequest.head({ url: this.url, f404: [ pApplicationUI.askBackAndReload, notifyDeletion ] });
				return;
			}
			
			pHTTPRequest.head({ url: window.location.pathname, f200: this.f_fdeleted, f404: [ pApplicationUI.askBackAndReload, notifyDeletion ] });
			
			//if (this.fdeleted)
			//	pHTTPRequest.head({ url: pURL.addQueryParameter(this.url, 'head', ''), f404: this.fdeleted });
			
		}.bind(i_data);
		
		//i_data.lib = that;
		return i_data;
	};

	/*******************************************************************************/
	
	this.removeThis = function(p_doc_url, p_delete_file_option) {
		return that.remove({ url: p_doc_url || window.location.pathname, delete_file: p_delete_file_option, main: true });
	};

	this.remove = function(i_data) {
		
		i_data = that.prepareRefreshRequest(i_data);
		i_data.url = i_data.url || window.location.pathname;
		
		var p_doc_url = i_data.url;
		var p_delete_file_option = i_data.delete_file;
		var p_msg = i_data.msg;
		var p_error = i_data.error;
		var p_main = i_data.main;
		
		var i_dialog = {
			id: 'remove',
			title: i_data.title,
			icon: i_data.icon,
			options: [
				(p_error)? { id: 'error', text: p_error, disabled: true, className: 'dialog-error' } : null,
				{ id: 'x', sep: true, text: p_msg || 'Are you sure to remove this item from the library?<p>' },
				(null==p_delete_file_option || p_delete_file_option === true)? pDialog.newCheckbox('delete', 'Delete the file on disk as well') : null,
				(p_error)?	pDialog.newCheckbox('force', 'Force removal from the library (even if the file is not found)') : null,
				'<hr>', 'yes', 'no'
			],
			fcall: function(did) {
				var i_data = this;

				var i_url = pURL.addQueryParameter(i_data.url, [ 'action', 'delete', 'v', Math.random(), 'wait', 1, 
					'delete-file', did.inputs.getValue('delete', false)? 1:0, 'force', did.inputs.getValue('force', false)? 1:0 ] );

					pTransaction.simpleTransaction({
						trace: that, 
						url: i_url, 
						method: "delete", 
						method_name: "Remove",
						msg_start: 'Removing media(s)...',
						msg_403: function(req) {
							this.error = 'Removal failed: you are not authorized to remove this resource...';
							that.remove(this);
						}.bind(i_data),
						msg_404: function(req) {
							this.error = 'Removal failed: resource not found...';
							that.remove(this);
						}.bind(i_data),
						msg_end: [ 'Removing media(s)... Done.', i_data.c_deleted ]
					});
			  }.bind(i_data)
		};
		
		i_data.error = null;
		pDialog.dialogQuestion(i_dialog);
	};

	/*******************************************************************************/
	/***  REMOVE PLAYLIST  *********************************************************/
	/*******************************************************************************/

	this.removePlaylist = function(p_doc_url, p_delete_file_option, p_msg) {
		var T = pText.remove_playlist;
		
		that.remove({ 
			url: window.location.pathname, 
			msg: T.question, 
			icon: { src: '/resources/html/images/32x32/script_delete.png' },
			delete_file: false
		});
	};

	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/

	//TODO: name regex
	this.createPlaylist = function(r) {
		var T = pText.create_playlist;
		
		return new Promise(function(resolve, reject) {
			r = r || {};
			//r.lib = this;
			r.url = r.url || that.getLibraryURL();
			
			var i_dialog = {
				id: 'create-playlist',
				options: [
					(r.error)? { id: 'error', text: r.error, disabled: true, className: 'dialog-error' } : null,
					{ id: 'name', input: { required: true, spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false }, text: T.name, value: r.name },
					'ok', 'cancel'
				],
				fcall: function(did, v) {
					var n = r.name = did.inputs.getValue('name', '');
	
					pTransaction.simpleTransaction({
						trace: that, url: pURL.addQueryParameter(r.url, [ 'action', 'create-playlist', 'name', n, 'v', Math.random(), 'wait', 1 ]), method_name: "CreatePlaylist", responseType: 'text',
						msg_start: pText.safe(T.msg_start, n),
						msg_end: [ pText.safe(T.msg_end, n), function(r){
							pListManager.notify(pListManager.EVENT_CREATED_PLAYLIST);
							
							pNoteUI.notesDialog('help-create-playlist').then(function(r) {
								var o = pJSON.parse(r.responseText);
								resolve(o.actions[0].object);
							})
						} ],
						msg_409: function(req) {
							r.error = pText.safe(T.msg_409, n);
							that.createPlaylist(r).then(resolve, reject);
						},
						msg_500: function(req) {
							pApplicationUI.errorDialog({ text: pText.safe(T.failure, n, req.statusText), icon: { src: '/resources/html/images/32x32/script_error.png' } }).then(reject);
						},
					});
				}
			};
			
			r.error = null;
			pDialog.dialogQuestion(i_dialog);
		});
	};

	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/
	
	this.getFile = function(r) {
		return new Promise(function(resolve, reject) {
			r = r || {};
			r.url = r.url || window.location.pathname;
		
			pHTTPRequest.get(pURL.addParam(r.url, [ 'action', 'get-file', 'v', ''+Math.random() ]), function(rp, i_data) {
				
				//handle errors
				//if (pROSE.handleHTTPResponse(i_request) === false)
				//	return false;
		
				//console.log(i_request.responseText);
				//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
				var i_obj = pJSON.parse(rp.responseText);
				resolve(i_obj.file);
				
			}, false);
		});
	};
	
	this.createFolder = function(r) {
		var T = pText.create_folder;
		r  = r || {};
		
		return new Promise(function(resolve, reject) {
			r.url = r.url || window.location.pathname;
			if (!r.attempt) r.attempt = 0;
			r.resolve = function(rep, fresolve) {
				pListManager.notify(pListManager.EVENT_CREATED_FOLDER);
				
				var o = pJSON.parse(rep.responseText);
				fresolve(o.actions[0].object, r, rep);
			};
			r.reject = function(rep, freject) {
				freject(r, rep);
			};
			r.errorUI = function(rep, freject) {
				pApplicationUI.errorDialog({ text: pText.safe(T.failure, r.name, req.statusText), icon: { src: '/resources/html/images/32x32/folder_error.png' } }).then(
					function() { r.reject(rep, freject) });
			};
			
			if (r.silent) {
				var n = r.name;
				pTransaction.transaction({
					trace: that, url: pURL.addQueryParameter(r.url, [ 'action', 'create-folder', 'name', n, 'v', Math.random(), 'wait', 1 ]), method_name: "CreateFolder",
					msg_start: pText.safe(T.msg_start, n),
					msg_end: [ pText.safe(T.msg_end, n), function(rep) { r.resolve(rep, resolve) }],
					msg_409: function(req) {
						if (!r.org_name) r.org_name = n;
						r.name = r.org_name + ' ('+(++r.attempt)+')';
						that.createFolder(r).then(resolve, reject);
					},
					msg_500: function(rep) { r.errorUI(rep, reject); }
				});
			}
			else {
				var i_dialog = {
					id: 'create-folder',
					options: [
						(r.error)? { id: 'error', text: r.error, disabled: true, className: 'dialog-error' } : null,
						{ id: 'name', input: pDialog.resources.input_foldername(), text: T.name, value: r.name },
						'ok', 'cancel'
					],
					fcall: function(did, v) {
						var n = r.name = did.inputs.getValue('name', '');
						
						pTransaction.transaction({
							trace: that, url: pURL.addQueryParameter(r.url, [ 'action', 'create-folder', 'name', n, 'v', Math.random(), 'wait', 1 ]), method_name: "CreateFolder",
							msg_start: pText.safe(T.msg_start, n),
							msg_end: [ pText.safe(T.msg_end, n), function(rep) { r.resolve(rep, resolve) }],
							msg_409: function(req) {
								r.attempt++;
								r.error = pText.safe(T.msg_409, n);
								that.createFolder(r).then(resolve, reject);
							},
							msg_500: function(rep) { r.errorUI(rep, reject); }
						});
					}
				};
				
				r.error = null;
				pDialog.dialogQuestion(i_dialog);
			}
		});
	};
	
	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/
	
	this.changeMetadata = function(r) {
		pTransaction.simpleTransaction({
			trace: that, 
			url: r.url, 
			method_name: r.method,
			msg_start: r.msg,
			msg_end: pArray.append([ pMetadataDialog.c_completed ], r.msg_end || []),
			headers: pHTTPRequest.headers_post,
			body: r.body
		});
	};
		
	function f_changeMetadata(p_url, p_method, p_msg, p_body, p_id) {
		pTransaction.simpleTransaction({
			id: p_id,
			trace: that, 
			url: p_url, 
			method_name: p_method,
			msg_start: p_msg,
			msg_end: pMetadataDialog.c_completed,
			headers: pHTTPRequest.headers_post,
			body: p_body
		});
	}

	this.selectActors = function(p_doc_url, p_value) {

		function f_setActors(p_doc_url, v) {
			that.changeMetadata({
				url: pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-actors.json', 'others', that.getMLIDfromURL() ]),
				method: "SetActors", 
				msg: "Updating actors...", 
				body: pURL.addBodyParameter('', 'metadata_actors', (typeof v=="string")? v : pString.concat(v, '|'))
			});
		}
		
		if (!p_value) p_value = (pPage.getValue)? pPage.getValue("metadata_actors") : pPage.metadata_actors;
		var i_data = {
			doc_url: p_doc_url || window.location.pathname
		};

		pDialog.dialogQuestion({
			id: 'select-actors',
			options: [
				{ id: 'actors', input: { spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false, icons: [ pDialog.resources.icon_delete ] }, text: 'Comma-separated list of actor(s):', value: (p_value)? pString.concat(p_value.split('|'), ', ') : '' },
				'ok', 'cancel'
			],
			fcall: function(p_dialog, p_value) { f_setActors(i_data.doc_url, pString.concat(pHumanText.split(p_dialog.inputs.getValue('actors', '')), '|')); }
		});
	};

	this.selectAuthors = function(p_doc_url, p_value) {

		function f_setAuthor(p_doc_url, v) {
			f_changeMetadata(
				pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-author.json', 'others', that.getMLIDfromURL() ]), 
				"SetAuthors", 
				"Updating authors...", 
				'metadata_author='+pURL.fixedEncodeURIComponent((typeof p_value=="string")? v : pString.concat(v, '|'))
			);
		}
		
		if (!p_value) p_value = (pPage.getValue)? pPage.getValue("metadata_author") : pPage.metadata_author;
		var i_data = {
			doc_url: p_doc_url || window.location.pathname
		};

		pDialog.dialogQuestion({
			id: 'select-authors',
			options: [
				{ id: 'authors', input: { spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false, icons: [ pDialog.resources.icon_delete ] }, text: 'Comma-separated list of authors(s):', value: (p_value)? pString.concat(p_value.split('|'), ', ') : '' },
				'ok', 'cancel'
			],
			fcall: function(p_dialog, p_value) { f_setAuthor(i_data.doc_url, pString.concat(pHumanText.split(p_dialog.inputs.getValue('authors', '')), '|')); }
		});
	};

	this.selectGenres = function(r) {

		pDocument.wait();

		function f_setGenre(p_doc_url, v) {
			f_changeMetadata(
				pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-genre.json', 'others', that.getMLIDfromURL() ]), 
				"SetGenres", 
				"Updating genres...", 
				'metadata_genre='+pURL.fixedEncodeURIComponent((typeof p_value=="string")? v : pString.concat(v, '|'))
			);
		}

		r = r || {};
		var p_value = r.value, p_list_url = r.list_url, p_fcall = r.fcall;

		if (!p_value && p_value!="") p_value = (pPage.getValue)? pPage.getValue("metadata_genre") : pPage.metadata_genre;
		var i_data = {
			doc_url: r.doc_url || window.location.pathname,
			genres: pString.split((p_value || '').toLowerCase(), '|')
		};
		if (!pString.v(p_list_url) || p_list_url == 'auto') p_list_url = that.getGenresURL(i_data.doc_url);
		if (p_list_url) {
			pHTTPRequest.get(p_list_url+'?format=json&contents=names&v='+Math.random()).then(function(i_request) {

				var i_data = this;
				
				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				var i_obj = pJSON.parse(i_request.responseText);
				var i_dialog = {
					id: 'select-genres',
					title: r.title,
					options: [ 'ok', 'cancel' ],
					multiple: true,
					fcall: p_fcall || function(p_dialog, p_value) { f_setGenre(i_data.doc_url, p_value); },
					footer: [
						{ id: 'dialog-genre-new', text: (pDevice.isSmallScreen())? '+':'New...', className: 'dialog-footer-button-new dialog-footer-button', scall: function(p_dialog, value) {
							var i_data = this;
							pDialog.dialogQuestion({
								id: 'new-genres',
								options: [
									'ok', 'cancel',
									{ id: 'genres-new', input: { type: 'text', required: true, spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false }, text: 'Comma-separated list of genre(s): ' }
								],
								fcall: function(p_dialog, p_value) {
									var i_data = this;
									pString.split(p_dialog.inputs.getValue('genres-new', ''), ',').sort().filter(pMetadata.f_filter).forEach(function(g) {
										if (!i_data.genres.includes(g)) {
											pDialog.removeDialogOption('select-genres', 'no-genre-help');
											pDialog.addDialogOption('select-genres', 'content', { id: g, value: g, text: pString.encodeHTML(pString.capitalize(g)), className: 'dialog-genre genre' });
										}
									});
								}.bind(i_data)
							});
						}.bind(i_data)}
					]//footer: [
				};
				
				var added = 0, ll = pString.split(pString.concat(i_obj.reply, ","), ',');
				
				//*** REMOVE DUPLICATES
				var l = [];
				ll.forEach(function(t) { if (!l.includes(t)) l[l.length] = t; });
				
				if (that.getLIDfromURL(i_data.doc_url).endsWith('.tags')) {
					var gl = new pDefinedUniqueObjectList();
					i_obj.genre.filter(pObject.isNotNull).forEach(function(g) { pString.split(g, '|').forEach(gl.push) });
					l = gl.values();
				}
				
				l.sort().filter(pMetadata.f_filter).forEach(function(g) {
					i_dialog.options.push({ id: g, value: g, text: pString.encodeHTML(pString.capitalize(g)), className: 'dialog-genre genre', selected: i_data.genres.includes(g.toLowerCase()) });
					added++;
				});

				if (added == 0)
					i_dialog.options.push({ id: 'no-genre-help', sep: true, text: '<div class="help">No genre to select yet...<p>Click on the "'+((pDevice.isSmallScreen())? '+':'New...')+'" button below to enter new genre(s)...</div>'});
				
				pDocument.stopwait();
				pDialog.dialogQuestion(i_dialog);

			}.bind(i_data));
		}
		else {
			pDocument.stopwait();
			pDialog.dialogQuestion({
				id: 'select-genres',
				options: [
					{ id: 'genres', input: { spellcheck: false, autocomplete: false, autocorrect: false, autocapitalize: false }, text: 'Comma-separated list of genres(s):', value: pString.concat((p_value || '').split('|'), ', ') },
					'ok', 'cancel'
				],
				fcall: function(p_dialog, p_value) { f_setGenre(i_data.doc_url, pString.concat(pString.split(p_dialog.inputs.getValue('genres', '')), '|')); }
			});
		}
	};

	this.selectModels = function(p_doc_url, p_value, p_list_url, p_fcall) {

		pDocument.wait();
		
		function f_setModels(p_doc_url, p_value) {

			var i_value = (typeof p_value=="string")? p_value : pString.concat(p_value, '|');
			var i_url = pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-models.json', 'others', that.getMLIDfromURL() ]);
			f_changeMetadata(i_url, "SetModels", "Updating models...", 'metadata_models='+pURL.fixedEncodeURIComponent(i_value));
		}

		if (!p_value) p_value = (pPage.getValue)? pPage.getValue("metadata_models") : pPage.metadata_model;
		var i_data = {
			doc_url: p_doc_url || window.location.pathname,
			models: (p_value)? pString.split(p_value, '|') : []
		};
		if (p_list_url == 'auto') p_list_url = that.getModelsURL(i_data.doc_url);
		if (p_list_url) {
			pHTTPRequest.get(p_list_url+'?format=json&contents=names&v='+Math.random(), function(i_request, i_data) {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//try {
					//console.log(i_request.responseText);
					var i_obj = pJSON.parse(i_request.responseText);
					//alert("reply :" + pJSON.pretty(i_request.responseText));
					//var i_data = pHTTPRequest.remove(i_requbannerest);

					var i_dialog = {
						id: 'select-models',
						options: [ 'ok', 'cancel' ],
						multiple: true,
						fcall: (p_fcall)? p_fcall : function(p_dialog, p_value) { f_setModels(i_data.doc_url, p_value); },
						footer: [
							{ id: 'dialog-model-new', text: (pDevice.isSmallScreen())? '+':'New...', className: 'dialog-footer-button-new dialog-footer-button', scall: function(p_dialog, value) {
								var i_data = this;
								pDialog.dialogQuestion({
									id: 'new-model',
									options: [
										'ok', 'cancel',
										{ id: 'models-new', input: { type: 'text', required: true, raw: true }, text: 'Comma-separated list of model(s): ' }
									],
									fcall: function(p_dialog, p_value) {
										var i_data = this;
										pString.split(p_dialog.inputs.getValue('models-new', '')).sort().filter(pMetadata.f_filter).forEach(function(m) {
											if (!i_data.models.includes(m)) {
												pDialog.removeDialogOption('select-models', 'no-model-help');
												pDialog.addDialogOption('select-models', 'content',
													{ id: m, value: m, text: pString.capitalize(m), className: 'dialog-model model' });
											}
										});
									}.bind(i_data)
								});
							}.bind(i_data)}
						]//footer: [
					};
					
					var added = 0;
					pString.split(pString.concat(i_obj.reply, ",")/*.toLowerCase()*/, ",").sort().filter(pMetadata.f_filter).forEach(function(m) {
						i_dialog.options.push({	id: m, value: m, text: pString.capitalize(m), className: 'dialog-model model', selected: i_data.models.includes(m)/*.toLowerCase())*/});
						added++;
					});

					if (added == 0)
						i_dialog.options.push({ id: 'no-model-help', sep: true, text: '<div class="help">No model to select yet...<p>Click on the "'+((pDevice.isSmallScreen())? '+':'New...')+'" button below to enter new model(s)...</div>'});
					
					pDocument.stopwait();
					pDialog.dialogQuestion(i_dialog);
				//}
			}, false, i_data);
		}
		else {
			pDocument.stopwait();
			pDialog.dialogQuestion({
				id: 'select-models',
				options: [
					{ id: 'models', input: { icons: [ pDialog.resources.icon_delete ] }, text: 'Comma-separated list of model(s):', value: (p_value)? pString.concat(p_value.split('|'), ', ') : '' },
					'ok', 'cancel'
				],
				fcall: function(p_dialog, p_value) { f_setModels(i_data.doc_url, pString.concat(pString.split(p_dialog.inputs.getValue('models', '')), '|')); }
			});
		}
	};
	
	this.changeTags = function(p_doc_url) {
	
		var i_url = p_doc_url+'?action=get-metadata&format=json&contents=values&v='+Math.random();
	
		pConsole.info(this, "GetMetadata: " + i_url);
		pHTTPRequest.get(i_url, function(i_request, i_data) {
	
			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;
	
			var i_lid = that.getLIDfromURL(i_data.doc_url);
			var i_obj = pJSON.parse(i_request.responseText);
			//alert(pJSON.pretty(i_request.responseText));
			pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
	
			var i_values = new pMap(i_obj.values);
			i_obj.values.forEach(function(v, i) { if (v.see) i_values.put(i_obj.values[i-1], i_values.getValue(v.see)); });
			
			that.selectTags(this.url, i_values.getValue("metadata_tags"));
			
		}.bind({ url: p_doc_url }));
	};
	
	this.selectTags = function(p_doc_url, p_value, p_list_url, p_fcall) {
		
		pDocument.wait();

		function f_setTags(p_doc_url, v) {
			f_changeMetadata(
				pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'log', 1, 'sync', 1, 'template', 'object-tags.json', 'others', that.getMLIDfromURL() ]), 
				"SetTags", 
				"Updating tags...", 
				'metadata_tags='+pURL.fixedEncodeURIComponent((typeof p_value=="string")? v : pString.concat(v, '|'))
			);
		}
		
		var i_data = {
			doc_url: p_doc_url || window.location.pathname, 
			tags: pTag.parse(p_value || ((pPage.getValue)? pPage.getValue("metadata_tags") : pPage.metadata_tags))
		};
		if (!p_list_url || p_list_url == 'auto') p_list_url = that.getTagsURL(i_data.doc_url);
		if (p_list_url) {
			pHTTPRequest.get(p_list_url+'?format=json&contents=names'/*&v='+Math.random()*/, function(i_request, i_data) {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//try {
					//console.log(i_request.responseText);
					var i_obj = pJSON.parse(i_request.responseText);
					//alert("reply :" + pJSON.pretty(i_request.responseText));
					//var i_data = pHTTPRequest.remove(i_requbannerest);

					var i_dialog = {
						id: 'select-tags',
						className: 'select-tags',
						options: [ 'ok', 'cancel' ],
						multiple: true,
						fcall: p_fcall || function(p_dialog, p_value) { f_setTags(i_data.doc_url, p_value); },
						footer: [
							{ id: 'dialog-tag-new', text: (pDevice.isSmallScreen())? '+':'New...', className: 'dialog-footer-button-new dialog-footer-button', scall: function(p_dialog, value) {
								var i_data = this;
								pDialog.dialogQuestion({
									id: 'new-tags',
									options: [
										'ok', 'cancel',
										{ id: 'tags-new', input: { type: 'text', required: true }, text: 'Comma-separated list of tag(s): ' }
									],
									fcall: function(p_dialog, p_value) {
										var i_data = this;
										pTag.parse(p_dialog.inputs.getValue('tags-new'), ',').sort().filter(pMetadata.f_filter).forEach(function(t) {
											if (!i_data.tags.includes(t)) {
												pDialog.removeDialogOption('select-tags', 'no-tag-help');
												pDialog.addDialogOption('select-tags', 'ALL', { id: t.replace(/ /g, '_'), value: t, title: 'New Tag', text: pString.encodeHTML(pTag.format(t)), className: 'dialog-tag tag' });
											}
										});
									}.bind(i_data)	
								});
							}.bind(i_data)}
						]//footer: [
					};
					
					var added = 0, ttags = pString.sortByName(i_obj.reply.map(function(t, i) { 
						return { 
							name: t.toLowerCase(), 
							genre: pString.split(i_obj.genre[i] || '', '|'), 
							desc: pString.validOrNull(i_obj.description[i]), 
							text: pString.encodeHTML(pTag.format(t)) 
						}; 
					}));
					
					//*** REMOVE DUPLICATES
					var tags = [];
					ttags.forEach(function(t) { if (!tags.find(function(tt) { return tt.name == t.name })) tags[tags.length] = t; });
					
					tags = tags.filter(function(t) { return t.name != '(unknown)' });
					
					var g = new pDefinedUniqueObjectList();
					tags.forEach(function(t) { t.genre.forEach(g.push) });
										
					if (g.length>0) {
						g.values().sort().forEach(function(g) {
							i_dialog.options.push({ id: g, text: g, tab: true });
							tags.forEach(function(t) {
								if (t.genre.includes(g)) {
									i_dialog.options.push({ id: t.name.replace(re_space, '_'), value: t.name, title: t.desc, text: t.text, className: 'dialog-tag tag', selected: i_data.tags.includes(t.name) });
									added++;
								}
							});
						});
						i_dialog.options.push({ id: 'Others', text: 'Others', tab: true });
						tags.forEach(function(t) {
							if (t.genre.length<1) {
								i_dialog.options.push({ id: t.name.replace(re_space, '_'), value: t.name, title: t.desc, text: t.text, className: 'dialog-tag tag', selected: i_data.tags.includes(t.name) });
								added++;
							}
						});
					}
					
					i_dialog.options.push({ id: 'ALL', text: 'ALL', tab: true });
					tags.forEach(function(t) {
						i_dialog.options.push({ id: t.name.replace(re_space, '_'), value: t.name, title: t.desc, text: t.text, className: 'dialog-tag tag', selected: i_data.tags.includes(t.name) });
						added++;
					});
					
					if (added == 0)
						i_dialog.options.push({ id: 'no-tag-help', sep: true, text: '<div class="help">No tag to select yet...<p>Click on the "'+((pDevice.isSmallScreen())? '+':'New...')+'" button below to enter new tag(s)...</div>'});

					pDocument.stopwait();
					pDialog.dialogQuestion(i_dialog);
				//}
			}, false, i_data);
		}
		else {
			pDocument.stopwait();
			pDialog.dialogQuestion({
				id: 'select-tags',
				options: [
					{ id: 'tags', input: true, text: 'Comma-separated list of tag(s):', value: (p_value)? pString.concat(p_value.split('|'), ',') : '' },
					'ok', 'cancel'
				],
				fcall: function(p_dialog, p_value) { f_setTags(i_data.doc_url, pString.concat(p_dialog.inputs.getValue('tags', '').split(','), '|')); }
			});
		}
	};

	/*******************************************************************************/
	
	this.setAccess = function(p_doc_url) {

		pDocument.wait();

		if (!p_doc_url) p_doc_url = window.location.pathname;
		var i_data = {
			doc_url: p_doc_url
		};
			pHTTPRequest.get(p_doc_url+'?action=get-access&format=json&v='+Math.random(), function(i_request, i_data) {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//try {
					//console.log(i_request.responseText);
					var i_obj = pJSON.parse(i_request.responseText);

					i_data.obj = i_obj;
					i_data.allow = i_obj.allow;
					i_data.deny = i_obj.deny;
					i_data.user_map = new pMap();
					i_data.user_fullname_map = new pMap();
					for(var i=0 ; i<i_obj.users.length ; i++) {
						i_data.user_map.put(i_obj.users[i].name, i_obj.users[i]);
						i_data.user_fullname_map.put(i_obj.users[i].fullname, i_obj.users[i]);
					}
					//alert("reply :" + pJSON.pretty(i_request.responseText));
					//var i_data = pHTTPRequest.remove(i_requbannerest);

					var i_dialog = {
						id: 'set-access',
						options: [
							{ id: 'tab-allow', tab: true, text: 'Allow Access...' },
							'Users in this list will have access to this page or media even if:<ul><li>They are in the "Deny Access..." list,<li>Or Parental Control settings would prevent them from accessing it...</ul>',
						    { id: "allow", input: { oninput: function() {
								var i_dialog = pDialog.getValue('set-access');
								if (!i_dialog)
									return;

								i_data.allow = that.parseUserList(pDialog.getInputValue('set-access', 'allow').split(','), i_data.user_fullname_map).split(',');
								pDialog.setInputValue('set-access', 'allow', that.formatUserList(i_data.allow, i_data.user_map));

							}, icons: [
							{
								className: 'dialog-question-button-adduser',
								title: 'Click here to add a user...',
								onclick: function(oid, iid){
									that.selectUser(i_data.obj.users).then(function(u) {
										if (!i_data.allow.includes(u))
											i_data.allow.push(u);
										pElement.x(iid).value = that.formatUserList(i_data.allow, i_data.user_map);
									});
								}
							}
							]}, text: 'Users:', value: that.formatUserList(i_data.allow, i_data.user_map) },

							{ id: 'tab-deny', tab: true, text: 'Deny Access...', selected: true },
							'Users in this list will not have access to this page or media even if:<ul><li>Parental Control settings allow them accessing it...</ul>',
							{ id: "deny", input: { oninput: function() {
								var i_dialog = pDialog.getValue('set-access');
								if (!i_dialog)
									return;

								i_data.deny = that.parseUserList(pDialog.getInputValue('set-access', 'deny').split(','), i_data.user_fullname_map).split(',');
								pDialog.setInputValue('set-access', 'deny', that.formatUserList(i_data.deny, i_data.user_map));

							}, icons: [
							{
								className: 'dialog-question-button-adduser',
								title: 'Click here to add a user...',
								onclick: function(oid, iid){
									that.selectUser(i_data.obj.users).then(function(u) {
										if (!i_data.deny.includes(u))
											i_data.deny.push(u);
										pElement.x(iid).value = that.formatUserList(i_data.deny, i_data.user_map);
									});
								}
							}
							]}, text: 'Users:', value: that.formatUserList(i_data.deny, i_data.user_map) },

							'ok', 'cancel'
						],
						multiple: true,
						fcall: function(p_dialog, p_value) {
							//alert('here: ' + pString.concat(p_value, ','));
							//for a multiple choice dialog, p_value is an array of the selected values...
							var i_data = this;
							//alert('deny: '+ pString.concat(i_data.deny, ','));

							var i_url = pURL.addQueryParameter(i_data.doc_url, [ 'action', 'set-access', 'v', Math.random() ]);
							pTransaction.simpleTransaction({
								trace: that, url: i_url, method_name: "SetAccess",
								msg_start: "Updating access...",
								headers: pHTTPRequest.headers_post,
								body: 'allow='+pURL.fixedEncodeURIComponent(pString.concat(i_data.allow, ','))+'&deny='+pURL.fixedEncodeURIComponent(pString.concat(i_data.deny, ','))
							});

							//that.setMetadata(i_data.doc_url, 'metadata_tags', pString.concat(p_value, '|'));
						}.bind(i_data)
					};

					pDocument.stopwait();
					pDialog.dialogQuestion(i_dialog);
				//}
			}, false, i_data);
	};

	/*******************************************************************************/
	
	this.setOwners = function(p_doc_url) {

		pDocument.wait();

		var i_data = {
			doc_url: p_doc_url || window.location.pathname
		};
		var i_url = i_data.doc_url + '?action=get-owners&format=json&v='+Math.random();
		pConsole.info(this, "GetOwners: " + i_url);
		pHTTPRequest.get(i_url, function(i_request, i_data) {

			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;

			//try {
				//console.log(i_request.responseText);
				var i_obj = pJSON.parse(i_request.responseText);

				i_data.obj = i_obj;
				i_data.owners = (i_obj.owners || '').split(',');
				i_data.user_map = new pMap();
				i_data.user_fullname_map = new pMap();
				for(var i=0 ; i<i_obj.users.length ; i++) {
					i_data.user_map.put(i_obj.users[i].name, i_obj.users[i]);
					i_data.user_fullname_map.put(i_obj.users[i].fullname, i_obj.users[i]);
				}
				//alert("reply :" + pJSON.pretty(i_request.responseText));
				//var i_data = pHTTPRequest.remove(i_requbannerest);

				var i_dialog = {
					id: 'select-owners',
					options: [
						pDialog.newCheckbox("metadata_library_private", 'Private', null, i_obj.vprivate),
						'If selected, this library is only visible and editable by the users listed below:<p>',
						{ id: "owners", input: { oninput: function() {
							var i_dialog = pDialog.getValue('select-owners');
							if (!i_dialog)
								return;

							i_data.owners = that.parseUserList(pDialog.getInputValue('select-owners', 'owners').split(','), i_data.user_fullname_map).split(',');
							pDialog.setInputValue('select-owners', 'owners', that.formatUserList(i_data.owners, i_data.user_map));

						}, icons: [
						           {
						        	   className: 'dialog-question-button-adduser',
										title: 'Click here to add a user...',
										onclick: function(oid, iid){
											var i_data = this;
											that.selectUser(i_data.obj.users).then(function(u) {
												if (!i_data.owners.includes(u))
													i_data.owners.push(u);
												pElement.x(iid).value = that.formatUserList(i_data.owners, i_data.user_map);
											});
										}.bind(i_data)
						           }
						]}, text: 'Owners:', value: that.formatUserList(i_data.owners, i_data.user_map) },

						'ok', 'cancel'
					],
					multiple: true,
					fcall: function(p_dialog, p_value) {
						//alert('here: ' + pString.concat(p_value, ','));
						//for a multiple choice dialog, p_value is an array of the selected values...
						var i_data = this;
						//alert('deny: '+ pString.concat(i_data.deny, ','));

						pTransaction.simpleTransaction({
							trace: that, url: pURL.addQueryParameter(i_data.doc_url, [ 'set-owners', '' , 'v', Math.random() ]), method_name: "SetOwners",
							msg_start: "Updating owners...",
							headers: pHTTPRequest.headers_post,
							body: pURL.addBodyParameter('', [ 'owners', pString.concat(i_data.owners, ','), 'metadata_library_private', p_dialog.inputs.getValue('metadata_library_private', null) ])
						});

						//that.setMetadata(i_data.doc_url, 'metadata_tags', pString.concat(p_value, '|'));
					}.bind(i_data)/*,
					footer: [
						{
							id: 'dialog-user-add', text: 'Add User...', className: 'dialog-footer-button',
							scall: function(p_dialog, value) {
								var i_data = this;
								that.selectUser(i_data.obj.users).then(function(u) {
									if (!i_data.owners.includes(u))
										i_data.owners.push(u);
									pDialog.setInputValue('select-owners', 'owners', that.formatUserList(i_data.owners, i_data.user_map));
								});
							}.bind(i_data)
						}
					]//footer: [*/
				};

				pDocument.stopwait();
				pDialog.dialogQuestion(i_dialog);
			//}
		}, false, i_data);
	};

	this.parseUserList = function(p_obj_users, p_fullname_map) {
		var i_map = p_fullname_map;
		return pString.concat(p_obj_users, ", ", false, function(p_obj_user) { var i_user = i_map.getValue(p_obj_user, null); return (i_user)? i_user.name : null; });
	};
	this.formatUserList = function(p_obj_users, p_map) {
		var i_map = p_map;
		return pString.concat(p_obj_users, ", ", false, function(p_obj_user) { var i_user = i_map.getValue(p_obj_user, null); return (i_user)? i_user.fullname : null; });
	};

	this.selectUser = function(p_obj_users) {
		return new Promise(function(resolve, reject) {
			var d = {
				id: 'select-user',
				options: ['cancel'],
			};
			p_obj_users.forEach(function(u) {
				if (u.name != 'admin')
					d.options.push({
						id: u.name,
						value: u.name,
						text: u.fullname,
						title: u.description,
						className: 'dialog-user user user-'+u.name,
						fcall: function(d, v) { 
							resolve(v);
						}
					});
			});
			d.options = pArray.sortByText(d.options);//.sort(function(a,b) { if (a.text < b.text) return -1; if (a.text > b.text) return 1; return 0; });
	
			pDocument.stopwait();
			pDialog.dialogQuestion(d);
		});
	};

	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/
	
	this.setMetadata = function(p_doc_url, p_name, p_value) {

		var i_url = pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', p_name, p_value, 'children', 1, 'v', Math.random() ]);
		var i_data = { name: p_name, value: p_value };

		pConsole.info(this, "SetMetadata: " + i_url);
		pHTTPRequest.post(i_url, function(i_request, i_data) {
			var i_data = this;

			var x = pElement.x(i_data.name.substring('metadata.'.length));
			if (x)
				x.setAttribute('data-text', p_value);

			pDocument.renderEllipsisText(i_data.name, 'container-'+i_data.name, '|');
		}.bind(i_data), false);
	};

	/*******************************************************************************/
	/*******************************************************************************/
	/*******************************************************************************/
	
	this.getDocURL = function(p_library_id, p_lid, p_id) {
		return url_prefix+(p_library_id || that.getLibraryIDfromURL()) + '/' + p_lid + '/' + ((p_id)? p_id + '/' : '');
	};

	this.getLibraryURL = function(p_doc_url) {
		return url_prefix+this.getLibraryIDfromURL(p_doc_url)+'/library/';
	};

	this.getIDfromURL = function(p_doc_url) {
		p_doc_url = p_doc_url || window.location.pathname;
		if (p_doc_url.indexOf(url_prefix)==0)
			return pString.split(p_doc_url, '/')[3];
		return p_doc_url;
	};

	this.getMLIDfromURL = function(p_doc_url) {
		p_doc_url = p_doc_url || window.location.pathname;
		if (p_doc_url.indexOf(url_prefix)==0) {
			var i_parts = pString.split(p_doc_url, '/');
			return i_parts[2]+'/'+i_parts[3];
		}
		return p_doc_url;
	};

	this.getLIDfromURL = function(p_doc_url) {
		p_doc_url = p_doc_url || window.location.pathname;
		if (p_doc_url.indexOf(url_prefix)==0)
			return pString.split(p_doc_url, '/')[2];
		return p_doc_url;
	};

	this.getLibraryIDfromURL = function(p_doc_url) {
		p_doc_url = p_doc_url || window.location.pathname;
		if (p_doc_url.indexOf(url_prefix)==0)
			return pString.split(p_doc_url, '/')[1];
		return p_doc_url;
	};

	this.getGenresURL = function(p_doc_url) {
		return that.getURL(p_doc_url, 'genres');
	};
	this.getModelsURL = function(p_doc_url) {
		return that.getURL(p_doc_url, 'models');
	};
	this.getTagsURL = function(p_doc_url) {
		return that.getURL(p_doc_url, 'tags');
	};

	const url_parts = [ 'books.', 'music.', 'movies.', 'pictures.', 'homevideos.', 'tvshows.', 'podscasts.' ];
	this.getURL = function(p_doc_url, p_id) {
		const i_parts = pString.split(p_doc_url || window.location.pathname, '/');
		if (i_parts.length==2 && i_parts[0] == url_prefix_noslash)
			return url_prefix+i_parts[1]+'/'+p_id+'/';

		if (i_parts.length>=3 && i_parts[0] == url_prefix_noslash) {
			if (p_id.indexOf('.')>0)
				return url_prefix+i_parts[1]+'/'+p_id+'/';

			for(var i=0 ; i<url_parts.length ; i++) {
				const p = url_parts[i];
				if (i_parts[2].indexOf(p)==0)
					return url_prefix+i_parts[1]+'/'+p+p_id+'/';
			}
		}
	};
	
	this.sameLibrary = function(u) {
		if (pString.v(u))
			return pLocation.path(u).startsWith(url_prefix + that.getLibraryIDfromURL()+'/');
		/*if (pString.v(u)) {
			var l = new pLocationInstance(u);
			if (l.path.startsWith(url_prefix + that.getLibraryIDfromURL()+'/'))
				return true;
		}*/
		return false;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

window.pApplicationUI = (new function() {
	var that = this, ids = { button_toolbar_expand: 'button-expand-toolbar', toolbar: 'movie-menu' }, resources = { expand: "\u2228", collapse: "\u2227" };

	this.disable_showToolbar = false;
	
	this.showToolbar = function(x, p_toolbar_id, s1, s2) {

		if (that.disable_showToolbar)
			return;

		x = x || ids.button_toolbar_expand;
		if (x = pElement.x(x)) {
			p_toolbar_id = p_toolbar_id || pElement.getAttribute(x, 'toolbar_id');
			p_toolbar_id = p_toolbar_id || ids.toolbar;
	
			if (pDocument.isHidden(p_toolbar_id) === false) {//isShown(p_toolbar_id)) {
				pDocument.hide(p_toolbar_id);//pDocument.hideSmooth(p_toolbar_id);
				pElement.setTextContent(x, resources.expand);
	
				if (history.replaceState) {
					var i_state = history.state || { showToolbar: [] };
					if (!i_state.showToolbar)
						i_state.showToolbar = [];
					
					i_state.showToolbar = i_state.showToolbar.filter(function(tb) { return tb.tid != p_toolbar_id });
					history.replaceState(i_state, "");
				}
			}
			else {
				pDocument.show(p_toolbar_id);//pDocument.showSmooth(p_toolbar_id, null);
				setTimeout(function() { pDocument.scrollTo(s1 || 'movie', s2); }, 500);
				pElement.setTextContent(x, resources.collapse);
	
				if (history.replaceState) {
					var i_state = history.state || { showToolbar: [] };
					if (!i_state.showToolbar)
						i_state.showToolbar = [];
					
					if (!i_state.showToolbar.find(function(tb) { return tb.tid == p_toolbar_id })) i_state.showToolbar.push({ tid: p_toolbar_id, eid: x.id });
					history.replaceState(i_state, "");
				}
			}
		}
	};

	this.isToolbarShown = function(x, p_toolbar_id) {

		x = x || ids.button_toolbar_expand;
		p_toolbar_id = p_toolbar_id || pElement.getAttribute(x, 'toolbar_id');
		p_toolbar_id = p_toolbar_id || ids.toolbar;

		return pDocument.isHidden(p_toolbar_id);
	};

	var m_init;
	this.initPage = function() {
		
		//*** DETECT MOUSE WHEEL
		if (window.addWheelListener)
			window.addWheelListener(document.body, function( e ) {
				//console.log('dialog: ' + pDialog + ' - ' + pDialog.isDialogOn());
				if (pDialog && pDialog.isDialogOn()) {
					//console.log('wheel: here0');
					return true;
				}
				
				if (e.deltaY > 0) {
					if ((!pDialog || pDialog.isDialogOn() === false) && pDocument.componentIsShown('view-item') === true)
						if (pElement.click('toolbar-img-next') === true) {
							e.preventDefault();
							//console.log('wheel: here1');
							return false;
						}
				}
				else {
					if ((!pDialog || pDialog.isDialogOn() === false) && pDocument.componentIsShown('view-item') === true)
						if (pElement.click('toolbar-img-previous') === true) {
							e.preventDefault();
							//console.log('wheel: here2');
							return false;
						}
				}
				//console.log('wheel: here');
				return true;
			});

		//*** TAB KEY
		/*pDocumenti.addOnKeyDown(function(e) {
			pConsole.info('TAB Key', 'keyCode: ' + e.keyCode);
			if (e.keyCode == 9) {
				var fc = pDocumenti.activeElement();
				if ((window.pDialog && pDialog.isDialogOn()) || pDocument.componentIsShown('view-item')) {
					e.preventDefault();
					return false;
				}
			}
		});*/

		//*** REGISTER KEYS
		pDocument.clickOnKey('toolbar-img-previous', [ 38, 33, 37 ]); // UP, Page UP, LEFT

		pDocument.clickOnKey('toolbar-img-next', [ 40, 34, 39 ]); // DOWN, Page Down, RIGHT
		pDocument.clickOnKey('toolbar-img-last', [ 35 ]); // END
		pDocument.clickOnKey('toolbar-img-first', [ 36 ]); // HOME

		pDocument.clickOnKey('toolbar-delete-item', [ 46 ]); // DELETE

		pDocument.clickOnKey('toolbar-img-zoomin', [ 61 ]); // +
		pDocument.clickOnKey('toolbar-img-zoomout', [ 173 ]); // -

		pDocumenti.addOnKeyDown(function(e, k) {
			pConsole.debug('ToolbarNext', 'keyCode: ' + k);
			if (k >= 97 && k <= 105) { //1 to 9
				var x = pElement.c('key-'+(k - 96));
				if (x.length>0)
					return pElement.click(x[0]);
				return false;
			}
		});

		var i_library_id = pMediaLibrary.getLibraryIDfromURL();
		if (i_library_id) {

			pDocumenti.addOnKeyDown(function(e, k) {
				pConsole.info('ToolbarNext', 'keyCode: ' + e.keyCode);
				if (k >= 65 && k <= 65+26 && e.altKey === true) {
					var x = pElement.x('generic-list-default-link-' + e.key.toUpperCase());
					if (x && pElement.hasClassName(x, 'link')) {
						if (pDocument.isHidden('generic-list-default-' + e.key.toUpperCase()))
							return false;
						x.click();
						return true;
					}
					return false;
				}
			});
			pLocation.assignOnKey(url_prefix+i_library_id, [ 72 ]); // H
			pLocation.assignOnKey(url_prefix+i_library_id+'/tvshows.seasons/', [ 84 ]); // T
			pLocation.assignOnKey(url_prefix+i_library_id+'/pictures.groups/', [ 80 ]); // P
			pLocation.assignOnKey(url_prefix+i_library_id+'/music.albums/', [ 77 ]); // M
			pLocation.assignOnKey(url_prefix+i_library_id+'/music.artists/', [ 65 ]); // A
			pLocation.assignOnKey(url_prefix+i_library_id+'/books.groups/', [ 66 ]); // B
			pLocation.assignOnKey(url_prefix+i_library_id+'/movies.actors/', [ 67 ]); // C
			pLocation.assignOnKey(url_prefix+i_library_id+'/movies.movies/', [ 70 ]); // F
			pLocation.assignOnKey(url_prefix+i_library_id+'/homevideos.movies/', [ 86 ]);// V

					//if (e.keyCode == 83) { //S
					//	return pElement.focus('form-search-text', [ 83 ] ); // S
					//}

			pDocumenti.addOnKeyDown(function(e) {
				pApplicationMenu.mainMenu();
				return true;
			}, [ 83 ]);
		}//if (i_library_id) {

		pLocation.assignOnKey('/', [ 72 ]); // H
		pLocation.assignOnKey('/user', [ 85 ]); // U

		pDocument.addOnResize(f_resize_play);
		f_resize_play();
		
		//*** DECORATE
		pElement.decorateAll();

		//pElement.setOnClick(ids.button_toolbar_expand, this.showToolbar);
		var i_toolbar_done = false;

		//if (window.pPage)
		//	pMediaLibrary.showBackdrop(pPage.metadata_backdrop_url);

		if (pLocation.getQueryParameter('no-expand', null))
			return; //TODO...

		if (that.OPTION_EXPAND_TOOLBAR) {
			this.showToolbar();
			i_toolbar_done = true;
		}

		//*** SETUP BUTTONS
		pElement.setOnClick('button-expand-toolbar', function() {
			that.showToolbar.call(pApplicationUI, this);
		});
		pElement.setOnClick('button-expand-toolbar2', function() {
			that.showToolbar.call(pApplicationUI, this);
		});

		//*** EXECUTE ACTIONS
		//var i_actions = pLocation.getQueryParameter('actions', '').split(',');
		var i_actions_done = false;
		pLocation.getQueryParameter('actions', '').split(',').forEach(function(i_action) {
			if (i_action == 'expand' && (!history.state || history.state.showToolbar === true))
				if (i_actions_done === false) {
					this.showToolbar();
					i_toolbar_done = true;
					i_actions_done = true;
				}
		});

		//*** RE-OPEN TOOLBARS FROM PREVIOUS VISIT
		if (!i_toolbar_done && history && history.state) {
			if (history.state.showToolbar)
				pArray.clone(history.state.showToolbar).forEach(function(id) { that.showToolbar(id.eid, id.tid); });
		}

		if (!window.pPage) window.pPage = { vprivate: { tracePrefix: 'pPage' }};
		pPage.change = function(p_key, p_value) {
			pPage[p_key] = p_value;

			var p = pDocument.getHistoryState("pPage", {});
			p[p_key] = p_value;
			pDocument.setHistoryState("pPage", p);
		};
		pPage.getValue = function(p_key, p_default) {
			const p = pDocument.getHistoryState("pPage", {}), r = p[p_key] || pPage[p_key];
			return r || p_default;
		};

		pMetadata.data_ids.forEach(function(id) {
			var i_value = pPage.getValue("metadata_"+id.id);
			pElement.setAttribute(id.id, 'data-text', (i_value)? i_value : '');
			pDocument.renderEllipsisText(id);
		});
		setTimeout(pDocument.onResize, 100);

		//*** PROCESS HISTORY STATE
		pDocument.handleHistoryState();

		//var i_disclaimer = pNoteUI.createNoteDialogOption('cookie-cookie-disclaimer');
		//if (i_disclaimer)
		//	pNotificationUI.addNotification('<div id="cookie-cookie-disclaimer">'+i_disclaimer.text+'</div>');
		
		pLocalStorage.addListener('options.'+that.OPTION_TOOLS_CONVERT.name, that.showToolsConvert);
		that.showToolsConvert();
		
		pLocalStorage.addListener('options.'+that.OPTION_TOOLS_CONVERT.name, that.showToolsConvertAll);
		that.showToolsConvertAll();
		
		m_init = true;
	};
	
	this.showToolsConvert = function(e) {
		if (!e || (e.newValue && e.newValue!='undefined')) { 
			if (that.OPTION_TOOLS_CONVERT)
				pDocument.show(pElement.c('toolbar-convert'), 'inline-block');
			else
				pDocument.hide(pElement.c('toolbar-convert'));
		}
	};

	this.showToolsConvertAll = function(e, a) {
		if (!e || (e.newValue && e.newValue!='undefined')) { 
			if (that.OPTION_TOOLS_CONVERT && a===true) {
				pDocument.show('toolbar-convertall', 'inline-block');
				pDocument.show('toolbar-convertbatch', 'inline-block');
			}
			else {
				pDocument.hide('toolbar-convertall');
				pDocument.hide('toolbar-convertbatch');
			}
		}
	};


	
	this.askBackAndReload = function() {
		if (window.history.length<2)
			pNotificationUI.addNotification('This page has been deleted. Click <a href=\"javascript:void(window.close())\">here</a> to close this window.');
		else
			pNotificationUI.addNotification('This page has been deleted. Click <a href=\"javascript:void(pLocation.backAndReload())\">here</a> to go back and refresh...');
	};

	this.description = function(x) {
		var d = x, c = pElement.x(d+'-content'), dd = pElement.x(d+'-display'), m = pElement.x(d+'-more');
		pElement.setInnerHTML(c, pDescription.format);
		
		pElement.setInnerHTML(dd, pElement.x(c).innerHTML);
		
		pElement.setInnerHTML(m, '<span class="link noselect">\u{22EF}</span>');
		pElement.setInnerHTML(d+'-more-bg', '<span class="link noselect">\u{22EF}</span>');
		
		pElement.setOnClick(m, function() { 
			//alert('here'); 
			var x = pElement.x(this.id.replace('-more', ''));
			if (x) {
				//var mh = (new pStyle(x).maxHeight());
				if ((new pStyle(x).maxHeight()) == '5000ex')
					x.style.maxHeight = '102px';
				else
					x.style.maxHeight = '5000ex';
				
				setTimeout(1000, function() { pElement.go(d); });
			}
			//pElement.x(this.id.replace('-more', '')).style.maxHeight = '5000px'
		})
		
		//var ex = pDocument.height(pElement.create('div', null, d, 'x'));
		if (pDocument.height(c) > 102) {
			pDocument.show(m);
			pDocument.show(d+'-more-bg');
			//pElement.addClassName(m, 'link');
		}
	};

	this.title = function(x) {
		pElement.setInnerHTML(x, pTitle.format);
	};



	

	
	var images_ext = [ '.jpg', '.jpeg', '.png', '.gif', '.bmp', '.webp' ];
	
	this.acceptImageFile = function(f) {
		return that.acceptImageFileName(f.name);
	};
	
	this.acceptImageFileName = function(n) {
		return null!=images_ext.find(function(e) { return n.toLowerCase().endsWith(e); });
	};
	
	this.acceptImageFileSize = function(f) {
		 return parseInt(f.size)!='NaN' && f.size>0 && f.size<1024*1024*1024;
	};
	
	this.containsImageFiles = function(e) {
		if (e.isDirectory && e.files)
			return e.files.filter(that.acceptImageFile).length>0;
	};
	
	this.readEXIF = function(f, c) {
		var d = {
			total: f.length,
			ended: 0 
		};
		
		for(var i= 0 ; i<f.length ; i++) {
			EXIF.getData(f[i].file, function() {
				d.ended++;
				if (d.ended == d.total)
					c.call(c);
			});
		}
	};
	
	/**
	 * @param files - dropped files
	 * @param tu - target URL
	 * @param name - target name
	 */
	this.f_addImages = function(files, tu, name) {
		if (files) {
			//files.forEach(function(f) { console.log(f.name + ' ' + f.size)});
			
			files = [].filter.call(files, that.acceptImageFile).filter(that.acceptImageFileSize);
			if (!tu && files.length>max_add_pictures) files = files.slice(0, max_add_pictures);
			
			if (tu) {
				pFiles.read(files, function(f) {
					var imgs = f.map(function(f) { 
						return { uri: f.data, name: f.file.name, lastModified: f.file.lastModified };
					});
					pMediaLibrary.addPicture(tu, imgs, name);
				});
				return;
			}
			
			if (files.length>0) {
				pDocument.wait();
				pFiles.read(files, function(f) {
					
					that.readEXIF(f, function() {
						var imgs = f.map(function(f) { 
							var d = { uri: f.data, name: f.file.name, lastModified: f.file.lastModified };
							if (f.file.exifdata) {
								d.orientation = f.file.exifdata.Orientation; 
								d.width = f.file.exifdata.PixelXDimension;
								d.height = f.file.exifdata.PixelYDimension;
								d.date = f.file.exifdata.DateTimeOriginal;
								if (d.orientation == 6 || d.orientation == 8) {
									var w = d.width; d.width = d.height; d.height = w;
								}
							}
							return d;
						});
						pMediaLibrary.selectAddImage(null, null, { images: imgs }, name);
					});
				});
			}
			//else
			//	pConsole.info(this, 'Dropped file list has no image...');
		}
	};

	this.initDragAndDrop = function() {
		//*** ENABLE DROP ON BUTTONS
		pElement.setOnDrop('toolbar-properties', function(f, u) {
			if (f) {
				pFile.readAsDataURL(f[0], function(p_file, p_bin) {
					pMediaLibrary.getMetadata(false, null, p_bin);
				});
			}
			else if (u)
				pMediaLibrary.getMetadata(false, null, u);
		});

		pElement.setOnDrop('toolbar-properties-all', function(f, u) {
			if (f) {
				pFile.readAsDataURL(f[0], function(p_file, p_bin) {
					pMediaLibrary.getMetadata(true, null, p_bin);
				});
			}
			else if (u)
				pMediaLibrary.getMetadata(true, null, u);
		});

		if (that.OPTION_PICTURES_DRAG_AND_DROP && (pResources.get('admin') === "true") && (pResources.get("canAddPictures") === "true")) {
			pElement.setOnDrop('toolbar-add-picture', pMediaDrop.copyPictures);
			pElement.setOnDrop('page-data-data-pictureFolder', pMediaDrop.movePictures);
			pElement.setOnDrop('pictures', pMediaDrop.movePictures);
		}
		
		pElement.setOnDrop('toolbar-artwork', function(f, u) {
			if (f)
				pFile.readAsDataURL(f[0], function(p_file, p_bin) {
					pMediaLibrary.selectArtworkImage(null, pPage.metadata_artwork_url, { images:[ { uri: p_bin } ]});
				});
			else if (u)
				pMediaLibrary.selectArtworkImage(null, pPage.metadata_artwork_url, { images:[ { uri: u } ]});
		});

		pElement.setOnDrop('toolbar-backdrop', function(f, u) {
			if (f) {
				pFile.readAsDataURL(f[0], function(p_file, p_bin) {
					pMediaLibrary.selectBackdropImage(null, pPage.metadata_backdrop_url, { images:[ { uri: p_bin } ]});
				});
			}
			else if (u)
				pMediaLibrary.selectBackdropImage(null, pPage.metadata_backdrop_url, { images:[ { uri: u } ]});
		});

		pElement.setOnDrop('toolbar-poster', function(f, u) {
			if (f) {
				pFile.readAsDataURL(f[0], function(p_file, p_bin) {
					pMediaLibrary.selectPosterImage(null, pPage.metadata_poster_url, null, { images:[ { uri: p_bin } ]});
				});
			}
			else if (u)
				pMediaLibrary.selectPosterImage(null, pPage.metadata_poster_url, null, { images:[ { uri: u } ]});
		});
		
		var x = pElement.x('button-expand-toolbar');
		if (x)
			x.ondragenter = function() {
				if (that.OPTION_DRAG_AND_DROP)
					showMovieMenu(true);
			};
	};

	this.errorDialog = function(p_request, p_dialog_id) {
		pDocument.stopwait();
		
		if (typeof p_request === 'string')
			p_request = { text: p_request };
		
		return new Promise(function(resolve, reject) {
			pDialog.dialogQuestion({
				id: p_dialog_id || 'error',
				title: p_request.title,
				icon: p_request.icon,
				options: [ 'ok', p_request.text, '<hr>' ],
				fcall: resolve
			});
		});
	};

	//*** FUNCTIONS FOR TV EPISODES...
	this.episode = {
		getTitle: function(x) {
			if (x = pElement.x(x)) {
				var i_async_t = pElement.getAttribute(x, 'async_m_title'), i_t = pElement.getAttribute(x, 'm_title');
	
				if (that.OPTION_SHOW_TVEPISODE_ASYNC_TITLE && pString.v(i_async_t))
					return i_async_t;
				return i_t;
			}
		},
		showTitle: function(x) {
			if (x = pElement.x(x)) 
				pElement.setInnerHTML(x, that.episode.getTitle(x));
		}
	};

	this.canShowDialog = function() {
		if ((screen.width <= 700 || screen.height <= 700) && !that.OPTION_SHOW_LARGE_DIALOGS) {
			that.errorDialog('Sorry, your device screen is not large enough for this operation...\nPlease retry with a tablet or laptop/desktop.');
			return false;
		}
		return true;
	};
	

	

	this.gallery = function() {
		if (window.clickPersonPoster)
			window.clickPersonPoster.call(this);
		else
			window.open(this.src, "_blank");
			//pLocation.assign(this.src);
	};

	this.toolbarItem = function(id, c, x, cc, s) {
		var t = pDocument.getStyleValue('btn-'+id+'-title');
		return '<div class="toolbar-item toolbar-'+id+' link '+ (cc || '') +'" '+(s? ' style="'+s+'"':'')+' onclick="'+c+'"'+ (t? ' title="'+ t +'"':'') + (x? ' id="'+ x +'"':'') + '>'+(pDocument.getStyleValue('btn-'+id) || pDocument.getStyleValue('btn-'+id+'-content'))+'</div>';
	};
	
	this.createToolbarItem = function(id, c, x, cc, s) {
		var x = pElement.create('div', x, 'toolbar-item toolbar-'+id+' link '+ (cc || ''), pDocument.getStyleValue('btn-'+id), s, [ 'onclick', c ]);
		pElement.decorate(x);
		return x;
	};
	
	this.options = {
		OPTION_PAGE_INDEX_THRESHOLD: 		{ name: 'page-index-threshold', type: 'int', vdefault: 64 },
		OPTION_PAGE_MAXSIZE: 				{ name: 'page-max-size', type: 'int', vdefault: 100 },

		OPTION_GOOGLE_IMAGE_SEARCH: 		pROSE.newBooleanProp('google-image-search'),

		OPTION_NOTIFICATION_CLOSE_TIMEOUT_MS: { name: 'notification-close-ms', type: 'long', vdefault: 20000, min: 2000 },

		OPTION_NO_BACKDROP: 				pROSE.newBooleanProp('view-no-backdrop'),
		OPTION_SCALE_BACKDROP: 				pROSE.newBooleanProp('view-scale-backdrop', true ),

		OPTION_SHOW_USER_RATINGS_DIALOG: 	{ name: 'user-ratings-show-dialog', type: "boolean", vdefault: pDevice.isIOS() || pDevice.isAndroid() }, //TODO: phones, touch devices...

		OPTION_SHOW_TVEPISODE_ASYNC_TITLE: 	pROSE.newBooleanProp('tvepisode-async-title', true),

		OPTION_NOTIFICATION_API: 			pROSE.newBooleanProp('notification-api'),

		OPTION_SHOW_LARGE_DIALOGS: 			pROSE.newBooleanProp('show-large-dialogs'),
		
		OPTION_THUMB_SHOW_THUMB:			pROSE.newBooleanProp('thumb-show-thumb', true),
		OPTION_THUMB_SHOW_NAME: 			pROSE.newBooleanProp('thumb-show-name', true),
		OPTION_THUMB_USE_BACKDROP: 			pROSE.newBooleanProp('thumb-use-backdrop'),
		OPTION_THUMB_USE_POSTER: 			pROSE.newBooleanProp('thumb-use-poster'),
		OPTION_THUMB_USE_BANNER: 			pROSE.newBooleanProp('thumb-use-banner'),
		
		OPTION_EXPAND_TOOLBAR: 				pROSE.newBooleanProp('expand-toolbar'),
		
		OPTION_PICTURES_GET_ORIENTATION:	pROSE.newBooleanProp('pictures-get-orientation', pDevice.isChrome() || pDevice.isAndroid()),
		OPTION_PICTURES_ROTATE:				pROSE.newBooleanProp('pictures-rotate', pDevice.isChrome() || pDevice.isAndroid()),
		OPTION_PICTURES_SCALE:				pROSE.newBooleanProp('pictures-scale', true),
		OPTION_PICTURES_SORT_DATETAKEN:		pROSE.newBooleanProp('pictures-sort-dateTaken', true),
		OPTION_PICTURES_TOOLTIPS:			pROSE.newBooleanProp('pictures-tooltip', true),
		OPTION_PICTURES_RESIZE_GALLERY:		pROSE.newBooleanProp('pictures-resize-gallery', true),
		OPTION_PICTURES_DRAG_AND_DROP:		pROSE.newBooleanProp('pictures-drag-drop', !pDevice.isIOS() && !pDevice.isAndroid()),
		
		OPTION_PREFERRED_LANGS:				{ name: 'preferred-langs', type: 'array', vdefault: pResources.get("langs").split(',') },
		
		OPTION_DATALISTS:					pROSE.newBooleanProp('dlist', pDevice.isChrome() || pDevice.isFirefox()),
		OPTION_DATALIST_METADATA_ACTOR:		pROSE.newBooleanProp('dlist-metadata_actor', true),
		OPTION_DATALIST_METADATA_ALBUM:		pROSE.newBooleanProp('dlist-metadata_album', true),
		OPTION_DATALIST_METADATA_ARTIST:	pROSE.newBooleanProp('dlist-metadata_artist', true),
		OPTION_DATALIST_METADATA_AUTHOR:	pROSE.newBooleanProp('dlist-metadata_author', true),
		OPTION_DATALIST_METADATA_NODEL:		pROSE.newBooleanProp('dlist-metadata_model', true),
		OPTION_DATALIST_METADATA_SHOW:		pROSE.newBooleanProp('dlist-metadata_show', true),
		OPTION_DATALIST_METADATA_TITLE:		pROSE.newBooleanProp('dlist-metadata_title', true),
		OPTION_DATALIST_METADATA_TITLE_SORT:pROSE.newBooleanProp('dlist-metadata_title_sort', true),
		
		OPTION_LISTVIEW_BUTTONS:			pROSE.newBooleanProp('listv-buttons', !pDevice.isMobile()),//Chrome() || pDevice.isFirefox()),
		
		OPTION_DRAG_AND_DROP:				pROSE.newBooleanProp('drag-drop', pDevice.isChrome() || pDevice.isFirefox()),
		
		OPTION_LIST_NOTIFY:					pROSE.newBooleanProp('list-notify', true),
		
		OPTION_TOOLS_CONVERT:				pROSE.newBooleanProp('tools-convert'),
		
		OPTION_DIALOG_MAX_TAB_TITLES:		pROSE.newBooleanProp('dialog-max-tab-titles', true),
		
		OPTION_SHOW_COLUMN_LANGS:				pROSE.newBooleanProp('lang-show-column'),
		OPTION_SHOW_NATIVE_LANGS:				pROSE.newBooleanProp('lang-show-native', true),
		OPTION_SHOW_NATIVE_AND_ENGLISH_LANGS:	pROSE.newBooleanProp('lang-show-native-english', true)
	};
	
	this.options.OPTION_PICTURES_SORT_DATETAKEN.getValue = function(v) {
		return (window.pres_overides_device == "true")? window.pres_sort_date_taken == "true" : v;
	};

	//*** INIT OPTIONS
	pROSE.initOptions(this);
	
	this.update = function(pre, post) {
		pHTTPRequest.get(window.location.pathname).then(function(req) {
			var s = pElement.x('main-generic-list-script');
			if (s) {
				var n = parseInt(s.getAttribute('now')), x = pElement.create('div', null, null, req.responseText);
				
				pElement.t(x, 'script').find(function(y) {
					if (y.id == 'main-generic-list-script' && parseInt(y.getAttribute('now')) > n) {
						if (pre) pre.call(pre);
						pDocument.exec(y);
						if (post) pre.call(post);
						
						s.setAttribute('now', y.getAttribute('now'));
						return true;
					}
				});
				/*x = x.getElementsByTagName('script');
				for(var i=0 ; i<x.length ; i++) {
					var y = x.item(i);
					if (y.id == 'main-generic-list-script' && parseInt(y.getAttribute('now')) > n) {
						if (pre) pre.call(pre);
						pDocument.exec(y);
						if (post) pre.call(post);
						
						s.setAttribute('now', y.getAttribute('now'));
						return;
					}
				}*/
			}
		});
	};
});

//[].forEach.call(pApplicationUI.OPTION_PREFERRED_LANGS, function(l) { console.log("Preferred lang: " + l) });
//console.log(pROSE.getMainLang());
//console.log(pROSE.getMainLongLang());

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pUsers = new (function() {
	var that = this;
	
	// un: username, n: name, v: value
	this.setUserData = function(un, n, v) {
		var u = '/user-update?user='+un;
	
		pConsole.debug(that, "SetUserProperties: " + u);
		pHTTPRequest.post({
			url: u,
			headers: pHTTPRequest.headers_post,
			body: pURL.addBodyParameter('', n, v)
		});
	};
	
	// un: username
	this.getUserData = function(un, p_fields, p_func) {
		var u = '/server?get-user&id='+un+'&format=json&contents=values&v='+Math.random();
		if (p_fields.forArray)
			p_fields.forArray(function(f) { u = pURL.addQueryParameter(u, f, '') });
		else
			u = pURL.addQueryParameter(u, p_fields, '')
			
		pConsole.debug(that, "GetUserProperties: " + u);
		pHTTPRequest.get(u, function(i_request, i_data) {
	
			//handle errors
			//if (pROSE.handleHTTPResponse(i_request) === false)
			//	return false;
	
			var i_obj = pJSON.parse(i_request.responseText);
			var i_values = new pMap(i_obj.values);
			this.f.call(this, i_values);
			
		}.bind({ f: p_func }), false);
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

var m_mobile_search = false;

function showMobileSearch() {
	var x = pElement.x("menu-mobile-search");

	if (m_mobile_search === true) {
		x.style.display = "none";
		m_mobile_search = false;
		return;
	}

	x.style.display = "block";
	pElement.focus('form-mobile-search-text');
	m_mobile_search = true;
}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pMediaDrop = new (function() {
	var that = this;
	
	/*this.getString = function(e) {
		var l = e.dataTransfer.items;
		//console.log('z: ' + that.getString(e));
		for(var i=0 ; i<l.length ; i++) {
			if (l[i].type == 'text/plain')
				l[i].getAsString(function(s) { console.log(s); });
			//console.log(''+i+': ' + l[i].kind + ' - ' + l[i].type);
		}
		//return e.dataTransfer.getData('text/plain');
	};*/
	
	this.filter = function(e, tu) {
		
		//e.dataTransfer.
		//console.log()
		 //that.getString(e);
		//var l = e.dataTransfer.items;
		//console.log('z: ' + that.getString(e));
		//for(var i=0 ; i<l.length ; i++)
		//	console.log(''+i+': ' + l[i].kind + ' - ' + l[i].type);// + ' - ' + l[i].getAsString());
		/*console.log(e.dataTransfer.types);
		if (e.dataTransfer.getData)
			console.log(e.dataTransfer.types.map(function(t) { return e.dataTransfer.getData(t) }).join(' - '));
		else
			console.log('no dt: ' + e.dataTransfer);*/
		var u = localStorage.getItem('drag.url');//e.dataTransfer.getData('URL');
		if (u) {
			if (!that.accept(u, tu) || u.indexOf('/play/')>0)
				return false;
			return true;
		}
		return false;
	};
	
	this.filterPicture = function(e, tu) {
		var u = localStorage.getItem('drag.url');//e.dataTransfer.getData('URL');
		//console.log('filterPicture: ' + u + ' - ' + tu);
		if (u && that.accept(u, tu) && u.indexOf('/play')>0)
			return true;
		
		console.log('filterPicture: false');
		return false;
	};
	
	this.setup = function() {
		if (!pApplicationUI.OPTION_DRAG_AND_DROP) return;
		if (pResources.get('admin') != "true" && pResources.get('own')!='true') return;
		if (!pString.v(metadata_name)) return;
		if (metadata_name == '(Unknown)') return;
		if (metadata_name == '(Featured In)') return;
		
		var lid = pMediaLibrary.getLIDfromURL() || '';
		if (lid.endsWith('.actors'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnActor(f, u, e, metadata_name, true);
			});

		if (lid.endsWith('.artists'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnArtist(f, u, e, metadata_name, true);
			});

		if (lid.endsWith('.authors'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnAuthor(f, u, e, metadata_name, true);
			});
		
		if (lid.endsWith('.genres'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnGenre(f, u, e, metadata_name, true);
			});
		
		if (lid.endsWith('.models'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnModel(f, u, e, metadata_name, true);
			});
		
		if (lid.endsWith('.tags'))
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnTag(f, u, e, metadata_name, true);
			});
		
/*		if (lid == 'homevideos.movies')
			pElement.setOnDrop('page-data', function(f, u, e) {
				if (pMediaLibrary.sameLibrary(u))
					if (pLocation.path(u) != window.location.pathname)
					pMediaLibrary.changeMetadata({
						url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1 ]), 
						method: "ChangeMetadata", 
						msg: "Moving to this Music Album...", 
						body: pURL.addBodyParameter('', [ 'metadata_type', 'Music', 'metadata_album', metadata_name, 'metadata_artist', metadata_artist ])
					});
			});
	*/	
		if (lid == 'music.albums')
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnMusicAlbum(f, u, e, metadata_name, metadata_artist, true);
			});
		
		if (lid == 'playlists.medias')
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnPlaylist(f, u, e, metadata_name, pMediaLibrary.getIDfromURL(), true);
			});
		
		if (lid == 'tvshows.seasons')
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.dropOnTVShowSeason(f, u, e, metadata_show, metadata_season, true);
			});
		
		if (lid == 'pictures.groups')
			pElement.setOnDrop('page-data', function(f, u, e) {
				that.copyPictures(f, u, e, null);
			});
	};
	
	this.accept = function(u, tu) {
		if (pMediaLibrary.sameLibrary(u)) {
			//console.log(pLocation.path(u) +' - '+ pLocation.path(tu || window.location.pathname));
			
			if (pLocation.path(u) != pLocation.path(tu || window.location.pathname))
				return true;
		}
	};
	
	this.dropOnActor = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-actor', replace: { metadata_name: v, verb: e.ctrlKey? 'add':'set' }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
					method: "ChangeMetadata", 
					msg: "Setting this actor...",
					msg_end: reload? pLocation.reload : null,
					//TODO: if model enabled...
					body: pURL.addBodyParameter('', [ 'metadata_actors', v, 'metadata_model', v ])
				});
			});
	};

	this.dropOnAuthor = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-author', replace: { metadata_name: v, verb: e.ctrlKey? 'add':'set' }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
					method: "ChangeMetadata", 
					msg: "Setting this author...",
					msg_end: reload? pLocation.reload : null,
					//TODO: if model enabled...
					body: pURL.addBodyParameter('', [ 'metadata_author', v ])
				});
			});
	};

	this.dropOnArtist = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-artist', replace: { metadata_name: v, verb: e.ctrlKey? 'add':'set' }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
					method: "ChangeMetadata", 
					msg: "Setting this artist...",
					msg_end: reload? pLocation.reload : null,
					//TODO: if model enabled...
					body: pURL.addBodyParameter('', [ 'metadata_artist', v ])
				});
			});
	};

	this.dropOnGenre = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-genre', replace: { metadata_name: v, verb: e.ctrlKey? 'add':'set' }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
					method: "ChangeMetadata", 
					msg: "Setting this genre...",
					msg_end: reload? pLocation.reload : null,
					//TODO: if model enabled...
					body: pURL.addBodyParameter('', [ 'metadata_genre', v ])
				});
			});
	};

	this.dropOnModel = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
				pDialog.question({ id: 'drop-on-model', replace: { metadata_name: v, verb: e.ctrlKey? 'add':'set' }}).then(function() {
					pMediaLibrary.changeMetadata({
						url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
						method: "ChangeMetadata", 
						msg: "Setting this model...",
						msg_end: reload? pLocation.reload : null,
						//TODO: if model enabled...
						body: pURL.addBodyParameter('', [ 'metadata_actors', v, 'metadata_models', v ])
					});
				});
	};

	this.dropOnTag = function(f, u, e, v, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-tag', replace: { metadata_name: pTag.format(v), verb: e.ctrlKey? 'add':'set' }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1, 'add', e.ctrlKey? 1:0 ]), 
					method: "ChangeMetadata", 
					msg: "Setting this tag...",
					msg_end: reload? pLocation.reload : null,
					body: pURL.addBodyParameter('', [ 'metadata_tags', v ])
				});
			});
	};

	this.dropOnMusicAlbum = function(f, u, e, v, artist, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-album', replace: { metadata_name: v, metadata_artist: artist }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1 ]), 
					method: "ChangeMetadata", 
					msg: "Moving to this Music Album...", 
					msg_end: reload? pLocation.reload : null,
					body: pURL.addBodyParameter('', [ 'metadata_type', 'Music', 'metadata_album', v, 'metadata_albumArtist', artist ])
				});
			});
	};

	this.dropOnPlaylist = function(f, u, e, v, pid, reload, tu) {
		if (that.accept(u, tu)) {
			var lid = pMediaLibrary.getLIDfromURL(u);
			if (lid.startsWith('books.')) return;
			if (lid.startsWith('pictures.')) return;
			
			v = pPlaylists.format(v);
			pDialog.question({ id: 'drop-on-playlist', replace: { name: v }}).then(function() {
				pMediaLibrary.addToPlaylist(u, pid, v, reload? pLocation.reload : null);
			});
		}
	};
	
	this.dropOnTVShowSeason = function(f, u, e, v, season, reload, tu) {
		if (that.accept(u, tu))
			pDialog.question({ id: 'drop-on-tvshow', replace: { metadata_name: v, metadata_season: season }}).then(function() {
				pMediaLibrary.changeMetadata({
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u), 'children', 1 ]), 
					method: "ChangeMetadata", 
					msg: "Moving to this TV Show Season...", 
					msg_end: reload? pLocation.reload : null,
					body: pURL.addBodyParameter('', [ 'metadata_type', 'TV Show', 'metadata_show', v, 'metadata_season', season ])
				});
			});
	};
	
	this.dropOnPictureFolder = function(f, u, e, v, season, reload, tu) {
		if (that.accept(u, tu))
			that.movePictures(f, u, e, tu);
	};
	
	/**
	 * @param f - dropped files
	 * @param u - dropped URL
	 * @param e - drop event
	 * @param tu - target URL
	 */
	this.copyPictures = function(f, u, e, tu) {
		
		pFiles.toFiles(e, function(ff) {
			
			if (ff) {
				var d = ff.filter(pFiles.isDirectory);
				if (d.length == ff.length)
					if (pMediaLibrary.getLIDfromURL() == 'pictures.groups') {
						
						if (d.length==1 && d[0].files.every(pFiles.isDirectory) && d[0].files.every(pApplicationUI.containsImageFiles))
							d[0].files.forEach(function(dd) {
								var q = { name: d[0].name+'/'+dd.name, silent: true };
								pMediaLibrary.createFolder(q).then(function(o) {
									var tu = pMediaLibrary.getDocURL(null, o.lid, o.id);
									pApplicationUI.f_addImages(dd.files, tu, q.name);
								});
							});
						else
							d.forEach(function(dd) {
								var q = { name: dd.name, silent: true };
								pMediaLibrary.createFolder(q).then(function(o) {
									var tu = pMediaLibrary.getDocURL(null, o.lid, o.id);
									pApplicationUI.f_addImages(dd.files, tu, q.name);
								});
							});
						return;
					}
				
				pApplicationUI.f_addImages(ff, tu);
			}
			else if (f)
				pApplicationUI.f_addImages(f, tu);
			else if (u)
				pMediaLibrary.selectAddImage(tu, null, { images:[ { uri: u } ]});
			
			//console.log(JSON.stringify(ff));
			//alert('here');
		}, function(e) { return ![ '=thumbs', '=html', '[originals]' ].includes(e.name.toLowerCase())});
		
		/*if (e.dataTransfer.items) {
			var done = 0, dt = 0, ff = [], l = e.dataTransfer.items.length;
			for(var i=0 ; i<l; i++)
				if (e.dataTransfer.items[i].webkitGetAsEntry) {
					var d = e.dataTransfer.items[i].webkitGetAsEntry();
					if (d) {
						done++;
						
						if (pMediaLibrary.getLIDfromURL() == 'pictures.groups' && d.isDirectory) {
							//alert(e.name);
							pMediaLibrary.createFolder({ name: d.name, silent: true }).then(function(r) {
								
								var o = pJSON.parse(r.responseText);
								var tu = pMediaLibrary.getDocURL(null, o.actions[0].object.lid, o.actions[0].object.id);
								//alert(tu)
								
								pFiles.toFiles(d, function(fs) {
									pApplicationUI.f_addImages(fs, tu);
								});
							});
							continue;
						}
						
						
						pFiles.toFiles(d, function(fs) {
							pArray.append(ff, fs);
							dt++;
							if (dt == l)
								pApplicationUI.f_addImages(ff);
						});
					}
				}
			
			if (done == l) {
				//pApplicationUI.f_addImages(ff);
				return;
			}
		}
		
		if (f)
			pApplicationUI.f_addImages(f);
		else if (u)
			pMediaLibrary.selectAddImage(tu, null, { images:[ { uri: u } ]});*/
	};
	
	/**
	 * @param f - dropped files
	 * @param u - dropped URL
	 * @param e - drop event
	 * @param tu - target URL
	 */
	this.movePictures = function(f, u, e, tu) {
		if (u) {
			if (!e || e.ctrlKey===false) { 
				var lib = pMediaLibrary.getLibraryIDfromURL();
				var l = new pLocationInstance(u);
				
				if (l.path.startsWith(url_prefix+lib+'/play/')) {
					var ref = l.getQueryParameter('ref');
					
					//cannot move picture into its own folder...
					if (ref && pURL.pathEqual(ref, tu || window.location.pathname))
						return;
					
					var r = { id: 'move-picture', options: [ '!question', pImages.createDialogOption({ uri: u }) ], };
					pDialog.question(r).then(function () {

						pMediaLibrary.movePicture(ref, u, function(){ 
							pElement.click('toolbar-refresh');
							localStorage.setItem(l.path, 'removed');
							localStorage.removeItem(l.path);
						}, tu || window.location.pathname);
					}.bind(r));
					return;
				}
				
				/*if (l.path.startsWith(url_prefix+lib+'/')) {
					var lid = pMediaLibrary.getLIDfromURL(u);
					if (lid == 'pictures.medias') {
					}
				}*/
			}
			pMediaLibrary.selectAddImage(tu, null, { images:[ { uri: u } ]});
		}
		else
			that.copyPictures(f, u, e, tu);
	};
});

/******************************************************************************/

const pPictureFolder = (new function() {
	var that = this;
	
	/**
	 * @param f - dropped files
	 * @param u - dropped URL
	 * @param e - drop event
	 * @param tu - target URL
	 */
	this.drop = function(f, u, e, tu) {

		if (u) {
			var lib = pMediaLibrary.getLibraryIDfromURL(), lu = pLocation.path(u), lt = pLocation.path(tu);
			
			if (lu.startsWith(url_prefix+lib+'/pictures.medias/'))
				if (lt.startsWith(url_prefix+lib+'/pictures.medias/') || lt.startsWith(url_prefix+lib+'/pictures.groups/')) {
					that.moveFolder(f, u, e, tu);
					return;
				}
			
			if (lu.startsWith(url_prefix+lib+'/play/')) {
				that.dropLibraryImage(f, u, e, tu);
				return;
			}
			
			return;
		}
		
		pMediaDrop.copyPictures(f, u, e, tu);
	};

	/**
	 * @param f - dropped files
	 * @param u - dropped URL
	 * @param e - drop event
	 * @param tu - target URL
	 */
	this.dropLibraryImage = function(f, u, e, tu) {
		var lib = pMediaLibrary.getLibraryIDfromURL(), l = new pLocationInstance(u);
		var ref = l.getQueryParameter('ref');
		
		//cannot move picture into its own folder...
		if (ref && pURL.pathEqual(ref, tu || window.location.pathname))
			return;
		
		var r = { id: 'move-picture', options: [ '!question', pImages.createDialogOption({ uri: u }) ], };
		pDialog.question(r).then(function () {

			pMediaLibrary.movePicture(ref, u, function(){ 
				pElement.click('toolbar-refresh');
				localStorage.setItem(l.path, 'removed');
				localStorage.removeItem(l.path);
			}, tu || window.location.pathname);
		}.bind(r));
		return;
	};
	
	this.moveFolder = function(f, u, e, tu) {
		var lib = pMediaLibrary.getLibraryIDfromURL(), lu = new pLocationInstance(u), lt = new pLocationInstance(tu);
		
		var i_url = pURL.addParam(tu, [ 'action', 'get-metadata', 'format', 'json', 'contents', 'values', 'v', ''+Math.random(), 'files', 1, 'artwork', 0,
			'content_ratings', 0, "user_country", 0, "langs", 0, "types", 0 ]);
		pConsole.info(that, "GetProperties: " + i_url);
		pHTTPRequest.get(i_url).then(function(i_request) {

			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;

			//console.log(i_request.responseText);
			//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
			var i_obj = pJSON.parse(i_request.responseText);
			
			var i_values = new pMap(i_obj.values), f = i_obj.file || (i_obj.files && i_obj.files.length>0? i_obj.files[0] : null);
			
			var i_url2 = pURL.addParam(u, [ 'action', 'get-metadata', 'format', 'json', 'contents', 'values', 'v', ''+Math.random(), 'files', 1, 'artwork', 0,
				'content_ratings', 0, "user_country", 0, "langs", 0, "types", 0 ]);
			pConsole.info(this, "GetProperties: " + i_url2);
			pHTTPRequest.get(i_url2).then(function(i_request) {

				//handle errors
				if (pROSE.handleHTTPResponse(i_request) === false)
					return false;

				//console.log(i_request.responseText);
				//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
				var i_obj2 = pJSON.parse(i_request.responseText);
				
				var i_values = new pMap(i_obj2.values), f2 = i_obj.file || (i_obj.files && i_obj.files.length>0? i_obj.files[0] : null);
				
				var fn = i_obj.file.path + '/' + i_obj2.file.name;
				/*
				var fn = pString.dirname(i_obj.file.path) + '/' + i_obj2.file.name;
				
				if (lu.path.startsWith(url_prefix+lib+'/pictures.medias/'))
					if (lt.path.startsWith(url_prefix+lib+'/pictures.medias/'))
						fn = i_obj.file.path + '/' + i_obj2.file.name;
				*/
				var i_data = pMediaLibrary.prepareRefreshRequest(u);
				
				pTransaction.simpleTransaction({
					trace: that, 
					url: pURL.addQueryParameter(u, [ 'action', 'set-metadata', 'v', Math.random(), 'wait', 1, 'log', 1, 'template', 'object-all.json', 'others', pMediaLibrary.getMLIDfromURL(u) ]), 
					method_name: "SetMetadata",
					msg_start: 'Moving...',
					msg_end: [ 'Moving... Done.', i_data.c_deleted, pMetadataDialog.c_completed ],
					headers: pHTTPRequest.headers_post,
					body: pURL.addBodyParameter('rename_file=1', 'filename_new', fn),
				});
			});
		});
		
	};
});

/******************************************************************************/

const pAudioUtil = (new function() {
	var that = this, audio;

	this.codecs = new pMap([
	    'PCM/WAV', 'audio/wav; codecs="1"', 
	    'MP3', 'audio/mpeg', 
	    'AAC', 'audio/mp4; codecs="mp4a.40.2"',
	    
	    'audio/mp4; codecs="aac"', 'audio/mp4; codecs="mp4a.40.2"',
	    
	    'AC3 (Dolby Digital)', 'audio/mp4; codecs="ac-3"',
	    'Enhanced AC3 (Dolby Digital Plus)', 'audio/mp4; codecs="ec-3"',
	    'Ogg Vorbis', 'audio/ogg; codecs="vorbis"',
	    'Ogg Opus', 'audio/ogg; codecs="opus"', 
	    'WebM with Vorbis', 'audio/webm; codecs="vorbis"', 
	    'WebM with Opus', 'audio/webm; codecs="opus"'
	]);
	
	function canPlayType(x, type) {
		/*
			There is a bug in iOS 4.1 or earlier where probably and maybe are switched around.
			This bug was reported and fixed in iOS 4.2
		*/

        //TODO: if (Browsers.isOs('iOS', '<', '4.2'))
        //    return x.canPlayType(type) == 'probably' || x.canPlayType(type) == 'maybe';
        //else
            return x.canPlayType(type) == 'probably';
    }
    
	this.supports = function(m) {
		audio = audio || pElement.create('audio');
		
		/* mp3 codec */
		if ('MP3' == m) {
			if (audio.canPlayType) {
			    var t = audio.canPlayType('audio/mpeg');
			    if (t == 'maybe')
			        // We need to check if the browser really supports playing MP3s by loading one and seeing if the
			        // loadedmetadata event is triggered... but for now assume it does support it...
			       return true;
			    if (t == 'probably')
			        return true;
			}
			return false;
		}
		
		var c = that.codecs.getValue(m, that.codecs.getValue(m.toLowerCase(), [ m.toLowerCase() ]));
		return !!audio.canPlayType && canPlayType(audio, c);
		//return !!audio.canPlayType && canPlayType(audio, m.toLowerCase());
	};
});

/******************************************************************************/

const pVideoUtil = (new function() {
	var that = this, video;

	this.codecs = new pMap([ 
	    'MPEG-4 ASP', [ 'video/mp4; codecs="mp4v.20.8"' ], 
	    'H.264', [ 'video/mp4; codecs="avc1.42E01E"', 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"' ],
	    
	    'video/mp4; codecs="avc"', [ 'video/mp4; codecs="avc1.42E01E"', 'video/mp4; codecs="avc1.42E01E, mp4a.40.2"' ],
	    
	    'H.265', [ 'video/mp4; codecs="hvc1.1.L0.0"', 'video/mp4; codecs="hev1.1.L0.0"' ], 
	    'Ogg Theora', [ 'video/ogg; codecs="theora"' ], 
	    'WebM VP8', [ 'video/webm; codecs="vp8"' ], 
	    'WebM VP9', [ 'video/webm; codecs="vp9"' ], 
		'WebM AV1', [ 'video/webm; codecs="av1.experimental.e87fb2378f01103d5d6e477a4ef6892dc714e614"' ]
    ]);
	
	function canPlayType(x, type) {
		/*
			There is a bug in iOS 4.1 or earlier where probably and maybe are switched around.
			This bug was reported and fixed in iOS 4.2
		*/

        //TODO: if (Browsers.isOs('iOS', '<', '4.2'))
        //    return x.canPlayType(type) == 'probably' || x.canPlayType(type) == 'maybe';
        //else
            return x.canPlayType(type) == 'probably';
    }
    
	this.supports = function(m) {
		video = video || pElement.create('video');
		
		var c = that.codecs.getValue(m, that.codecs.getValue(m.toLowerCase(), [ m.toLowerCase() ]));
		return !!video.canPlayType && c.some(function(cc) { return canPlayType(video, cc); });
		//return !!video.canPlayType && canPlayType(video, m.toLowerCase());
	};
});

/******************************************************************************/

function showMovieMenu(noclose) {
	if (pDocument.isShown('movie-menu') && !noclose) {
		pDocument.hideSmooth('movie-menu');
		pElement.setTextContent('button-expand-toolbar', '\u2228');

		pElement.x('movie').style.top = 'auto';
		pElement.x('movie').style.bottom = '0px';
	}
	else {
		//pElement.x('movie').style.top = pDocument.top('movie')+'px';
		//pElement.x('movie').style.bottom = 'auto';

		var i_top = 58;
		var i_h = pDocument.showSmooth('movie-menu', null);
		//console.log('height: '+i_h + ' w: ' + window.innerHeight);

		i_h = pDocument.height('movie');//parseInt(i_h.substring(0, i_h.indexOf('px')));

		if (i_h + i_top > window.innerHeight) {
			pElement.x('movie').style.top = i_top+'px';
			pElement.x('movie').style.bottom = 'auto';
		}

		setTimeout(function() { pDocument.scrollTo('movie'); }, 500);
		pElement.setTextContent('button-expand-toolbar', '\u2227');
	}
}

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pUserJobManager = new (function() {
	var that  =this;
	
	/*this.vprivate = {
		id: ''+Math.random(),
		tracePrefix: 'LibraryJobList',
		updatems: 60000,
		element_id: 'jobs'
	};*/
	var url = window.location.pathname;

	this.createTask = function() {
		pTransaction.transaction({
			trace: that, url: url+'?action=create-autoadd-user-job&v='+Math.random(), method_name: "CreateJob",
			msg_start: 'Creating new task...',
			msg_error: 'Task has NOT been created...',
			msg_end: function(p_request) {
				var o = pJSON.parse(p_request.responseText);
				if (!o.job_id)
					return; //TODO

				that.getProperties(o.job_id);
			}
		});
	};

	this.getImportOptions = function(f_callback, p_reimport) {

		pNoteUI.createNoteDialogOption('help-optimize-pictures').then(function(op) {
		
			var i_options = new pLocationInstance('/?' + pCookieManager.getCookie("import-options"));
			var i_job = {};
			var i_dialog = {
				id: 'import-options',
				options: [
					'ok', 'cancel',
	
					{ id: "import_files_imported", input: { type: 'checkbox', readonly: p_reimport === true }, text_after: 'Re-Import files already in the library', text_after_class: 'library-properties-checkbox', value: "true" },//i_job.import_files_imported },
					'<hr>',
	
					{ id: "disable_books_medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import Books', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_movies.medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import Movies', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_homevideos.medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import Home Videos', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_music.medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import Songs', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_pictures.medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import Picture Folders', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_tvshows.medias", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Do not import TV Shows', text_after_class: 'library-properties-checkbox' },
					'<hr>',
					{ id: "import_files_playlist_files", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Import playlist files (.m3u, .pls, etc...)', text_after_class: 'library-properties-checkbox', value: i_job.import_files_playlist_files },
					'<hr>',
					{ id: "import_videos_as_home_videos", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Import all video files as Home Videos', text_after_class: 'library-properties-checkbox', value: i_job.import_videos_as_home_videos },
					'<hr>',
					op,
					{ id: "optimize_picture_folders", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Optimize imported Picture Folders', text_after_class: 'library-properties-checkbox', value: i_job.optimize_picture_folders },
					{ id: "scale", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Create scaled pictures as well', text_after_class: 'library-properties-checkbox', value: i_job.scale },
				],
				//submitChangedInputsOnly: true,
				fcall: function(p_dialog) {
					var i_data = this;
					var body = pURL.toFormSubmitBody(p_dialog.inputs, function(p_name, p_value) { return (p_value)? "1":((p_name=='import_files_imported')? "0":null); });
					pCookieManager.setGlobal("import-options", body);
					i_data.callback(body);
				}.bind({ callback: f_callback })
			};
	
			i_dialog.options.forEach(function(op) {
				if (op && op.input && op.input.type=='checkbox') {
					var i_value = i_options.getQueryParameter(op.id);
					if (i_value) op.value = i_value;
				}
				if (op && op.id == "import_files_imported" && p_reimport === true)
					op.value = true;
			});
	
			pDocument.stopwait(); //TODO: inside dialogQuestion...
			pDialog.dialogQuestion(i_dialog);//.doc_url = i_data.doc_url;
		});
	};

	this.getProperties = function(p_job_id) {

		pDocument.wait();

		var i_data = {
			job_id: p_job_id,
			mgr: that
		};

		var i_url = url+'?action=get-autoadd-user-job&format=json&contents=object&job-id='+p_job_id+'&v='+Math.random();
		pConsole.info(that, "GetProperties: " + i_url);
		pHTTPRequest.get(i_url, function(i_request, i_data) {

			//handle errors
			if (pROSE.handleHTTPResponse(i_request) === false)
				return false;

			//console.log(i_request.responseText);
			//pConsole.debug(that, "GetMetadata: response: " + pJSON.pretty(i_request.responseText));
			var i_job = pJSON.parse(i_request.responseText);
			pConsole.debug(that, "GetProperties: response: " + pJSON.pretty(i_request.responseText));

			var i_dialog = {
				id: 'job-properties',
				options: [
					'ok', 'cancel',

					{ id: 'tab-name', tab: true, text: 'Name' },
					{ id: 'display_name', input: { type: 'text', required: true, readonly: i_job.readonly, placeholder: 'Name for this task...' }, text: 'Name:', value: i_job.display_name },
					{ id: 'description', input: { type: 'textarea', rows: 3, icons: [ pDialog.resources.icon_delete ], readonly: i_job.readonly }, text: 'Description:', value: i_job.description }
				],
				submitChangedInputsOnly: true,
				fcall: function(p_dialog) {
					var i_data = this;

					var i_body = pURL.toFormSubmitBody(p_dialog.inputs, function(n, v) {
						if (n == 'timer_ms') return v * 60000;
						if (n == 'delay_ms') return v * 60000;
						if (n == 'start_ms') return pDate.inputTimeToMS(v);
						return v;
					});

					pTransaction.simpleTransaction({
						trace: that, 
						url: pURL.addQueryParameter(i_data.mgr.url, [ 'action', 'update-autoadd-user-job', 'job-id', i_data.job_id, 'v', Math.random() ]), 
						method_name: "UpdateJob",
						msg_start: 'Updating task...',
						msg_end: function(p_request) { this.obj.update(this.library_url); }.bind({ obj: pLibraryJobList, library_url: i_data.library_url }),
						headers: pHTTPRequest.headers_post,
						body: i_body,
					});

				}.bind(i_data)
			};

			if (!i_job.folder || !i_job.folder.endsWith('false'))
				pArray.append(i_dialog.options, [
					{ id: 'tab-folder', tab: true, text: 'Folder' },
					{ id: 'folder', input: { type: 'server-folder', required: true, readonly: i_job.readonly }, text: 'Folder:', value: i_job.folder },
					'<hr>',
					{ id: "import_files_imported", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Re-Import files already in the library', text_after_class: 'library-properties-checkbox', value: i_job.import_files_imported },
					//{ id: "import_folders_pictures_only", input: { type: 'checkbox', readonly: i_job.readonly }, text_after: 'Import only picture folders', text_after_class: 'library-properties-checkbox', value: i_job.import_folders_pictures_only },
					'<hr>',

					{ id: "disable_books_medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import Books', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_movies.medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import Movies', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_homevideos.medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import Home Videos', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_music.medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import Songs', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_pictures.medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import Picture Folders', text_after_class: 'library-properties-checkbox' },
					{ id: "disable_tvshows.medias", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Do not import TV Shows', text_after_class: 'library-properties-checkbox' },
					'<hr>',
					{ id: "import_files_playlist_files", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Import playlist files (.m3u, .pls, etc...)', text_after_class: 'library-properties-checkbox', value: i_job.import_files_playlist_files },
					'<hr>',
					{ id: "import_videos_as_home_videos", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Import all video files as Home Videos', text_after_class: 'library-properties-checkbox', value: i_job.import_videos_as_home_videos },
					'<hr>',
					{ id: "optimize_picture_folders", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Optimize imported Picture Folders', text_after_class: 'library-properties-checkbox', value: i_job.optimize_picture_folders },
					{ id: "scale", input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Create scaled pictures as well', text_after_class: 'library-properties-checkbox', value: i_job.scale },
				]);
			
			pArray.append(i_dialog.options, [
				{ id: 'tab-schedule', tab: true, text: 'Schedule' },
				{ id: 'enabled',  input: { type: 'checkbox', xreadonly: i_job.readonly }, text_after: 'Enabled', text_after_class: 'library-properties-checkbox', value: i_job.enabled },
				'<hr>',
				{ id: 'repeats-no', input: { type: 'radio', xreadonly: i_job.readonly, name: 'repeats', selected: i_job.repeats==0 }, text_after: 'No Repeat', text_after_class: 'library-properties-checkbox', value: 0 },
				{ id: 'repeats', input: { type: 'radio', xreadonly: i_job.readonly, name: 'repeats', selected: i_job.repeats==1 }, text_after: 'Repeats', text_after_class: 'library-properties-checkbox', value: 1 },
				{ id: 'timer_ms', input: { type: 'number', xreadonly: i_job.readonly, min: 1, max: 24*60 }, text: 'Every', className: 'dialog-question-input-inline', text_after: ' minute(s)', text_after_class: 'library-properties-checkbox', value: i_job.timer_ms / 60000 },
				{ id: 'delay_ms', input: { type: 'number', xreadonly: i_job.readonly, min: 0, max: 3600 }, text: 'Starts', className: 'dialog-question-input-inline', text_after: ' minute(s) after you update this task or the library is opened', text_after_class: 'library-properties-checkbox', value: i_job.delay_ms / 60000 },
				{ id: 'repeats-day', input: { type: 'radio', xreadonly: i_job.readonly, name: 'repeats', selected: i_job.repeats==2 }, text_after: 'Repeats every day', text_after_class: 'library-properties-checkbox', value: 2 },
				{ id: 'start_ms', input: { type: 'Starts at', type: 'time', xreadonly: i_job.readonly }, text: 'At:', className: 'dialog-question-input-inline', value: pDate.msToInputTime(i_job.start_ms) }
			]);

			//i_dialog.options.forEach(function(op) {
			//	if (op.input)
			//		op.value = i_job[op.id];
			//});

			pDocument.stopwait(); //TODO: inside dialogQuestion...
			pDialog.dialogQuestion(i_dialog);//.doc_url = i_data.doc_url;

		}, false, i_data);
	}.bind(this);
});

/******************************************************************************/

pApplicationUI.setup = function() {
	pApplicationUI.initDragAndDrop();
	pApplicationUI.initPage();
	//pNotificationUI.addNotification('Notification 1 this is a very long text which takes a lot of space and is difficult to read<br>Bla bla bla...<br>Bla bla bla... this is a very long text which takes a lot of space and is difficult to read');
	//pNotificationUI.addNotification('Notification 2');
	//pNotificationUI.addNotification('Notification 3');
	try { pMediaDrop.setup(); } catch(ex) {}
	
	pPageData.init();
};

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pPoster = (new function() {
	
	this.onError = function(img, u) {
		img.onerror = function() { //this is IMG
			this.onerror = null;
			this.src = url_prefix + pMediaLibrary.getLibraryIDfromURL() + pGenericList.getListThumbnail();
			//console.log('src : ' + this.src);
		};
		img.src = u;
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pHomeVideo = (new function() {
	var that = this;
	
	this.click = function(lv, i) {
		if (pROSE.getProp('view-user-advanced-play-homevideos') === true)
			that.play(lv, i);
		else
			that.open(lv, i);
	};

	function item(lv, i, i_item) {
		return {
			id: ''+i,
			url: url_prefix+lv.library_id+'/'+ i_item.lid +'/'+i_item.id + '?play&amp;mode=auto',
			title: pGenericList.getItemTitle(i_item)+' ('+pDate.toLocaleDateString(pDate.create(i_item.release_date), null, options_full_date, i_item.release_date)+')',
			askResume: false
		};
	};
	
	this.play = function(lv, i) {
		var ii = lv.items[i];
		if (ii)
			pMoviePlaylist.play(item(lv, i, ii));
	};
	
	this.open = function(lv, i) {
		var item = lv.items[i];
		if (item)
			pLocation.assign(url_prefix+lv.library_id+'/'+item.lid+'/'+item.id);
	};
	
	this.preload = function(lv, i) {
		var ii = lv.items[i];
		if (ii)
			pMoviePlaylist.preload(item(lv, i, ii));
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pRadio = (new function() {
	var that = this;
	
	this.click = function(lv, i) {
		if (pROSE.getProp('view-user-advanced-play-radios') === true)
			that.play(lv, i);
		else
			that.open(lv, i);
	};

	function item(lv, i, i_item) {
		return {
			id: ''+i,
			url: url_prefix+lv.library_id+'/'+ i_item.lid +'/'+i_item.id + '?play&amp;mode=auto',
			title: pGenericList.getItemTitle(i_item),//+' ('+pDate.toLocaleDateString(pDate.create(i_item.release_date), null, options_full_date, i_item.release_date)+')',
			askResume: false
		};
	};
	
	this.play = function(lv, i) {
		var ii = lv.items[i];
		if (ii)
			pMoviePlaylist.play(item(lv, i, ii));
	};
	
	this.open = function(lv, i) {
		var ii = lv.items[i];
		if (ii)
			pLocation.assign(url_prefix+lv.library_id+'/'+item.lid +'/'+item.id);//i_url);
	};
	
	this.preload = function(lv, i) {
		var ii = lv.items[i];
		if (ii)
			pMoviePlaylist.preload(item(lv, i, ii));
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pApplicationMenu = (new function() {
	var that = this;
	
	var mains = [
		{ id: 'home', icon: '/resources/html/images/16x16/house.png', text: 'Home' },
	
		{ id: 'music.albums', icon: '/resources/html/images/16x16/music.png', text: 'Music Albums' },
		{ id: 'music.artists', text: 'Music Artists' },
		{ id: 'music.genres', text: 'Music Genres' },
	
		{ id: 'movies.movies', icon: '/resources/html/images/16x16/movies.png', text: 'Movies' },
		{ id: 'movies.genres', text: 'Movie Genres' },
		{ id: 'movies.tags', text: 'Movie Tags' },
		{ id: 'movies.actors', text: 'Movie Actors' },
	
		{ id: 'tvshows.seasons', icon: '/resources/html/images/16x16/television.png', text: 'TV Shows' },
		{ id: 'tvshows.genres', text: 'TV Shows Genres' },
	
		{ id: 'books.groups', icon: '/resources/html/images/16x16/book.png', text: 'Books' },
		{ id: 'books.authors', text: 'Book Authors' },
		{ id: 'books.genres', text: 'Book Genres' },
		{ id: 'books.tags', text: 'Book Tags' },
	
		{ id: 'pictures.groups', icon: '/resources/html/images/16x16/photos.png', text: 'Pictures' },
		{ id: 'pictures.authors', text: 'Picture Authors' },
		{ id: 'pictures.models', text: 'Picture Models' },
		{ id: 'pictures.genres', text: 'Picture Genres' },
		{ id: 'pictures.tags', text: 'Picture Tags' },
	
		{ id: 'homevideos.movies', icon: '/resources/html/images/16x16/video.png', text: 'Home Videos' },
		{ id: 'homevideos.tags', text: 'Home Video Tags' },
	
		{ id: 'radios.radios', icon: '/resources/html/images/16x16/radio_modern.png', text: 'Radios' },
		{ id: 'radios.genres', text: 'Radio Genres' },
	
		{ id: 'playlists.groups', icon: '/resources/html/images/16x16/script.png', text: 'Playlists' }
	];

	this.mainMenu = function() {
	
		var i_dialog = {
			id: 'main-menu',
			className: 'dialog-main-menu'
		};
	
		i_dialog.options = mains.filter(function(m) { 
			return m.id=='home' || (pResources.get('library.list.('+m.id.split('.')[0]+'.medias).enabled') != 'false' && pResources.get('library.list.('+m.id+').enabled') != 'false') 
		}).map(function(i_menu) {
			var i_url = url_prefix;
			if (i_menu.id != 'home')
				i_url = pMediaLibrary.getURL(null, i_menu.id);
			return {
				id: i_menu.id,
				icon: i_menu.icon || 'null',
				text: i_menu.text,
				disabled: window.location.pathname == i_url,
				className: (window.location.pathname == i_url)? 'dialog-selected' : '',
				fcall: function() { pLocation.assign(this); }.bind(i_url) 
			};
		});
		i_dialog.options.push({ id: 'sep', sep: true, text: '<hr>' });
		i_dialog.options.push({ id: 'user', icon: new pStyle('menu-user').backgroundImageURL(), text: pPageData.resolve('{resources.username}'), fcall: function() { pLocation.assign('/user'); } });
		i_dialog.options.push({ id: 'device-settings', icon: new pStyle('menu-device').backgroundImageURL(), text: 'Device Settings', fcall: function() { pLocation.assign('/device'); } });
		if (pResources.get('admin') === "true")
			i_dialog.options[i_dialog.options.length] = { id: 'server-settings', icon: new pStyle('menu-server').backgroundImageURL(), text: 'Server Settings', fcall: function() { pLocation.assign('/server'); } };
		//i_dialog.options.push('cancel');
		i_dialog.footer = [ 
		       { id: 'previous', text: '<div id="menu-history-back"><!-- --></div>', className: 'dialog-footer-button noselect', scall: pLocation.back },
		       'cancel',
		       { id: 'next', text: '<div id="menu-history-next"><!-- --></div>', className: 'dialog-footer-button noselect', scall: pLocation.forward } 
		];
	
		pDialog.dialogQuestion(i_dialog);
	};
	
	this.menu = function(p_id, p_links, bypass_display_none) {
		var i_dialog = {
			id: p_id,
			title: pDocument.getStyleValue((p_id.startsWith('dq')? p_id : '.dq-'+p_id)+'-title'),
			icon: { src: pDocument.getStyleValue((p_id.startsWith('dq')? p_id : '.dq-'+p_id)+'-icon') },
			className: 'dialog-main-menu',
			options: [ 'cancel' ]
		};
	
		if (p_links) {
			p_links.forEach(function(l, i) {
				if (l) {
					if (l.url)
						i_dialog.options.push({
							id: l.id || ''+i,
							text: l.text,
							icon: l.icon || 'null',
							title: l.title,
							className: l.className,
							fcall: l.fcall || function() { pLocation.assign(this); }.bind(l.url),
							value: l.value,
							selected: l.selected
						});
					else if (l.startsWith)
						i_dialog.options.push(l);
					else
						i_dialog.options.push({
							id: l.id || ''+i,
							text: l.text,
							icon: l.icon || 'null',
							title: l.title,
							className: l.className,
							fcall: l.fcall,
							disabled: l.disabled,
							value: l.value,
							selected: l.selected
						});
					
					if (l.description)
						i_dialog.options.push({ id: 'desc', className: 'desc', text: l.description, disabled: true, sep: true });
				}
			});
		}
		else {
			var i_x = pElement.x(p_id);
			for(var i=0 ; i<i_x.children.length ; i++) {
				var m = i_x.children[i], c = (m.className || '').split(' ');
	
				if (m.tagName.toLowerCase() != 'div') continue;
				if (m.innerHTML == '...') continue; //TODO:...
				if (m.innerHTML == 'More...') continue; //TODO:...
				if (m.innerHTML == '') continue; //TODO:...
				if (!bypass_display_none && m.style.display == 'none') continue;
	
				i_dialog.options.push({
					id: m.id || ''+Math.random(),
					text: m.innerHTML,
					icon: 'null',
					title: m.title,
					className: pString.concat(pArray.remove(pArray.remove(c, 'toolbar-item'), 'dialog-tab-title'), ' ') + ' dialog-item',
					disabled: m.onclick==null,
					fcall: m.onclick==null? null : function() {
						try {
							pElement.click(this);
						}
						catch(ex) {
							console.log(ex);
							alert(ex + '\n' + ex.stack);
						}
					}.bind(m)
				});
			}
		}
		pDialog.dialogQuestion(i_dialog);
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pNotificationUI = (new function() {
	var that = this, notifications, default_close =  { close: function() {} };
	this.tracePrefix = 'Notifications';
	
	this.addNotification = function(p_content, p_type, p_options) {

		if (pApplicationUI.OPTION_NOTIFICATION_API) {
			if (window.Notification) {
				if (Notification.permission === "granted") {
					var i_options = { body: p_content };
					if (p_options)
						for(var i in p_options)
							i_options[i] = p_options[i];

					var i_n = new Notification('Hello World...', i_options);

					var i_id = ''+Math.random();
					(notifications = notifications || new pMap()).put(i_id, i_n);

					if (p_type === "success")
						setTimeout(function() { that.removeNotification(i_id); }, pApplicationUI.OPTION_NOTIFICATION_CLOSE_TIMEOUT_MS);

					return i_id;
				}
				else if (Notification.permission != "denied") {
					Notification.requestPermission();
					return that.addNotification(p_content, p_type);
				}
			}
		}

		var c = pElement.x("notifications");
		if (c) {
			var x = pElement.create("div", "n-"+Math.random(), "notification notification-notvisible");
			x.innerHTML = "<div id=\"content-"+x.id+"\" class=\"notification-content "+((p_type)? "notification-content-"+p_type : "")+"\">"+p_content+"</div>" +
				"<div id=\"close-"+x.id+"\" class=\"notification-close\" onclick=\"pNotificationUI.removeNotification(this.parentElement.id)\">&times;</div>";
	
			c.appendChild(x);
			pElement.addAndRemoveClassName(x, "notification-visible", "notification-notvisible");
	
			if (p_type === "success")
				setTimeout(function() { that.removeNotification(x.id); }, pApplicationUI.OPTION_NOTIFICATION_CLOSE_TIMEOUT_MS);
	
			return x.id;
		}
	};
	
	this.removeNotification = function(x) {
		if (typeof x=="string") {
			if (x = pElement.x(x)) {
				x.parentElement.removeChild(x);
			/*
			//pElement.x('content-'+i_n.id).style.display = 'none';
			//pElement.x('close-'+i_n.id).style.display = 'none';
			pElement.addAndRemoveClassName('content-'+i_n.id, "notification-notvisible", "notification-visible");
			setTimeout(function(){
				//pElement.x('content-'+i_n.id).style.display = 'none';
				//pElement.x('close-'+i_n.id).style.display = 'none';
				i_n.parentElement.removeChild(i_n);
			},	2000);*/
			}
		}
		else if (notifications)
			try {
				notifications.get(p_id, default_close).close();
				notifications.remove(p_id);
			}
			catch (e) {
				pConsole.error(that, "Failed to remove notification", e);
			}
	};
	
	this.getNotificationText = function(x, d) {
		return (x = pElement.x('content-'+x))? x.innerHTML : d;
	};
	
	this.setNotificationText = function(x, t) {
		pElement.setInnerHTML('content-'+x, t);
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pHelpUI = (new function() {
	var that = this;
	
	this.helpDialog = function(p_id, p_dialog_id) {
		return new Promise(function(resolve, reject) {
			var p_request = { id: p_id };
			
			var i=1;
			while(pDialog.getValue('help'+i)) i++;
				
			pDialog.dialogQuestion({
				id: p_dialog_id || 'help'+i,
				title: pDocument.getStyleValue('dialog-help-title', 'content'),
				icon: { src: pDocument.getStyleValue('dialog-help-icon', 'content') },
				options: [ { id: 'content', sep: true, text: pString.decodeHTML(pDocument.getStyleValue(p_id, 'content')) }, { id: 'cancel', text: 'OK' } ],
				fcall: resolve
			});
		});
	};
	
	this.helpToolbar = function() { 
		that.helpDialog('help-toolbar'); 
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pNoteUI = (new function() {
	var that = this;
	
	this.notesDialog = function(p_id, p_dialog_id) {
		return new Promise(function(resolve, reject) {
			var r = { id: p_id, dialog_id: p_dialog_id };
				
			pUsers.getUserData(null, p_id, function(p_values) {
				if (p_values.getValue(r.id) != 'done') {
					pDialog.dialogQuestion({
						id: r.dialog_id || 'notes',
						options: [ { id: 'content', sep: true, text: pString.decodeHTML(pDocument.getStyleValue(r.id, 'content')) } ], 
						footer: [
						    { id: 'cancel', text: 'OK' },
						    { id: 'iknow', text: 'Do not show this again', className: 'help-iknow dialog-footer-button noselect', scall: function() {
						    	that.doNotShowNotesAgain(r.id);
						    	pDialog.dialogQuestionClose('notes', 'ok');
						    }}
						],
						fcall: resolve,
						ccall: resolve
					});
				}
				else
					resolve();
					
			});
		});
	};
	
	this.doNotShowNotesAgain = function(p_id) {
		pUsers.setUserData(null, p_id, 'done');
	};
		
	this.notesDiv = function(p_id) {
		var h = '<div class="help-container">';
		h += '<div style="display: inline-block;" class="help"><img src="/resources/html/images/16x16/help.png"/>&#8203;</div>'; //TODO: 32x32 on retina...
		h += '<div style="display: inline-block;">'; //TODO: 32x32 on retina...
		h += pString.decodeHTML(pDocument.getStyleValue(p_id, 'content'));
		h += '</div>';
		h += '<div class="help help-close"><a href="javascript:void(pNoteUI.closeNoteDialogOption(\''+p_id+'\'))">Do not show this again</a></div>';
		h += '</div>';
		return h;
	};
	
	this.closeNoteDialogOption = function(p_id) {
		that.doNotShowNotesAgain(p_id);
		pDocument.hide(p_id);
	};
		
	this.createNoteDialogOption = function(p_id) {
		return new Promise(function(resolve, reject) {
			var r = { id: p_id };
		
			if (p_id.startsWith('cookie-')) {
				if (!pString.v(pCookieManager.xgetCookie(p_id.substring('cookie-'.length))))
					resolve({ id: p_id, disabled: true, text: that.notesDiv(p_id) });
				else
					resolve();
			}
			else		
				pUsers.getUserData(null, p_id, function(p_values) {
					if (p_values.getValue(r.id) != 'done')
						resolve({ id: r.id, disabled: true, text: that.notesDiv(r.id) });
					else
						resolve();
				});
		});
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pTransaction = (new function() {
	var that = this;
	
	/*
	 { url, method_name, trace, msg_start, msg_error, msg_end }
	 */
	function f_transaction_error(r, p_request, p_message) {
	
		//this is transaction request
		//p_request is p_xml_http_request
	
		pConsole.error(r.trace, p_request.statusText);
		if (r.msg_error) {
			var e = r.msg_error;
			(e.forEach? e : [ e ]).forEach(function(e) {
				if (typeof e == 'function')
					e(p_request);
				else {
					var s = p_message || p_request.statusText;
					if (!s || s == '')
						s = "Failed to contact server...";
					if (s.indexOf("200 ")==0)
						s = s.substring(4);
					pNotificationUI.addNotification(e + ': ' + s, 'error');
				}
			});
		}
	
		r._vprivate.removeStartNotifications();
	}
	
	//*** HANDLE NORMAL TRANSACTION RESPONSE
	function f_transaction_handle(r, p_request, p_handler) {
	
		//p_request is p_xml_http_request
		
		if (r.body && r.body.forEach) {
			r.n = (r.n || 0)+1;
			if (r.n < r.body.length) {
				r.msg_start = null;
				
				(r._vprivate.start_ids || []).forEach(function(x) { 
					var t = pNotificationUI.getNotificationText(x);
					if (t.endsWith('...')) 
						pNotificationUI.setNotificationText(x, t+'.');
				});
				that.transaction(r);
				return;
			}
		}
		
		if (r.responseType != 'text') {
			var o = pJSON.parse(p_request.responseText);
			if (o && o.async) {
				r.n = (r.n || 0)+1;
				if (r.n==1)
					r.url_org = r.url;
				r.url = pURL.addQueryParameter(r.url_org, [ 'status', 1, 'statusid', o.statusid ]);
				r.method = 'GET';
				r.headers = null;
				r.body = null;
				
				r.msg_start = null;
				
				setTimeout(function() {
					pTransaction.transaction(r);
				}, o.nextms || (r.n<20? 500 : 10000));
				return;
			}
		}
		
		if (p_handler) {
			(p_handler.forEach? p_handler : [ p_handler ]).forEach(function(h) {
				if (h) {
					if (typeof h == 'function')
						h(p_request);
					else
						pNotificationUI.addNotification(h, 'success');
				}
			});
		}
		
		r._vprivate.removeStartNotifications();
	}
	
	this.transaction = function(p_request) {
		
		if (p_request.body && p_request.body.forEach)
			p_request.n = p_request.n || 0;
	
		if (!p_request._vprivate) p_request._vprivate = {};
		p_request._vprivate.interval_ids = [];
		p_request._vprivate.removeStartNotifications = function() {
			(p_request._vprivate.start_ids || []).forEach(pNotificationUI.removeNotification);
			p_request._vprivate.start_ids = null;
		};
	
		if (p_request.msg_start) {
			p_request._vprivate.start_ids = [];
			
			var s = p_request.msg_start;
			(s.forEach? s : [ s ]).forEach(function(s) {
				if (typeof s =='function')
					s.call(this, p_request);
				else
					p_request._vprivate.start_ids.push(pNotificationUI.addNotification(s, 'start'));
			});
		}
	
		try {
			pConsole.info(p_request.trace, p_request.method_name + ": " + p_request.url + (p_request.n!=null? ' ('+p_request.n+')':''));
			pHTTPRequest.invoke({
				url: p_request.url,
				method: p_request.method || 'POST',
				onerror: function(httpr) { f_transaction_error(p_request, httpr); },
				ontimeout: function(httpr) { f_transaction_error(p_request, httpr, "Timeout after " + p_request.timeout + " ms..."); },
				onreadystatechange: function(httpr) {
	
					try {
						if (p_request._vprivate.interval_ids) //interval: [ { ms: 5000, func: pMediaLibrary.refreshIndex} ]
							p_request._vprivate.interval_ids.forEach(function(i_id) { clearInterval(i_id); });
	
						if (p_request["msg_"+httpr.status]) {
							f_transaction_handle(p_request, httpr, p_request["msg_"+httpr.status]);
							return;
						}
	
						if (p_request.msg_handle && (typeof p_request.msg_handle == 'function')) {
							if (p_request.msg_handle(httpr)) {
								p_request._vprivate.removeStartNotifications();
								return;
							}
						}
	
						if (httpr.status>399 && httpr.status<=500) {//} && !httpr.n) {
							f_transaction_error(p_request, httpr);
							return false;
						}
	
						if (p_request.errorCodes && p_request.errorCodes.includes(httpr.status)) {
							f_transaction_error(p_request, httpr);
							return false;
						}
	
						if (httpr.status==200 && httpr.statusText.indexOf('200 Error')==0) {
							f_transaction_error(p_request, httpr);
							return false;
						}
	
						//handle errors
						if (pROSE.handleHTTPResponse(httpr) === false)
							return false;
	
						f_transaction_handle(p_request, httpr, p_request.msg_end);
					}
					catch (exception) {
						pConsole.error(httpr.trace, "Error: " + exception + "\n" + exception.stack);
						if (httpr.msg_error)
							pNotificationUI.addNotification(httpr.msg_error + ': ' + exception, 'error');
					}
	
				}.bind(p_request),
				sync: false,
				body: (p_request.body && p_request.body.forEach)? p_request.body[p_request.n || 0] : p_request.body,
				headers: p_request.headers,
				responseType: p_request.responseType
			});
	
			if (p_request.interval) //interval: [ { ms: 5000, func: pMediaLibrary.refreshIndex} ]
				p_request.interval.forEach(function(i) {
					p_request._vprivate.interval_ids.push(setInterval(i.func, i.ms));
				});
		}
		catch (exception) {
			pConsole.error(p_request.trace, "Error: " + exception + "\n" + exception.stack);
			if (p_request.msg_error)
				pNotificationUI.addNotification(p_request.msg_error + ': ' + exception, 'error');
		}
	};
	
	this.simpleTransaction = function(rq) {
		rq.trace = rq.trace || this;
		rq.method_name = rq.method_name || "Transaction";
		rq.msg_error = rq.msg_error || rq.msg_start.trim()+ ' Failure.';
		rq.msg_end = rq.msg_end || rq.msg_start.trim()+ ' Done.';
		return that.transaction(rq);
	};
});

/******************************************************************************/
/******************************************************************************/
/******************************************************************************/

const pUserRating = new (function() {
	var that = this, star = "\u2605", zerostar = "\u2606";
	
	this.selectUserRatingDialog = function(p_id, p_value, p_doc_url) {
		var i_scall = function(p_dialog, p_value) {
			pDialog.dialogDestroy(p_dialog.id);
			that.setRating(this.id, this.doc_url, p_value);
		}.bind({ id: p_id, doc_url: p_doc_url });
		var i_dialog = {
			id: 'user-rating',
			className: 'dialog-main-menu',
			options: [ 'cancel', { id: '0', text: ' 0 star', scall: i_scall, value: '' } ]
		};

		for(var i=1 ; i<=5 ; i++) {
			var s = star.repeat(i);
			i_dialog.options[i_dialog.options.length] = { id: ''+i, className: 'rating-normal', text: s, scall: i_scall, value: s  };
		}
		pDialog.dialogQuestion(i_dialog);
	};

	this.rating = function(p_id, p_value, p_doc_url) {
		
		p_doc_url = p_doc_url || window.location.pathname;
		
		if (pApplicationUI.OPTION_SHOW_USER_RATINGS_DIALOG) {

			var x = pElement.x(p_id);
			x.innerHTML = (p_value=='')? '\u{22EF}' : p_value;//'...' : p_value;
			x.className = x.className+' link';
			x.setAttribute('data-text', p_value);
			x.setAttribute('data-doc-url', p_doc_url);
			x.onclick = function() { that.selectUserRatingDialog(this.id, this.getAttribute('data-text'), this.getAttribute('data-doc-url')); };
		}
		else {
			p_value = pDocument.getHistoryState('rating_'+pMediaLibrary.getLIDfromURL(p_doc_url)+'_'+pMediaLibrary.getIDfromURL(p_doc_url)) || p_value;
			
			var i_x = pElement.x(p_id);
			i_x.innerHTML = '';
			i_x.setAttribute('data-text', p_value);
			i_x.title = 'Move the mouse to select the rating stars...\nClick to save your rating selection...';
			i_x.onmouseleave = function() {
				var p_value = this.getAttribute('data-text');
				for(var i=1 ; i<=5 ; i++)
					pElement.addClassName(this.id + '-'+i+'star', (p_value.substring(i-1, i) == star)? 'rating-star-selected':'');
			};

			var i_span;

			i_span = document.createElement('span');
			i_span.id = p_id + '-0star';
			i_span.className = 'rating-star';
			i_span.innerHTML = '0';
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, ''); };
			i_x.appendChild(i_span);

			i_span = document.createElement('span');
			i_span.id = p_id + '-1star';
			i_span.className = 'rating-star ' + ((p_value.substring(0, 1) == star)? 'rating-star-selected':'');
			i_span.innerHTML = star;
			i_span.onmouseenter = function() { pElement.addClassName(this, 'rating-star-selected'); };
			i_span.onmouseleave = function() { pElement.removeClassName(this, 'rating-star-selected'); };
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, star); };
			i_x.appendChild(i_span);

			i_span = document.createElement('span');
			i_span.id = p_id + '-2star';
			i_span.className = 'rating-star ' + ((p_value.substring(1, 2) == star)? 'rating-star-selected':'');
			i_span.innerHTML = star;
			i_span.onmouseenter = function() {
				pElement.addClassName(this.parentElement.id+'-1star', 'rating-star-selected');
				pElement.addClassName(this, 'rating-star-selected'); };
			i_span.onmouseleave = function() {
				pElement.removeClassName(this.parentElement.id+'-1star', 'rating-star-selected');
				pElement.removeClassName(this, 'rating-star-selected'); };
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, star.repeat(2)); };
			i_x.appendChild(i_span);

			i_span = document.createElement('span');
			i_span.id = p_id + '-3star';
			i_span.className = 'rating-star ' + ((p_value.substring(2, 3) == star)? 'rating-star-selected':'');
			i_span.innerHTML = star;
			i_span.onmouseenter = function() {
				for(var i=1 ; i<=2 ; i++)
					pElement.addClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.addClassName(this, 'rating-star-selected'); };
			i_span.onmouseleave = function() {
				for(var i=1 ; i<=3 ; i++)
					pElement.removeClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.removeClassName(this, 'rating-star-selected'); };
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, star.repeat(3)); };
			i_x.appendChild(i_span);

			i_span = document.createElement('span');
			i_span.id = p_id + '-4star';
			i_span.className = 'rating-star ' + ((p_value.substring(3, 4) == star)? 'rating-star-selected':'');
			i_span.innerHTML = star;
			i_span.onmouseenter = function() {
				for(var i=1 ; i<=3 ; i++)
					pElement.addClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.addClassName(this, 'rating-star-selected'); };
			i_span.onmouseleave = function() {
				for(var i=1 ; i<=3 ; i++)
					pElement.removeClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.removeClassName(this, 'rating-star-selected'); };
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, star.repeat(4)); };
			i_x.appendChild(i_span);

			i_span = document.createElement('span');
			i_span.id = p_id + '-5star';
			i_span.className = 'rating-star ' + ((p_value.substring(4, 5) == star)? 'rating-star-selected':'');
			i_span.innerHTML = star;
			i_span.onmouseenter = function() {
				for(var i=1 ; i<=4 ; i++)
					pElement.addClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.addClassName(this, 'rating-star-selected'); };
			i_span.onmouseleave = function() {
				for(var i=1 ; i<=4 ; i++)
					pElement.removeClassName(this.parentElement.id+'-'+i+'star', 'rating-star-selected');
				pElement.removeClassName(this, 'rating-star-selected'); };
			i_span.onclick = function() { that.setRating(p_id, p_doc_url, star.repeat(5)); };
			i_x.appendChild(i_span);
		}
	};
	
	this.setRating = function(p_id, p_doc_url, p_value) {

		var i_url = pURL.addQueryParameter(p_doc_url, [ 'action', 'set-metadata', 'children', 1, 'v', Math.random(), 'wait', 1, 'sync', 1, 'return', 1, 'log', 1 ]);

		pTransaction.transaction({
			trace: that, url: i_url, method_name: "SetRating",
			//msg_start: 'Updating rating to <span class="rating-star-selected">'+p_value+'</span>...',
			//msg_error: 'Rating have NOT been updated to <span class="rating-star-selected">'+p_value+'</span>...',
			msg_end: function(p_request) {

				var i_obj = pJSON.parse(p_request.responseText);
				if (i_obj.actions && i_obj.actions.length>0 && i_obj.actions[0].object)
					p_value = i_obj.actions[0].object["metadata_rating"];
				
				that.rating(this.id, p_value, this.doc_url);
				pDocument.setHistoryState('rating_'+pMediaLibrary.getLIDfromURL(p_doc_url)+'_'+pMediaLibrary.getIDfromURL(p_doc_url), p_value);
				
			}.bind({ id: p_id, doc_url: p_doc_url }),
			headers: pHTTPRequest.headers_post,
			body: "metadata_rating="+pURL.fixedEncodeURIComponent(p_value)
		});
	};
});

/******************************************************************************/
/***  END OF FILE  ************************************************************/
/******************************************************************************/