using System;
using System.IO.Pipes;
using System.Security.AccessControl;
using System.Security.Principal;
using System.Threading;
using JetBrains.Annotations;
using log4net;

namespace RevitToIfcConverter.Core.Tools
{
    public interface IPipeServerCommunicateStrategy
    {
        void Communicate(PipeStream stream);
    }

    public class PipeServer : IDisposable
    {
        private readonly string _address;
        private readonly ILog _logger = LogManager.GetLogger(typeof(PipeServer));
        private readonly IPipeServerCommunicateStrategy _strategy;
        private NamedPipeServerStream _pipeServer;
        private readonly object _locker = new object();
        private readonly ManualResetEvent _startEvent = new ManualResetEvent(false);
        private bool _isStopped;

        public PipeServer([NotNull] IPipeServerCommunicateStrategy strategy, string address)
        {
            _strategy = strategy;
            _address = address;

            new Thread(Processing).Start();
            _startEvent.WaitOne();
        }

        private void Processing()
        {
            while (true)
            {
                try
                {
                    PipeSecurity ps = new PipeSecurity();
                    var rule = new PipeAccessRule(
                        new SecurityIdentifier(WellKnownSidType.AuthenticatedUserSid, null),
                        PipeAccessRights.ReadWrite,
                        AccessControlType.Allow);

                    ps.SetAccessRule(rule);
                    using (_pipeServer = new NamedPipeServerStream(_address, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.None, 0, 0, ps))
                    {
                        _startEvent.Set();
                        while (true)
                        {
                            _pipeServer.WaitForConnection();
                            _logger.Info("new connection");
                            lock (_locker)
                            {
                                if (_isStopped)
                                    return;
                            }
                            _strategy.Communicate(_pipeServer);
                            _pipeServer.Disconnect();
                        }
                    }
                }
                catch (Exception ex)
                {
                    _logger.Error("ShellServer", ex);
                    Thread.Sleep(10);
                }
            }
        }

        public void Dispose()
        {
            ConnectToShutdown();
        }

        private void ConnectToShutdown()
        {
            using (var client = new NamedPipeClientStream(".", _address, PipeDirection.Out))
            {
                lock (_locker)
                {
                    client.Connect();
                    _isStopped = true;
                }
            }
        }
    }
}
