/*
 * PS3 Media Server, for streaming any medias to your PS3.
 * Copyright (C) 2008  A.Brochard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; version 2
 * of the License only.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 */
package net.pms.dlna;

import java.io.*;
import java.io.IOException;
import java.io.InputStream;
import java.text.Collator;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.HashMap;

import com.sun.jna.Platform;	//regzamod

import net.pms.PMS;
import net.pms.configuration.MapFileConfiguration;
import net.pms.dlna.virtual.TranscodeVirtualFolder;
import net.pms.dlna.virtual.VirtualFolder;
import net.pms.dlna.rz_PlayMetaFile;	//regzamod
import net.pms.dlna.rz_WatchedFolder;	//regzamod
import net.pms.dlna.rz_VirtualVideoActionF;	//regzamod
import net.pms.network.HTTPResource;
import net.pms.util.NaturalComparator;
import net.pms.util.PMSUtil;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * TODO: Change all instance variables to private. For backwards compatibility
 * with external plugin code the variables have all been marked as deprecated
 * instead of changed to private, but this will surely change in the future.
 * When everything has been changed to private, the deprecated note can be
 * removed.
 */
public class MapFile extends DLNAResource {
	private static final Logger logger = LoggerFactory.getLogger(MapFile.class);
	private List<File> discoverable;
	private List<File> discoverable_pxf;	//regzamod
	public boolean isRemovableDrive;	//regzamod
	public boolean updateDiskLabel;		//regzamod
	private int menu_type=PMS.getConfiguration().getRZ_menu_type();

	/**
	 * @deprecated Use standard getter and setter to access this variable.
	 */
	@Deprecated
	public File potentialCover;

	/**
	 * @deprecated Use standard getter and setter to access this variable.
	 */
	@Deprecated
	protected MapFileConfiguration conf;

	private static final Collator collator;

	static {
		collator = Collator.getInstance();
		collator.setStrength(Collator.PRIMARY);
	}

	public MapFile() {
		setConf(new MapFileConfiguration());
		setLastmodified(0);
	}

	public MapFile(MapFileConfiguration conf) {
		setConf(conf);
		setLastmodified(0);
	}

	private boolean isFileRelevant(File f) {
		String fileName = f.getName().toLowerCase();
		return (PMS.getConfiguration().isArchiveBrowsing() && (fileName.endsWith(".zip") || fileName.endsWith(".cbz")
			|| fileName.endsWith(".rar") || fileName.endsWith(".cbr")))
			|| fileName.endsWith(".iso") || fileName.endsWith(".img")
			|| fileName.endsWith(".m3u") || fileName.endsWith(".m3u8") || fileName.endsWith(".pls") 
			|| fileName.endsWith(".cue") || fileName.endsWith("."+PMS.rz_MetafileSuffix)
			|| fileName.endsWith(".cue") || fileName.endsWith("."+PMS.rz_ResumeFileSuffix); 
			
	}

	private boolean isFolderRelevant(File f) {
		boolean excludeNonRelevantFolder = true;
		if (f.isDirectory() && PMS.getConfiguration().isHideEmptyFolders()) {
			File children[] = f.listFiles();
			for (File child : children) {
				if (child.isFile()) {
					if (PMS.get().getAssociatedExtension(child.getName()) != null || isFileRelevant(child)) {
						excludeNonRelevantFolder = false;
						break;
					}
				} else {
					if (isFolderRelevant(child)) {
						excludeNonRelevantFolder = false;
						break;
					}
				}
			}
		}

		return !excludeNonRelevantFolder;
	}

