// BorderEffectWindow.cs
// Copyright (c) 2016 Bentley Systems, Incorporated. All Rights Reserved.

using System;
using System.Drawing;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using Haestad.Support.Win32Api;

namespace Haestad.Framework.UI.Integration
{
    public partial class BorderEffectWindow : Form
    {
        #region Constructor
        public BorderEffectWindow(Form parentForm)
        {
            ParentForm = parentForm;

            InitializeComponent();
            InitializeDimensions();
            InitializeEvents();

            WindowLibrary.AddExtendedWindowStyle(Handle, User32Dll.WS_EX_TOOLWINDOW); // Note: <whar> Keeps window out of the window manager (ALT+TAB)
            WindowLibrary.AddWindowStyle(Handle, User32Dll.WS_THICKFRAME);

            MakeTransparent();

            Show();
        }
        #endregion

        #region Event Handlers
        #region Parent Form
        private void ParentForm_Load(object sender, EventArgs e)
        {
            UpdateBorderEffectWindowPosition();
        }

        private void ParentForm_Move(object sender, EventArgs e)
        {
            if (ParentForm.WindowState != FormWindowState.Maximized)
                UpdateBorderEffectWindowPosition();
        }

        private void ParentForm_FormClosed(object sender, FormClosedEventArgs e)
        {
            Close();
        }

        private void ParentForm_Activated(object sender, EventArgs e)
        {
            UpdateBorderEffectWindowPosition();
        }
        #endregion

        #region BorderEffectWindow
        private void BorderEffectWindow_Resize(object sender, EventArgs e)
        {
            UpdateParentFormWindowPosition();
        }
        #endregion
        #endregion

        #region Protected Override Methods
        protected override void WndProc(ref Message m)
        {
            switch (m.Msg)
            {
                case User32Dll.WM_MOUSEACTIVATE:
                    m.Result = (IntPtr)User32Dll.MA_NOACTIVATE; 
                    return;

                case WINDOWS_MESSAGES.WM_EXITSIZEMOVE:
                    ParentForm.Activate();
                    break;

                case User32Dll.WM_NCHITTEST:
                    m.Result = (IntPtr)WindowLibrary.GetNonClientHitTestConstant(GetMouseNonClientAreaPosition());
                    return;

                case WINDOWS_MESSAGES.WM_NCCALCSIZE:
                    {
                        if (m.WParam != IntPtr.Zero)
                        {
                            NCCALCSIZE_PARAMS ncCalcSizeParams = (NCCALCSIZE_PARAMS)Marshal.PtrToStructure(m.LParam, typeof(NCCALCSIZE_PARAMS));
                            Marshal.StructureToPtr(ncCalcSizeParams, m.LParam, false);
                        }
                        m.Result = (IntPtr)RETURN_VALUES.TRUE;
                        return;
                    }

                case WINDOWS_MESSAGES.WM_NCPAINT:
                    {
                        // https://msdn.microsoft.com/en-us/library/windows/desktop/dd145212%28v=vs.85%29.aspx
                        // An application returns zero if it processes this message.
                        m.Result = (IntPtr)RETURN_VALUES.FALSE;
                        return;
                    }

                case WINDOWS_MESSAGES.WM_NCACTIVATE:
                    {
                        // https://msdn.microsoft.com/en-us/library/windows/desktop/ms632633%28v=vs.85%29.aspx
                        // If the window is minimized when this message is received, the application should pass the message to the DefWindowProc function.
                        if (WindowState == FormWindowState.Minimized)
                            break;

                        m.Result = (IntPtr)RETURN_VALUES.TRUE; // Indicates the system should proceed with default processing.
                        return;
                    }
            }

            base.WndProc(ref m);
        }
        #endregion

        #region Private Methods
        private void InitializeDimensions()
        {
            SetBounds(
                ParentForm.Left + EnvironmentLibrary.NonClientAreaBorderWidth,
                ParentForm.Top + EnvironmentLibrary.NonClientAreaBorderWidth,
                ParentForm.Width + EnvironmentLibrary.NonClientAreaBorderWidth,
                ParentForm.Height + EnvironmentLibrary.NonClientAreaBorderWidth, 
                BoundsSpecified.All);
        }

        private void MakeTransparent()
        {
            if (RibbonExternalConfiguration.Instance.RibbonDebuggingEnabled)
                return;

            BackColor = Color.Black;
            WindowLibrary.MakeWindowTransparent(Handle, false);                
        }

        private void InitializeEvents()
        {
            ParentForm.Load += ParentForm_Load;
            ParentForm.Move += ParentForm_Move;
            ParentForm.FormClosed += ParentForm_FormClosed;
            ParentForm.Activated += ParentForm_Activated;

            Resize += BorderEffectWindow_Resize;
        }

