package com.jens.rhasspy.visualtool;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.security.SecureRandom;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Logger;

import javax.net.ssl.SSLServerSocket;
import javax.net.ssl.SSLServerSocketFactory;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpression;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

public class Miscellaneous
{
	protected static Calendar logDate;
	static Logger logger = Logger.getLogger(Miscellaneous.class.getName());
	static FileHandler fileHandler = null;	
	public static final String genericErrorMessage = "sldaskdm32oierror";
	
	protected static boolean logCleanerRunning = false;
	public static final String lineSeparator = System.getProperty("line.separator");
    
    public static String getIniLikeValue(String fullText, String searchObject, String separator)
    {
        fullText = fullText.trim();

        if (fullText.toLowerCase().contains(searchObject.toLowerCase()))
        {
            if (fullText.toLowerCase().contains(separator.toLowerCase()))    // multiple values
            {
                int start = fullText.toLowerCase().indexOf(searchObject.toLowerCase() + "=") + searchObject.length() + 1;
                int end = fullText.toLowerCase().indexOf(separator.toLowerCase(), start);

                if (end == -1)    // if the last index in fullText
                    return fullText.substring(start);
                else
                    return fullText.substring(start, end);
            }
            else                                // only one value
            {
                return fullText.substring(fullText.indexOf("=") + 1);
            }
        }
        else
        {
            logEvent("Index " + searchObject + " cannot be found in full text.", 5);
            return null;
        }
    }

    public static String addOrReplaceIniLikeValue(String fullText, String key, String value, String separator)
    {
        fullText = fullText.trim();

        if (fullText.startsWith(key + "=") | fullText.contains(separator + key + "="))
        {
            if (fullText.contains(separator))    // multiple values
            {
                int start = fullText.indexOf(key + "=") + key.length() + 1;
                int end = fullText.indexOf(separator, start);

                String oldValue;
                if (end == -1)    // if the last index in fullText
                    oldValue = fullText.substring(start);
                else
                    oldValue = fullText.substring(start, end);

                return fullText.replace(key + "=" + oldValue, key + "=" + value);
            }
            else                                // only one value
            {
                return key + "=" + value;
            }
        }
        else
        {
            if(fullText.length() > 0)
                return fullText + separator + key + "=" + value;
            else
                return key + "=" + value;
        }
    }

    public static String removeIniLikeValue(String fullText, String key, String separator)
    {
        fullText = fullText.trim();
        String toBeRemoved = getIniLikeValue(fullText, key, separator);

        if(toBeRemoved != null && fullText.contains(toBeRemoved))
        {
            if(fullText.contains(";"))
            {
                if(fullText.contains(";" + toBeRemoved))
                    return fullText.replace(";" + toBeRemoved, "");
                else
                    return fullText.replace(toBeRemoved + ";", "");
            }
            else
                return "";
        }
        else
            return fullText;
    }

    public static String updateIniLikeValue(String fullText, String key, String value, String separator)
    {
        if (fullText.contains(key))
        {
            if (fullText.contains(separator))    // multiple values
            {
                String[] lines = fullText.split(separator);
                for(int i=0; i < lines.length; i++)
                {
                    if(lines[i].startsWith(key))
                    {
                        int equalsPosition = lines[i].indexOf(key + "=") + key.length() + 1;
                        lines[i] = lines[i].substring(0, equalsPosition) + value;
                        break;
                    }
                }

                return Miscellaneous.arrayImplode(separator, lines);
            }
            else                                // only one value
            {
                return fullText + separator + key + "=" + value;
            }
        }
        else
        {
            return fullText + separator + key + "=" + value;
        }
    }

    public static boolean isNumeric(String stringToCheck)
    {
        String character;

        for(int i = 0; i < stringToCheck.length(); i++)
        {
            character = stringToCheck.substring(i, i + 1);

            if(!character.matches("[1234567890]") && !character.equals(".") && !character.equals(","))
                return false;
        }

        return true;
    }

    public static boolean arraySearch(String[] haystack, String needle, boolean caseSensitive, boolean matchFullLine)
    {
        if(matchFullLine)
        {
            if(caseSensitive)
            {
                for (String s : haystack)
                {
                    if (s.equals(needle))
                        return true;
                }
            }
            else
            {
                for (String s : haystack)
                {
                    if (s.toLowerCase().equals(needle.toLowerCase()))
                        return true;
                }
            }
        }
        else
        {
            if(caseSensitive)
            {
                for (String s : haystack)
                {
                    if (s.contains(needle))
                        return true;
                }
            }
            else
            {
                for (String s : haystack)
                {
                    if (s.toLowerCase().contains(needle.toLowerCase()))
                        return true;
                }
            }
        }

        return false;
    }

