import netscape.javascript.JSObject;

import java.applet.Applet;
import java.io.*;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Iterator;
import java.util.List;
import java.util.zip.ZipEntry;
import java.util.zip.ZipFile;

public class MediaPlayerLoader extends Applet {

	private JSObject jsObject = null;
	private String jsOnProgress = null;

	public MediaPlayerLoader() {
	}

	public void start() {
		jsObject = JSObject.getWindow(this);
		jsOnProgress = getParameter("ON_PROGRESS");

		// check java version
		String currentVersion = System.getProperty("java.version");
		String minJavaVersion = getParameter("MIN_JAVA_VERSION");
		if (minJavaVersion != null && minJavaVersion.length() > 0) {
			if (compareVersions(currentVersion, minJavaVersion) < 0) {
				invokeCompleteCallback(
						false,
						"Stratus requires Java Runtime Environment version "
								+ minJavaVersion
								+ " or higher. The version of JRE installed on this computer is "
								+ currentVersion
								+ ". Please download latest JRE from http://www.sun.com");
				return;
			}
		}
		
		String jsOnInitCompleted = getParameter("ON_INIT_COMPLETED");
		if (jsOnInitCompleted != null) {
			invokeJSFunc(jsOnInitCompleted + "()");
		}

		// load Media Player library
		try {
			loadMediaPlayerLib();
			invokeCompleteCallback(true,"");
		} catch (Exception e) {
			String msg = "Error loading Video Player components: " + (e.getMessage() == null ? e.toString() : e.getMessage());
			log(msg);
			e.printStackTrace(System.out);
			invokeCompleteCallback(false, msg);
		}
	}