        private void UpdateBorderEffectWindowPosition()
        {
            // Note: <whar> 05/15/2017. Abar reported the following exception during spot testing of storm/sewer trunk builds.
            // System.ObjectDisposedException: Cannot access a disposed object.
            // Object name: 'BorderEffectWindow'.
            if (IsDisposed || ParentForm.IsDisposed)
                return;

            User32Dll.SetWindowPos(Handle, ParentForm.Handle, ParentForm.Left - EnvironmentLibrary.NonClientAreaBorderWidth,
                ParentForm.Top - EnvironmentLibrary.NonClientAreaBorderWidth,
                ParentForm.Width + (2 * EnvironmentLibrary.NonClientAreaBorderWidth),
                ParentForm.Height + (2 * EnvironmentLibrary.NonClientAreaBorderWidth),
                User32Dll.SWP_NOACTIVATE);
        }

        private void UpdateParentFormWindowPosition()
        {
            bool borderFormSnappedVertically = GetIsFormVerticallySnapped(this);

            ParentForm.SetBounds(Left + EnvironmentLibrary.NonClientAreaBorderWidth,
                Top + (borderFormSnappedVertically ? 0 : EnvironmentLibrary.NonClientAreaBorderWidth),
                Width - (2 * EnvironmentLibrary.NonClientAreaBorderWidth),
                Height - (borderFormSnappedVertically ? 0 : (2 * EnvironmentLibrary.NonClientAreaBorderWidth)),
                BoundsSpecified.All);
        }

        private bool GetIsFormVerticallySnapped(Form form)
        {
            if (GetIsFormTopEdgeSnapped(form) && GetIsFormBottomEdgeSnapped(form))
                return true;
            else
                return false;
        }

        private bool GetIsAnyFormEdgeSnapped(Form form)
        {
            if (GetIsFormTopEdgeSnapped(form))
                return true;

            if (GetIsFormRightEdgeSnapped(form))
                return true;

            if (GetIsFormLeftEdgeSnapped(form))
                return true;

            if (GetIsFormBottomEdgeSnapped(form))
                return true;

            return false;
        }

        private bool GetIsFormTopEdgeSnapped(Form form)
        {
            Screen currentMonitor = Screen.FromControl(form);
            return form.Top == currentMonitor.WorkingArea.Top;
        }

        private bool GetIsFormRightEdgeSnapped(Form form)
        {
            Screen currentMonitor = Screen.FromControl(form);
            return form.Right == currentMonitor.WorkingArea.Right;
        }

        private bool GetIsFormLeftEdgeSnapped(Form form)
        {
            Screen currentMonitor = Screen.FromControl(form);
            return form.Left == currentMonitor.WorkingArea.Left;
        }

        private bool GetIsFormBottomEdgeSnapped(Form form)
        {
            Screen currentMonitor = Screen.FromControl(form);
            return form.Bottom == currentMonitor.WorkingArea.Bottom;
        }
        
        private MouseNonClientAreaPosition GetMouseNonClientAreaPosition()
        {
            Point borderEffectWindowPosition = WindowLibrary.GetClientCursorPosition(this);

            // Note: <whar> Order is important in this method.

            if (GetIsTopBorder(borderEffectWindowPosition) && GetIsLeftBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.TopLeft;

            if (GetIsTopBorder(borderEffectWindowPosition) && GetIsRightBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.TopRight;

            if (GetIsBottomBorder(borderEffectWindowPosition) && GetIsLeftBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.BottomLeft;

            if (GetIsBottomBorder(borderEffectWindowPosition) && GetIsRightBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.BottomRight;

            if (GetIsTopBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.Top;

            if (GetIsLeftBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.Left;

            if (GetIsRightBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.Right;

            if (GetIsBottomBorder(borderEffectWindowPosition))
                return MouseNonClientAreaPosition.Bottom;

            return MouseNonClientAreaPosition.None;
        }

        private bool GetIsTopBorder(Point borderEffectWindowPosition)
        {
            return borderEffectWindowPosition.Y <= EnvironmentLibrary.NonClientAreaBorderWidth;
        }

        private bool GetIsBottomBorder(Point borderEffectWindowPosition)
        {
            return borderEffectWindowPosition.Y >= ParentForm.Height - EnvironmentLibrary.NonClientAreaBorderWidth;
        }

        private bool GetIsLeftBorder(Point borderEffectWindowPosition)
        {
            return borderEffectWindowPosition.X <= EnvironmentLibrary.NonClientAreaBorderWidth;
        }

        private bool GetIsRightBorder(Point borderEffectWindowPosition)
        {
            return borderEffectWindowPosition.X >= ParentForm.Width - EnvironmentLibrary.NonClientAreaBorderWidth;
        }
#endregion

#region Private Properties
        private new Form ParentForm
        {
            get;
            set;
        }
#endregion
    }
}