    public static boolean arraySearch(ArrayList<String> requestList, String needle, boolean caseSensitive, boolean matchFullLine)
    {
        return arraySearch(requestList.toArray(new String[requestList.size()]), needle, caseSensitive, matchFullLine);
    }

    public static String arrayImplode(String glue, String... values)
    {
        String returnString = "";

        for (String s : values)
            returnString += s + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, ArrayList<String> arrayList)
    {
        String returnString = "";

        for (Object s : arrayList)
            returnString += s + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, Object[] array)
    {
        String returnString = "";

        for (Object s : array)
            returnString += s.toString() + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String arrayImplode(String glue, HashMap<String, String> map)
    {
        String returnString = "";

        for (String key : map.keySet())
            returnString += map.get(key) + glue;

        if (returnString.length() > 0)
            returnString = returnString.substring(0, returnString.length() - glue.length());

        return returnString;
    }

    public static String getSystemTimezone()
    {
//		https://coderanch.com/t/386398/java/System-Timezone
        TimeZone tz = Calendar.getInstance().getTimeZone();
//		System.out.println(tz.getDisplayName());// (i.e. Moscow Standard Time)
        return tz.getID();

//		Should return something like this: Asia/Calcutta
    }

    public static String createPassword(int length)
    {
        final String allowedChars = "0123456789abcdefghijklmnopqrstuvwABCDEFGHIJKLMNOP!§$%&?*+#";
        SecureRandom random = new SecureRandom();
        StringBuilder pass = new StringBuilder(length);
        for (int i = 0; i < length; i++)
        {
            pass.append(allowedChars.charAt(random.nextInt(allowedChars.length())));
        }
        return pass.toString();
    }

    public static Element getXmlTree(String inputString) throws SAXException, IOException, ParserConfigurationException
    {
        DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
        DocumentBuilder builder = factory.newDocumentBuilder();

//		Create a Document from a file or stream
		/*
		StringBuilder xmlStringBuilder = new StringBuilder();
		xmlStringBuilder.append("<?xml version="1.0"?> <class> </class>");
		ByteArrayInputStream input = new ByteArrayInputStream(xmlStringBuilder.toString().getBytes("UTF-8"));
		*/
//		Document doc = builder.parse(input);
        Document doc = builder.parse(new InputSource(new StringReader(inputString)));

        Element rootElement = doc.getDocumentElement();

        return rootElement;
	/*
//		Examine attributes

		//returns specific attribute
		root.getAttribute("attributeName");

		//returns a Map (table) of names/values
		root.getAttributes();

//		Examine sub-elements

		//returns a list of subelements of specified name
		root.getElementsByTagName("subelementName");

		//returns a list of all child nodes
		root.getChildNodes();
	*/
    }

    public static String xmlToString(Node node, boolean omitXmlDeclaration, boolean prettyPrint)
    {
        if (node == null)
        {
            throw new IllegalArgumentException("node is null.");
        }

        try
        {
            // Remove unwanted whitespaces
            node.normalize();
            XPath xpath = XPathFactory.newInstance().newXPath();
            XPathExpression expr = xpath.compile("//text()[normalize-space()='']");
            NodeList nodeList = (NodeList) expr.evaluate(node, XPathConstants.NODESET);

            for (int i = 0; i < nodeList.getLength(); ++i)
            {
                Node nd = nodeList.item(i);
                nd.getParentNode().removeChild(nd);
            }

            // Create and setup transformer
            Transformer transformer = TransformerFactory.newInstance().newTransformer();
            transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");

            if (omitXmlDeclaration == true)
            {
                transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
            }

            if (prettyPrint == true)
            {
                transformer.setOutputProperty(OutputKeys.INDENT, "yes");
                transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");
            }

            // Turn the node into a string
            StringWriter writer = new StringWriter();
            transformer.transform(new DOMSource(node), new StreamResult(writer));
            return writer.toString();
        }
        catch (TransformerConfigurationException e)
        {
            e.printStackTrace();
        }
        catch (TransformerException e)
        {
            throw new RuntimeException(e);
        }
        catch (XPathExpressionException e)
        {
            throw new RuntimeException(e);
        }

        return null;
    }


    public static String getStackTrace()
    {
        StringBuilder trace = new StringBuilder();
        for (StackTraceElement ste : Thread.currentThread().getStackTrace())
            trace.append(ste + Miscellaneous.lineSeparator);

        trace.delete(trace.length(), trace.length());

        return trace.toString();
    }

    public static String getStackTraceAsString(Throwable aThrowable)
    {
        Writer result = new StringWriter();
        PrintWriter printWriter = new PrintWriter(result);
        aThrowable.printStackTrace(printWriter);
        return result.toString();
    }

    /**
     * Defines a custom format for the stack trace as String.
     */
    public static String getCustomStackTrace(Throwable aThrowable)
    {
        //add the class name and any message passed to constructor
        StringBuilder result = new StringBuilder( "Stacktrace: " );
        result.append(aThrowable.toString());
        String NEW_LINE = System.getProperty("line.separator");
        result.append(NEW_LINE);

        //add each element of the stack trace
        for (StackTraceElement element : aThrowable.getStackTrace())
        {
            result.append(element);
            result.append(NEW_LINE);
        }
        return result.toString();
    }

    public static ArrayList<Element> getSubElements(Node node, String parentName, String childName)
    {
        ArrayList<Element> returnList = new ArrayList<Element>();
        NodeList nodeElements = ((Element)node).getElementsByTagName("houses");
        for(int i = 0; i < nodeElements.getLength(); i++)
        {
            if (nodeElements.item(i).getNodeType() == Node.ELEMENT_NODE)
            {
                NodeList childElements = nodeElements.item(i).getChildNodes();
                for (int j = 0; j < childElements.getLength(); j++)
                {
                    Element element = (Element) childElements.item(j);
//					HouseTemplate house = HouseTemplate.fromXmlStringStatic(Diverse.getXmlFull(element));
                    returnList.add(element);
                }
            }
        }

		/*ArrayList<Element> returnList = new ArrayList<>();

		NodeList children = ((Element)node).getElementsByTagName(parentName);

		for(int i=0; i < children.getLength(); i++)
		{
			returnList.add((Element)children.item(i));
		}*/

        return returnList;
    }

    public static double round(double value, int places)
    {
        if (places < 0) throw new IllegalArgumentException();

        BigDecimal bd = new BigDecimal(Double.toString(value));
        bd = bd.setScale(places, RoundingMode.HALF_UP);
        return bd.doubleValue();
    }

    public static Class getCallerClass()
    {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        for (int i=1; i<stElements.length; i++)
        {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(Miscellaneous.class.getName()) && ste.getClassName().indexOf("java.lang.Thread")!=0)
            {
                return ste.getClass().getDeclaringClass();
            }
        }
        return null;
    }

