using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Media;
using System.Windows.Interop;

// Name of this partial class has to be the same as the name
// of the window, which you want to modify for custom chrome.
public partial class MainWindow : Window {
	private const int WM_DWMCOMPOSITIONCHANGED = 0x031E;
	private const int WM_GETMINMAXINFO = 0x0024;

	protected override void OnSourceInitialized(EventArgs e) {
		base.OnSourceInitialized(e);
		// This can’t be done any earlier than the SourceInitialized event: 
		GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
		// Attach a window procedure in order to detect later enabling of desktop 
		// composition 
		IntPtr hwnd = new WindowInteropHelper(this).Handle;
		HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
	}

	private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) {
		if (msg == WM_DWMCOMPOSITIONCHANGED) {
			// Reenable glass: 
			GlassHelper.ExtendGlassFrame(this, new Thickness(-1));
			handled = true;
		} else if (msg == WM_GETMINMAXINFO) {
			WindowSizing.WmGetMinMaxInfo(hwnd, lParam);
			handled = true;
		}

		return IntPtr.Zero;
	}

	public static class GlassHelper {
		[DllImport("dwmapi.dll", PreserveSig = false)]
		static extern void DwmExtendFrameIntoClientArea(
			IntPtr hWnd, ref MARGINS pMarInset);

		[DllImport("dwmapi.dll", PreserveSig = false)]
		static extern bool DwmIsCompositionEnabled();

		[StructLayout(LayoutKind.Sequential)]
		public struct MARGINS {
			public MARGINS(Thickness t) {
				Left = (int)t.Left;
				Right = (int)t.Right;
				Top = (int)t.Top;
				Bottom = (int)t.Bottom;
			}

			public int Left;
			public int Right;
			public int Top;
			public int Bottom;
		}

		public static bool ExtendGlassFrame(Window window, Thickness margin) {
			if (!DwmIsCompositionEnabled()) {
				return false;
			}
			IntPtr hwnd = new WindowInteropHelper(window).Handle;
			if (hwnd == IntPtr.Zero)
				throw new InvalidOperationException("The Window must be shown before extending glass.");
			// Set the background to transparent from both the WPF and Win32 perspectives 
			window.Background = Brushes.Transparent;
			HwndSource.FromHwnd(hwnd).CompositionTarget.BackgroundColor = Colors.Transparent;
			MARGINS margins = new MARGINS(margin);
			DwmExtendFrameIntoClientArea(hwnd, ref margins);
			return true;
		}
	}

	public static class WindowSizing {
		private const int MONITOR_DEFAULTTONEAREST = 0x00000002;

		#region DLLImports

		[DllImport("shell32", CallingConvention = CallingConvention.StdCall)]
		public static extern int SHAppBarMessage(int dwMessage, ref APPBARDATA pData);

		[DllImport("user32", SetLastError = true)]
		static extern IntPtr FindWindow(string lpClassName, string lpWindowName);

		[DllImport("user32")]
		internal static extern bool GetMonitorInfo(IntPtr hMonitor, MONITORINFO lpmi);

		[DllImport("user32")]
		internal static extern IntPtr MonitorFromWindow(IntPtr handle, int flags);

		#endregion

		private static MINMAXINFO AdjustWorkingAreaForAutoHide(IntPtr monitorContainingApplication, MINMAXINFO mmi) {
			IntPtr hwnd = FindWindow("Shell_TrayWnd", null);
			if (hwnd == null) return mmi;
			IntPtr monitorWithTaskbarOnIt = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
			if (!monitorContainingApplication.Equals(monitorWithTaskbarOnIt)) return mmi;
			APPBARDATA abd = new APPBARDATA();
			abd.cbSize = Marshal.SizeOf(abd);
			abd.hWnd = hwnd;
			SHAppBarMessage((int)ABMsg.ABM_GETTASKBARPOS, ref abd);
			int uEdge = GetEdge(abd.rc);
			bool autoHide = System.Convert.ToBoolean(SHAppBarMessage((int)ABMsg.ABM_GETSTATE, ref abd));

			if (!autoHide) return mmi;

			switch (uEdge) {
				case (int)ABEdge.ABE_LEFT:
					mmi.ptMaxPosition.x += 2;
					mmi.ptMaxTrackSize.x -= 2;
					mmi.ptMaxSize.x -= 2;
					break;
				case (int)ABEdge.ABE_RIGHT:
					mmi.ptMaxSize.x -= 2;
					mmi.ptMaxTrackSize.x -= 2;
					break;
				case (int)ABEdge.ABE_TOP:
					mmi.ptMaxPosition.y += 2;
					mmi.ptMaxTrackSize.y -= 2;
					mmi.ptMaxSize.y -= 2;
					break;
				case (int)ABEdge.ABE_BOTTOM:
					mmi.ptMaxSize.y -= 2;
					mmi.ptMaxTrackSize.y -= 2;
					break;
				default:
					return mmi;
			}
			return mmi;
		}

		private static int GetEdge(RECT rc) {
			int uEdge = -1;
			if (rc.top == rc.left && rc.bottom > rc.right)
				uEdge = (int)ABEdge.ABE_LEFT;
			else if (rc.top == rc.left && rc.bottom < rc.right)
				uEdge = (int)ABEdge.ABE_TOP;
			else if (rc.top > rc.left)
				uEdge = (int)ABEdge.ABE_BOTTOM;
			else
				uEdge = (int)ABEdge.ABE_RIGHT;
			return uEdge;
		}

		public static void WmGetMinMaxInfo(IntPtr hwnd, IntPtr lParam) {
			MINMAXINFO mmi = (MINMAXINFO)Marshal.PtrToStructure(lParam, typeof(MINMAXINFO));
			IntPtr monitorContainingApplication = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);

			if (monitorContainingApplication != System.IntPtr.Zero) {
				MONITORINFO monitorInfo = new MONITORINFO();
				GetMonitorInfo(monitorContainingApplication, monitorInfo);
				RECT rcWorkArea = monitorInfo.rcWork;
				RECT rcMonitorArea = monitorInfo.rcMonitor;
				mmi.ptMaxPosition.x = Math.Abs(rcWorkArea.left - rcMonitorArea.left);
				mmi.ptMaxPosition.y = Math.Abs(rcWorkArea.top - rcMonitorArea.top);
				mmi.ptMaxSize.x = Math.Abs(rcWorkArea.right - rcWorkArea.left);
				mmi.ptMaxSize.y = Math.Abs(rcWorkArea.bottom - rcWorkArea.top);
				mmi.ptMaxTrackSize.x = mmi.ptMaxSize.x;                                                 //maximum drag X size for the window
				mmi.ptMaxTrackSize.y = mmi.ptMaxSize.y;                                                 //maximum drag Y size for the window
				mmi.ptMinTrackSize.x = 0;                                                             //minimum drag X size for the window
				mmi.ptMinTrackSize.y = 0;                                                             //minimum drag Y size for the window
				mmi = AdjustWorkingAreaForAutoHide(monitorContainingApplication, mmi);                  //need to adjust sizing if taskbar is set to autohide
			}
			Marshal.StructureToPtr(mmi, lParam, true);
		}

		public enum ABEdge {
			ABE_LEFT = 0,
			ABE_TOP = 1,
			ABE_RIGHT = 2,
			ABE_BOTTOM = 3
		}

		public enum ABMsg {
			ABM_NEW = 0,
			ABM_REMOVE = 1,
			ABM_QUERYPOS = 2,
			ABM_SETPOS = 3,
			ABM_GETSTATE = 4,
			ABM_GETTASKBARPOS = 5,
			ABM_ACTIVATE = 6,
			ABM_GETAUTOHIDEBAR = 7,
			ABM_SETAUTOHIDEBAR = 8,
			ABM_WINDOWPOSCHANGED = 9,
			ABM_SETSTATE = 10
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct APPBARDATA {
			public int cbSize;
			public IntPtr hWnd;
			public int uCallbackMessage;
			public int uEdge;
			public RECT rc;
			public bool lParam;
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct MINMAXINFO {
			public POINT ptReserved;
			public POINT ptMaxSize;
			public POINT ptMaxPosition;
			public POINT ptMinTrackSize;
			public POINT ptMaxTrackSize;
		};

		[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
		public class MONITORINFO {
			public int cbSize = Marshal.SizeOf(typeof(MONITORINFO));
			public RECT rcMonitor = new RECT();
			public RECT rcWork = new RECT();
			public int dwFlags = 0;
		}

		[StructLayout(LayoutKind.Sequential)]
		public struct POINT {
			public int x;
			public int y;

			public POINT(int x, int y) {
				this.x = x;
				this.y = y;
			}
		}

		[StructLayout(LayoutKind.Sequential, Pack = 0)]
		public struct RECT {
			public int left;
			public int top;
			public int right;
			public int bottom;
		}
	}
}
