using Microsoft.Windows.Sdk;
using NotificationFlyout.Wpf.UI.Helpers;
using System;
using System.Runtime.InteropServices;
using System.Windows;

namespace NotificationFlyout.Wpf.UI
{
    public class Screen
    {
        private const int CCHDEVICENAME = 32;
        private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D);
        private const int SM_CMONITORS = 80;
        private static readonly bool _multiMonitorSupport;

        private readonly IntPtr _monitorHandle;

        static Screen()
        {
            _multiMonitorSupport = PInvoke.GetSystemMetrics(SM_CMONITORS) != 0;
        }

        internal Screen(IntPtr monitorHandle)
        {
            if (!_multiMonitorSupport || monitorHandle == (IntPtr)PRIMARY_MONITOR)
            {
                Bounds = SystemInformationHelper.VirtualScreen;
                Primary = true;
                DeviceName = "DISPLAY";
            }
            else
            {
                var monitorData = GetMonitorData(monitorHandle);

                Bounds = new Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top, monitorData.MonitorRect.right - monitorData.MonitorRect.left,  monitorData.MonitorRect.bottom - monitorData.MonitorRect.top);
                Primary = (monitorData.Flags & (int)MonitorFlag.MONITOR_DEFAULTTOPRIMARY) != 0;
                DeviceName = monitorData.DeviceName;
            }

            _monitorHandle = monitorHandle;
        }

        private enum MonitorFlag : uint
        {
            MONITOR_DEFAULTTONULL = 0,
            MONITOR_DEFAULTTOPRIMARY = 1,
            MONITOR_DEFAULTTONEAREST = 2
        }

        public Rect Bounds { get; }

        public string DeviceName { get; }

        public bool Primary { get; }

        public Rect WorkingArea => GetWorkingArea();

        public static Screen FromHandle(IntPtr handle)
        {
            return _multiMonitorSupport ? new Screen(PInvoke.MonitorFromWindow((HWND)handle, (uint)MonitorFlag.MONITOR_DEFAULTTONEAREST)) : new Screen((IntPtr)PRIMARY_MONITOR);
        }

        public override bool Equals(object obj)
        {
            if (obj is Screen monitor)
            {
                if (_monitorHandle == monitor._monitorHandle)
                {
                    return true;
                }
            }
            return false;
        }

        public override int GetHashCode()
        {
            return (int)_monitorHandle;
        }

        [DllImport("user32.dll", EntryPoint = "GetMonitorInfo", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern bool GetMonitorInfoEx(IntPtr hMonitor, ref MonitorData lpmi);

        private MonitorData GetMonitorData(IntPtr monitorHandle)
        {
            var monitorData = new MonitorData();
            monitorData.Size = Marshal.SizeOf(monitorData);
            GetMonitorInfoEx(monitorHandle, ref monitorData);

            return monitorData;
        }
        private Rect GetWorkingArea()
        {
            if (!_multiMonitorSupport || _monitorHandle == (IntPtr)PRIMARY_MONITOR)
            {
                return SystemInformationHelper.WorkingArea;
            }

            var monitorData = GetMonitorData(_monitorHandle);
            return new Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top);
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        private struct MonitorData
        {
            public int Size;
            public RECT MonitorRect;
            public RECT WorkAreaRect;
            public uint Flags;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
            public string DeviceName;
        }
    }
}