package lu.tudor.santec.gecamed.core.gui.plugin.filehandler;

import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import lu.tudor.santec.gecamed.core.gui.LoginScreen;
import lu.tudor.santec.gecamed.core.gui.MainFrame;
import lu.tudor.santec.gecamed.core.gui.utils.SystemInfo;
import lu.tudor.santec.gecamed.core.utils.FileUtils;
import lu.tudor.santec.gecamed.core.utils.ManagerFactory;
import lu.tudor.santec.gecamed.patient.ejb.entity.beans.IncidentEntry;
import lu.tudor.santec.gecamed.patient.ejb.session.beans.IncidentManagerBean;
import lu.tudor.santec.gecamed.patient.ejb.session.interfaces.IncidentManager;
import lu.tudor.santec.gecamed.patient.utils.EntryDeletedException;
import lu.tudor.santec.i18n.Translatrix;

import org.apache.commons.io.FilenameUtils;
import org.apache.log4j.Level;
import org.apache.log4j.Logger;

/**
 * @author jens.ferring(at)tudor.lu
 * 
 * @version
 * <br>$Log: FileOpener.java,v $
 * <br>Revision 1.12  2014-02-07 14:11:33  ferring
 * <br>catch error that appears in listener
 * <br>
 * <br>Revision 1.11  2014-01-02 08:25:01  ferring
 * <br>Option added to enter an opening command into the machine settings for all OS
 * <br>
 * <br>Revision 1.10  2013-12-10 11:54:16  ferring
 * <br>function added to get a temp file by name
 * <br>
 * <br>Revision 1.9  2013-11-25 13:15:52  ferring
 * <br>using default GECAMed method to write file
 * <br>
 * <br>Revision 1.8  2013-10-21 08:17:08  ferring
 * <br>message shown when file opening failed
 * <br>
 * <br>Revision 1.7  2013-10-11 16:50:09  donak
 * <br>Added pdf conversion support for all remaining file types (open office, ms office, flash, rtf). New office file types (docx, etc.) are not yet supported. An open office installation on the client system is necessary to make the new feature work.
 * <br>
 * <br>Revision 1.6  2013-10-08 08:53:11  ferring
 * <br>make sure all folders exist
 * <br>
 * <br>Revision 1.5  2013-09-13 14:18:56  ferring
 * <br>eSante bugs fixed and database implementation started
 * <br>
 * <br>Revision 1.4  2013-07-18 09:31:37  ferring
 * <br>gecamed template folder will now be created
 * <br>
 * <br>Revision 1.3  2013-07-15 06:18:37  ferring
 * <br>logging changed
 * <br>
 * <br>Revision 1.2  2013-07-02 09:21:44  ferring
 * <br>*** empty log message ***
 * <br>
 * <br>Revision 1.1  2013-06-10 08:08:26  ferring
 * <br>Handling to open files changed
 * <br>
 */

public class FileOpener extends Thread
{
	/* ======================================== */
	// CONSTANTS
	/* ======================================== */
	
	private static final String DEFAULT_SYSTEM_CALL_SETTING	= "defaultSystemCall";
	
	private static final String GECAMED_TMP_PATH	= System.getProperty("java.io.tmpdir") + File.separator + "gecamed" + File.separator;
	
	private static final File	GECAMED_TMP_DIR		= new File(GECAMED_TMP_PATH);
	
	private static final String[] LINUX_OPEN_CALLS	= new String[] 
	{
		"xdg-open",			// default
		"gnome-open",		// GNOME
		"kioclient exec",	// KDE>4
		"kfmclient exec"	// KDE<=3
	};
	
	
	
	/* ======================================== */
	// 		MEMBERS
	/* ======================================== */
	
	/** the logger Object for this class */
	private static Logger logger = Logger.getLogger(FileOpener.class.getName());
	
	private static boolean shutdownHookAdded	= false;
	
	private static String[]	linuxOpenCalls;
	
