using System;
using System.IO;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using System.Text;
using Ascon.Pilot.BimConverters.Core;

namespace RevitToIfcConverter.Core.Tools
{
    public static class PipeClient
    {
        private const int CONNECT_TIMEOUT = 10;

        public static string ProcessRvtFile(string pipeAddress, ConverterRequest request)
        {
            string ifcFileRequestPath = Request(
                request,
                (s, p) => s.WriteRequest(p),
                s => s.ReadString(),
                pipeAddress);

            return ifcFileRequestPath;
        }

        public static void SendRevitAppInitialized(string pipeAddress)
        {
            using (var client = new NamedPipeClientStream(".", pipeAddress, PipeDirection.InOut))
            {
                try
                {
                    try
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                    catch (TimeoutException)
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                    catch (IOException)
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                }
                catch
                {
                    
                }

                client.WriteString(RevitIntegration.REVIT_APP_INITIALIZED_OK);
                client.WaitForPipeDrain();
            }
        }

        private static TResponse Request<TRequest, TResponse>(TRequest request, Action<PipeStream, TRequest> write, Func<PipeStream, TResponse> read, string address)
        {
            using (var client = new NamedPipeClientStream(".", address, PipeDirection.InOut))
            {
                try
                {
                    try
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                    catch (TimeoutException)
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                    catch (IOException)
                    {
                        client.Connect(CONNECT_TIMEOUT);
                    }
                }
                catch
                {
                    return default(TResponse);
                }

                write(client, request);
                client.WaitForPipeDrain();
                return read(client);
            }
        }
    }

    [ComVisible(false)]
    public static class PipeStreamExtensions
    {
        private static readonly Encoding DefaultEncoding = Encoding.Unicode;

        public static ConverterRequest ReadRequest(this PipeStream stream)
        {
            IFormatter f = new BinaryFormatter();
            return (ConverterRequest)f.Deserialize(stream);
        }


        public static string ReadString(this PipeStream stream)
        {
            var first = stream.ReadByteSafe();
            var second = stream.ReadByteSafe();
            int length = first * 256 + second;
            var inBuffer = new byte[length];
            stream.Read(inBuffer, 0, length);
            return DefaultEncoding.GetString(inBuffer);
        }

        public static void WriteRequest(this PipeStream stream, ConverterRequest req)
        {
            IFormatter f = new BinaryFormatter();
            f.Serialize(stream, req);
        }

        public static void WriteString(this PipeStream stream, string str)
        {
            byte[] outBuffer = DefaultEncoding.GetBytes(str);
            int len = Math.Min(outBuffer.Length, UInt16.MaxValue);

            var first = (byte) (len / 256);
            stream.WriteByte(first);

            var second = (byte) (len & 255);
            stream.WriteByte(second);

            stream.Write(outBuffer, 0, len);
            stream.Flush();
        }

        public static int ReadByteSafe(this PipeStream stream)
        {
            int result = -1;
            while (result == -1)
                result = stream.ReadByte();
            return result;
        }
    }
}
