using System;
using System.Runtime.InteropServices;
using HandyControl.Tools.Interop;

namespace HandyControl.Data
{
    public abstract class HwndWrapper : DisposableObject
    {
        private IntPtr _handle;

        private bool _isHandleCreationAllowed = true;

        private ushort _wndClassAtom;

        private Delegate _wndProc;

        protected ushort WindowClassAtom
        {
            get
            {
                if (_wndClassAtom == 0) _wndClassAtom = CreateWindowClassCore();
                return _wndClassAtom;
            }
        }

        public IntPtr Handle
        {
            get
            {
                EnsureHandle();
                return _handle;
            }
        }

        protected virtual bool IsWindowSubclassed => false;

        protected virtual ushort CreateWindowClassCore() => RegisterClass(Guid.NewGuid().ToString());

        protected virtual void DestroyWindowClassCore()
        {
            if (_wndClassAtom != 0)
            {
                var moduleHandle = InteropMethods.GetModuleHandle(null);
                InteropMethods.UnregisterClass(new IntPtr(_wndClassAtom), moduleHandle);
                _wndClassAtom = 0;
            }
        }

        protected ushort RegisterClass(string className)
        {
            var wndClass = default(InteropValues.WNDCLASS);
            wndClass.cbClsExtra = 0;
            wndClass.cbWndExtra = 0;
            wndClass.hbrBackground = IntPtr.Zero;
            wndClass.hCursor = IntPtr.Zero;
            wndClass.hIcon = IntPtr.Zero;
            wndClass.lpfnWndProc = _wndProc = new InteropValues.WndProc(WndProc);
            wndClass.lpszClassName = className;
            wndClass.lpszMenuName = null;
            wndClass.style = 0u;
            return InteropMethods.RegisterClass(ref wndClass);
        }

        private void SubclassWndProc()
        {
            _wndProc = new InteropValues.WndProc(WndProc);
            InteropMethods.SetWindowLong(_handle, -4, Marshal.GetFunctionPointerForDelegate(_wndProc));
        }

        protected abstract IntPtr CreateWindowCore();

        protected virtual void DestroyWindowCore()
        {
            if (_handle != IntPtr.Zero)
            {
                InteropMethods.DestroyWindow(_handle);
                _handle = IntPtr.Zero;
            }
        }

        protected virtual IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam) =>
            InteropMethods.DefWindowProc(hwnd, msg, wParam, lParam);

        public void EnsureHandle()
        {
            if (_handle == IntPtr.Zero)
            {
                if (!_isHandleCreationAllowed) return;
                _isHandleCreationAllowed = false;
                _handle = CreateWindowCore();
                if (IsWindowSubclassed) SubclassWndProc();
            }
        }

        protected override void DisposeNativeResources()
        {
            _isHandleCreationAllowed = false;
            DestroyWindowCore();
            DestroyWindowClassCore();
        }
    }
}