	private static Map<Integer, File>	fileCache	= new HashMap<Integer, File>();
	
	
	private File				file;
	
	private FileOpenerListener	listener;
	
	
	
	/* ======================================== */
	// 		CONSTRUCTORS
	/* ======================================== */
	
	private FileOpener (File file, FileOpenerListener l)
	{
		this.file		= file;
		this.listener	= l;
	}
	
	
	
	/* ======================================== */
	// 		CLASS BODY
	/* ======================================== */
	
	@Override
	public void run()
	{
		try
		{
			openViaDesktopAPI();
			if (listener != null)
			{
				try
				{
					listener.openedSuccessfull(file);
				}
				catch (Throwable t)
				{
					logger.error(t.getMessage(), t);
				}
			}
		}
		catch (NullPointerException e)
		{
			handleOpenerException(e.getMessage(), e);
		}
		catch (IllegalArgumentException e)
		{
			handleOpenerException(e.getMessage(), e);
		}
//		catch (UnsupportedOperationException e)
//		{
//			handleOpenerException(e);
//		}
		catch (IOException e)
		{
			String fileExt = "";
			try {
				FilenameUtils.getExtension(""+file.getName());
			} catch (Exception e2) {}
			handleOpenerException(
					Translatrix.getTranslationString("DefaultFileViewHandler.errorOpeningFileMissingApplication",
					new String[] { fileExt }), e);
		}
		catch (SecurityException e)
		{
			handleOpenerException(e.getMessage(), e);
		}
		catch (Throwable e)
		{
			try
			{
				openViaSystemCall();
				if (listener != null)
				{
					try
					{
						listener.openedSuccessfull(file);
					}
					catch (Throwable t)
					{
						logger.error(t.getMessage(), t);
					}
				}
			}
			catch (Throwable innerExeption)
			{
				handleOpenerException(innerExeption.getMessage(), innerExeption);
			}
		}
	}
	
	
	public static File open (byte[] data, String fileExtension, FileOpenerListener l)
	{
		try
		{
			File tmp = createGECAMedTempFile("", "."+fileExtension);
			FileUtils.writeFile(data, tmp);
			open(tmp, l);
			
			return tmp;
		}
		catch (IOException e)
		{
			logger.log(Level.ERROR, e.getMessage(), e);
			return null;
		}
	}
	
	
	public static void open (File file, FileOpenerListener l)
	{
		new FileOpener(file, l).start();
	}
	
	/**
	 * 
	 * @param prefix
	 * @param suffix 
	 * @return
	 * @throws IOException
	 */
	public static File createGECAMedTempFile (String prefix, String suffix) throws IOException
	{
		if (!shutdownHookAdded)
			addShutdownHook();
		
		if (GECAMED_TMP_DIR.exists() && !GECAMED_TMP_DIR.isDirectory())
		{
			GECAMED_TMP_DIR.delete();
			GECAMED_TMP_DIR.mkdirs();
		}
		else if (!GECAMED_TMP_DIR.exists() || !GECAMED_TMP_DIR.isDirectory())
		{
			GECAMED_TMP_DIR.mkdirs();
		}
		
		return File.createTempFile("gecamed_" + prefix, suffix, GECAMED_TMP_DIR);
	}
	
	
	public static void clearGECAMedTemps ()
	{
		if (GECAMED_TMP_DIR.exists())
		{
			for (File f : GECAMED_TMP_DIR.listFiles())
			{
				f.delete();
			}
		}
	}
	
	
	
	/* ======================================== */
	// 		HELP METHODS
	/* ======================================== */
	
	/**
	 * Try to open the file using the Desktop API. If it fails for some reason, 
	 * <code>false</code> is returned.
	 * 
	 * @return <code>true</code> if the file was open successfully, else <code>false</code>
	 * @throws IOException 
	 */
	private void openViaDesktopAPI () throws IOException
	{
		java.awt.Desktop.getDesktop().open(file);
		logger.info("using Desktop API to open file");
	}
	

