/*
 * 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;

import java.awt.GraphicsEnvironment;
import java.awt.Toolkit;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.FileOutputStream;

import java.net.BindException;
import java.net.InetAddress;
import java.net.InetSocketAddress;

import java.net.NetworkInterface;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map.Entry;
import java.util.Properties;
import java.util.UUID;
import java.util.logging.LogManager;
import java.util.List;

import javax.swing.JOptionPane;


import net.pms.configuration.Build;
import net.pms.configuration.PmsConfiguration;
import net.pms.configuration.RendererConfiguration;
import net.pms.dlna.DLNAMediaDatabase;
import net.pms.dlna.RootFolder;
import net.pms.dlna.virtual.MediaLibrary;
import net.pms.dlna.DLNAResource; //regzamod
import net.pms.dlna.virtual.VirtualFolder;  //regzamod
import net.pms.dlna.rz_PlayMetaFile; //regzamod
import net.pms.dlna.Feed;	//regzamod
import net.pms.encoders.FFMpegAudio;
import net.pms.encoders.FFMpegDVRMSRemux;
import net.pms.encoders.FFMpegVideo;
import net.pms.encoders.MEncoderAviSynth;
import net.pms.encoders.MEncoderVideo;
import net.pms.encoders.MEncoderWebVideo;
import net.pms.encoders.MPlayerAudio;
import net.pms.encoders.MPlayerWebAudio;
import net.pms.encoders.MPlayerWebVideoDump;
import net.pms.encoders.Player;
import net.pms.encoders.RAWThumbnailer;
import net.pms.encoders.TSMuxerVideo;
import net.pms.encoders.TsMuxerAudio;
import net.pms.encoders.VideoLanAudioStreaming;
import net.pms.encoders.VideoLanVideoStreaming;
import net.pms.encoders.RzAudioAsVideoTC;	//regzamod
import net.pms.encoders.RzImageAsVideoTC;	//regzamod
import net.pms.external.ExternalFactory;
import net.pms.external.ExternalListener;
import net.pms.formats.DVRMS;
import net.pms.formats.FLAC;
import net.pms.formats.Format;
import net.pms.formats.GIF;
import net.pms.formats.ISO;
import net.pms.formats.JPG;
import net.pms.formats.M4A;
import net.pms.formats.MKV;
import net.pms.formats.MP3;
import net.pms.formats.MPG;
import net.pms.formats.OGG;
import net.pms.formats.PNG;
import net.pms.formats.RAW;
import net.pms.formats.TIF;
import net.pms.formats.WAV;
import net.pms.formats.WEB;
import net.pms.formats.BMP;  //add, regzam
import net.pms.gui.DummyFrame;
import net.pms.gui.IFrame;
import net.pms.io.BasicSystemUtils;
import net.pms.io.OutputParams;
import net.pms.io.OutputTextConsumer;
import net.pms.io.ProcessWrapperImpl;
import net.pms.io.SystemUtils;
import net.pms.io.WinUtils;
import net.pms.logging.LoggingConfigFileLoader;
import net.pms.network.HTTPServer;
import net.pms.network.ProxyServer;
import net.pms.network.UPNPHelper;
import net.pms.newgui.GeneralTab;
import net.pms.newgui.LooksFrame;
import net.pms.newgui.rz_WebTab;
import net.pms.newgui.ProfileChooser;
import net.pms.update.AutoUpdater;
import net.pms.util.PMSUtil;
import net.pms.util.PmsProperties;
import net.pms.util.ProcessUtil;
import net.pms.util.SystemErrWrapper;
import net.pms.util.TaskRunner;

import net.pms.logging.PMSThresholdFilter;	//regzamod, add
import net.pms.dlna.rz_Resume;	//regzamod, add
import net.pms.dlna.rz_SessionCtl;	//regzamod
import net.pms.dlna.rz_SessionInfo;	//regzamod
import net.pms.dlna.rz_KeywordMenu;	//regzamod

import org.apache.commons.configuration.ConfigurationException;
import org.apache.commons.configuration.event.ConfigurationEvent;
import org.apache.commons.configuration.event.ConfigurationListener;

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

import com.sun.jna.Platform;
import org.fourthline.cling.model.meta.*;


//---- UPnP
/*
import org.fourthline.cling.DefaultUpnpServiceConfiguration;
import org.fourthline.cling.UpnpService;
import org.fourthline.cling.UpnpServiceImpl;
import org.fourthline.cling.controlpoint.ActionCallback;
import org.fourthline.cling.controlpoint.SubscriptionCallback;
import org.fourthline.cling.model.ServerClientTokens;
import org.fourthline.cling.model.action.*;
import org.fourthline.cling.model.gena.*;
import org.fourthline.cling.model.message.UpnpHeaders;
import org.fourthline.cling.model.message.UpnpResponse;
import org.fourthline.cling.model.message.header.DeviceTypeHeader;
import org.fourthline.cling.model.message.header.UpnpHeader;
*/
import org.fourthline.cling.model.meta.*;
/*
import org.fourthline.cling.model.types.DeviceType;
import org.fourthline.cling.model.types.ServiceId;
import org.fourthline.cling.model.types.UDADeviceType;
import org.fourthline.cling.model.types.UDN;
import org.fourthline.cling.registry.DefaultRegistryListener;
import org.fourthline.cling.registry.Registry;
import org.fourthline.cling.registry.RegistryListener;
*/
//---- UPnP

public class PMS {
	private static final String SCROLLBARS = "scrollbars";
	private static final String NATIVELOOK = "nativelook";
	private static final String CONSOLE = "console";
	private static final String NOCONSOLE = "noconsole";
	private static final String PROFILES = "profiles";
	
	//--- regzamod start
	public static final String REGZAMOD_VERSION= "v1.69";	//regzamod version
	public static final int SORT_NAME 		=0; // Locale-sensitive A-Z (default)
	public static final int SORT_DATE_R		=1; // Sort by modified date, newest first
	public static final int SORT_DATE		=2; // Sort by modified date, oldest first
	public static final int SORT_NAME_ASC	=3; // Case-insensitive ASCIIbetical sort
	public static final int SORT_NAME_NUM	=4; // Locale-sensitive natural sort
	public static final int SORT_ORIGIN		=5; // source's original order
	public static final int SORT_ORIGIN_R	=6; // source's original order reverse
	public static final int SORT_DEFAULT	=-1; // use common sort_type
	public static final int SORT_INVALID	=-2; // invalid sort_type
	
	public static final int ENC_DIRECT = 1;
	public static final int ENC_REMUX  = 2;
	public static final int ENC_TRANSCODE  =3;
	public static final int TRS_MPEGPS  =1;
	public static final int TRS_MPEGTS  =2;	// by Mencoder only
	public static final int TRS_MPEGTS2 =3;	// by Mencoder+TsMuxer
	public static final int TRS_M2TS    =4;
	
	public static boolean rz_is_audio_as_video;	// process audioAsVideo
	public static boolean rz_is_image_as_video;	// process imageAsVideo
	public static boolean rz_is_root_per_session;
	public static boolean rz_is_rend_script_enabled;

	public static int rz_resume_mode;	// =0:disable,=1:from any stop,=2:from stop_button only
	public static int rz_resume_per_folder_web;
	public static int rz_update_check_nls; //for normal folder entries, OR of 0:noop,1:cre/del/ren/mov,2:modify
	public static int rz_update_check_pls; //for playlist entries,      OR of 0:noop,1:cre/del/ren/mov,2:modify
	public static int rz_update_check_min; //minimum check interval (msec)
	public static int rz_netty_conn_max; //
	public static int rz_buff_rollback; //
	public static rz_Resume rz_resume;	// resume class
	public static rz_SessionCtl rz_SessCtl;	// session controll
	public static rz_SessionInfo CurrentSession;
	public static boolean isMediaCacheClearRunning;
	public static boolean serverStopping;
	public static boolean rz_minfo_multi;
	public static int  rz_dlna_prof_cache;
	public static int  rz_rend_judge_type;
	public static int  rz_media_parse_thread=3;
	//--- regzamod end


	/**
	 * Update URL used in the {@link AutoUpdater}.
	 */
	private static final String UPDATE_SERVER_URL = "http://ps3mediaserver.googlecode.com/svn/trunk/ps3mediaserver/update2.data";