    public static Class getCallerCallerClassName()
    {
        StackTraceElement[] stElements = Thread.currentThread().getStackTrace();
        String callerClassName = null;
        for (int i=1; i<stElements.length; i++)
        {
            StackTraceElement ste = stElements[i];
            if (!ste.getClassName().equals(Miscellaneous.class.getName())&& ste.getClassName().indexOf("java.lang.Thread")!=0)
            {
                if (callerClassName==null)
                {
                    callerClassName = ste.getClassName();
                }
                else if (!callerClassName.equals(ste.getClassName()))
                {
                    try
                    {
                        Class returnClass = Class.forName(ste.getClassName());
                        return returnClass;
                    }
                    catch(Exception e)
                    {}
//                    return ste.getClassName();
                }
            }
        }
        return null;
    }
    
    public static int countOccurences(String input, String needle)
    {
    	String out = input.replace (needle, "");
    	int lenDiff = input.length () - out.length ();
    	return lenDiff / needle.length();
    }
    
	static File getLogFileFullPath()
	{
		File logFile;
		File folder = new File(Settings.logFolderPath);
					
		if((folder.exists() && folder.canWrite()) | folder.mkdir())
			logFile = new File(folder.getAbsolutePath() + "/" + Settings.logFileName);
		else
			logFile = new File(Settings.logFileName);

//		System.out.println("Using " + logFile.getAbsolutePath().toString() + " as log file path.");
		
		return logFile;
	}
	
