using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;

// ReSharper disable InconsistentNaming

namespace IPCamAppBar
{
    [StructLayout(LayoutKind.Sequential)]
    internal struct RECT
    {
        public int left;
        public int top;
        public int right;
        public int bottom;
    }

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

    internal enum ABMsg
    {
        ABM_NEW = 0,
        ABM_REMOVE,
        ABM_QUERYPOS,
        ABM_SETPOS,
        ABM_GETSTATE,
        ABM_GETTASKBARPOS,
        ABM_ACTIVATE,
        ABM_GETAUTOHIDEBAR,
        ABM_SETAUTOHIDEBAR,
        ABM_WINDOWPOSCHANGED,
        ABM_SETSTATE
    }

    internal enum ABNotify
    {
        ABN_STATECHANGE = 0,
        ABN_POSCHANGED,
        ABN_FULLSCREENAPP,
        ABN_WINDOWARRANGE
    }

    internal enum ABEdge
    {
        ABE_LEFT = 0,
        ABE_TOP,
        ABE_RIGHT,
        ABE_BOTTOM
    }


    public enum AppBarPosition
    {
        Top,
        Left,
        Bottom,
        Right
    }


    public class AppBar : IDisposable
    {
        [DllImport("SHELL32", CallingConvention = CallingConvention.StdCall)]
        private static extern uint SHAppBarMessage(int dwMessage, ref APPBARDATA pData);

        [DllImport("User32.dll", ExactSpelling = true, CharSet = CharSet.Auto)]
        private static extern bool MoveWindow(IntPtr hWnd, int x, int y, int cx, int cy, bool repaint);

        [DllImport("User32.dll", CharSet = CharSet.Auto)]
        private static extern int RegisterWindowMessage(string msg);


        private class PositionParameters
        {
            public Screen Monitor { get; set; }
            public AppBarPosition Position { get; set; }
            public int Size { get; set; }
        }


        private bool disposed;
        private APPBARDATA appBarData;
        private PositionParameters lastPosition;



        public AppBar(IntPtr windowHandle)
        {
            appBarData.cbSize = Marshal.SizeOf(appBarData);
            appBarData.hWnd = windowHandle;
            appBarData.uCallbackMessage = RegisterWindowMessage("AppBarMessage");

            SHAppBarMessage((int)ABMsg.ABM_NEW, ref appBarData);
        }


        public void Dispose()
        {
            if (disposed)
                throw new ObjectDisposedException(GetType().Name);

            SHAppBarMessage((int)ABMsg.ABM_REMOVE, ref appBarData);
            disposed = true;
        }


        public void SetPosition(Screen monitor, AppBarPosition position, int size)
        {
            appBarData.rc.top = monitor.Bounds.Top;
            appBarData.rc.left = monitor.Bounds.Left;
            appBarData.rc.bottom = monitor.Bounds.Bottom;
            appBarData.rc.right= monitor.Bounds.Right;

            ApplyPosition(position, size);
            SHAppBarMessage((int)ABMsg.ABM_QUERYPOS, ref appBarData);

            ApplyPosition(position, size);
            SHAppBarMessage((int)ABMsg.ABM_SETPOS, ref appBarData);

            MoveWindow(
                appBarData.hWnd, 
                appBarData.rc.left, 
                appBarData.rc.top,
                appBarData.rc.right - appBarData.rc.left, 
                appBarData.rc.bottom - appBarData.rc.top,
                true);

            lastPosition = new PositionParameters
            {
                Monitor = monitor,
                Position = position,
                Size = size
            };
        }


        public void HandleMessage(Message m)
        {
            if (m.Msg == appBarData.uCallbackMessage && m.WParam.ToInt32() == (int)ABNotify.ABN_POSCHANGED) 
                UpdatePosition();
        }


        private void UpdatePosition()
        {
            if (lastPosition == null)
                return;

            SetPosition(lastPosition.Monitor, lastPosition.Position, lastPosition.Size);
        }


        private void ApplyPosition(AppBarPosition position, int size)
        {
            switch (position)
            {
                case AppBarPosition.Top:
                    appBarData.uEdge = (int)ABEdge.ABE_TOP;
                    appBarData.rc.bottom = appBarData.rc.top + size;
                    break;

                case AppBarPosition.Left:
                    appBarData.uEdge = (int)ABEdge.ABE_LEFT;
                    appBarData.rc.right = appBarData.rc.left + size;
                    break;

                case AppBarPosition.Bottom:
                    appBarData.uEdge = (int)ABEdge.ABE_BOTTOM;
                    appBarData.rc.top = appBarData.rc.bottom - size;
                    break;

                case AppBarPosition.Right:
                    appBarData.uEdge = (int)ABEdge.ABE_RIGHT;
                    appBarData.rc.left = appBarData.rc.right - size;
                    break;

                default:
                    throw new ArgumentOutOfRangeException(nameof(position), position, @"Invalid position value");
            }
        }
    }
}