	/**
	 * @deprecated The version has moved to the resources/project.properties file. Use {@link #getVersion()} instead. 
	 */
	@Deprecated
	public static String VERSION;

	public static final String AVS_SEPARATOR = "\1";
	
	public static boolean rz_mod = false;	//regzamod true: useing PMS with regza modificatons, false: official PMS
	public static int rz_debug = 0;	// regzamod, debug print 
	public static int rz_trace = 0;	// regzamod, debug print 
	public static int rz_test = 0;	// regzamod, test mode
	public static int rz_stream_dump = 0;	// regzamod
	public static int rz_web_sort_type = SORT_ORIGIN;
	public static int rz_resume_sort_type = SORT_DATE_R;
	public static int rz_watched_sort_type = SORT_DATE_R;
	public static String rz_VisualClipPath;	//regzamod, should be herer?
	public static String rz_MetafileSuffix = "pxm"; 	//PlayMetafile
	public static int rz_ProfileEnabled = 1;  	//PlayProfile enabled
	public static String rz_ProfileSuffix = "pxf";  	//PlayProfile
	public static String rz_ProfileFF = "_folder.pxf";  //PlayProfile or Folder under itsself
	public static String rz_ResumeFileSuffix="rsm";		//regzamod
	public static String FSEP=File.separator;
	public static boolean rz_direct_seek;
	public static String rz_server_name;
	public static String rz_machine_name;
	public static long rz_dupmsg_supp_msec;
	public static int rz_dump_ua_header;

	public static String[] rz_itunes_ignore_kinds;
	public static int rz_reply_error;
	public static String myServerUAH;
	public static String myHost;
	public static String mySock;
	public static InetSocketAddress mySockAddr;
	public static InetAddress myHostAddr;
	public static int myPort;

	// (innot): The logger used for all logging.
	private static final Logger logger = LoggerFactory.getLogger(PMS.class);

	// TODO(tcox):  This shouldn't be static
	private static PmsConfiguration configuration;

	/**
	 * General properties for the PMS project.
	 */
	private static PmsProperties projectProperties = new PmsProperties();
	
	/**Returns a pointer to the main PMS GUI.
	 * @return {@link IFrame} Main PMS window.
	 */
	public IFrame getFrame() {
		return frame;
	}
	
	public rz_WebTab getRzw() {	//regzamod
		if(frame==null) return null;
		return ((LooksFrame)frame).getRzw();
	}
	
	public static void rz_SetKwd(String kwd) {
		List<RootFolder> dlist;
		if(rz_SessCtl==null) return;
		dlist=rz_SessCtl.getRootFolders();
		if(dlist==null) return;
		for(RootFolder d: dlist) {
			rz_KeywordMenu kwm=d.getKwdMenuBase();
			if(kwm!=null) {
				kwm.dt_sc_str=kwd;
			}
		}
	}
	
	public static void setCurrentSession (rz_SessionInfo sess) {
		CurrentSession=sess;
	}
	public static rz_SessionInfo getCurrentSession () {
		return CurrentSession;
	}

	/**getRootFolder returns the Root Folder for a given renderer. There could be the case
	 * where a given media renderer needs a different root structure.
	 * @param renderer {@link RendererConfiguration} is the renderer for which to get the RootFolder structure. If <b>null</b>, then
	 * the default renderer is used.
	 * @return {@link RootFolder} The root folder structure for a given renderer
	 */
	public RootFolder getRootFolder(RendererConfiguration renderer) {
		// something to do here for multiple directories views for each renderer
		if (renderer == null) {
			renderer = RendererConfiguration.getDefaultConf();
		}
		return renderer.getRootFolder();
	}
	
	public static RootFolder getRootFolderByDlna(DLNAResource dlna) { //regzamod, yet onother getRootFolder
		RendererConfiguration renderer=dlna.getDefaultRenderer();
		//PMS.dbg("getRootFolder: renderer="+renderer);
		if (renderer==null) {
			renderer = RendererConfiguration.getDefaultConf();
		}
		return renderer.getRootFolder();
	}
	
	public static rz_SessionInfo getSessionInfoByDlna(DLNAResource dlna) {
		RendererConfiguration renderer=dlna.getDefaultRenderer();
		if (renderer==null) {
			renderer = RendererConfiguration.getDefaultConf();
		}
		//PMS.dbg("getSessionInfoByDlna: renderer="+renderer+", sess="+renderer.sess);
		return renderer.sess;
	}
	
	public static void PurgeMemory() {
		List <rz_SessionInfo> sessList=PMS.rz_SessCtl.getSessAll(); 
		for(rz_SessionInfo s : sessList) {
			if(s.renderer!=null) {
				RootFolder root=s.renderer.rootFolder;
				if(root!=null) {
					root.PurgeMemory(root,2);  // 2:exec recursive sub-folders
				}
			}
		}
		System.gc();
	}

	/**
	 * Pointer to a running PMS server.
	 */
	private static PMS instance = null;

	static {
		sdfHour = new SimpleDateFormat("HH:mm:ss.SSS", Locale.US);
		sdfDate = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss", Locale.US);
	}

	public void setMyServerUAH(String uah) {
		myServerUAH=uah;
	}
	public String getMyServerUAH() {
		return myServerUAH;
	}
	/**
	 * Array of {@link RendererConfiguration} that have been found by PMS.
	 */
	//private final ArrayList<RendererConfiguration> foundRenderers = new ArrayList<RendererConfiguration>();
	private ArrayList<RendererConfiguration> foundRenderers = new ArrayList<RendererConfiguration>();  //regzam

	/**Adds a {@link RendererConfiguration} to the list of media renderers found. The list is being used, for
	 * example, to give the user a graphical representation of the found media renderers.
	 * @param mediarenderer {@link RendererConfiguration}
	 */
	
	//not use ipAddr ()
	public void setRendererfound (RendererConfiguration mediarenderer) {
		frame.addRendererIcon(mediarenderer.getRank(), mediarenderer.getRendererName(), "", "", mediarenderer.getRendererIcon());
		frame.setStatusCode(0, Messages.getString("PMS.18"), "apply-220.png");
	}
	
	//use ipAddr (ipAddr+port_no)
	public void setRendererfound_port(RendererConfiguration mediarenderer, InetAddress ia, 
		InetSocketAddress ias, rz_SessionInfo sess) {
		
		//PMS.dbg("setRendererfound_port: called, sockAddr="+ias);
		if(false) {
			setRendererfound(mediarenderer);
		}
		else {
			if (!foundRenderers.contains(mediarenderer) && !mediarenderer.isFDSSDP()) {
				foundRenderers.add(mediarenderer);
				
				String str_name="[ "+mediarenderer.getRendererName()+" ]";
				String str_fname=null;
				//String str_addr=ias.getAddress()+"."+ias.getPort();
				String str_addr=ia.getHostAddress();  //even if Device is Same, portNo may be differ per request
				if(str_addr.startsWith("/")) {
					str_addr=str_addr.substring(1,str_addr.length());
				}
				
				//--- get UPNP FriendlyName of the device, if exists
				Boolean getFname=true;
				if(str_addr.contains(myHost)) {
					getFname=false; 
					//Currently, only ipAddr is usefull to identify devicesis
					//So, Multi-Devices on same Machine(ipAddr) can't be identified
					//ToDO: should be refined, are there any method to get exactly matched Device?
					//UPnP's uuid seems to be Most adequet for, but unfortunetly it never appears in individual request
					//so, can't use uuid for detect device that send the request. 
				}
				if(getFname) {
					UPNPHelper uhp=UPNPHelper.getInstance();
					if(uhp!=null) {
						Device d=uhp.getAnyDevice(ias);
						str_fname=uhp.getFriendlyName(d);
					}
				}
				//--- get friendly name end
				frame.addRendererIcon(mediarenderer.getRank(), str_name, str_fname, str_addr, mediarenderer.getRendererIcon());
				frame.setStatusCode(0, Messages.getString("PMS.18"), "apply-220.png");
				if(rz_SessCtl!=null && str_fname!=null && sess!=null) {
					sess.setFname(str_fname);
				}
			}
		}
	}