	public static synchronized void logEvent(String eventDescription, int logLevel)
	{	
		if(logLevel <= Settings.logLevel)
		{
			logDate = Calendar.getInstance();
			
			String formattedDate = formatDate(logDate.getTime());
			
			String callingClass = Thread.currentThread().getStackTrace()[2].getClassName();
			
			String textToLog = Miscellaneous.lineSeparator + formattedDate + ": " + callingClass + ": " + eventDescription;
			
			File logFile = null;
			try
			{
				if(Settings.logFileEnabled)
				{
					logFile = getLogFileFullPath();
					
					if(!logFile.exists())
					{
						try
						{
							logFile.createNewFile();
						}
						catch (IOException e1)
						{
							e1.printStackTrace();
						}
					}
					
					try
					{
					    Files.write(Paths.get(logFile.toString()), textToLog.getBytes(), StandardOpenOption.APPEND);
					}
					catch (IOException e)
					{
					    //exception handling left as an exercise for the reader
		//				System.out.println("Error appending to file " + logFile + ": " + Diverse.getStackTraceAsString(e));
					}
				}
			}
			catch(Exception e)
			{
				System.out.println("Error writing logfile.");
			}
			
			if(Miscellaneous.isDevelopment())
				System.out.println(textToLog);
			
			if(!logCleanerRunning && Math.random() < 0.01)	// tidy up with 0.1% probability
			{
				logCleanerRunning = true;
				
				Miscellaneous.logEvent("Cleaning up log file.", 3);
				
				long maxSizeInBytes = (long)Settings.logMaxSize * 1024 * 1024;
				
				if(logFile != null && logFile.exists() && logFile.length() > (maxSizeInBytes))
				{						
					File archivedLogFile = new File(logFile.getAbsolutePath() + "-old");
					logFile.renameTo(archivedLogFile);
					Miscellaneous.logEvent("Log renamed to " + archivedLogFile.getAbsolutePath(), 3);
				}

				Miscellaneous.logEvent("Cleaning up log file finished.", 3);
				
				logCleanerRunning = false;
			}
		}
	}
	
	public static String getGCStats()
	{
		StringBuilder returnString = new StringBuilder();
		
	    long totalGarbageCollections = 0;
	    long garbageCollectionTime = 0;

	    for(GarbageCollectorMXBean gc : ManagementFactory.getGarbageCollectorMXBeans())
	    {

	        long count = gc.getCollectionCount();

	        if(count >= 0) {
	            totalGarbageCollections += count;
	        }

	        long time = gc.getCollectionTime();

	        if(time >= 0)
	            garbageCollectionTime += time;
	    }

	    returnString.append("Total Garbage Collections: " + totalGarbageCollections + Miscellaneous.lineSeparator);
	    returnString.append("Total Garbage Collection Time (ms): " + garbageCollectionTime);
	    
	    return returnString.toString();
	}
	  
	  public static UncaughtExceptionHandler getUncaughtExceptionHandler(final int criticality)
	  {
		  	UncaughtExceptionHandler handler = new UncaughtExceptionHandler()
		  	{			
		  		@Override
				public void uncaughtException(Thread t, Throwable e)
				{
		  			try
		  			{
			  			StringBuilder builder = new StringBuilder();
			  			builder.append(Settings.programName + " service just experienced an uncaught Exception. You may want to check it and possibly restart it.");
			  			builder.append(Miscellaneous.lineSeparator);
			  			builder.append("Error details:");
			  			builder.append(Miscellaneous.lineSeparator);
			  			builder.append(Miscellaneous.getStackTraceAsString(e));
			  			
						Miscellaneous.logEvent(builder.toString(), 1);
		  			}
		  			catch(Exception ex)
		  			{
						Miscellaneous.logEvent("Error in UncaughtExceptionHandler of thread " + Thread.currentThread().getName() + ". Details: " + Miscellaneous.getStackTraceAsString(ex), 1);		
		  			}
				}
		  	};
			return handler;
	  }
	  
	  public static String readFile(File file)
	  {
	     String content = null;
	     try
	     {
	         FileReader reader = new FileReader(file);
	         char[] chars = new char[(int) file.length()];
	         reader.read(chars);
	         content = new String(chars);
	         reader.close();
	     }
	     catch (IOException e)
	     {
	         e.printStackTrace();
	     }
	     return content;
	  }