	/**
	 * Check if local computer got MediaPlayer library, specified by parameter
	 * mediaPlayerVersion
	 *
	 * @throws Exception
	 */
	protected void loadMediaPlayerLib() throws Exception {
		String mediaPlayerVersion = getParameter("PLAYER_VERSION");
		if (mediaPlayerVersion == null) {
			throw new Exception("Please specify \"PLAYER_VERSION\" param");
		}

		// Get library filename
		//
		String localPath = System.getProperty("java.io.tmpdir");
		String tempDirForExtractedFiles = localPath + File.separator + Math.random();
		String sys = System.getProperty("os.name").toLowerCase();
		String vnDirName = "videoNEXT";
		String urlStr = getParameter("SOURCE");
		if (urlStr == null) {
			urlStr = getCodeBase().getPath();
		}

		File tempDirForExtractedFilesHandle = new File(tempDirForExtractedFiles);
		boolean isCreated = tempDirForExtractedFilesHandle.mkdir();
		log("create temp dir for extracted files " + tempDirForExtractedFilesHandle.getAbsolutePath() + " is " +isCreated);


		log("Add MPlayer plugin dll");
		if (sys == null) {
			throw new Exception("Unable to identify your operation system");
		} else if (System.getProperty("os.name").toLowerCase().startsWith("linux")) { // Linux
			String libName = "libMediaPlayer.so";
			String localInstallPath = localPath;
			String localLibName = "libMediaPlayer" + mediaPlayerVersion + ".so";
	                
			File localFile = new File(localInstallPath + File.separator + localLibName);
			log("Looking for " + localFile.getAbsolutePath());
			log("exist is " + localFile.exists());
			if (!(localFile.exists() && localFile.canRead())) {
				if (!addLibrary(urlStr + "MediaPlayer.linux.jar", libName, localPath, localInstallPath, localLibName, mediaPlayerVersion))
				{
					throw new Exception("Library was not added");
				}
			}
			
		} else if (System.getProperty("mrj.version") != null) { // Mac OS X		
			String libName = "vnMPlayer.plugin";
			String localInstallPath = System.getProperty("user.home") + File.separator + "Library" + File.separator + "Internet Plug-Ins";
			String mime = gerURLText(urlStr + libName + ".mime").replace("/", "-");
			String localLibName = libName + "." + mime;

			File localFile = new File(localInstallPath + File.separator + localLibName);
			log("Looking for downloaded file: " + localFile.getAbsolutePath());
			log("exist is " + localFile.exists());
			// every time reload MPlayer plugin for now
			if (!localFile.exists()) {
				// boolean isDeleted = deleteDirectory(localFile);
				// log("delete of " + localFile.getAbsolutePath() + " is " + isDeleted);

				log("try to add");
				if (!addLibrary(urlStr + libName + ".zip", libName, tempDirForExtractedFiles, localInstallPath, localLibName, mime)){
					throw new Exception("Library was not added");
				}
				File fe = new File(localFile, "Contents" + File.separator + "MacOS" + File.separator + "vnMPlayer");
				boolean isExeced = fe.setExecutable(true);
				log("Execed " + fe.getAbsolutePath() + " is " + isExeced);
			}			
		} else if (sys.startsWith("windows")) { // Win
			String libName = "vnMPlayer.win32.plugin";
			String localInstallPath = System.getProperty("user.home") + File.separator + "Application Data" + File.separator + vnDirName;
			String mime = gerURLText(urlStr + libName.replace("win32.", "") + ".mime").replace("/", "-");
			
			File dirForPluginOnWindowsHandle = new File(localInstallPath);			
			if(!dirForPluginOnWindowsHandle.exists()){				
				boolean isVNDirCreated = dirForPluginOnWindowsHandle.mkdir();
				log("create " + localInstallPath + " directory for plugin on Windows is " + isVNDirCreated);
			}
			
			boolean isOneOfLibsNotFound = false;
			String clientLibName = "";
			for(int i = 0; i < 4; i++){
				if(i == 0){
					clientLibName = "libgcc_s_sjlj-1";
				}
				else if(i == 1){
					clientLibName = "VNMediaClient." + mime;
				}
				else if(i == 2){
					clientLibName = "libstdc++-6";
				}
				else if(i == 3){
					clientLibName = "npvnMPlayer." + mime;
				}
				File localFile = new File(localInstallPath + File.separator + clientLibName + ".dll");
				log("Looking for installed file: " + localFile.getAbsolutePath() + " " + localFile.exists());
				if (!localFile.exists()) {
					isOneOfLibsNotFound = true;
					break;
				}
			}			
						
			// if one of libraries not found
			if (isOneOfLibsNotFound) {
				log("try to add");
				if (!addLibrary(urlStr + libName + ".zip", libName, tempDirForExtractedFiles, localInstallPath, "", mime)){
					throw new Exception("Library was not added");
				}
			}			
		}

		if (isCreated) {
			boolean isDeleted = deleteDirectory(tempDirForExtractedFilesHandle);
			log("delete of " + tempDirForExtractedFilesHandle.getAbsolutePath() + " is " + isDeleted);
		}
	}