	//private void manageFile(File f) {  //origin
	private DLNAResource manageFile(File f, boolean add) {
		DLNAResource dlna=null;
		if ((f.isFile() || f.isDirectory()) && !f.isHidden()) {
			if (PMS.getConfiguration().isArchiveBrowsing() && (f.getName().toLowerCase().endsWith(".zip") || f.getName().toLowerCase().endsWith(".cbz"))) {
				addChild(new ZippedFile(f));
			} else if (PMS.getConfiguration().isArchiveBrowsing() && (f.getName().toLowerCase().endsWith(".rar") || f.getName().toLowerCase().endsWith(".cbr"))) {
				addChild(new RarredFile(f));
			} else if ((f.getName().toLowerCase().endsWith(".iso") || f.getName().toLowerCase().endsWith(".img")) || (f.isDirectory() && f.getName().toUpperCase().equals("VIDEO_TS"))) {
				//PMS.dbg("manageFile: addChild DVDISOFile="+f);
				addChild(new DVDISOFile(f));
			} else if (f.getName().toLowerCase().endsWith(".m3u") || f.getName().toLowerCase().endsWith(".m3u8") || f.getName().toLowerCase().endsWith(".pls")) {
				addChild(new PlaylistFolder(f));
			} else if (f.getName().toLowerCase().endsWith(".cue")) {
				addChild(new CueFolder(f));
			} else if (f.getName().toLowerCase().endsWith("."+PMS.rz_MetafileSuffix)) {	//regzamod, Play eXtended Metafile
				//addChild(new rz_PlayMetaFile(f));
				rz_PlayMetaFile.managePxm(f,this,1);
			} else if (f.getName().toLowerCase().endsWith("."+PMS.rz_ResumeFileSuffix)) {	//regzamod, resumefile (a kind of pxm)
				rz_PlayMetaFile.managePxm(f,this,2);
			} else {
				/* Optionally ignore empty directories */
				if (f.isDirectory() && PMS.getConfiguration().isHideEmptyFolders() && !isFolderRelevant(f)) {
					logger.debug("Ignoring empty/non relevant directory: " + f.getName());
				} /* Otherwise add the file */ else {
					RealFile file = new RealFile(f);
					if(add) addChild(file);
					dlna=(DLNAResource)file;
				}
			}
		}
		if (f.isFile()) {
			String fileName = f.getName().toLowerCase();
			if (fileName.equalsIgnoreCase("folder.jpg") || fileName.equalsIgnoreCase("folder.png") || (fileName.contains("albumart") && fileName.endsWith(".jpg"))) {
				setPotentialCover(f);
			}
		}
		return dlna;
	}
	
	/*
	public List<File> getFileList_ORIGIN() {
		List<File> out = new ArrayList<File>();
		for (File file : this.conf.getFiles()) {
			if (file != null && file.isDirectory() && file.canRead()) {
				out.addAll(Arrays.asList(file.listFiles()));
			}
		}
		return out;
	}
	*/
	
	public List<File> getFileList() {  //regzamod
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: Start");
		
		List<File> out = new ArrayList<File>();
		for (File file : this.conf.getFiles()) {
			
			if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: pos-1");
			if (file != null && file.canRead()) {
				if(file.isDirectory()) {
					//regzam, listFiles() may fail by permission, even if canRead, why?
					File [] flist=file.listFiles();
					
					if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: pos-2");
					int onBDMV=0;
					try {
						if(Platform.isWindows() && file.getCanonicalPath().matches("^[A-Z]:\\\\BDMV\\\\STREAM")) {
							if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: seems Files under BDMV, ignore small files size < 5MB");
							onBDMV=1;
						}
					} catch (IOException e) {
						logger.error("IOException error="+e);
					}

					if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: pos-3");
					if(flist!=null) {
						//regzam, if flist==null, Arrays.asList will cause NULL pointer exception
						if(onBDMV>0) {
							for (File f: flist) {
								if(f.length()>5000000) {  // 1MB
									out.add(f);
								}
							}
						}
						else {
							out.addAll(Arrays.asList(flist));
						}
					}
					else {
						logger.warn("Folder contents is null, or Permission denied");
					}
				}
				else {
					out.add(file);
				}
			}
		}
		
		long tim2=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.getFileList: End, count="+out.size()+", elapsed="+(tim2-tim1));
		return out;
	}

	@Override
	public boolean isValid() {
		return true;
	}

	@Override
	public boolean analyzeChildren (int count) {
		int currentChildrenCount = getChildren().size();
		int vfolder = 0;
		DLNAResource ch;
		toString_sb=null;  //clear cache of toString()
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_trace>0) PMS.trace("MapFile.analyzeChildren: Start, Name="+getName());
		