	public static SSLServerSocket getSslServerSocket(int port)
	{
		SSLServerSocket sslServerSocket = null;
		
		try
	    {	    	
    		if((new File(Settings.keystoreFilename).exists()))
			{
				System.setProperty("javax.net.ssl.keyStore", Settings.keystoreFilename);
			    System.setProperty("javax.net.ssl.keyStorePassword", Settings.keystorePassword);		    	
		    	System.setProperty("javax.net.ssl.trustStore", Settings.keystoreFilename);
		    	System.setProperty("javax.net.ssl.trustStorePassword", Settings.keystorePassword);
//		    	System.setProperty("javax.net.ssl.trustStoreType","JCEKS");
			}
			else
			{
				Miscellaneous.logEvent("No keystore file found to start SSL daemon from.", 1);
				return null;
			}
		    
		    if(Settings.sslDebug)
		    	System.setProperty("javax.net.debug", "ssl,handshake,record");
		    
			Miscellaneous.logEvent("Starting ssl socket for encrypted communication on port " + String.valueOf(port) + "...", 1);
    		SSLServerSocketFactory sslServerSocketFactory = (SSLServerSocketFactory) SSLServerSocketFactory.getDefault();
			sslServerSocket = (SSLServerSocket) sslServerSocketFactory.createServerSocket(port);

			/*
			Exception in thread "Thread-0" java.lang.IllegalArgumentException: Cannot support TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA with currently installed providers
			at sun.security.ssl.CipherSuiteList.<init>(CipherSuiteList.java:92)
			at sun.security.ssl.SSLServerSocketImpl.setEnabledCipherSuites(SSLServerSocketImpl.java:191)
			at com.jens.homecontrol.server.TcpServer.startDaemonSsl(TcpServer.java:281)
			at com.jens.homecontrol.server.TcpServer.access$0(TcpServer.java:247)
			at com.jens.homecontrol.server.TcpServer$Starter.run(TcpServer.java:211)
			at java.lang.Thread.run(Thread.java:724)
			*/
			
			String[] desiredProtocolsToSet = { "TLSv1.3", "TLSv1.2", "TLSv1.1" };
			/*final String allowedCipherSuitesToSet[] = {
				    								"TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA",
											        "TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA",
												    "TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA",
											        "TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA",
												    "TLS_DHE_RSA_WITH_AES_256_CBC_SHA",
											        "TLS_DHE_RSA_WITH_AES_128_CBC_SHA",
											        "TLS_DHE_DSS_WITH_AES_128_CBC_SHA",
											        "TLS_ECDHE_RSA_WITH_RC4_128_SHA",
											        "TLS_ECDHE_ECDSA_WITH_RC4_128_SHA",
												    "TLS_RSA_WITH_AES_256_CBC_SHA",
											        "TLS_RSA_WITH_AES_128_CBC_SHA"
											    };*/
			final String allowedCipherSuitesToSet[] = { };
			
			String[] supportedProtocols = sslServerSocket.getSupportedProtocols();
			ArrayList<String> actualProtocolsToSet = new ArrayList<String>();
			for(String dp : desiredProtocolsToSet)
				for(String sp : supportedProtocols)
					if(dp.equalsIgnoreCase(sp))
						actualProtocolsToSet.add(dp);
			
			String[] supportedCipherSuites = sslServerSocket.getSupportedCipherSuites();
			ArrayList<String> actualCipherSuitesToSet = new ArrayList<String>();
			if(allowedCipherSuitesToSet.length > 0)
			{
				for(String acs : allowedCipherSuitesToSet)
					for(String scs : supportedCipherSuites)
						if(acs.equalsIgnoreCase(scs))
							actualCipherSuitesToSet.add(acs);
			}
			else
			{
				for(String scs : supportedCipherSuites)
					actualCipherSuitesToSet.add(scs);
			}
				
			
			Miscellaneous.logEvent("POSSIBLE PROTOCOLS:", 5);
			for(String s : supportedProtocols)
				Miscellaneous.logEvent(s, 5);
			
			Miscellaneous.logEvent("POSSIBLE CIPHER SUITES:", 5);
			for(String s : supportedCipherSuites)
				Miscellaneous.logEvent(s, 5);
			
			Miscellaneous.logEvent("ACTUALLY ACTIVATED PROTOCOLS:", 5);
			for(String s : actualProtocolsToSet)
				Miscellaneous.logEvent(s, 5);
			
			Miscellaneous.logEvent("ACTUALLY ACTIVATED CIPHER SUITES:", 5);
			for(String s : actualCipherSuitesToSet)
				Miscellaneous.logEvent(s, 5);
			
			sslServerSocket.setEnabledProtocols(actualProtocolsToSet.toArray(new String[actualProtocolsToSet.size()]));
			sslServerSocket.setEnabledCipherSuites(actualCipherSuitesToSet.toArray(new String[actualCipherSuitesToSet.size()]));
	    }
	    catch (IOException e)
	    {
	        System.err.println("Could not start server socket: " + e);
	        System.exit(-1);
	    }
		
		return sslServerSocket;
	}
	