	/**
	 * add library to system
	 * @param urlPath link to zip archive gor download
	 * @param libName name of library for extracting
	 * @param localTempPath temp dir for extracting
	 * @param localInstallPath dir where library should be after extracting
	 * @param localLibName name of library localInstallPath dir after extracting
	 * @param version
	 * @return
	 */
	private boolean addLibrary(String urlPath, String libName, String localTempPath, String localInstallPath, String localLibName, String version)
	{
		String sys = System.getProperty("os.name").toLowerCase();
		boolean result = true;

		File extractedFile, resultedFile;
		String localZipName = libName + "." + version + ".zip";
		String localZipPath = localTempPath + File.separator + localZipName;
		log("Download " + urlPath + " to " + localZipPath);
		try {
			downloadURL(urlPath, localZipPath);
		} catch (IOException e) {
			e.printStackTrace();
			result = false;
		} finally {
			log("Extract " + localZipPath + " to " + localTempPath);
			extractZip(localZipPath, localTempPath);

			File zipFile = new File(localZipPath);
			boolean isDeleted = zipFile.delete();
			log("Delete of " + zipFile.getAbsolutePath() + " is " + isDeleted);

			if (System.getProperty("mrj.version") != null || sys.startsWith("linux")) { // Mac OS X and Linux
				extractedFile = new File(localTempPath + File.separator + libName);
				log("Looking for " + extractedFile.getAbsolutePath());
				if (!extractedFile.exists())
				{
					result = false;
				} else {
					resultedFile = new File(localInstallPath + File.separator + localLibName);
					// renameTo may not work for separate volumes
					copyAndDelete(extractedFile, resultedFile);
				}
			} 
			else if (sys.startsWith("windows")) { // Win				
				if(libName.indexOf("plugin") != -1){ // browser plugin
					for(int i = 0; i < 4; i++){
						if(i == 0){
							libName = "libgcc_s_sjlj-1";
						}
						else if(i == 1){
							libName = "VNMediaClient." + version.substring("application-x-vnmplayer-".length());
						}
						else if(i == 2){
							libName = "libstdc++-6";
						}
						else if(i == 3){
							libName = "npvnMPlayer";
						}
				
						extractedFile = new File(localTempPath + File.separator + libName + ".dll");
						log("Looking for extracted file: " + extractedFile.getAbsolutePath());
						if (!extractedFile.exists())
						{
							result = false;
							break;
						} else {
							String resultedFileName = libName;
							if(resultedFileName.equals("npvnMPlayer")){
								resultedFileName+= "." + version;
							}
						
							resultedFile = new File(localInstallPath + File.separator + resultedFileName + ".dll");
							if (!resultedFile.exists()){
								// renameTo may not work for separate volumes
								copyAndDelete(extractedFile, resultedFile);
							}
							else{
								log("File " + resultedFile.getAbsolutePath() + " already exists");
							}
						
							if(libName.equals("npvnMPlayer")){
								if(resultedFile.exists()){
									if(!registerLibraryForWindows(resultedFile.getAbsolutePath())){
										log("File " + resultedFile.getAbsolutePath() + " not registered");
									}
								}
								else{
									log("File " + resultedFile.getAbsolutePath() + " not exists");
								}
							}
						}
					}
				}
				else{ // old java library
					extractedFile = new File(localTempPath + File.separator + libName);
					log("Looking for extracted file: " + extractedFile.getAbsolutePath());
					
					if (extractedFile.exists()){
						resultedFile = new File(localInstallPath + File.separator + localLibName);
						if (!resultedFile.exists()){
							// renameTo may not work for separate volumes
							copyAndDelete(extractedFile, resultedFile);
						}
						else{
							log("File " + resultedFile.getAbsolutePath() + " already exists");
						}
					}
				}
			}
		}

		return result;
	}

	/**
	 * download file from url to dir, call JS function for downloading progress
	 * @param fromURLpath url for downloading
	 * @param toFilePath dir for extract
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	private void downloadURL(String fromURLpath, String toFilePath) throws MalformedURLException, IOException {
		BufferedInputStream in = null;
		FileOutputStream fout = null;
		try {
			URL url = new URL(fromURLpath);
			URLConnection urlConnection = url.openConnection();
			urlConnection.setUseCaches(false);

			in = new BufferedInputStream(urlConnection.getInputStream());
			fout = new FileOutputStream(toFilePath);

			int percent = 0, lastPercent = 0;
			int length = 0;
			Object[] params = new Object[1];
			byte buffer[] = new byte[65536];
			int size;
			int fileLength = urlConnection.getContentLength();

			while ((size = in.read(buffer, 0, buffer.length)) != -1) {
				fout.write(buffer, 0, size);

				length += size;
				percent = (int)Math.floor(100 * (double)length / (double)fileLength);
				if (Math.abs(percent - lastPercent) >= 3)
				{
					lastPercent = percent;
					params[0] = percent;
					jsObject.call(jsOnProgress, params);
				}
			}
		} finally {
			if (in != null)
				in.close();
			if (fout != null)
				fout.close();
		}
	}

	/**
	 * get text from specified file
	 * @param fromURLpath url to etxt file
	 * @return
	 * @throws MalformedURLException
	 * @throws IOException
	 */
	private String gerURLText(String fromURLpath) throws MalformedURLException, IOException {
		BufferedReader in = null;
		String result = "";
		try {
			URL url = new URL(fromURLpath);
			URLConnection urlConnection = url.openConnection();
			urlConnection.setUseCaches(false);

			in = new BufferedReader(new InputStreamReader(urlConnection.getInputStream()));

			String line;
			while ((line = in.readLine()) != null) {
				result += line;
			}
		} finally {
			if (in != null)
				in.close();
		}

		return result;
	}