		while ((getChildren().size() - currentChildrenCount) < count || count == -1) {
			if(PMS.rz_debug>1) PMS.dbg("MapFile.analyzeChildren: PerChild Start, myName="+getName());
			if (vfolder < getConf().getChildren().size()) {
				addChild(new MapFile(getConf().getChildren().get(vfolder)));
				++vfolder;
			} else {
				if (discoverable.isEmpty()) {
					break;
				}
				
				File f=discoverable.get(0); //must memory before delete
				ch=manageFile(discoverable.remove(0),false);
				if(ch!=null) {
					//ch==null indicates addChild() not executed yet, 
					//you should read_in pxf before addChild()
					//File pxf=find_pxf_InList(discoverable,f,true);
					if(PMS.rz_debug>1) PMS.dbg("MapFile.analyzeChildren: PerChild find_pxf_InList Start, myName="+getName());
					
					File pxf=find_pxf_InList(discoverable_pxf,f,true);
					if(pxf!=null) {	// pxf exists
						rz_PlayMetaFile.pxf_read(ch,pxf);
					}
					else if(PMS.rz_ProfileEnabled>1 && f.isDirectory()) {
					 	pxf=new File(f.getPath()+PMS.FSEP+PMS.rz_ProfileFF);
					 	if(pxf.exists()) { // _folder.pxf exists
							rz_PlayMetaFile.pxf_read(ch,pxf);
					 	}
					}
					if(PMS.rz_debug>1) PMS.dbg("MapFile.analyzeChildren: PerChild AddChild Start, myName="+getName());
					addChild(ch);
					if(PMS.rz_debug>1) PMS.dbg("MapFile.analyzeChildren: PerChild End, myName="+getName());
				}
			}
		}
		if(ctlFolder==null && menu_type>=4) {
			//disp menues, even if only folders
			int ures=(isUnderResumeFolder(this)?1:0);// 
			createSortMenu(this,ures);
			createScriptMenuFolder(this,ures);
			createClearFolderCahceMenu(this,ures);
			createSelClipPath(this,ures);
			createVideoSettingMenu(this);
		}
		setLastRefreshTime(System.currentTimeMillis());
		
		if(PMS.rz_trace>0) {
			long tim2=System.currentTimeMillis();
			PMS.trace("MapFile.analyzeChildren: End, elapsed="+(tim2-tim1)+", myName="+getName());
		}

