using NotificationFlyout.Wpf.UI.Extensions;
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Input;
using System.Windows.Interop;

namespace NotificationFlyout.Wpf.UI.Helpers
{
    public class NotificationIconHelper : IDisposable
    {
        private const int CallbackMessage = 0x400;
        private const uint IconVersion = 0x4;

        private readonly object _lock = new();
        private readonly IntPtr _windowHandle;
        private bool _isDisposed;
        private NotifyIconData _notifyIconData;

        private NotificationIconHelper(Window window)
        {
            _windowHandle = window.GetHandle();

            var source = HwndSource.FromHwnd(_windowHandle);
            source.AddHook(new HwndSourceHook(WndProc));

            CreateNotificationIcon();
        }


        ~NotificationIconHelper()
        {
            Dispose(false);
        }

        public event EventHandler<NotificationIconInvokedEventArgs> IconInvoked;

        private enum NotifyIconBalloonType
        {
            None = 0x00,
            Info = 0x01,
            Warning = 0x02,
            Error = 0x03,
            User = 0x04,
            NoSound = 0x10,
            LargeIcon = 0x20,
            RespectQuietTime = 0x80
        }

        private enum NotifyIconCommand : uint
        {
            Add = 0x0,
            Delete = 0x2,
            Modify = 0x1,
            SetVersion = 0x4
        }

        [Flags]
        private enum NotifyIconDataMember : uint
        {
            Message = 0x01,
            Icon = 0x02,
            Tip = 0x04,
            State = 0x08,
            Info = 0x10,
            Realtime = 0x40,
            UseLegacyToolTips = 0x80
        }

        private enum NotifyIconState : uint
        {
            Visible = 0x00,
            Hidden = 0x01
        }

        public static NotificationIconHelper Create(Window window)
        {
            return new NotificationIconHelper(window);
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        public void SetIcon(IntPtr iconHandle)
        {
            lock (_lock)
            {
                _notifyIconData.IconHandle = iconHandle;
                WriteNotifyIconData(NotifyIconCommand.Modify, NotifyIconDataMember.Icon);
            }
        }

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam);

        [DllImport("shell32.dll", SetLastError = true)]
        private static extern int Shell_NotifyIcon(NotifyIconCommand notifyCommand, ref NotifyIconData notifyIconData);

        private void CreateNotificationIcon()
        {
            lock (_lock)
            {
                _notifyIconData = new NotifyIconData();

                _notifyIconData.cbSize = (uint)Marshal.SizeOf(_notifyIconData);
                _notifyIconData.WindowHandle = _windowHandle;
                _notifyIconData.TaskbarIconId = 0x0;
                _notifyIconData.CallbackMessageId = CallbackMessage;
                _notifyIconData.VersionOrTimeout = IconVersion;

                _notifyIconData.IconHandle = IntPtr.Zero;

                _notifyIconData.IconState = NotifyIconState.Hidden;
                _notifyIconData.StateMask = NotifyIconState.Hidden;

                WriteNotifyIconData(NotifyIconCommand.Add, NotifyIconDataMember.Message | NotifyIconDataMember.Icon | NotifyIconDataMember.Tip);
            }
        }

        private void Dispose(bool disposing)
        {
            if (_isDisposed || !disposing) return;

            lock (_lock)
            {
                _isDisposed = true;
                RemoveNotificationIcon();
            }
        }

        private void InvokeIconInvoked(MouseButton mouseButton)
        {
            IconInvoked?.Invoke(this, new NotificationIconInvokedEventArgs { MouseButton = mouseButton });
        }

        private void RemoveNotificationIcon() => WriteNotifyIconData(NotifyIconCommand.Delete, NotifyIconDataMember.Message);

        private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            if (msg == CallbackMessage)
            {
                switch ((uint)lParam)
                {
                    case (uint)WndProcMessages.WM_LBUTTONUP:
                        InvokeIconInvoked(MouseButton.Left);
                        break;
                    case (uint)WndProcMessages.WM_MBUTTONUP:
                        InvokeIconInvoked(MouseButton.Middle);
                        break;
                    case (uint)WndProcMessages.WM_RBUTTONUP:
                        InvokeIconInvoked(MouseButton.Right);
                        break;
                }
            }

            return DefWindowProcW(hwnd, (uint)msg, wParam, (lParam));
        }

        private void WriteNotifyIconData(NotifyIconCommand command, NotifyIconDataMember flags)
        {
            _notifyIconData.ValidMembers = flags;
            lock (_lock)
            {
                Shell_NotifyIcon(command, ref _notifyIconData);
            }
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        private struct NotifyIconData
        {
            public uint cbSize;
            public IntPtr WindowHandle;
            public uint TaskbarIconId;
            public NotifyIconDataMember ValidMembers;
            public uint CallbackMessageId;
            public IntPtr IconHandle;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string ToolTipText;

            public NotifyIconState IconState;
            public NotifyIconState StateMask;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string BalloonText;

            public uint VersionOrTimeout;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
            public string BalloonTitle;

            public NotifyIconBalloonType BalloonFlags;
            public Guid TaskbarIconGuid;
            public IntPtr CustomBalloonIconHandle;
        }
    }
}