using System.IO.Pipes;
using System.Diagnostics;
using System.Text;
using System;
using System.Reflection;
using System.Runtime.InteropServices;
using System.ComponentModel;
using Microsoft.Win32;

using C4IT.Logging;
using System.Threading;
using System.Windows;
using static C4IT.Logging.cLogManager;
using System.Collections.Generic;

namespace FasdDesktopUi.Basics
{
    static public class cF4sdPipeClient
    {
        static public bool Send(string SendStr, string PipeName, int TimeOut = 1000)
        {
            var CM = MethodBase.GetCurrentMethod();
            LogMethodBegin(CM);

            try
            {
                if (DefaultLogger.IsDebug)
                {
                    var _msg = new List<string>() { $"send to  pipe {PipeName}", SendStr };
                    DefaultLogger.LogList(LogLevels.Debug, _msg);
                }

                NamedPipeClientStream pipeStream = new NamedPipeClientStream
                   (".", PipeName, PipeDirection.Out, PipeOptions.WriteThrough);

                // The connect function will indefinitely wait for the pipe to become available
                // If that is not acceptable specify a maximum waiting time (in ms)
                pipeStream.Connect(TimeOut);

                byte[] _buffer = Encoding.UTF8.GetBytes(SendStr);
                pipeStream.Write(_buffer, 0, _buffer.Length);
                pipeStream.Flush();
                pipeStream.Close();
                pipeStream.Dispose();

                return true;
            }
            catch (TimeoutException oEX)
            {
                if (cLogManager.DefaultLogger.IsDebug)
                    cLogManager.DefaultLogger.LogException(oEX);
            }
            catch { }
            finally
            {
                LogMethodEnd(CM);
            }

            return false;
        }

        static public void RegisterProtocol(string protocolString, string urlCommand)
        {
            try
            {
                string codeBase = Assembly.GetExecutingAssembly().CodeBase;
                var u = new Uri(codeBase);
                var Path = u.LocalPath;
                var RegBase = Registry.CurrentUser.OpenSubKey("Software\\Classes", true);
                var RegHandler = RegBase.CreateSubKey(protocolString.ToUpperInvariant());
                RegHandler.SetValue("", $"URL: {protocolString.ToUpperInvariant()} Protocol Handler");
                RegHandler.SetValue("URL Protocol", "");
                var RegHandler2 = RegHandler.CreateSubKey("DefaultIcon");
                RegHandler2.SetValue("", Path);
                var RegHandler3 = RegHandler.CreateSubKey("shell\\open\\command");
                RegHandler3.SetValue("", $"\"{Path}\" {urlCommand.ToLowerInvariant()} \"%1\"");

                RegHandler = Registry.CurrentUser.CreateSubKey($"SOFTWARE\\Microsoft\\Internet Explorer\\ProtocolExecute\\{protocolString.ToLowerInvariant()}");
                RegHandler.SetValue("WarnOnOpen", 0,RegistryValueKind.DWord);
            }
            catch { }
        }
    }

    public delegate void DelegateMessage(string Reply);

    public class cF4sdPipeServer : IDisposable
    {
        public event DelegateMessage PipeMessage;
        string _pipeName;
        NamedPipeServerStream pipeServer;
        bool _LowerIntegrity;
        int _MaxSize;

        public void Listen(string PipeName, int MaxSize = 255, bool LowerIntegrity = false, int Timeout = 10000)
        {
            try
            {
                // Set to class level var so we can re-use in the async callback method
                _pipeName = PipeName;
                _MaxSize = MaxSize;
                _LowerIntegrity = LowerIntegrity;

                if (pipeServer != null )
                {
                    try
                    {
                        pipeServer.Close();
                        pipeServer.Dispose();
                        pipeServer = null;
                    } catch { }
                }

                bool _connected = false;
                bool _firstTry = true;
                var _startTime = DateTime.UtcNow;
                while ((DateTime.UtcNow - _startTime).TotalMilliseconds < Timeout && !_connected)
                {
                    try
                    {
                        if (!_firstTry)
                            Thread.Sleep(200);
                        _firstTry = false;

                        // Create the new async pipe
                        if (pipeServer == null)
                            pipeServer = new NamedPipeServerStream(PipeName, PipeDirection.In, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
                        if (_LowerIntegrity)
                            InterProcessSecurity.SetLowIntegrityLevel(pipeServer.SafePipeHandle);

                        // Wait for a connection
                        var _res = pipeServer.BeginWaitForConnection(new AsyncCallback(WaitForConnectionCallBack), pipeServer);
                        _connected = _res != null;
                    } catch { }
                }

                if (!_connected)
                {
                    cLogManager.DefaultLogger.LogEntry(LogLevels.Error, $"The pipe '{PipeName}' could not be opened in listen mode.");
                }

            }
            catch (Exception oEX)
            {
                cLogManager.DefaultLogger.LogException(oEX);
            }
        }

        private void WaitForConnectionCallBack(IAsyncResult iar)
        {
            try
            {
                // Get the pipe
                NamedPipeServerStream pipeServer = (NamedPipeServerStream)iar.AsyncState;
                // End waiting for the connection
                pipeServer.EndWaitForConnection(iar);

                byte[] buffer = new byte[_MaxSize];

                // Read the incoming message
                int count = pipeServer.Read(buffer, 0, _MaxSize);

                // Convert byte buffer to string
                string stringData = Encoding.UTF8.GetString(buffer, 0, count);

                // Pass message back to calling form
                PipeMessage.Invoke(stringData);

                // Kill original sever and create new wait server
                pipeServer.Close();
                pipeServer = null;
                pipeServer = new NamedPipeServerStream(_pipeName, PipeDirection.In,
                   1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);

                if (_LowerIntegrity)
                    InterProcessSecurity.SetLowIntegrityLevel(pipeServer.SafePipeHandle);

                // Recursively wait for the connection again and again....
                pipeServer.BeginWaitForConnection(
                   new AsyncCallback(WaitForConnectionCallBack), pipeServer);
            }
            catch
            {
                return;
            }
        }

        public void Stop()
        {
            if (pipeServer != null)
            {
                pipeServer.Close();
                pipeServer.Dispose();
                pipeServer = null;
            }
        }

        public void Dispose()
        {
            Stop();
        }
    }