	public void deviceAdded(Device d) {
		UPNPHelper uhp=UPNPHelper.getInstance();
		String str_fname=uhp.getFriendlyName(d);
		String str_addr=uhp.getURL(d).getHost();
		
		//str_fname="( "+str_fname+" )";
		if(PMS.rz_debug>1) {
			PMS.dbg("PMS.deviceAdded: device="+d);
			PMS.dbg("PMS.deviceAdded: iAddr="+str_addr+", FriendlyName="+str_fname);
			PMS.dbg("PMS.deviceAdded: myHost="+myHost+", myPort="+myPort);
		}
		
		if(str_addr!=null && myHost!=null && str_addr.contains(myHost)) {
			//Deveice is on myHost: UPNPHelper use only HostAddr for distinguish devices.
			//So, if the Deveice is on myHost, he can't distinguish the Device from many other devices existing on myHost.
			//Subsequently, 'str_fname' got by uhp.getFriendlyName(d) may be of other dev's, instead of The Device's
			//So, don't set FriendlyName
			//Noop
		}
		else if(str_fname!=null) {
			//Device is on other Host, set(update) FriendlyName, on Device that match with str_addr
			//Play for multi-devices on same Host NOT have same IPaddr!
			frame.updateRendererIcon(str_addr,str_fname);
			if(rz_SessCtl!=null) {
				if(true) {
					//PMS.dbg("PMS.deviceAdded: Type1  rz_SessCtl.setFname Start");
					rz_SessCtl.setFname(str_addr,str_fname);
					//PMS.dbg("PMS.deviceAdded: Type1 rz_SessCtl.setFname End");
				} 
				else {
					try {  
						//InetAddress.getByName() is always slow? OR only when name not contains hostAaddr?
						//PMS.dbg("PMS.deviceAdded: Type2 rz_SessCtl.setFname Start");
						InetAddress ipAddr = InetAddress.getByName(uhp.getURL(d).getHost());
						rz_SessCtl.setFname(ipAddr,str_fname);
						//PMS.dbg("PMS.deviceAdded: Type2  rz_SessCtl.setFname End");
					} catch (Exception e) {
						//Noop
					}
				}
			}
		}
	}
	/**
	 * HTTP server that serves the XML files needed by UPnP server and the media files.
	 */
	private HTTPServer server;

	/**
	 * User friendly name for the server.
	 */
	private String serverName;

	private ArrayList<Format> extensions;

	/**
	 * List of registered {@link Player}s.
	 */
	private ArrayList<Player> players;

	private ArrayList<Player> allPlayers;

	/**
	 * @return ArrayList of {@link Player}s.
	 */
	public ArrayList<Player> getAllPlayers() {
		return allPlayers;
	}

	private ProxyServer proxyServer;

	public ProxyServer getProxy() {
		return proxyServer;
	}

	public static SimpleDateFormat sdfDate;
	public static SimpleDateFormat sdfHour;
	public ArrayList<Process> currentProcesses = new ArrayList<Process>();

	private PMS() {
	}

	/**
	 * {@link IFrame} object that represents PMS GUI.
	 */
	IFrame frame;

	/**
	 * @see Platform#isWindows()
	 */
	public boolean isWindows() {
		return Platform.isWindows();
	}

	private int proxy;

	/**Interface to Windows specific functions, like Windows Registry. registry is set by {@link #init()}.
	 * @see WinUtils
	 */
	private SystemUtils registry;

	/**
	 * @see WinUtils
	 */
	public SystemUtils getRegistry() {
		return registry;
	}

	/**Executes a new Process and creates a fork that waits for its results. 
	 * TODO:Extend explanation on where this is being used.
	 * @param name Symbolic name for the process to be launched, only used in the trace log
	 * @param error (boolean) Set to true if you want PMS to add error messages to the trace pane
	 * @param workDir (File) optional working directory to run the process in
	 * @param params (array of Strings) array containing the command to call and its arguments
	 * @return Returns true if the command exited as expected
	 * @throws Exception TODO: Check which exceptions to use
	 */
	private boolean checkProcessExistence(String name, boolean error, File workDir, String... params) throws Exception {
		logger.debug("launching: " + params[0]);

		try {
			ProcessBuilder pb = new ProcessBuilder(params);
			if (workDir != null) {
				pb.directory(workDir);
			}
			final Process process = pb.start();

			OutputTextConsumer stderrConsumer = new OutputTextConsumer(process.getErrorStream(), false);
			stderrConsumer.start();

			OutputTextConsumer outConsumer = new OutputTextConsumer(process.getInputStream(), false);
			outConsumer.start();

			Runnable r = new Runnable() {
				public void run() {
					ProcessUtil.waitFor(process);
				}
			};

			Thread checkThread = new Thread(r);
			checkThread.start();
			checkThread.join(60000);
			checkThread.interrupt();
			checkThread = null;

			// XXX no longer used
			if (params[0].equals("vlc") && stderrConsumer.getResults().get(0).startsWith("VLC")) {
				return true;
			}

			// XXX no longer used
			if (params[0].equals("ffmpeg") && stderrConsumer.getResults().get(0).startsWith("FF")) {
				return true;
			}

			int exit = process.exitValue();
			if (exit != 0) {
				if (error) {
					logger.info("[" + exit + "] Cannot launch " + name + " / Check the presence of " + params[0] + " ...");
				}
				return false;
			}
			return true;
		} catch (Exception e) {
			if (error) {
				logger.error("Cannot launch " + name + " / Check the presence of " + params[0] + " ...", e);
			}
			return false;
		}
	}

	/**
	 * @see System#err
	 */
	@SuppressWarnings("unused")
	private final PrintStream stderr = System.err;

	/**Main resource database that supports search capabilities. Also known as media cache.
	 * @see DLNAMediaDatabase
	 */
	private DLNAMediaDatabase database;

	private void initializeDatabase() {
		database = new DLNAMediaDatabase("medias");
		database.init(false);
	}

	/**Used to get the database. Needed in the case of the Xbox 360, that requires a database.
	 * for its queries.
	 * @return (DLNAMediaDatabase) a reference to the database instance or <b>null</b> if one isn't defined
	 * (e.g. if the cache is disabled).
	 */
	public synchronized DLNAMediaDatabase getDatabase() {
		return database;
	}