	/**
	 * extract zip
	 * @param pathZip path to zip file
	 * @param extractToDir path to dir for extract
	 * @return
	 */
	private boolean extractZip(String pathZip, String extractToDir) {

		boolean result = true;
		try {
			ZipFile zipFile = new ZipFile(pathZip);
			Enumeration<?> enu = zipFile.entries();
			while (enu.hasMoreElements()) {
				ZipEntry zipEntry = (ZipEntry) enu.nextElement();

				String name = zipEntry.getName();
				long size = zipEntry.getSize();
				long compressedSize = zipEntry.getCompressedSize();
				System.out.printf("name: %-20s | size: %6d | compressed size: %6d\n", name, size, compressedSize);

				File file = new File(extractToDir + File.separator + name);
				if (zipEntry.isDirectory()) {
					file.mkdirs();
					continue;
				}

				File parent = file.getParentFile();
				if (parent != null) {
					parent.mkdirs();
				}

				InputStream is = zipFile.getInputStream(zipEntry);
				FileOutputStream fos = new FileOutputStream(file);
				byte[] bytes = new byte[1024];
				int length;
				while ((length = is.read(bytes)) >= 0) {
					fos.write(bytes, 0, length);
				}
				is.close();
				fos.close();

			}
			zipFile.close();
		} catch (IOException e) {
			e.printStackTrace();
		}

		return result;
	}

	/**
	 * delete directory
	 * @param path
	 * @return
	 */
	private boolean deleteDirectory(File path) {
		if (path.exists()) {
			File[] files = path.listFiles();
			for (File file : files) {
				if (file.isDirectory()) {
					deleteDirectory(file);
				} else {
					file.delete();
				}
			}
		}
		return (path.delete());
	}

	/**
	 * copy file
	 * @param source Source file
	 * @param destination Destination file
	 * @return Is coping operation successful
	 */
	private boolean copyFile(File source, File destination) {
		try {
			InputStream in = new FileInputStream( source );
			OutputStream out = new FileOutputStream( destination );
			byte[] buf = new byte[4096]; // 4K blocks
			int len;
			while( ( len = in.read( buf ) ) > 0 ) {
				out.write(buf, 0, len);
			}
			in.close();
			out.close();
		}
		catch( FileNotFoundException ex ) {
			return false;
		}
		catch( IOException ex ) {
			return false;
		}
		return true;
	}

	/**
	 * copy directory tree
	 * @param sourcePath Source directory which should be copied
	 * @param destinationPath Destination directory where copy should be
	 * @return Is operation successful
	 */
	private boolean copyDirectory(File sourcePath, File destinationPath) {
		if( !sourcePath.exists() )
			return false;
		if( !destinationPath.mkdir() )
			return false;
		File[] files = sourcePath.listFiles();
		for (File file : files) {
			if (file.isDirectory()) {
				if( !copyDirectory(file, new File(destinationPath, file.getName())) )
					return false;
			} else {
				if( !copyFile(file, new File(destinationPath, file.getName())) )
					return false;
			}
		}
		return true;
	}

