package com.nanotec.example.loggingcallbackexample;

import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter;
import java.time.format.FormatStyle;

import com.nanotec.nanolib.BusHWIdVector;
import com.nanotec.nanolib.BusHardwareId;
import com.nanotec.nanolib.LogLevel;
import com.nanotec.nanolib.LogLevelConverter;
import com.nanotec.nanolib.NlcLoggingCallback;

/**
 * Logging Callback Example
 */
public class LoggingCallbackExample {

	private static NanolibHelper nanolibHelper = new NanolibHelper();
	private static LoggingCallback loggingCallback;
	
	/// <summary>
	/// Callback class derived from Nlc.NlcLoggingCallback. Create as static inner class of example.
	/// </summary>
	static class LoggingCallback extends NlcLoggingCallback // override
	{
		// get system specific new line character(s)
		private static String newLineSeparator = System.getProperty("line.separator");

		// converts time since epoch in s into a localized date time string
		private String timeSinceEpochToLocaltimeString(java.math.BigInteger time_since_epoch) {
			LocalDateTime dateTime = LocalDateTime.ofInstant(Instant.ofEpochSecond(time_since_epoch.longValue()), ZoneId.systemDefault());
			DateTimeFormatter formatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.SHORT);
			String formattedDate = dateTime.format(formatter);
			return formattedDate;
		}

		// IMPORTANT: the member function pointer of NlcLoggingCallback::callback gets
		// bound to a std::function pointer in between (because of SWIG not supporting std::function)
		// and has to be (manually) unset/deleted in the destructor / during garbage collection!
		protected void finalize()  
		{  
			System.out.println("finalize() of LoggingCallback called");
			if (nanolibHelper != null) {
				nanolibHelper.getAccessor().unsetLoggingCallback();
			} 
		}  

		/// <summary>
		/// Callback used for logging output.
		/// </summary>
		/// <param name="payload_str">The unformatted payload of the log message</param>
		/// <param name="formatted_str">The formatted string of the log message, contains a newline at end of string</param>
		/// <param name="logger_name">The name of the logger instance</param>
		/// <param name="log_level>"The log level of the log message</param>
		/// <param name="time_since_epoch"> Time since epoch in s (can be converted in local time)</param>
		/// <param name="thread_id">The thread id of the process that has triggered the logging message</param>
		/// <returns>void</returns>
		@Override
		public void callback(String payload_str, String formatted_str, String logger_name, long log_level, java.math.BigInteger time_since_epoch, long thread_id) {
			// do your callback stuff here ...
			// e.g. print to file or console
			System.out.println("#######################################################################################");
			System.out.println("# Payload: = '" + payload_str + "'");
			// formatted_str contains a line separator (\r\n on windows or \n on linux) at the end of log message
			System.out.println("# Formatted string = '" + formatted_str.substring(0, formatted_str.indexOf(newLineSeparator)) + "'");
			System.out.println("# Logger name = '" + logger_name + "'");
			System.out.println("# nlc_log_level = '"  + LogLevelConverter.toString(LogLevel.swigToEnum((int)log_level)) + "'");
			System.out.println("# Local Time = '" + timeSinceEpochToLocaltimeString(time_since_epoch) + "'");
			System.out.println("# Thread id = '" + Long.toString(thread_id) + "'");
			System.out.println("#######################################################################################");
		}
	}

	public static void main(String[] args) {
		try {
			nanolibHelper.setup();
			// its possible to set the logging level to a different level
			nanolibHelper.setLoggingLevel(LogLevel.Trace);

			// list all hardware available, load all dlls, decide for the first one
			BusHWIdVector busHwIds = nanolibHelper.getBusHardware();

			// create instance of LoggingCallback
			loggingCallback = new LoggingCallback();
			// sink (callback) log level should always be >= the common log level
			nanolibHelper.setLoggingCallback(loggingCallback, LogLevel.Debug);
			// set function callback pointer again in accessor with other log level,
			// old callback gets released and new callback is set
			nanolibHelper.setLoggingCallback(loggingCallback, LogLevel.Trace);

			if (busHwIds.isEmpty()) {
				System.out.println("No bus hardware found");
				// IMPORTANT!! call finalize method of LoggingCallback ('destructor'), 
				// this will unset the bound callback in the nanolib library
				if (loggingCallback != null) {
					loggingCallback.finalize();
				}
				return;
			}

			System.out.println();
			System.out.println("Available bus hardware:");
			System.out.println();

			int lineNum = 0;

			// just for better overview: print out available hardware
			for (BusHardwareId adapter : busHwIds) {
				System.out.print(String.format("%d. %s", lineNum, adapter.getName()));
				System.out.println(String.format(" with protocol: %s", adapter.getProtocol()));
				lineNum++;
			}

			System.out.println();
			System.out.println("Press any key to exit ...");
			System.in.read();

		} catch (Exception e) {
			System.out.println(String.format("Error occured: %s", e.getMessage()));
		}

		// IMPORTANT!! call finalize method of LoggingCallback ('destructor'), 
		// this will unset the bound callback in the nanolib library
		if (loggingCallback != null) {
			loggingCallback.finalize();
		}
	}
}