	/**Initialisation procedure for PMS.
	 * @return true if the server has been initialized correctly. false if the server could
	 * not be set to listen on the UPnP port.
	 * @throws Exception
	 */
	private boolean init() throws Exception {
		AutoUpdater autoUpdater = null;

		// Read the project properties resource file.
		initProjectProperties();

		if (Build.isUpdatable()) {
			String serverURL = Build.getUpdateServerURL();
			autoUpdater = new AutoUpdater(serverURL, getVersion());
		}

		registry = createSystemUtils();
		
		if (System.getProperty(CONSOLE) == null) {
			frame = new LooksFrame(autoUpdater, configuration);
		} else {
			System.out.println("GUI environment not available");
			System.out.println("Switching to console mode");
			frame = new DummyFrame();
		}
		configuration.addConfigurationListener(new ConfigurationListener() {
			@Override
			public void configurationChanged(ConfigurationEvent event) {
				if (!event.isBeforeUpdate()) {
					if (PmsConfiguration.NEED_RELOAD_FLAGS.contains(event.getPropertyName())) {
						frame.setReloadable(true);
					}
				}
			}
		});

		frame.setStatusCode(0, Messages.getString("PMS.130"), "connect_no-220.png");
		proxy = -1;

		logger.info("Starting PS3 Media Server " + getVersion());
		logger.info("by shagrath / 2008-2012");
		logger.info("http://ps3mediaserver.org");
		logger.info("http://code.google.com/p/ps3mediaserver");
		logger.info("http://ps3mediaserver.blogspot.com");
		logger.info("");
		logger.info("Java: " + System.getProperty("java.version") + "-" + System.getProperty("java.vendor"));
		logger.info("OS: " + System.getProperty("os.name") + " " + System.getProperty("os.arch") + " " + System.getProperty("os.version"));
		logger.info("Encoding: " + System.getProperty("file.encoding"));

		//---- regzamod add Start------------------------
		//set globals frequently accessed params
		rz_mod=getConfiguration().getRZ_Mod();
		rz_debug=getConfiguration().getRZ_Debug();
		rz_trace=getConfiguration().getRZ_Trace();
		rz_test=getConfiguration().getRZ_Test();
		rz_stream_dump=getConfiguration().getRZ_StreamDump();
		rz_MetafileSuffix=getConfiguration().getRZ_MetafileSuffix();
		rz_ProfileEnabled=getConfiguration().getRZ_ProfileEnabled();
		rz_ProfileSuffix=getConfiguration().getRZ_ProfileSuffix();
		rz_ProfileFF=getConfiguration().getRZ_ProfileFF();
		rz_is_audio_as_video=getConfiguration().isRZ_audio_as_video();
		rz_is_image_as_video=getConfiguration().isRZ_image_as_video();
		rz_is_root_per_session=getConfiguration().isRZ_root_per_session();
		rz_is_rend_script_enabled=getConfiguration().isRZ_rend_script_enabled();
		
		rz_resume_mode=getConfiguration().getRZ_resume_mode();
		rz_resume_per_folder_web=getConfiguration().getRZ_resume_per_folder_web();
		rz_update_check_nls=getConfiguration().getRZ_update_check_nls();
		rz_update_check_pls=getConfiguration().getRZ_update_check_pls();
		rz_update_check_min=getConfiguration().getRZ_update_check_min();
		
		rz_netty_conn_max=getConfiguration().getRZ_netty_conn_max();
		rz_direct_seek=getConfiguration().isRZ_direct_seek();
		rz_SessCtl=new rz_SessionCtl();
		//createCommonFolder_OK(); // this place is too fast to execute this, common menu elements
		rz_server_name=getConfiguration().getRZ_server_name();
		rz_machine_name=getConfiguration().getRZ_machine_name();
		rz_dupmsg_supp_msec=(long)(getConfiguration().getRZ_dupmsg_supp_time()*1000);  //msec
		rz_dump_ua_header=getConfiguration().getRZ_dump_ua_header();
		rz_minfo_multi=getConfiguration().isRZ_minfo_multi();
		rz_buff_rollback=getConfiguration().getRZ_buffer_rollback();
		rz_itunes_ignore_kinds=getConfiguration().getRZ_itunes_ignore_kinds();
		rz_dlna_prof_cache=getConfiguration().getRZ_dlna_prof_cache();
		rz_rend_judge_type=getConfiguration().getRZ_rend_judge_type();
		rz_media_parse_thread=getConfiguration().getRZ_media_parse_thread();
		
		//---- test
		rz_reply_error=PMS.getConfiguration().getRZ_reply_error();
		//---- test end
		
		String str=configuration.getRZ_web_sort_type();
		int sort_type=rz_SortTypeString2id(2,str);
		if(sort_type==SORT_INVALID) {
			logger.error("invalid web_sort_type="+str +" --> use default");
			rz_web_sort_type=SORT_ORIGIN;
		}
		else {
			rz_web_sort_type=sort_type;
		}
		PMS.dbg("web_sort_type="+rz_web_sort_type);
		
		str=configuration.getRZ_resume_sort_type();
		rz_resume_sort_type=rz_SortTypeString2id(1,str);
		if(rz_resume_sort_type==SORT_INVALID) {
			logger.error("invalid resume_sort_type="+str +" --> use default");
			rz_resume_sort_type=SORT_ORIGIN;
		}
		
		Feed.refreshIntervalWeb=(long)getConfiguration().getRZ_web_refresh_interval()*1000;
			
		String enc_p=configuration.getRZ_PlaylistEncode();
		String enc_m=configuration.getRZ_MetafileEncode();
		String level_d=configuration.getRZ_LogbackLevel_disp();
		String level_f=configuration.getRZ_LogbackLevel_file();
		
		if(level_f!=null) {
			PMSThresholdFilter tf= new PMSThresholdFilter();
			tf.SetLoggerLevel(level_f);
		}
		logger.info("======================================================================================");
		logger.info("== PMS for REGZA "+REGZAMOD_VERSION);
		logger.info("== rz_mod="+rz_mod+", rz_test="+rz_test+", rz_debug="+rz_debug+", rz_trace="+rz_trace);
		logger.info("== rz_logback_level_disp="+level_d	+", rz_logback_level_file="+level_f);
		logger.info("== rz_netty_conn_max="+rz_netty_conn_max+", rz_media_parse_thread_max="+rz_media_parse_thread);
		logger.info("== rz_update_check_nls="+rz_update_check_nls+" (for normal folder entries, OR of 0:noop,1:cre/del/ren/mov,2:modify)");
		logger.info("== rz_update_check_pls="+rz_update_check_pls+" (for playlist entries,      OR of 0:noop,1:cre/del/ren/mov,2:modify)");
		logger.info("== rz_playlist_encode=" + enc_p+", rz_metafile_encode=" + enc_m
			+", rz_metafile_suffix="+ rz_MetafileSuffix);
		logger.info("== rz_profile_enabled=" + rz_ProfileEnabled+", rz_profile_suffix=" + rz_ProfileSuffix
			+", rz_profile_ff (for folder)="+ rz_ProfileFF);
		logger.info("== rz_buffer_check_interval(sec)="+configuration.getRZ_BufferCheckInterval()
			+", rz_force_gc_interval(sec)="+configuration.getRZ_force_gc_interval()
			+", rz_web_refresh_interval(sec)="+(Feed.refreshIntervalWeb/1000));
		logger.info("== rz_audio_as_video="+rz_is_audio_as_video+", rz_image_as_video="+rz_is_image_as_video);
		logger.info("== defaults: rz_auto_search_lock="+configuration.getRZ_auto_search_lock()
			+", rz_action_button_type="+configuration.getRZ_action_button_type());
		logger.info("== rz_minfo_multi="+rz_minfo_multi);
		
		/* StartWait values (Old: depricated)
		logger.info("======================================================================================");
		logger.info("== key_videotranscode_start_delay (secs)="+configuration.getVideoTranscodeStartDelay());
		logger.info("== minvideobuffer (MegaBytes)="+configuration.getMinMemoryBufferSize());
		logger.info("== minwebbuffer (MegaBytes)="+configuration.getMinStreamBuffer());
		logger.info("== minwebbuffer_secondary (Bytes)="+configuration.getMinStreamBufferSecondary());
		logger.info("======================================================================================");
		*/

		//---- StartWait values (New)
		double mval_def[]=configuration.getRZ_def_startwait();
		double mval_trans[]=configuration.getRZ_trans_startwait();
		double mval_remux[]=configuration.getRZ_remux_startwait();
		double mval_web[]=configuration.getRZ_web_startwait();
		double mval_aav[]=configuration.getRZ_aav_startwait();
		double mval_iav[]=configuration.getRZ_iav_startwait();
		logger.info("======================================================================================");
		logger.info(" StartWait values [ wait_time(msec), wait_buf1(MB), wait_buf2(MB) ]");
		
		logger.info( "  --> trans: [ "+mval_trans[0]+", "+mval_trans[1]+", "+mval_trans[2]+" ]"
					+"  --> remux: [ "+mval_remux[0]+", "+mval_remux[1]+", "+mval_remux[2]+" ]"
					+"  --> web  : [ "+mval_web[0]  +", "+mval_web[1]  +", "+mval_web[2]  +" ]");
		
		logger.info( "  --> aav  : [ "+mval_aav[0]  +", "+mval_aav[1]  +", "+mval_aav[2]  +" ]"
					+"  --> iav  : [ "+mval_iav[0]  +", "+mval_iav[1]  +", "+mval_iav[2]  +" ]");
		logger.info("======================================================================================");
		logger.info("");
		
		//---- System Folder paths
		String cwd = new File("").getAbsolutePath();
		logger.info("Working directory: " + cwd);
		logger.info("UserData folder: " + configuration.getUserDataFolder());
		logger.info("Temp folder: " + configuration.getTempFolder());

		//---- profile config paths
		logger.info("Profile directory: " + configuration.getProfileDirectory());
		String profilePath = configuration.getProfilePath();
		logger.info("Profile path: " + profilePath);

		File profileFile = new File(profilePath);
		if (profileFile.exists()) {
			String status = String.format("%s%s",
				profileFile.canRead()  ? "r" : "-",
				profileFile.canWrite() ? "w" : "-"
			);
			logger.info("Profile status: " + status);
		} else {
			logger.info("Profile status: no such file");
		}
		logger.info("Profile name: " + configuration.getProfileName());

		//---- logging config file paths
		logger.info("Logging config file: " + LoggingConfigFileLoader.getConfigFilePath());
		HashMap<String, String> lfps = LoggingConfigFileLoader.getLogFilePaths();
		if (lfps != null && lfps.size() > 0) {
			if (lfps.size() == 1) {
				Entry<String, String> entry = lfps.entrySet().iterator().next();
				logger.info(String.format("%s: %s", entry.getKey(), entry.getValue()));
			} else {
				logger.info("Logging to multiple files:");
				Iterator<Entry<String, String>> logsIterator = lfps.entrySet().iterator();
				Entry<String, String> entry;
				while (logsIterator.hasNext()) {
					entry = logsIterator.next();
					logger.info(String.format("%s: %s", entry.getKey(), entry.getValue()));
				}
			}
		}
		logger.info("");
		
		//---- Trasco Engine list 
		if(true) {
			List<String> engines=getConfiguration().getEnginesAsList(getRegistry());
			logger.debug("getEnginesAsList="+engines);
		}
		
		//---- Load renderer confs
		RendererConfiguration.loadRendererConfigurations();
		
		/*---- moved down
		//---- from UMS start
		// Now that renderer confs are all loaded, we can start searching for renderers
		// Cause Runtime error at pos-10 in UPnPControl.java 
		// Configuration error: org/seamless/util/Iterators$Synchronized
		if(configuration.isRZ_UpnpEnabled()) {
			PMS.dbg("Starting UPNPHelper Listner...");
			PMS.dbg("If error occures, set rz_upnp_enable=false in PMS.conf");
			UPNPHelper.getInstance().init();
		}
		//---- from UMS end
		*/

		logger.info("Checking MPlayer font cache. It can take a minute or so.");
		checkProcessExistence("MPlayer", true, null, configuration.getMplayerPath(), "dummy");
		if (isWindows()) {
			checkProcessExistence("MPlayer", true, configuration.getTempFolder(), configuration.getMplayerPath(), "dummy");
		}
		logger.info("Done!");

		// check the existence of Vsfilter.dll
		if (registry.isAvis() && registry.getAvsPluginsDir() != null) {
			logger.info("Found AviSynth plugins dir: " + registry.getAvsPluginsDir().getAbsolutePath());
			File vsFilterdll = new File(registry.getAvsPluginsDir(), "VSFilter.dll");
			if (!vsFilterdll.exists()) {
				logger.info("VSFilter.dll is not in the AviSynth plugins directory. This can cause problems when trying to play subtitled videos with AviSynth");
			}
		}

		if (registry.getVlcv() != null && registry.getVlcp() != null) {
			logger.info("Found VideoLAN version " + registry.getVlcv() + " at: " + registry.getVlcp());
		}

		//check if Kerio is installed
		if (registry.isKerioFirewall()) {
			//todo: Warning message
		}

		// force use of specific dvr ms muxer when it's installed in the right place
		File dvrsMsffmpegmuxer = new File("win32/dvrms/ffmpeg_MPGMUX.exe");
		if (dvrsMsffmpegmuxer.exists()) {
			configuration.setFfmpegAlternativePath(dvrsMsffmpegmuxer.getAbsolutePath());
		}

		// disable jaudiotagger logging
		LogManager.getLogManager().readConfiguration(new ByteArrayInputStream("org.jaudiotagger.level=OFF".getBytes()));

		// wrap System.err
		System.setErr(new PrintStream(new SystemErrWrapper(), true));

		extensions = new ArrayList<Format>();
		players = new ArrayList<Player>();
		allPlayers = new ArrayList<Player>();
		server = new HTTPServer(configuration.getServerPort());
		

		registerExtensions();

		/*
		 * XXX: keep this here (i.e. after registerExtensions and before registerPlayers) so that plugins
		 * can register custom players correctly (e.g. in the GUI) and/or add/replace custom formats
		 *
		 * XXX: if a plugin requires initialization/notification even earlier than
		 * this, then a new external listener implementing a new callback should be added
		 * e.g. StartupListener.registeredExtensions()
		 */
		try {
			ExternalFactory.lookup();
		} catch (Exception e) {
			logger.error("Error loading plugins", e);
		}

		// a static block in Player doesn't work (i.e. is called too late).
		// this must always be called *after* the plugins have loaded.
		// here's as good a place as any
		Player.initializeFinalizeTranscoderArgsListeners();
		registerPlayers();

		boolean binding = false;

		try {
			binding = server.start();
		} catch (BindException b) {
			logger.info("FATAL ERROR: Unable to bind on port: " + configuration.getServerPort() + ", because: " + b.getMessage());
			logger.info("Maybe another process is running or the hostname is wrong.");
		}

		new Thread() {
			@Override
			public void run() {
				try {
					Thread.sleep(7000);
				} catch (InterruptedException e) {
				}
				if (foundRenderers.isEmpty()) {
					frame.setStatusCode(0, Messages.getString("PMS.0"), "messagebox_critical-220.png");
				} else {
					frame.setStatusCode(0, Messages.getString("PMS.18"), "apply-220.png");
				}
			}
		}.start();

		if (!binding) {
			return false;
		}

		if (proxy > 0) {
			logger.info("Starting HTTP Proxy Server on port: " + proxy);
			proxyServer = new ProxyServer(proxy);
		}

		//memory my addrs
		mySockAddr=server.getSockAddr();	//InetSocketAddress
		myHostAddr=mySockAddr.getAddress();	//InetAddress
		myPort=mySockAddr.getPort();  		//inet Port No. (int)
		
		myHost=myHostAddr.getHostAddress();	//InetAddress (String)
		mySock=myHost+":"+myPort;  			//InetSocketAddress (String)
		logger.info("PMS.init: HTTPServer created, mySockAddr="+mySockAddr+", myHost="+myHostAddr);

		// initialize the cache
		if (configuration.getUseCache()) {
			initializeDatabase(); // XXX: this must be done *before* new MediaLibrary -> new MediaLibraryFolder
			//mediaLibrary = new MediaLibrary();  //del by regzam, instead create per rootFolder
			logger.info("A tiny cache admin interface is available at: http://" + server.getHost() + ":" + server.getPort() + "/console/home");
		}

		createCommonFolder_OK(); // regzamod, common menu elements
		if(rz_resume_mode!=0) {
			rz_resume=new rz_Resume();
			//rz_ResumeFolder vf=rz_resume.createResumeFolder0("#--RESUME ALL--#");
		}
		
		// XXX: this must be called:
		//     a) *after* loading plugins i.e. plugins register root folders then RootFolder.discoverChildren adds them
		//     b) *after* mediaLibrary is initialized, if enabled (above)
		getRootFolder(RendererConfiguration.getDefaultConf());
		
		start_cyclic_gc();	// regzamod, if enabled

		frame.serverReady();

		//---- from UMS start
		// Now that renderer confs are all loaded, we can start searching for renderers
		// Cause Runtime error at pos-10 in UPnPControl.java 
		// Configuration error: org/seamless/util/Iterators$Synchronized
		if(configuration.isRZ_UpnpEnabled()) {
			PMS.dbg("Starting UPNPHelper Listner...");
			PMS.dbg("If error occures, set rz_upnp_enable=false in PMS.conf");
			UPNPHelper.getInstance().init();
		}


		//UPNPHelper.sendByeBye();
		Runtime.getRuntime().addShutdownHook(new Thread() {
			@Override
			public void run() {
				try {
					setServerStopping(true);
					for (ExternalListener l : ExternalFactory.getExternalListeners()) {
						l.shutdown();
					}
					UPNPHelper.shutDownListener();
					UPNPHelper.sendByeBye();
					logger.debug("Forcing shutdown of all active processes");
					for (Process p : currentProcesses) {
						try {
							p.exitValue();
						} catch (IllegalThreadStateException ise) {
							logger.trace("Forcing shutdown of process: " + p);
							ProcessUtil.destroy(p);
						}
					}
					get().getServer().stop();
					Thread.sleep(500);
				} catch (Exception e) {
				}
			}
		});

		UPNPHelper.sendAlive();
		logger.trace("Waiting 250 milliseconds...");
		Thread.sleep(250);
		UPNPHelper.listen();

		return true;
	}
	