	/**
	 * move file or directory using copy+delete
	 * used cause of BUGGY MAC which cant do renameTo to other volume
	 * @param source Source file or directory
	 * @param destination Destination file or directory
	 * @return Is operation successful
	 */
	private boolean copyAndDelete(File source, File destination) {
		boolean isCopied, isDeleted;
		if( source.isDirectory() ) {
			isCopied = copyDirectory(source, destination);
			log("Coping " + source.getAbsolutePath() + " to " + destination.getAbsolutePath() + " is " + isCopied);
			isDeleted = deleteDirectory(source);
			log("Deleting " + source.getAbsolutePath() + " is " + isDeleted);
		} else {
			isCopied = copyFile(source, destination);
			log("Coping " + source.getAbsolutePath() + " to " + destination.getAbsolutePath() + " is " + isCopied);
			isDeleted = source.delete();
			log("Delete of " + source.getAbsolutePath() + " is " + isDeleted);
		}
		return isCopied && isDeleted;
	}

	/**
	 *
	 * @param msg
	 */
	private void log(String msg) {
		System.out.println("[MediaPlayerLoader] " + msg);		
	}

	/**
	 * invokeCompleteCallback
	 * @param isLoadedSuccessfully
	 * @param message
	 */
	private void invokeCompleteCallback(boolean isLoadedSuccessfully, String message) {
		String jsOnLoadCompleted = getParameter("ON_LOAD_COMPLETED");
		if (jsOnLoadCompleted != null) {
			invokeJSFunc(jsOnLoadCompleted + "("
					+ (isLoadedSuccessfully ? "true" : "false") + "," + "\""
					+ message + "\")" );
		}
	}

	/**
	 * invokeJSFunc
	 * @param func - function_name(with parameters)
	 */
	private void invokeJSFunc(String func) {
		try {
			jsObject.eval(func);
		} catch (Exception e) {
			log("JSObject call failed (" + e + "). Trying javascript URL");
			try {
				getAppletContext().showDocument(new URL("javascript:" + func));
			} catch (MalformedURLException me) {
				log("Error: unable to call function" + func);
			}
		}
	}

	/**
	 *
	 * @param version1
	 * @param version2
	 * @return
	 */
	private int compareVersions(String version1, String version2) {
		Iterator iterator1 = split(version1).iterator();
		Iterator iterator2 = split(version2).iterator();
		Integer minInt = new Integer(Integer.MIN_VALUE);
		while (iterator1.hasNext() || iterator2.hasNext()) {
			Integer segment1 = iterator1.hasNext() ? (Integer) iterator1.next() : minInt;
			Integer segment2 = iterator2.hasNext() ? (Integer) iterator2.next() : minInt;
			int result = segment1.compareTo(segment2);
			if (result != 0) {
				return result;
			} // if equals - compare next segments
		}
		return 0; // versions are identical
	}

	/**
	 *
	 * @param str
	 * @return
	 */
	private List split(String str) {
		List list = new ArrayList(4);

		if (str != null) {
			int beginIndex = 0;
			for (int i = 0; i < str.length(); i++) {
				try {
					if (!Character.isDigit(str.charAt(i))) {
						if (i > beginIndex) {
							list.add(new Integer(str.substring(beginIndex, i)));
						}
						beginIndex = i + 1;
					} else if (i == str.length() - 1) {
						if (i >= beginIndex) {
							list.add(new Integer(str.substring(beginIndex, i + 1)));
						}
					}
				} catch (NumberFormatException e) {
					list.add(new Integer(Integer.MIN_VALUE));
				}
			}
		}
		return list;
	}
	
	/**
	 * register library in Windows
	 * @param libPath path to lirary
	 */
	private boolean registerLibraryForWindows(String libPath){
		boolean result = true;
		try
        { 		
			String command = "regsvr32 /s \"" + libPath + "\"";
			Process process = Runtime.getRuntime().exec(command);
			
			if (process.waitFor() == 0) {				
				log("Library " + libPath + " registered");
			}
			else{
				log("Library " + libPath + " registration failed with code " + process.exitValue());
				result = false;
			}
		} 
        catch (IOException e){
			e.printStackTrace();
			result = false;
        } 		
        catch (InterruptedException e){
			e.printStackTrace();
			result = false;
        }		  
		
		return result;
	}	
}