	public static double convertCelsiusToFahrenheit(double celsius)
	{
		return (celsius * 1.8 + 32);
	}
	
	public static double convertFahrenheitToCelsius(double fahrenheit)
	{
		return ((fahrenheit - 32)/1.8);
	}
	
	public static String downloadWebpage(String urlAddress)
	{
	    URL url;
	    InputStream is = null;
	    BufferedReader br = null;
	    String line;

	    try
	    {
	        url = new URL(urlAddress);
	        is = url.openStream();  // throws an IOException
	        br = new BufferedReader(new InputStreamReader(is));

	        StringBuilder returnString = new StringBuilder();
	        
	        while ((line = br.readLine()) != null)
	        {
//	            System.out.println(line);
	            returnString.append(line);
	        }
	        
        	if(br != null)
        		br.close();
        	
            if (is != null)
            	is.close();
            
            Miscellaneous.logEvent("Webpage downloaded: " + returnString.toString(), 5);
            
	        return returnString.toString();
	    }
	    catch (MalformedURLException mue)
	    {
	         mue.printStackTrace();
	    }
	    catch (IOException ioe)
	    {
	         ioe.printStackTrace();
	    }
	    finally
	    {
	        try
	        {
	        	if(br != null)
	        		br.close();
	        	
	            if (is != null)
	            	is.close();
	        }
	        catch (IOException ioe)
	        {
	            // nothing to see here
	        }
	    }
	    
	    return genericErrorMessage;
	}
	
	public static boolean isDevelopment()
	{
		Boolean desenv = null;
		
//	    if (desenv != null)
//	    	return desenv;

	    try
	    {
	    	File workingPath = new File(".");
	        desenv = workingPath.getCanonicalPath().contains("workspace") || workingPath.getCanonicalPath().contains("git");
	    }
	    catch (IOException e)
	    {
	        e.printStackTrace();
	    }

	    return desenv;
	}
	
	public static String getOnOffStringForBoolean(boolean state)
	{
		if(state)
			return "on";
		else
			return "off";
	}
	
//	public enum SolarEvent { dawn, sunrise, solarNoon, sunset, dusk };
	