	private MediaLibrary mediaLibrary;

	/**Returns the MediaLibrary used by PMS.
	 * @return (MediaLibrary) Used mediaLibrary, if any. null if none is in use.
	 */
	public MediaLibrary getLibrary() {
		return mediaLibrary;
	}

	private SystemUtils createSystemUtils() {
		if (Platform.isWindows()) {
			return new WinUtils();
		} else {
			return new BasicSystemUtils();
		}
	}

	/**Executes the needed commands in order to make PMS a Windows service that starts whenever the machine is started.
	 * This function is called from the Network tab.
	 * @return true if PMS could be installed as a Windows service.
	 * @see GeneralTab#build()
	 */
	public boolean installWin32Service() {
		logger.info(Messages.getString("PMS.41"));
		String cmdArray[] = new String[]{"win32/service/wrapper.exe", "-r", "wrapper.conf"};
		OutputParams output = new OutputParams(configuration);
		output.noexitcheck = true;
		ProcessWrapperImpl pwuninstall = new ProcessWrapperImpl(cmdArray, output);
		pwuninstall.run();
		cmdArray = new String[]{"win32/service/wrapper.exe", "-i", "wrapper.conf"};
		ProcessWrapperImpl pwinstall = new ProcessWrapperImpl(cmdArray, new OutputParams(configuration));
		pwinstall.run();
		return pwinstall.isSuccess();
	}

