using Ascon.Pilot.BimConverters.Core;
using Ascon.Pilot.DataClasses;
using RevitToIfcConverter.Core.Tools;
using System;
using System.Collections.Generic;
using System.ComponentModel.Composition;
using System.Diagnostics;
using System.IO.Pipes;
using System.Runtime.InteropServices;
using System.Threading;
using RevitToIfcConverter.Core;

namespace RevitToIfcConverter
{
    [Export(typeof(IConverterToIfc))]
    public class RevitToIfcConverter : IConverterToIfc, IPipeServerCommunicateStrategy
    {
        private const int REVIT_APP_INITIALIZATION_TIMEOUT_SECONDS = 360;

        private string _revitExecutablePath;
        private Process _revitProcess;
        private readonly ManualResetEvent _revitProcessInitialized = new ManualResetEvent(false);
        public string Name => "RVT to IFC converter";

        public bool IsConverterInstalled()
        {
            var versionInfo = RevitIntegration.GetLatestVersionInfo();
            return versionInfo != null;
        }

        public void Initialize()
        {
            var versionInfo = RevitIntegration.GetLatestVersionInfo();
            _revitExecutablePath = versionInfo.ExecutablePath;
            RevitIntegration.InitRevitAddinManifest(versionInfo.Version, nameof(RevitToIfcConverter));
            InitRevitConnection();
        }

        public void Dispose()
        {
            if (_revitProcess != null && !_revitProcess.HasExited)
                SendMessage(_revitProcess.MainWindowHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero);
            _revitProcess?.Dispose();
        }


        public IEnumerable<DRule> Rules => new[]
        {
            new DRule
            {
                Id = new Guid("{96DAADA2-A643-4342-81BF-66BF4040A207}"),
                ChangeType = ChangeType.Create,
                FileExtension = ".rvt"
            },
            new DRule
            {
                Id = new Guid("{FA03D8DD-C518-4083-800E-D8C20C9FB472}"),
                ChangeType = ChangeType.Update,
                FileExtension = ".rvt"
            }
        };

        public IEnumerable<ConverterSettingDescriptor> Settings => new[]
        {
            new ConverterSettingDescriptor
            {
                Name = RevitIntegration.COMMON_SETTINGS_NAME,
                Title = "Common settings",
                Description = "Common settings"
            }
        };

        private void InitRevitConnection()
        {
            if (_revitProcess != null && !_revitProcess.HasExited)
                return;

            _revitProcessInitialized.Reset();
            _revitProcess = new Process { StartInfo = { FileName = _revitExecutablePath } };
            _revitProcess.Start();

            var revitInitializationPipeServer = new PipeServer(this, RevitIntegration.BuildInitializationPipeAddress(_revitProcess.Id));
            var revitAppInitialized = _revitProcessInitialized.WaitOne(TimeSpan.FromSeconds(REVIT_APP_INITIALIZATION_TIMEOUT_SECONDS));
            revitInitializationPipeServer.Dispose();

            if (!revitAppInitialized)
                throw new Exception("Revit initialization timeout exceeded.");
        }

        public string Convert(ConverterRequest request)
        {
            InitRevitConnection();
            var pipeAddress = RevitIntegration.BuildConverterPipeAddress(_revitProcess.Id);

            var ifcFilePath = PipeClient.ProcessRvtFile(pipeAddress, request);
            if (ifcFilePath.StartsWith("Error"))
                throw new Exception(ifcFilePath);

            if (string.IsNullOrEmpty(ifcFilePath))
                throw new Exception($"{request.Path} file has not been converted. Connection is lost.");

            return ifcFilePath;
        }

        public void Communicate(PipeStream stream)
        {
            var response = stream.ReadString();
            if (response == RevitIntegration.REVIT_APP_INITIALIZED_OK)
                _revitProcessInitialized.Set();
        }

        #region pInvoke
        static uint WM_CLOSE = 0x10;

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        static extern IntPtr SendMessage(IntPtr hWnd, UInt32 msg, IntPtr wParam, IntPtr lParam);
        #endregion
    }
}