    public static class InterProcessSecurity
    {

        public const string LOW_INTEGRITY_SSL_SACL = "S:(ML;;NW;;;LW)";

        public static int ERROR_SUCCESS = 0x0;

        public const int LABEL_SECURITY_INFORMATION = 0x00000010;

        public enum SE_OBJECT_TYPE
        {
            SE_UNKNOWN_OBJECT_TYPE = 0,
            SE_FILE_OBJECT,
            SE_SERVICE,
            SE_PRINTER,
            SE_REGISTRY_KEY,
            SE_LMSHARE,
            SE_KERNEL_OBJECT,
            SE_WINDOW_OBJECT,
            SE_DS_OBJECT,
            SE_DS_OBJECT_ALL,
            SE_PROVIDER_DEFINED_OBJECT,
            SE_WMIGUID_OBJECT,
            SE_REGISTRY_WOW64_32KEY
        }

        [DllImport("advapi32.dll", EntryPoint = "ConvertStringSecurityDescriptorToSecurityDescriptorW")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern Boolean ConvertStringSecurityDescriptorToSecurityDescriptor(
            [MarshalAs(UnmanagedType.LPWStr)] String strSecurityDescriptor,
            UInt32 sDRevision,
            ref IntPtr securityDescriptor,
            ref UInt32 securityDescriptorSize);

        [DllImport("kernel32.dll", EntryPoint = "LocalFree")]
        public static extern UInt32 LocalFree(IntPtr hMem);

        [DllImport("Advapi32.dll", EntryPoint = "SetSecurityInfo")]
        public static extern int SetSecurityInfo(SafeHandle hFileMappingObject,
                                                    SE_OBJECT_TYPE objectType,
                                                    Int32 securityInfo,
                                                    IntPtr psidOwner,
                                                    IntPtr psidGroup,
                                                    IntPtr pDacl,
                                                    IntPtr pSacl);
        [DllImport("advapi32.dll", EntryPoint = "GetSecurityDescriptorSacl")]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern Boolean GetSecurityDescriptorSacl(
            IntPtr pSecurityDescriptor,
            out IntPtr lpbSaclPresent,
            out IntPtr pSacl,
            out IntPtr lpbSaclDefaulted);

        public static void SetLowIntegrityLevel(SafeHandle hObject)
        {
            IntPtr pSD = IntPtr.Zero;
            uint securityDescriptorSize = 0;

            if (ConvertStringSecurityDescriptorToSecurityDescriptor(LOW_INTEGRITY_SSL_SACL, 1, ref pSD, ref securityDescriptorSize))
            {
                try
                {
                    if (GetSecurityDescriptorSacl(pSD, out IntPtr lpbSaclPresent, out IntPtr pSacl, out IntPtr lpbSaclDefaulted))
                    {
                        var err = SetSecurityInfo(hObject,
                                                      SE_OBJECT_TYPE.SE_KERNEL_OBJECT,
                                                      LABEL_SECURITY_INFORMATION,
                                                      IntPtr.Zero,
                                                      IntPtr.Zero,
                                                      IntPtr.Zero,
                                                      pSacl);
                        if (err != ERROR_SUCCESS)
                        {
                            throw new Win32Exception(err);
                        }
                    }
                }
                finally
                {
                    LocalFree(pSD);
                }
            }
        }
    }


}