	/**Add a known set of extensions to the extensions list.
	 * @see PMS#init()
	 */
	private void registerExtensions() {
		extensions.add(new WEB());
		extensions.add(new MKV());
		extensions.add(new M4A());
		extensions.add(new MP3());
		extensions.add(new ISO());
		extensions.add(new MPG());
		extensions.add(new WAV());
		extensions.add(new JPG());
		extensions.add(new OGG());
		extensions.add(new PNG());
		extensions.add(new GIF());
		extensions.add(new TIF());
		extensions.add(new FLAC());
		extensions.add(new DVRMS());
		extensions.add(new RAW());
		extensions.add(new BMP());  //add, regzam
	}

	/**Register a known set of audio/video transcoders (known as {@link Player}s). Used in PMS#init().
	 * @see PMS#init()
	 */
	private void registerPlayers() {
		if (Platform.isWindows()) {
			registerPlayer(new FFMpegVideo());
		}
		registerPlayer(new FFMpegAudio(configuration));
		registerPlayer(new MEncoderVideo(configuration));
		if (Platform.isWindows()) {
			registerPlayer(new MEncoderAviSynth(configuration));
		}
		registerPlayer(new MPlayerAudio(configuration));
		registerPlayer(new MEncoderWebVideo(configuration));
		registerPlayer(new MPlayerWebVideoDump(configuration));
		registerPlayer(new MPlayerWebAudio(configuration));
		registerPlayer(new TSMuxerVideo(configuration));
		registerPlayer(new TsMuxerAudio(configuration));
		registerPlayer(new VideoLanAudioStreaming(configuration));
		registerPlayer(new VideoLanVideoStreaming(configuration));
		if (Platform.isWindows()) {
			registerPlayer(new FFMpegDVRMSRemux());
		}
		registerPlayer(new RAWThumbnailer());
		registerPlayer(new RzAudioAsVideoTC(configuration));	//regzamod
		registerPlayer(new RzImageAsVideoTC(configuration));	//regzamod
		frame.addEngines();
	}

	/**Adds a single {@link Player} to the list of Players. Used by {@link PMS#registerPlayers()}.
	 * @param p (Player) to be added to the list
	 * @see Player
	 * @see PMS#registerPlayers()
	 */
	public void registerPlayer(Player p) {
		allPlayers.add(p);
		boolean ok = false;
		if (Player.NATIVE.equals(p.executable())) {
			ok = true;
		} else {
			if (isWindows()) {
				if (p.executable() == null) {
					logger.info("Executable of transcoder profile " + p + " not found");
					return;
				}
				File executable = new File(p.executable());
				File executable2 = new File(p.executable() + ".exe");

				if (executable.exists() || executable2.exists()) {
					ok = true;
				} else {
					logger.info("Executable of transcoder profile " + p + " not found");
					return;
				}
				if (p.avisynth()) {
					ok = false;
					if (registry.isAvis()) {
						ok = true;
					} else {
						logger.info("Transcoder profile " + p + " will not be used because AviSynth was not found");
					}
				}
			} else if (!p.avisynth()) {
				ok = true;
			}
		}
		if (ok) {
			logger.info("Registering transcoding engine: " + p /*+ (p.avisynth()?(" with " + (forceMPlayer?"MPlayer":"AviSynth")):"")*/);
			players.add(p);
		}
	}

	/**Transforms a comma separated list of directory entries into an array of {@link String}.
	 * Checks that the directory exists and is a valid directory.
	 * @param log whether to output log information
	 * @return {@link File}[] Array of directories.
	 * @throws IOException
	 */

	// this is called *way* too often (e.g. a dozen times with 1 renderer and 1 shared folder),
	// so log it by default so we can fix it.
	// BUT it's also called when the GUI is initialized (to populate the list of shared folders),
	// and we don't want this message to appear *before* the PMS banner, so allow that call to suppress logging	
	public File[] getFoldersConf(boolean log) {
		String folders = getConfiguration().getFolders();
		if (folders == null || folders.length() == 0) {
			return null;
		}
		ArrayList<File> directories = new ArrayList<File>();
		String[] foldersArray = folders.split(",");
		for (String folder : foldersArray) {
			// unescape embedded commas. note: backslashing isn't safe as it conflicts with
			// Windows path separators:
			// http://ps3mediaserver.org/forum/viewtopic.php?f=14&t=8883&start=250#p43520
			folder = folder.replaceAll("&comma;", ",");
			if (log) {
				logger.info("Checking shared folder: " + folder);
			}
			File file = new File(folder);
			if (file.exists()) {
				if (!file.isDirectory()) {
					logger.warn("The file " + folder + " is not a directory! Please remove it from your Shared folders list on the Navigation/Share Settings tab");
				}
			} else {
				//regzamod, RemovableStorages(DVD,USB etc)will be this case, if media not mounted/inserted
				logger.warn("The directory " + folder + " does not exist. Please remove it from your Shared folders list on the Navigation/Share Settings tab");
			}

			// add the file even if there are problems so that the user can update the shared folders as required.
			directories.add(file);
		}
		File f[] = new File[directories.size()];
		directories.toArray(f);
		return f;
	}

	public File[] getFoldersConf() {
		return getFoldersConf(true);
	}

	/**Restarts the server. The trigger is either a button on the main PMS window or via
	 * an action item.
	 * @throws IOException
	 */
	// XXX: don't try to optimize this by reusing the same server instance.
	// see the comment above HTTPServer.stop()
	/*
	public void reset_old() throws IOException {
		logger.trace("Waiting 1 second...");
		UPNPHelper.sendByeBye();
		server.stop();
		server = null;
		RendererConfiguration.resetAllRenderers();
		try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		server = new HTTPServer(configuration.getServerPort());
		server.start();
		UPNPHelper.sendAlive();
		frame.setReloadable(false);
	}
	*/
	
	public static void setServerStopping(boolean b) {
		serverStopping=b;
	}
	public static boolean getServerStopping() {
		return serverStopping;
	}
	
	public void reset() {  //server restart
		TaskRunner.getInstance().submitNamed("restart", true, new Runnable() {
			@Override
			public void run() {
				try {
					setServerStopping(true);
					logger.trace("Waiting 1 second...");
					try {
					        Thread.sleep(1000);
					} catch (InterruptedException e) {
					        logger.trace("Caught exception", e);
					}
					
					UPNPHelper.sendByeBye();
					server.stop();
					server = null;
					
					setServerStopping(false);
					
					//-------------------------------------------------
					// Reset Misc. Variables Start
					//-------------------------------------------------
					//---- Config values  
					RendererConfiguration.resetAllRenderers();
					
					//---- Session info
					rz_SessCtl.reset();
					
					//---- Frame (CUI/GUI)
					frame.reset();  //regzam, Reset Variables for Server Restart (reset Connected RendererIcons etc.)
					
					//---- PMS main (it's me)
					foundRenderers = new ArrayList<RendererConfiguration>();
					//-------------------------------------------------
					// Reset Misc. Variables End
					//-------------------------------------------------
					
					try {
				        Thread.sleep(1000);
					} catch (InterruptedException e) {
				        logger.trace("Caught exception", e);
					}
					
					server = new HTTPServer(configuration.getServerPort());
					server.start();
					UPNPHelper.sendAlive();
					frame.setReloadable(false);
				} catch (IOException e) {
					logger.error("error during restart :" +e.getMessage(), e);
				}
			}
		});
	}
	