		return discoverable.isEmpty();
	}
	
	private List<File> get_pxf_list(List<File> files,boolean remove) {  //regzamod
		List<File> out = new ArrayList<File>();
		String suffix="."+PMS.rz_ProfileSuffix;
		int size=files.size();
		File f;
		for (int i=0;i<size;i++) {
			f=files.get(i);
			if(f.getName().endsWith(suffix)) {
				out.add(f);
				if(remove) files.remove(i);
			}
		}
		return out;
	}
	
	private File find_pxf_InList(List<File> files,File file,boolean remove) {  //regzamod
		if(files==null || files.isEmpty())
			return null;
		if(file==null)
			return null;
		String path=file.getPath();
		
		//change original file's suffix to according pxf
		if(true) {
			//pxf filename must contain original suffix
			//othetwze, file with other suffix may conflicts with original file
			path=path+"."+PMS.rz_ProfileSuffix;
		}

		int size=files.size();
		File f;
		for (int i=0;i<size;i++) {
			f=files.get(i);
			if(f.getPath().equals(path)) {
				if(remove) files.remove(i);
				return f;
			}
		}
		return null;
	}
	
	public int activate(int type) {
		//PMS.dbg("MapFile.activate: called type="+type);
		if(isFolder() && type==1) { //folder open
			if(getDelayedClear()) {
				//PMS.dbg("MapFile.activate: exec ClearFolder()");
				ClearFolder();
				setDelayedClear(false);
			}
		}
		return -1;
	}
	
	public void ClearFolder() {	//regzamod
		//clear children under the folder
		if(!isFolder()) {
			return;
		}
		//PMS.dbg("MapFile.ClearFolder: exec clear");
		getChildren().clear();	// regzamod
		deleteMenus();
		setDiscovered(false);
		discoverable=null;
		discoverable_pxf=null;
	}
	
	@Override
	public void discoverChildren () {
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_trace>0) PMS.trace("discoverChildren: Start, name="+getName());
		
		super.discoverChildren();
		
		if (discoverable == null) {
			discoverable = new ArrayList<File>();
			discoverable_pxf = new ArrayList<File>();
		} else {
			long tim2=System.currentTimeMillis();
			if(PMS.rz_trace>0) PMS.trace("discoverChildren: End-1, Noop: discoverable not null, elapsed="+(tim2-tim1));
			return;
		}

		List<File> files = getFileList();
		//PMS.dbg("SortMethod:="+PMS.getConfiguration().getSortMethod());
		//PMS.dbg("discoverChildren: files="+files);
		
		if(false) {	//regzamod, will be done at discoverWithRenderer,refreshChildren
			switch (PMS.getConfiguration().getSortMethod()) {
				case 4: // Locale-sensitive natural sort
					Collections.sort(files, new Comparator<File>() {
						public int compare(File f1, File f2) {
							return NaturalComparator.compareNatural(collator, f1.getName(), f2.getName());
						}
					});
					break;
				case 3: // Case-insensitive ASCIIbetical sort
					Collections.sort(files, new Comparator<File>() {

						public int compare(File f1, File f2) {
							return f1.getName().compareToIgnoreCase(f2.getName());
						}
					});
					break;
				case 2: // Sort by modified date, oldest first
					Collections.sort(files, new Comparator<File>() {

						public int compare(File f1, File f2) {
							return new Long(f1.lastModified()).compareTo(new Long(f2.lastModified()));
						}
					});
					break;
				case 1: // Sort by modified date, newest first
					Collections.sort(files, new Comparator<File>() {

						public int compare(File f1, File f2) {
							return new Long(f2.lastModified()).compareTo(new Long(f1.lastModified()));
						}
					});
					break;
				default: // Locale-sensitive A-Z
					Collections.sort(files, new Comparator<File>() {

						public int compare(File f1, File f2) {
							return collator.compare(f1.getName(), f2.getName());
						}
					});
					break;
			}
		}
		for (File f : files) {
			if (f.isDirectory()) {
				discoverable.add(f); //manageFile(f);
			}
		}

		String suffix="."+PMS.rz_ProfileSuffix;
		for (File f : files) {
			if (f.isFile()) {
				if(f.getName().endsWith(suffix)) {
					discoverable_pxf.add(f);
				} else {
					discoverable.add(f); 
				}
			}
		}
		
		if(PMS.rz_trace>0) {
			long tim2=System.currentTimeMillis();
			PMS.trace("discoverChildren: End-2, elapsed="+(tim2-tim1));
		}
	}

	private long lastModif_prev=1;	//initial value for first startup
	private long lastCheckTime=0;	//initial value for first startup
	@Override
	public boolean isRefreshNeeded() {
		long lastModif = 0;
		long refreshTime=getLastRefreshTime();
		
		if(PMS.rz_debug>1) {
			PMS.dbg("==== MapFile.isRefreshNeeded: called, name="+getName()+", LastRefreshTime="+PMSUtil.convTime2Date(refreshTime));
		}
		//if(lastCheckTime+2000 > System.currentTimeMillis()) {
		if(lastCheckTime+PMS.rz_update_check_min > System.currentTimeMillis()) {
			//PMS.dbg("MapFile.isRefreshNeeded: Already Recently Checked, return FALSE");
			return false;
		}
		lastCheckTime=System.currentTimeMillis();
		
		if(refreshTime+PMS.rz_update_check_min > System.currentTimeMillis()) {
			//PMS.dbg("MapFile.isRefreshNeeded: Already recently updated, return FALSE");
			return false;
		}
		//regzamod, TODO: this original loop can't detect files update, 
		//can detect only file add/delete under the folder
		for (File f : this.getConf().getFiles()) {  
			//these are not child files, but folder myself
			if (f != null) {
				//PMS.dbg("MapFile.isRefreshNeeded: child="+f.getName()+", lastModif="+PMSUtil.convTime2Date(f.lastModified()));
				lastModif = Math.max(lastModif, f.lastModified());
			}
		}
		if(PMS.rz_debug>1) {
			PMS.dbg("MapFile.isRefreshNeeded: ChildLastModif="+PMSUtil.convTime2Date(lastModif));
		}
		
		//at least, Metafiles must be checked its self update
		//Meta.getFiles() means link target, not meta it's self
		//so, cant judge above loop
		for (DLNAResource d : getChildren()) {	//regzamod
			//PMS.dbg("MapFile: isRefreshNeeded d="+d.getName());
			if(d.needReCreate()) {
				//needReCreate() will return true if d is metafile & updated
				if(PMS.rz_debug>1) PMS.dbg("MapFile.isRefreshNeeded: child reed Recreate --> return TRUE, name="+getName());
				return true;
			}
		}
		//return refreshTime < lastModif;	//Original
		
		//---- regzamod start
		//PMS.dbg("MapFile.isRefreshNeeded: fname="+getName()+", refreshTime="+refreshTime
		//	+", lastModif="+lastModif+", lastModif_prev="+lastModif_prev);

		if(refreshTime < lastModif) {
			if(PMS.rz_debug>1) PMS.dbg("MapFile.isRefreshNeeded: refreshTime < lastModif --> return TRUE, name="+getName());
			return true;
		}
		else if(lastModif_prev!=lastModif) {
			// check for RemovableStorages(DVD,USB etc)
			// these may be exchanged to another ones, even if refreshTime > lastModif
			if(isRemovableDrive) {
				if(lastModif_prev!=1) {	// not first startup
					if(PMS.rz_debug>1) PMS.dbg("MapFile.isRefreshNeeded: Dates Differ from prev --> return TRUE & Clear children, name="+getName());
					getChildren().clear();
					deleteMenus();
					//this.getConf().setName(null);
					//this.getConf().getFiles().get(0)
					//setConf(new MapFileConfiguration());
				}
				this.updateDiskLabel=true;
				if(PMS.rz_debug>1) PMS.dbg("MapFile.isRefreshNeeded: for DVD, lastModif_prev!=lastModif --> return TRUE");
				lastModif_prev=lastModif;
				return true;
			}
		}
		if(PMS.rz_debug>1) PMS.dbg("MapFile.isRefreshNeeded: all check end, return FALSE");
		return false;
		//---- regzamod end
	}

	@Override
	public void refreshChildren () {
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_trace>0) PMS.trace("MapFile.refreshChildren Start, name="+getName());	//regzamod

		File pxf;
		String path;
		List<File> files = getFileList();
		List<File> pxf_list=get_pxf_list(files,false);
		List<File> addedFiles = new ArrayList<File>();
		List<DLNAResource> removedFiles = new ArrayList<DLNAResource>();
		
		//---- create hashmap for files (ToDO: should be created directly by getFileList())
		//for speedup test of foundInList()
        HashMap<String, File> files_hm = new HashMap<String, File>();
		for(File f : files) {
			files_hm.put(f.getName(),f);
		}
		//------------
		
		toString_sb=null;  //clear cache of toString()
		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1");	//regzamod
		//this proc is heavy!!
		long timw1=System.currentTimeMillis();
		int cnt=0;
		for (DLNAResource d : getChildren()) {
			
			//boolean isNeedMatching = !(d.getClass() == MapFile.class ||  (d instanceof VirtualFolder) );
			//boolean isNeedMatching = !(d.getClass() == MapFile.class ||  (d instanceof VirtualFolder && !(d instanceof DVDISOFile)) );
			//boolean isNeedMatching = d.needRealFileMatching();
			if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.1 child="+d.getName());	//regzamod
			
			//--- isNeedMatching: matching target is only normal files: except menus etc.
			boolean isNeedMatching = !(d.getClass() == MapFile.class 
				|| (d instanceof VirtualFolder && !(d instanceof DVDISOFile)) 
				|| (d instanceof rz_VirtualVideoActionF || d instanceof rz_WatchedFolder) 
			);
			
			if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: DLNAres="+d+", isNeedMatching="+isNeedMatching);
			
			if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2 child="+d.getName());	//regzamod
			if (isNeedMatching) {
				if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.1");	//regzamod
				if(d.needReCreate()) {	//regzamod, need reCreate(instead of resolve) for update
					if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.2");	//regzamod
					//don't call foundInList here, b/c foundInList will delete the file in files
					//this cause fail of re-create the obj
					removedFiles.add(d);
					if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.2b");	//regzamod
				}
				//else if (!foundInList(files, d)) {  //ToDO: foundInList() is slow!!
				else if (!foundInList_hm(files_hm, files, d)) {  
					if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.3");	//regzamod
					removedFiles.add(d);
					if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.3b");	//regzamod
				}
				else if(PMS.rz_ProfileEnabled>0 && (d instanceof RealFile) 
					&& !(d instanceof rz_PlayMetaFile) && rz_Profile==null) {
					if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.4");	//regzamod
					//need check if new PlayProfile added
					//RealFile only?
					//pxf=find_pxf_InList(files,((RealFile)d).getFile(),true);
					pxf=find_pxf_InList(pxf_list,((RealFile)d).getFile(),true);
					if(pxf!=null) {
						if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.5");	//regzamod
						rz_PlayMetaFile.pxf_read(d,pxf);
						rz_PlayMetaFile.pxf_resolve(d,0);
					}
					else if(PMS.rz_ProfileEnabled>1 && d.isFolder()) {
						//read pxf under folder
						if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.6");	//regzamod
						path=d.getSrcPath();
						if(path!=null) {
							if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-1.2.7");	//regzamod
						 	pxf=new File(d.getSrcPath()+PMS.FSEP+PMS.rz_ProfileFF);
						 	if(pxf.exists()) {
								//PMS.dbg("refreshChildren: find pxf under Folder, path="+pxf);
								rz_PlayMetaFile.pxf_read(d,pxf);
								//rz_PlayMetaFile.pxf_resolve(d);
						 	}
						}
					}
				}
			}
			long timw2=System.currentTimeMillis();
			long elapsed=timw2-timw1;
			if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: ChildNo="+cnt+", elapsed="+(elapsed)+", name="+d.getName());	
			cnt++;
			timw1=timw2;
		}

		//PMS.dbg("refreshChildren remaining file="+files);	//regzamod
		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-2");	//regzamod
		for (File f : files) {
			//PMS.dbg("refreshChildren file="+f.getName()+", isHidden="+f.isHidden()+", AssExt="+PMS.get().getAssociatedExtension(f.getName()));	//regzamod
				
			if (!f.isHidden()) {
				//if (f.isDirectory() || PMS.get().getAssociatedExtension(f.getName()) != null) {
				//regzamod, above judge exclude the m3u/pls files!!
				if (f.isDirectory() || PMS.get().getAssociatedExtension(f.getName()) != null ||
				 	isFileRelevant(f)) {	//regzamod
					//PMS.dbg("refreshChildren addedFiles file="+f.getName());
					addedFiles.add(f);
				}
			}
		}

		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-3");	//regzamod
		for (DLNAResource f : removedFiles) {
			if(PMS.rz_debug>1) PMS.dbg("File automatically removed: " + f.getName());
		}

		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-4");	//regzamod
		for (File f : addedFiles) {
			if(PMS.rz_debug>1) PMS.dbg("File automatically added: " + f.getName());
		}

		TranscodeVirtualFolder vf = getTranscodeFolder(false,-1);

		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-5");	//regzamod
		for (DLNAResource f : removedFiles) {
			getChildren().remove(f);
			int found=0;
			if (vf != null) {
				for (int j = vf.getChildren().size() - 1; j >= 0; j--) {
					if (vf.getChildren().get(j).getName0().equals(f.getName0())) {
						vf.getChildren().remove(j);
						found++;
					}
				}
				if(false && found==0) {
					//PMS.dbg("removed file not found in transcode_vf, name="+f.getName());
					for (int j = vf.getChildren().size() - 1; j >= 0; j--) {
						PMS.dbg("file No"+j+" in transcode_vf, name="+vf.getChildren().get(j).getName());
					}
				}
			}
		}

		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-6");	//regzamod
		DLNAResource ch;
		for (File f : addedFiles) {
			//manageFile(f);
			ch=manageFile(f,false);
			if(ch!=null) {
				//ch==null indicates addChild() not executed yet, 
				//you should read_in pxf before addChild()
				if(PMS.rz_ProfileEnabled>0) {
					//pxf=find_pxf_InList(files,f,true);
					pxf=find_pxf_InList(pxf_list,f,true);
					if(pxf!=null) {
						//read pxf under same folder
						//read-in pxf parameters to according dlna
						//PMS.dbg("refreshChildren: find pxf, path="+f);
						rz_PlayMetaFile.pxf_read(ch,pxf);
					}
					else if(PMS.rz_ProfileEnabled>1 && f.isDirectory()) {
						//read pxf under folder itsself
					 	pxf=new File(f.getPath()+PMS.FSEP+PMS.rz_ProfileFF);
					 	if(pxf.exists()) {
							//PMS.dbg("refreshChildren: find pxf under Folder, path="+pxf);
							rz_PlayMetaFile.pxf_read(ch,pxf);
					 	}
					}
				}
				addChild(ch);
			}
		}

		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-7");	//regzamod
		for (MapFileConfiguration f : this.getConf().getChildren()) {
			addChild(new MapFile(f));
		}
		
		//PMS.dbg("isRZ_sort_when_refresh="+PMS.getConfiguration().isRZ_sort_when_refresh());
		//PMS.dbg("addedFiles.size="+addedFiles.size()+", removedFiles.size="+removedFiles.size());
		// discoverChildren() has no effect after once list created!!
		
		if(PMS.rz_debug>1) PMS.dbg("MapFile.refreshChildren: pos-8");	//regzamod
		//if(PMS.getConfiguration().isRZ_sort_when_refresh()) {	//regzamod
		if(needSort() && PMS.getConfiguration().isRZ_sort_when_refresh()) {	//regzamod
			if(addedFiles.size()>0 || removedFiles.size()>0) {
				//PMS.dbg("MapFile.refreshChildren: found file add/delete --> set doSort=true");
				doSort=true;
				if(vf!=null) {
					vf.doSort=true;
				}
				//PMS.dbg("Do sort");
				//dlnaListSort(getChildren(),PMS.getConfiguration().getSortMethod());
				//PMS.dbg("refreshChildren: name="+getName()+", sort_type="+getSortType());
				//dlnaListSort(getChildren(),getSortType());
			}
		}
		if(PMS.rz_trace>0) {
			long tim2=System.currentTimeMillis();
			PMS.trace("MapFile.refreshChildren: End, elapsed="+(tim2-tim1));	//regzamod
		}
	}

	private boolean foundInList_hm(HashMap<String,File> files_hm, List<File> files, DLNAResource d) {
		boolean namematch;
		File f=null;
		String fname;
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.foundInList_hm: Start");
		
		if(d.rz_Metafile!=null&& (d instanceof rz_PlayMetaFile)) { // compare with real file name
			fname=d.rz_Metafile.getName();
			f=files_hm.get(fname);
			namematch=(f==null?false:true);
		}
		else {
			fname=d.getName0();
			f=files_hm.get(fname);
			namematch=(f==null?false:true);
			if(!namematch) {
				if((d instanceof DVDISOFile) && d.getName().startsWith(DVDISOFile.PREFIX)) {
					f=files_hm.get(d.getName().substring(DVDISOFile.PREFIX.length()));
					namematch=(f==null?false:true);
				}
			}
		}
		if (namematch && (isRealFolder(d) || isSameLastModified(f, d))) {
			//files_hm.remove(f);
			files.remove(f);
			return true;
		}
		
		long tim2=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.foundInList_hm: End, elapsed="+(tim2-tim1));	//regzamod
		//PMS.dbg("foundInList: return false");
		return false;
	}

	private boolean foundInList(List<File> files, DLNAResource d) {
		boolean namematch;
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.foundInList: Start");

		if(false) {  //Test for speed
			PMS.dbg("TEST: dummy return true");
			return true;
		}
		else {  //ToDO: serial search is slow!!, should use hashmap?
			for (File f: files) {
				if (!f.isHidden()) {
					if(d.rz_Metafile!=null&& (d instanceof rz_PlayMetaFile)) { // compare with real file name
						namematch=d.rz_Metafile.getName().equals(f.getName());
					}
					else {
						namematch=isNameMatch(f, d);
					}
					if (namematch && (isRealFolder(d) || isSameLastModified(f, d))) {
						files.remove(f);
						//PMS.dbg("foundInList: return true");
						return true;
					}
				}
			}
		}
		
		long tim2=System.currentTimeMillis();
		if(PMS.rz_debug>1) PMS.dbg("MapFile.foundInList: End, elapsed="+(tim2-tim1));	//regzamod
		//PMS.dbg("foundInList: return false");
		return false;
	}
	

	private boolean isSameLastModified(File f, DLNAResource d) {
		return d.getLastmodified() == f.lastModified();
	}

	private boolean isRealFolder(DLNAResource d) {
		if(d instanceof rz_PlayMetaFile) return false; //regzamod, rz_layMetafile isn't realFolder
		return d instanceof RealFile && d.isFolder();
	}

	private boolean isNameMatch(File file, DLNAResource resource) {
		return (resource.getName0().equals(file.getName()) || isDVDIsoMatch(file, resource));
	}

	private boolean isDVDIsoMatch(File file, DLNAResource resource) {
		return (resource instanceof DVDISOFile) && resource.getName().startsWith(DVDISOFile.PREFIX) && resource.getName().substring(DVDISOFile.PREFIX.length()).equals(file.getName());
	}

	@Override
	public String getSystemName() {
		return getName();
	}

	@Override
	public String getThumbnailContentType() {
		String thumbnailIcon = this.getConf().getThumbnailIcon();
		if (thumbnailIcon != null && thumbnailIcon.toLowerCase().endsWith(".png")) {
			return HTTPResource.PNG_TYPEMIME;
		}
		return super.getThumbnailContentType();
	}

	@Override
	public InputStream getThumbnailInputStream() throws IOException {
		return this.getConf().getThumbnailIcon() != null
			? getResourceInputStream(this.getConf().getThumbnailIcon())
			: super.getThumbnailInputStream();
	}

	@Override
	public long length() {
		return 0;
	}

	@Override
	public String getName() {
		return this.getConf().getName();
	}
	
	@Override
	public String getDpName() {	//regzamod
		return getName();
	}

	@Override
	public boolean isFolder() {
		return true;
	}

	@Override
	public InputStream getInputStream() throws IOException {
		return null;
	}

	@Override
	public boolean allowScan() {
		return isFolder();
	}

	/* (non-Javadoc)
	 * @see java.lang.Object#toString()
	 */
	@Override
	public String toString() {
		return "MapFile [name=" + getName() + ", id=" + getResourceId() + ", ext=" + getExt() + ", children=" + getChildren() + "]";
	}

	/**
	 * @return the conf
	 * @since 1.50
	 */
	protected MapFileConfiguration getConf() {
		return conf;
	}

	/**
	 * @param conf the conf to set
	 * @since 1.50
	 */
	protected void setConf(MapFileConfiguration conf) {
		this.conf = conf;
	}

	/**
	 * @return the potentialCover
	 * @since 1.50
	 */
	public File getPotentialCover() {
		return potentialCover;
	}

	/**
	 * @param potentialCover the potentialCover to set
	 * @since 1.50
	 */
	public void setPotentialCover(File potentialCover) {
		this.potentialCover = potentialCover;
	}
	
	public static void dlnaListSort(DLNAResource pa, List<DLNAResource> lst, int sort_type) {	//regzamod
		
		long tim1=System.currentTimeMillis();
		if(PMS.rz_trace>1) PMS.trace("MapFile.dlnaListSort: Start, pa="+pa.getName()
			+", sort_type="+PMS.rz_SortTypeID2string(sort_type)+", ListSize="+lst.size());
		//switch (PMS.getConfiguration().getSortMethod()) {
		
		//---- hide sort_immune objects ------
		List<DLNAResource> lst_top=new ArrayList<DLNAResource>();
		List<DLNAResource> lst_last=new ArrayList<DLNAResource>();
		DLNAResource d;
		for(int i=0;i<lst.size();i++) {
			d=lst.get(i);
			//PMS.dbg("i="+i+", name="+d.getName()+", dpname="+d.getDpName()+", isFolder="+d.isFolder());
			if(d.sort_immune==1) {	//indicate sort_immune ?
				lst_top.add(d);
				//lst.remove(i);
			}
			else if(d.sort_immune==2) {
				lst_last.add(d);
			}
		}

		switch (sort_type) {
			case 6: // Sort by serialId(resource specific order) descendant order, regzamod add
			  	//for web contents list's original got_order 
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						return new Long(d2.getSerialId()).compareTo(new Long(d1.getSerialId()));
					}
				});
				break;
			case 5: // Sort by serialId(resource specific order) ascendant order, regzamod add
			  	//for web contents list's original got_order 
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						return new Long(d1.getSerialId()).compareTo(new Long(d2.getSerialId()));
					}
				});
				break;
			case 4: // Locale-sensitive natural sort
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						return NaturalComparator.compareNatural(collator, d1.getDpName(), d2.getDpName());
					}
				});
				break;
			case 3: // Case-insensitive ASCIIbetical sort
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						return d1.getDpName().compareToIgnoreCase(d2.getDpName());
					}
				});
				break;
			case 2: // Sort by modified date, oldest first
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						//return new Long(d1.getLastmodified()).compareTo(new Long(d2.getLastmodified()));
						return new Long(d1.getDispDate()).compareTo(new Long(d2.getDispDate()));
					}
				});
				break;
			case 1: // Sort by modified date, newest first
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						//return new Long(d2.getLastmodified()).compareTo(new Long(d1.getLastmodified()));
						return new Long(d2.getDispDate()).compareTo(new Long(d1.getDispDate()));
					}
				});
				break;
			default: // Locale-sensitive A-Z
				Collections.sort(lst, new Comparator<DLNAResource>() {
					public int compare(DLNAResource d1, DLNAResource d2) {
						return collator.compare(d1.getDpName(), d2.getDpName());
					}
				});
				break;
		}

		//---- re-sort to make folders/immunes first ------
		List<DLNAResource> lst_imm=new ArrayList<DLNAResource>();
		List<DLNAResource> lst_file=new ArrayList<DLNAResource>();
		List<DLNAResource> lst_folder=new ArrayList<DLNAResource>();
		int size=lst.size();
		for(int i=0;i<size;i++) {
			d=lst.get(i);
			if(d.sort_immune!=0) {	//indicate sort_immune
				// noop
			}
			else if(d.isFolder()) {
				lst_folder.add(d);
			}
			else {
				lst_file.add(d);
			}
		}
		
		//---- restore objects ------
		int size_top=lst_top.size();
		int size_last=lst_last.size();
		int size_folder=lst_folder.size();
		int size_file=lst_file.size();
		
		//first, immune_top 
		int add=0;
		for(int i=0;i<size_top;i++) {
			lst.set(i,lst_top.get(i));
		}
		//2nd, folders
		add+=size_top;
		for(int i=0;i<size_folder;i++) {
			lst.set(i+add,lst_folder.get(i));
		}
		//3rd, files
		add+=lst_folder.size();
		for(int i=0;i<size_file;i++) {
			lst.set(i+add,lst_file.get(i));
		}
		//last, immune_last
		add+=lst_file.size();
		for(int i=0;i<size_last;i++) {
			lst.set(i+add,lst_last.get(i));
		}

		if(PMS.rz_debug>10) {
			PMS.dbg("---- MapFile.dlnaListSort:results ---------");
			size=lst.size();
			for(int i=0;i<size;i++) {
				d=lst.get(i);
				PMS.dbg("lst No."+i+", dlna="+d.getName());
			}
		}
		
		long tim2=System.currentTimeMillis();
		if(PMS.rz_trace>1) PMS.trace("MapFile.dlnaListSort: End, elapsed="+(tim2-tim1));
	}
}

	