package com.jens.rhasspy.shoppinglist;

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.lang.management.GarbageCollectorMXBean;
import java.lang.management.ManagementFactory;
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.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Properties;
import java.util.concurrent.TimeUnit;
import java.util.logging.FileHandler;
import java.util.logging.Logger;

import javax.mail.Authenticator;
import javax.mail.Message;
import javax.mail.PasswordAuthentication;
import javax.mail.Session;
import javax.mail.Transport;
import javax.mail.internet.AddressException;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;

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;

	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 String formatDate(Date input)
	{
		DateFormat sdf = null;
		SimpleDateFormat fallBackFormatter = new SimpleDateFormat(Settings.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;
	}
	
	public static synchronized void logEvent(String eventDescription, int logLevel)
	{	
		if(logLevel <= Settings.logLevel)
		{
			logDate = Calendar.getInstance();
			
			String formattedDate = Miscellaneous.formatDate(logDate.getTime());
			
			String callingClass = Thread.currentThread().getStackTrace()[2].getClassName();
			
			String textToLog = Diverse.lineSeparator + formattedDate + ": " + callingClass + ": " + eventDescription;
			
			File 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));
			}
			
			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.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 + Diverse.lineSeparator);
	    returnString.append("Total Garbage Collection Time (ms): " + garbageCollectionTime);
	    
	    return returnString.toString();
	}
	  
	  public static class SmtpSimple
	  {
		  /**
		   * Send a single email.
		   */
		   public boolean sendEmail(String mailServer, int mailServerPor, String encryptionType, boolean useAuthentication, final String username, final String password, String aFromEmailAddr, String aToEmailAddr, String aSubject, String aBody)
		   {
			    // Properties über die Systemeigenschaften anlegen
				Properties properties = System.getProperties();

				properties.setProperty("mail.smtp.host", mailServer);				
				properties.setProperty("mail.smtp.port", String.valueOf(mailServerPor));

				properties.setProperty("mail.smtp.auth", String.valueOf(useAuthentication));
								
				/*
				 * final Properties props = new Properties();
				 * props.put("mail.smtp.host", "SMTPHOST");
				 * props.put("mail.smtp.port", "PORTNUMBER");
				 * props.put("mail.transport.protocol","smtp");
				 * props.put("mail.smtp.auth", "true");
				 * props.put("mail.smtp.starttls.enable", "true");
				 * props.put("mail.smtp.tls", "true");
				 * props.put("mail.smtp.ssl.checkserveridentity", "true");
				*/				

				if(encryptionType.equalsIgnoreCase("STARTTLS"))
					properties.setProperty("mail.smtp.starttls.enable", "true");
				else if(encryptionType.equalsIgnoreCase("TLS"))
					properties.put("mail.smtp.tls", "true");
				else if(encryptionType.equalsIgnoreCase("SSL"))
					properties.setProperty("mail.smtp.ssl.enable", "true");
				
//				properties.put("mail.smtp.ssl.checkserveridentity", "false");
				
			    Session session;
		     
			    Authenticator auth = new Authenticator()
			    {
			    	protected PasswordAuthentication getPasswordAuthentication()
			    	{
			    		return new PasswordAuthentication(username, password);
			    	}
			    };

			    if(useAuthentication)
			    	session = Session.getDefaultInstance(properties, auth);
			    else
			    	session= Session.getDefaultInstance(properties, null);

//             	session.setDebug(true);
		     
			    MimeMessage message = new MimeMessage(session);
			    try
			    {
			    	//the "from" address may be set in code, or set in the
			    	//config file under "mail.from" ; here, the latter style is used
			    	message.setFrom(new InternetAddress(aFromEmailAddr));
			    	message.addRecipient(Message.RecipientType.TO, new InternetAddress(aToEmailAddr));
			    	message.setSubject(aSubject);
			    	message.setText(aBody);
			    	Transport.send(message);
			    	return true;
			    }
			    catch (AddressException e)
			    {
			    	System.err.println("Cannot send email. " + Diverse.getStackTraceAsString(e));
			    }
			    catch (javax.mail.MessagingException e)
			    {
			    	System.err.println("Cannot send email. " + Diverse.getStackTraceAsString(e));
			    }
		     
			    return false;
		   	}
	  }
	  
	  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 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
	    {
	        desenv = new File(".").getCanonicalPath().contains("workspace");
	    }
	    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
	 * @return Returns an array: 0=exit code, 1=cmdline output
	 */
	public static Object[] runExternalApplication(String commandToExecute, long timeout)
	{
		/*
		 * Classes stolen from https://github.com/stleary/JSON-java
		 */
		
		Miscellaneous.logEvent("Running external application " + commandToExecute + "...", 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;
			
			process = Runtime.getRuntime().exec(commandToExecute);
			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: " + Diverse.getStackTraceAsString(e), 4);
				Miscellaneous.logEvent(Diverse.getStackTraceAsString(e), 1);
			}
			
			Miscellaneous.logEvent("ReturnCode: " + String.valueOf(process.exitValue()), 4);
			
			returnObject[0] = process.exitValue();
			returnObject[1] = output.toString();
			
			return returnObject;
		}
		catch (IOException e)
		{
			Miscellaneous.logEvent(Diverse.getStackTraceAsString(e), 1);
		}

		Miscellaneous.logEvent("Error running external application.", 1);
		
		return null;
	}
	
	public static boolean restartNode()
	{
//		return true;
		return (boolean) runExternalApplication("sudo shutdown -r", 60000)[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;
	}
}