	/**
	 * 
	 * @param commandToExecute
	 * @param timeout
	 * @param slotMap 
	 * @return Returns an array: 0=exit code, 1=cmdline output
	 */
	public static Object[] runExternalApplication(String commandToExecute, long timeout, File workingDirectory, Map<String, String> slotMap)
	{
		/*
		 * Classes stolen from https://github.com/stleary/JSON-java
		 */
		
		String params = "";
		if(slotMap != null)
		{
			for(String key : slotMap.keySet())
				params += " --" + key + " " + slotMap.get(key);
		}
		
		String fullCommand = commandToExecute + params;
		
		Miscellaneous.logEvent("Running external application " + fullCommand, 4);
				
		Object[] returnObject = new Object[2];
		
		StringBuilder output = new StringBuilder();
		String line = null;
	    OutputStream stdin = null;
	    InputStream stderr = null;
	    InputStream stdout = null;

		try
		{		
			Process process = null;
			
			if(workingDirectory != null)
				process = Runtime.getRuntime().exec(fullCommand, null, workingDirectory);
			else
				process = Runtime.getRuntime().exec(fullCommand);
			stdin = process.getOutputStream ();
			stderr = process.getErrorStream ();
			stdout = process.getInputStream ();

			// "write" the parms into stdin
			/*line = "param1" + "\n";
			stdin.write(line.getBytes() );
			stdin.flush();
			
			line = "param2" + "\n";
			stdin.write(line.getBytes() );
			stdin.flush();
			
			line = "param3" + "\n";
			stdin.write(line.getBytes() );
			stdin.flush();*/
			
			stdin.close();			
			
			// clean up if any output in stdout
			BufferedReader brCleanUp = new BufferedReader (new InputStreamReader (stdout));
			while ((line = brCleanUp.readLine ()) != null)
			{
			    Miscellaneous.logEvent ("[Stdout] " + line, 4);
			    output.append(line);
			}
			brCleanUp.close();
			
			// clean up if any output in stderr
			brCleanUp = new BufferedReader (new InputStreamReader (stderr));
			while ((line = brCleanUp.readLine ()) != null)
			{
			    Miscellaneous.logEvent ("[Stderr] " + line, 4);
			    output.append(line);
			}
			brCleanUp.close();
		
			try
			{
				// Wait for the process to exit, we want the return code
				if(timeout > 0)
				{
					try
					{
						if(!process.waitFor(timeout, TimeUnit.MILLISECONDS))
						{
							Miscellaneous.logEvent("Timeout of " + String.valueOf(timeout) + " ms reached. Killing check attempt.", 3);
							process.destroyForcibly();
						}
					}
					catch(NoSuchMethodError e)
					{
						process.waitFor();
					}
				}
				else
					process.waitFor();
			}
			catch (InterruptedException e)
			{
				Miscellaneous.logEvent("Waiting for process failed: " + Miscellaneous.getStackTraceAsString(e), 4);
				Miscellaneous.logEvent(Miscellaneous.getStackTraceAsString(e), 1);
			}
			
			if(process.exitValue() == 0)
				Miscellaneous.logEvent("ReturnCode: " + String.valueOf(process.exitValue()), 4);			
			else
				Miscellaneous.logEvent("External execution (RC=" + String.valueOf(process.exitValue()) + ") returned error: " + output.toString(), 3);
						
			returnObject[0] = process.exitValue();
			returnObject[1] = output.toString();
			
			return returnObject;
		}
		catch (IOException e)
		{
			Miscellaneous.logEvent(Miscellaneous.getStackTraceAsString(e), 1);
		}

		Miscellaneous.logEvent("Error running external application.", 1);

//		if(slotMap != null)
//			for(String key : slotMap.keySet())
//				System.clearProperty(key);
		
		return null;
	}
	
	public static boolean restartNode()
	{
//		return true;
		return (boolean) runExternalApplication("sudo shutdown -r", 60000, null, null)[0];
	}

	public static boolean isLeapYear(int year)
	{
        boolean leap = false;

        if(year % 4 == 0)
        {
            if( year % 100 == 0)
            {
                // year is divisible by 400, hence the year is a leap year
                if ( year % 400 == 0)
                    leap = true;
                else
                    leap = false;
            }
            else
                leap = true;
        }
        else
            leap = false;

        return leap;
        
        /*if(leap)
            System.out.println(year + " is a leap year.");
        else
            System.out.println(year + " is not a leap year.");*/
    }
	
	public static double getStarDate(Calendar when)
	{		
		int y = when.get(Calendar.YEAR);
		
		int n = 0;
		if(Miscellaneous.isLeapYear(y))
			n = 366;
		else
			n = 365;
		
		int b = 2005;
		
		long c = 58000;
		
		int d = when.get(Calendar.DAY_OF_MONTH);
				
		int m = 0;
		switch(when.get(Calendar.MONTH))
		{
			case Calendar.JANUARY:
				m = 0;
				break;
			case Calendar.FEBRUARY:
				m = 31;
				break;
			case Calendar.MARCH:
				m = 59;
				break;
			case Calendar.APRIL:
				m = 90;
				break;
			case Calendar.MAY:
				m = 120;
				break;
			case Calendar.JUNE:
				m = 151;
				break;
			case Calendar.JULY:
				m = 181;
				break;
			case Calendar.AUGUST:
				m = 212;
				break;
			case Calendar.SEPTEMBER:
				m = 243;
				break;
			case Calendar.OCTOBER:
				m = 273;
				break;
			case Calendar.NOVEMBER:
				m = 304;
				break;
			case Calendar.DECEMBER:
				m = 334;
				break;
		}
		
		double stardate = c + (1000 * (y-b)) + (1000/n * (m+d-1));
		
		return stardate;
	}
	
	public static String formatDate(Date input)
	{
		DateFormat sdf = null;
		SimpleDateFormat fallBackFormatter = new SimpleDateFormat(Settings.default_dateFormat);
		
		if(sdf == null && Settings.dateFormat != null)
			sdf = new SimpleDateFormat(Settings.dateFormat);
		
		String formattedDate;
		if(sdf != null)
			formattedDate = sdf.format(input);
		else
			formattedDate = fallBackFormatter.format(input);
		
		return formattedDate;
	}
}