	// Cannot remove these methods because of backwards compatibility;
	// none of the PMS code uses it, but some plugins still do.
	
	/**
	 * @deprecated Use the SLF4J logging API instead.
	 * Adds a message to the debug stream, or {@link System#out} in case the
	 * debug stream has not been set up yet.
	 * @param msg {@link String} to be added to the debug stream.
	 */
	public static void debug(String msg) {
		logger.trace(msg);
	}

	/**
	 * @deprecated Use the SLF4J logging API instead.
	 * Adds a message to the info stream.
	 * @param msg {@link String} to be added to the info stream.
	 */
	public static void info(String msg) {
		logger.debug(msg);
	}

	/**
	 * @deprecated Use the SLF4J logging API instead.
	 * Adds a message to the minimal stream. This stream is also
	 * shown in the Trace tab.
	 * @param msg {@link String} to be added to the minimal stream.
	 */
	public static void minimal(String msg) {
		logger.info(msg);
	}

	/**
	 * @deprecated Use the SLF4J logging API instead.
	 * Adds a message to the error stream. This is usually called by
	 * statements that are in a try/catch block.
	 * @param msg {@link String} to be added to the error stream
	 * @param t {@link Throwable} comes from an {@link Exception} 
	 */
	public static void error(String msg, Throwable t) {
		logger.error(msg, t);
	}

	/**Universally Unique Identifier used in the UPnP server.
	 * 
	 */
	private String uuid;

	/**Creates a new {@link #uuid} for the UPnP server to use. Tries to follow the RFCs for creating the UUID based on the link MAC address.
	 * Defaults to a random one if that method is not available.
	 * @return {@link String} with an Universally Unique Identifier.
	 */
	public String usn() {
		if (uuid == null) {
			//retrieve UUID from configuration
			uuid = getConfiguration().getUuid();

			if (uuid == null) {
				//create a new UUID based on the MAC address of the used network adapter
				NetworkInterface ni = null;
				try {
					if (configuration.getServerHostname() != null && configuration.getServerHostname().length() > 0) {
						ni = NetworkInterface.getByInetAddress(InetAddress.getByName(configuration.getServerHostname()));
					} else if (get().getServer().getNi() != null) {
						ni = get().getServer().getNi();
					}

					if (ni != null) {
						byte[] addr = PMSUtil.getHardwareAddress(ni); // return null when java.net.preferIPv4Stack=true
						if (addr != null) {
							uuid = UUID.nameUUIDFromBytes(addr).toString();
							logger.info(String.format("Generated new UUID based on the MAC address of the network adapter '%s'", ni.getDisplayName()));
						}
					}
				} catch (Throwable e) {
					//do nothing
				}

				//create random UUID if the generation by MAC address failed
				if (uuid == null) {
					uuid = UUID.randomUUID().toString();
					logger.info("Generated new random UUID");
				}

				//save the newly generated UUID
				getConfiguration().setUuid(uuid);
				try {
					getConfiguration().save();
				} catch (ConfigurationException e) {
					logger.error("Failed to save configuration with new UUID", e);
				}
			}

			logger.info("Using the following UUID configured in PMS.conf: " + uuid);
		}
		return "uuid:" + uuid;
	}

	/**Returns the user friendly name of the UPnP server. 
	 * @return {@link String} with the user friendly name.
	 */
	public String getServerName() {
		if (serverName == null) {
			StringBuilder sb = new StringBuilder();
			sb.append(System.getProperty("os.name").replace(" ", "_"));
			sb.append("-");
			sb.append(System.getProperty("os.arch").replace(" ", "_"));
			sb.append("-");
			sb.append(System.getProperty("os.version").replace(" ", "_"));
			sb.append(", UPnP/1.0, PMS/" + getVersion());
			serverName = sb.toString();
		}
		return serverName;
	}

	/**Returns the PMS instance.
	 * @return {@link PMS}
	 */
	public static PMS get() {
		// XXX when PMS is run as an application, the instance is initialized via the createInstance call in main().
		// However, plugin tests may need access to a PMS instance without going
		// to the trouble of launching the PMS application, so we provide a fallback
		// initialization here. Either way, createInstance() should only be called once (see below)
		if (instance == null) {
			createInstance();
		}

		return instance;
	}