	private boolean openViaSystemCall ()
	{
		String[]	command				= null;
		String[]	tryouts				= null;
		String		system				= null;
		String		defaultSystemCall	= MainFrame.getMachineSetting(DEFAULT_SYSTEM_CALL_SETTING);
		
		
		if (defaultSystemCall != null)
		{
			system	= "defaultSystemCall";
			command	= defaultSystemCall.split(" ");
		}
		else if (SystemInfo.getOperatingSystem() == SystemInfo.SYSTEM_WINDOWS) 
		{
			system	= "Windows";
			command	= new String[] { "rundll32.exe", "url.dll,FileProtocolHandler" };
		} 
		else if (SystemInfo.getOperatingSystem() == SystemInfo.SYSTEM_OSX) 
		{
			system	= "OS X";
			command	= new String[] { "open" };
		}
		else if (SystemInfo.getOperatingSystem() == SystemInfo.SYSTEM_LINUX) 
		{
			system	= "Linux";
			tryouts	= getLinuxCalls();
		}
		else
		{
			logger.warn("Current OS not found in list to perform system call and no default system call specified in the GECAMed machine settings.");
			return false;
		}
		
		logger.info("using ("+system+") system call to open file");
		if (tryouts == null)
			 return doSystemCall(command);
		else return trySystemCalls(tryouts);
	}
	
	
	private String[] getLinuxCalls ()
	{
		if (linuxOpenCalls == null)
		{
			String defaultCall;
			
			linuxOpenCalls	= new String[LINUX_OPEN_CALLS.length+1];
			
			// load the default call from settings
			defaultCall	= MainFrame.getMachineSetting(DEFAULT_SYSTEM_CALL_SETTING);
			
			for (int i = 0; i < linuxOpenCalls.length; i++)
			{
				if (i == 0)
					linuxOpenCalls[i]	= defaultCall;
				else
					linuxOpenCalls[i]	= LINUX_OPEN_CALLS[i-1];
			}
		}
		
		return linuxOpenCalls;
	}
	
	
	/**
	 * @param call
	 */
	private boolean doSystemCall(String[] call) 
	{
		boolean			success	= false;
		Runtime 		rt 		= Runtime.getRuntime();
		String[]		command	= Arrays.copyOf(call, call.length+1);
		Process			proc;
		InputStream 	errorStream;
		StringBuilder	cmd;
		byte[]			buffer;
		
		
		MainFrame.getInstance().setWaitCursor(true);
		try {
			// add the path
			command[command.length-1]	= file.getAbsolutePath();
			
			// execute the command
			proc 			= rt.exec(command);
			
			// wait for the process
			proc.waitFor();
			
			// log output
			errorStream		= proc.getErrorStream();
			if (errorStream.available() > 0)
			{
				buffer	= new byte[errorStream.available()];
				errorStream.read(buffer);
				cmd		= new StringBuilder();
				
				for (String c : command)
					cmd.append(c).append(" ");
				
				logger.error("Error while trying to open a file via system command."
						+ "\nCOMMAND: " + cmd.toString()
						+ "\nERROR:\n" + new String(buffer, "UTF-8"));
			}
			
			// check, whether the call was successful
			if (proc.exitValue() == 0)
				success	= true;
		} 
		catch (Exception e) 
		{
			cmd = new StringBuilder();
			
			for (String c : command)
				cmd.append(c).append(" ");
			
			logger.error("Error while trying to open a file via system command.\nCOMMAND: "
					+ cmd.toString(), e);
		}
		MainFrame.getInstance().setWaitCursor(false);
		
		return success;
	}
	
	
	private boolean trySystemCalls (String[] tryout)
	{
		boolean		success	= false;
		String[]	calls	= linuxOpenCalls;
		String[]	call;
		
		
		MainFrame.getInstance().setWaitCursor(true);
		for (int i = 0; !success && i < calls.length; i++)
		{
			if (calls[i] == null)
				continue;
			
			// prepare command
			call						= calls[i].split(" ");
			
			success	= doSystemCall(call);
//			command						= Arrays.copyOf(call, call.length+1);
//			command[command.length-1]	= file.getAbsolutePath();
//			
//			// execute command
//			proc 						= rt.exec(command);
//			// wait for process
//			proc.waitFor();
			// check, if retVal == 0, we have a gnome environment and 
			// we can use gnome-open
//			if (proc.exitValue() == 0)
			if (success)
			{
//				success	= true;
				
				if (i > 0)
				{
					// set this call as default in client settings
					MainFrame.setMachineSetting(DEFAULT_SYSTEM_CALL_SETTING, calls[i]);
				}
			}
		}
		MainFrame.getInstance().setWaitCursor(false);
		
		if (!success)
			logger.warn(
					"No working Linux system call found. " +
					"Please close all GECAMed clients on this PC and enter a valid call into the GECAMed " +
					"client properties under the key \"" + 
					DEFAULT_SYSTEM_CALL_SETTING + "\".\n" +
					"You find the user properties under: " + 
					LoginScreen.PROPERTIES_USER_HOME.getAbsolutePath() + "\n" +
					"Add a line, that looks like this: \""+DEFAULT_SYSTEM_CALL_SETTING+"=xdg-open\"\n" +
					"Where \"xdg-open\" needs to be replaced, by a proper command to open your file. " +
					"The command depends on your operation system.");
		
		return success;
	}
	
	
	private void handleOpenerException (String reason, Throwable e)
	{
		if (listener == null)
			logger.error("Couln'd open file \""+file.getAbsolutePath()+"\" " + reason, e);
		else
		{
			try
			{
				listener.openingFailed(file, reason, e);
			}
			catch (Throwable t)
			{
				logger.error(t.getMessage(), t);
			}
		}
	}
	
	
	private static void addShutdownHook ()
	{
		shutdownHookAdded = true;
		MainFrame.addShutdownHook("clear GECAMed temps", new Thread()
		{
			@Override
			public void run ()
			{
				clearGECAMedTemps();
			}
		});
	}
	
	
	/**
	 * @author jens.ferring(at)tudor.lu
	 * 
	 * Notifies the listener, whether the opening of the file succeeded or failed.
	 */
	public interface FileOpenerListener
	{
		public void openedSuccessfull (File file);
		public void openingFailed (File file, String reason, Throwable cause);
	}
	
	
	public static File getGECAMedTempFile (String tmpFilename)
	{
		File tmpFile = new File(GECAMED_TMP_PATH, tmpFilename);
		
		
		if (tmpFile.exists())
			return tmpFile;
		else 
			return null;
	}
	
	
	public static byte[] loadBinary (IncidentEntry entry) throws FileNotFoundException, EntryDeletedException, Exception
	{
		if (entry.getBinaryContent() == null)
		{
			File tmpFile = getCachedFile(entry);
			if (tmpFile != null)
			{
				byte[] data = FileUtils.readFile(tmpFile);
				entry.setBinaryContent(data);
				return data;
			}
			
			IncidentManager iManager = (IncidentManager) ManagerFactory.getRemote(IncidentManagerBean.class);
			IncidentEntry ie = iManager.loadBinary(entry);
			if (ie == null)
				throw new EntryDeletedException(entry);
			
			entry.setBinaryContent(ie.getBinaryContent());
		}
		
		return entry.getBinaryContent();
	}
	
	
	public static File getCachedFile (IncidentEntry entry)
	{
		File tmpFile = fileCache.get(entry.getId());
		if (tmpFile != null && tmpFile.exists())
		{
			return tmpFile;
		}
		else
		{
			return null;
		}
	}
	
	
	public static void cacheFile (File file, IncidentEntry entry)
	{
		if (entry == null || entry.getId() == null)
			return;
		
		fileCache.put(entry.getId(), file);
	}
}
