using Nlc;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace NanolibCsharpExampleWin
{
    internal static class NanolibLoggingCallbackExample
    {
        private static NanolibHelper nanolibHelper = new NanolibHelper();

        /// <summary>
        /// Callback class derived from Nlc.NlcLoggingCallback.
        /// </summary>
        internal class LoggingCallback : Nlc.NlcLoggingCallback // override
        {
            /// <summary>
            /// Track wether dispose has been called
            /// </summary>
            private bool _disposed;

            /// <summary>
            /// Nanolib accessor object, needed during dispose
            /// </summary>
            private readonly Nlc.NanoLibAccessor _accessor = nanolibHelper.GetAccessor();

            /// <summary>
            /// Callback implementation used in logging messages
            /// </summary>
            /// <param name="payload_str">Payload of log message</param>
            /// <param name="formatted_str">Formatted string of message</param>
            /// <param name="logger_name">Name of the logger instance</param>
            /// <param name="log_level">Log level of the message</param>
            /// <param name="time_since_epoch">Time since epoch in milliseconds</param>
            /// <param name="thread_id">Thread id of the logging process</param>
            /// <returns></returns>
            public override void callback(string payload_str, string formatted_str, string logger_name, uint log_level, ulong time_since_epoch, uint thread_id)
            {
                // do your callback stuff here ...
                // e.g. print to file or console

                DateTimeOffset dateTimeOffset = DateTimeOffset.FromUnixTimeMilliseconds((long)time_since_epoch);
                DateTime dateTime = dateTimeOffset.DateTime;

                Console.WriteLine("#######################################################################################");
                Console.WriteLine("# Payload: = '" + payload_str + "'");
                // formatted_str contains a line separator (\r\n on windows or \n on linux) at the end of log message
                Console.WriteLine("# Formatted string = '" + formatted_str.Substring(0, formatted_str.IndexOf("\r\n")) + "'");
                Console.WriteLine("# Logger name = '" + logger_name + "'");
                Console.WriteLine("# nlc_log_level = '" + Nlc.LogLevelConverter.toString((LogLevel)log_level) + "'");
                Console.WriteLine("# Local Time = '" + dateTime.ToLocalTime().ToString() + "." + dateTimeOffset.Millisecond.ToString() + "'");
                Console.WriteLine("# Thread id = '" + thread_id.ToString() + "'");
                Console.WriteLine("#######################################################################################");
            }

            /// <summary>
            /// Protected implementation of Dispose pattern
            /// </summary>
            /// <param name="disposing"></param>
            protected override void Dispose(bool disposing)
            {
                if (!_disposed)
                {
                    if (disposing)
                    {
                        // unset the callback
                        _accessor.unsetLoggingCallback();
                    }

                    _disposed = true;
                }

                base.Dispose(disposing);
            }
        }

        private static void Main(string[] args)
        {
            try
            {
                // setup accessor
                nanolibHelper.Setup();

                // its possible to set the logging level to a different level
                nanolibHelper.SetLoggingLevel(Nlc.LogLevel.Trace);

                // list all hardware available, decide for the first one
                Nlc.BusHWIdVector busHwIds = nanolibHelper.GetBusHardware();

                // create logging callback instance
                LoggingCallback loggingCallback = new LoggingCallback();

                // sink (callback) with nanolib core module
                // only one module for core or specific fieldbus / protocol will be active
                // the callback sink will have the same logging level as the logger (Trace)
                nanolibHelper.GetAccessor().setLoggingCallback(loggingCallback, Nlc.LogModule.NanolibCore);
                // set function callback pointer again in accessor with other log module,
                // old callback gets released and new callback is set
                nanolibHelper.GetAccessor().setLoggingCallback(loggingCallback, Nlc.LogModule.NanolibModbus);

                if (busHwIds.Count() <= 0)
                {
                    Console.Error.WriteLine("No bus hardware found");
                    return;
                }

                Console.WriteLine();
                Console.WriteLine("Available bus hardware:");
                Console.WriteLine();

                uint lineNum = 0;

                // just for better overview: print out available hardware
                foreach (Nlc.BusHardwareId adapter in busHwIds)
                {
                    Console.Write("{0}. {1}", lineNum, adapter.getName());
                    Console.WriteLine(" with protocol: {0}", adapter.getProtocol());
                    lineNum++;
                }

                Console.WriteLine();
                Console.WriteLine("Press any key to exit ...");
                Console.ReadKey();
                
                /// 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!
                /// since C# GC does not clean-up immediately, we have to clean-up manually here!!
                loggingCallback.Dispose();
            }
            catch (NanolibException e)
            {
                Console.Error.WriteLine("Error occured: {0}", e.Message);
            }
        }
    }
}