	private synchronized static void createInstance() {
		assert instance == null; // this should only be called once
		instance = new PMS();

		try {
			if (instance.init()) {
				logger.info("The server should now appear on your renderer");
			} else {
				logger.error("A serious error occurred during PMS init");
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	/**
	 * @param filename
	 * @return The format.
	 */
	public Format getAssociatedExtension(String filename) {
		logger.trace("Search extension for " + filename);
		for (Format ext : extensions) {
			if (ext.match(filename)) {
				logger.trace("Found 1! " + ext.getClass().getName());
				return ext.duplicate();
			}
		}
		return null;
	}

	public Player getPlayer(Class<? extends Player> profileClass, Format ext) {
		for (Player p : players) {
			if (p.getClass().equals(profileClass) && p.type() == ext.getType() && !p.excludeFormat(ext)) {
				return p;
			}
		}
		return null;
	}

	public Player getPlayer(String id) {	//regzamod
		for (Player p : players) {
			if (p.id().equals(id)) {
				return p;
			}
		}
		return null;
	}
	
	public ArrayList<Player> getPlayers(ArrayList<Class<? extends Player>> profileClasses, int type) {
		ArrayList<Player> compatiblePlayers = new ArrayList<Player>();
		for (Player p : players) {
			if (profileClasses.contains(p.getClass()) && p.type() == type) {
				compatiblePlayers.add(p);
			}
		}
		return compatiblePlayers;
	}

	public static void main(String args[]) throws IOException, ConfigurationException {
		boolean displayProfileChooser = false;
		boolean headless = true;
		
		dbgw("---- PMS.main: Start");
		
		if (args.length > 0) {
			for (int a = 0; a < args.length; a++) {
				if (args[a].equals(CONSOLE)) {
					System.setProperty(CONSOLE, Boolean.toString(true));
				} else if (args[a].equals(NATIVELOOK)) {
					System.setProperty(NATIVELOOK, Boolean.toString(true));
				} else if (args[a].equals(SCROLLBARS)) {
					System.setProperty(SCROLLBARS, Boolean.toString(true));
				} else if (args[a].equals(NOCONSOLE)) {
					System.setProperty(NOCONSOLE, Boolean.toString(true));
				} else if (args[a].equals(PROFILES)) {
					displayProfileChooser = true;
				}
			}
		}

		try {
			Toolkit.getDefaultToolkit();
			if (GraphicsEnvironment.isHeadless()) {
				if (System.getProperty(NOCONSOLE) == null) {
					System.setProperty(CONSOLE, Boolean.toString(true));
				}
			} else {
				headless = false;
			}
		} catch (Throwable t) {
			System.err.println("Toolkit error: " + t.getMessage());
			if (System.getProperty(NOCONSOLE) == null) {
				System.setProperty(CONSOLE, Boolean.toString(true));
			}
		}

		if (!headless && displayProfileChooser) {
			ProfileChooser.display();
		}

		try {
			dbgw("---- PMS.main: call PmsConfiguration() start, configuration="+configuration);
			configuration = new PmsConfiguration();
			dbgw("---- PMS.main: call PmsConfiguration() end, configuration="+configuration);

			assert configuration != null;
			
			// Load the (optional) logback config file. This has to be called after 'new PmsConfiguration'
			// as the logging starts immediately and some filters need the PmsConfiguration.
			dbgw("---- PMS.main: call LoggingConfigFileLoader.load(), configuration="+configuration);
			LoggingConfigFileLoader.load();

			// create the PMS instance returned by get()
			dbgw("---- PMS.main: call createInstance()");
			createInstance(); 
			dbgw("---- PMS.main: End-1");
		} catch (Throwable t) {
			dbgw("---- PMS.main: Configuration error: " + t.getMessage());
			System.err.println("Configuration error: " + t.getMessage());
			JOptionPane.showMessageDialog(null, "Configuration error:"+t.getMessage(), "Error initalizing PMS!", JOptionPane.ERROR_MESSAGE);
		}
		dbgw("---- PMS.main: End-2");
	}

	public HTTPServer getServer() {
		return server;
	}

	public ArrayList<Format> getExtensions() {
		return extensions;
	}

	public ArrayList<Player> getPlayers() {
		return players;
	}

	public void save() {
		try {
			configuration.save();
		} catch (ConfigurationException e) {
			logger.error("Could not save configuration", e);
		}
	}

	public void storeFileInCache(File file, int formatType) {
		if (getConfiguration().getUseCache()) {
			if (!getDatabase().isDataExists(file.getAbsolutePath(), file.lastModified())) {
				
				//logger.info("pms: insertData"+ file.getAbsolutePath()+ "lastModified="+file.lastModified() +"Type=" +formatType); //regzamod
					
				getDatabase().insertData(file.getAbsolutePath(), file.lastModified(), formatType, null);
			}
		}
	}
	
	//----------------------------------------------------------------------
	// regzamod add Start
	//----------------------------------------------------------------------
	static long gc_interval;
	private void start_cyclic_gc() {
		gc_interval=configuration.getRZ_force_gc_interval();
		if(gc_interval<=0) { 
			return;
		}
		logger.info("PMS.start_cyclic_gc: start cyclic gc interval(sec)="+gc_interval);
		gc_interval *=1000; //msec
		new Thread() {
			@Override
			public void run() {
				logger.info("PMS.start_cyclic_gc: run start");
				while(true) {
					try {
						Thread.sleep(gc_interval);
					} catch (InterruptedException e) {
					}
					PMS.dbg("PMS.start_cyclic_gc: exec gc, interval(sec)="+(gc_interval/1000));
					System.gc();
					//System.gc();  //force free heap --- no effect: need some delay??
				}
				//logger.info("PMS.start_cyclic_gc: run end");
			}
		}.start();
	}
	
	//---- VirtualFolder for Button, that disp OK movie when opened
	private static VirtualFolder vf_folderOK;
	private void createCommonFolder_OK() {
		VirtualFolder vf=new VirtualFolder("folderOK",null);
		File okmovie=new File("videos/action_success-512.mpg");
		vf_folderOK=vf;
		//vf.sotype=DLNAResource.SO_BUTTON_ELEMENT;
		vf.mflags|=DLNAResource.MF_BUTTON_ELEMENT;
		rz_PlayMetaFile pxm=new rz_PlayMetaFile(okmovie,"OK", 0);
		pxm.rz_MetaNotExistSuffix=false;
		pxm.rz_MetaTransType[0]=PMS.ENC_DIRECT;
		vf.addChild(pxm);
	}
	public static VirtualFolder getCommonFolder_OK() {
		return vf_folderOK;
	}
	
	public boolean isFileInCache(File file, int formatType) {
		if (getConfiguration().getUseCache()) {
			if (getDatabase().isDataExists(file.getAbsolutePath(), file.lastModified())) {
				
				//PMS.dbg("isFileInCache: Found, return true" +file.getAbsolutePath()); //regzamod
				return true;	// exist in cache
			}
		}
		//PMS.dbg("isFileInCache: NOT Found, return false" +file.getAbsolutePath()); //regzamod
		return false;	// not in cache
	}
	public String getDefaultTextEncode() {
		if(isWindows()) return "MS932";
		else return "UTF-8";  // ok??
	}
	
	// dbg without using logback
	static PrintWriter pw;
	public static void dbgw(String msg) {
		if(pw==null) {
			try {
				//Don't use getConfiguration(): may be null, if called before config-setup
				//File dumpf= new File(getConfiguration().getTempFolder(),"dump.txt");
				File dumpf= new File(System.getProperty("java.io.tmpdir"),"pms_for_regza_dump.log");
				pw = new PrintWriter(new FileOutputStream(dumpf,false /* append = false */)); 
			} catch (Exception e) {
				// Could not create / write the file
				return;
			}
		}
		pw.println(msg);
		pw.flush();
	}

	// dbg using logback
	public static void dbg0(String msg) {
		logger.info(msg);
	}
	public static void dbg(String msg) {
		if(rz_debug>0) logger.debug(msg);
	}
	public static void dbg1(String msg) {
		if(rz_debug>1) logger.debug(msg);
	}
	public static void dbg2(String msg) {
		if(rz_debug>2) logger.debug(msg);
	}
	public static void trace(String msg) {
		if(rz_trace>1) logger.info(msg);
		else if(rz_trace>0) logger.debug(msg);
	}
	
	private static	RendererConfiguration currentRenderer=null;
	
	public void setCurrentRenderer (RendererConfiguration renderer) {
		currentRenderer=renderer;
	}
	
	public RendererConfiguration getCurrentRenderer () {
		return currentRenderer;
	}
	
	public static String rz_SortTypeID2string(int sort_type) {
		switch (sort_type) {
		case PMS.SORT_NAME: 	return "SORT_NAME";
		case PMS.SORT_NAME_NUM: return "SORT_NAME_NUM";
		case PMS.SORT_NAME_ASC: return "SORT_NAME_ASC";
		case PMS.SORT_DATE: 	return "SORT_DATE";
		case PMS.SORT_DATE_R: 	return "SORT_DATE_R";
		case PMS.SORT_DEFAULT: 	return "SORT_DEFAULT";
		case PMS.SORT_ORIGIN: 	return "SORT_ORIGIN";
		case PMS.SORT_ORIGIN_R: return "SORT_ORIGIN_R";
		default:				return "Unknown";
		}
	}
	
	public static int rz_SortTypeString2id(int mode, String str) {
		int	sort_type=SORT_INVALID;
		
		if(str.equals("name")) 			sort_type=PMS.SORT_NAME;
		else if(str.equals("name_num"))	sort_type=PMS.SORT_NAME_NUM;
		else if(str.equals("name_asc"))	sort_type=PMS.SORT_NAME_ASC;
		else if(str.equals("date"))		sort_type=PMS.SORT_DATE;
		else if(str.equals("date_r"))	sort_type=PMS.SORT_DATE_R;
		else if(str.equals("default"))	sort_type=PMS.SORT_DEFAULT;
		else if(str.equals("def"))		sort_type=PMS.SORT_DEFAULT;	//same as default
		else {	
			if(mode==1) {	//for local contents
				// invalid
			}
			else {	//for web contents
				if(str.equals("original"))	sort_type=PMS.SORT_ORIGIN;
				else if(str.equals("origin"))	sort_type=PMS.SORT_ORIGIN;
				else if(str.equals("orig"))	sort_type=PMS.SORT_ORIGIN;
				else if(str.equals("org"))		sort_type=PMS.SORT_ORIGIN;
				
				else if(str.equals("original_r"))	sort_type=PMS.SORT_ORIGIN_R;
				else if(str.equals("origin_r"))	sort_type=PMS.SORT_ORIGIN_R;
				else if(str.equals("orig_r"))	sort_type=PMS.SORT_ORIGIN_R;
				else if(str.equals("org_r"))		sort_type=PMS.SORT_ORIGIN_R;
				else {
					//invalid
				}
			}
		}
		return sort_type;
	}
	
	public static String dt_sc_api;
	public static String dt_sc_str;
	public static String dt_sc_opt;
	
	public static String dt_ch_api;
	public static String dt_ch_str;
	public static String dt_ch_opt;
	
	public static String dt_strm1;
	public static String dt_strm2;
	public static String dt_strm3;
	
	public static long dt_modified_time=0;
	public static long dt_web_update_time=0;

	//--- regzamod add End
	
	/**
	 * Retrieves the {@link net.pms.configuration.PmsConfiguration PmsConfiguration} object
	 * that contains all configured settings for PMS. The object provides getters for all
	 * configurable PMS settings.
	 * @return The configuration object
	 */
	public static PmsConfiguration getConfiguration() {
		return configuration;
	}

	/**
	 * Returns the project properties object.
	 *
	 * @return The properties object.
	 */
	private static PmsProperties getProjectProperties() {
		return projectProperties;
	}

	/**
	 * Returns the project version for PMS.
	 *
	 * @return The project version.
	 */
	public static String getVersion() {
		return getProjectProperties().get("project.version");
	}

	/**
	 * Reads the properties file with project specific settings.
	 */
	private void initProjectProperties() {
		try {
			// Read project properties resource file.
			getProjectProperties().loadFromResourceFile("/resources/project.properties");

			// Temporary fix for backwards compatibility
			VERSION = getVersion();
		} catch (IOException e) {
			logger.error("Could not load project.properties");
		}
	}
	
	
}
