﻿using System;
using System.Runtime.InteropServices;

namespace hwpconversion
{
    // Original Source Code Reference: 
    // http://cpueblo.com/programming/api/contents/197.html

    internal static class TrayWindowCleanup
    {
        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        private struct TRAYDATA
        {
            public IntPtr hwnd;
            public uint uID;
            public uint uCallbackMessage;
            public uint dwReserved1;
            public uint dwReserved2;
            public IntPtr hIcon;
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential, Pack = 1)]
        private struct TBBUTTON
        {
            public int iBitmap;
            public int idCommand;
            public byte fsState;
            public byte fsStyle;
            public byte bReserved0;
            public byte bReserved1;
            public int dwData;
            public int iString;
        }

        [Serializable]
        [StructLayout(LayoutKind.Sequential)]
        private struct NOTIFYICONDATA
        {
            public uint cbSize;
            public IntPtr hWnd;
            public uint uID;
            public uint uFlags;
            public uint uCallbackMessage;
            public IntPtr hIcon;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 128)]
            public string szTip;

            public uint dwState;
            public uint dwStateMask;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 256)]
            public string szInfo;

            public uint uTimeoutOrVersion;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 64)]
            public string szInfoTitle;

            public int dwInfoFlags;
        }

        [NonSerialized]
        private const uint WM_USER = 0x0400u;

        [NonSerialized]
        private const uint TB_GETBUTTON = (WM_USER + 23u);

        [NonSerialized]
        private const uint TB_BUTTONCOUNT = (WM_USER + 24u);

        [NonSerialized]
        private const uint PROCESS_TERMINATE = 0x00000001u;

        [NonSerialized]
        private const uint PROCESS_CREATE_THREAD = 0x00000002u;

        [NonSerialized]
        private const uint PROCESS_SET_SESSIONID = 0x00000004u;

        [NonSerialized]
        private const uint PROCESS_VM_OPERATION = 0x00000008u;

        [NonSerialized]
        private const uint PROCESS_VM_READ = 0x00000010u;

        [NonSerialized]
        private const uint PROCESS_VM_WRITE = 0x00000020u;

        [NonSerialized]
        private const uint PROCESS_DUP_HANDLE = 0x00000040u;

        [NonSerialized]
        private const uint PROCESS_CREATE_PROCESS = 0x00000080u;

        [NonSerialized]
        private const uint PROCESS_SET_QUOTA = 0x00000100u;

        [NonSerialized]
        private const uint PROCESS_SET_INFORMATION = 0x00000200u;

        [NonSerialized]
        private const uint PROCESS_QUERY_INFORMATION = 0x00000400u;

        [NonSerialized]
        private const uint STANDARD_RIGHTS_REQUIRED = 0x000F0000u;

        [NonSerialized]
        private const uint SYNCHRONIZE = 0x00100000u;

        [NonSerialized]
        private const uint PROCESS_ALL_ACCESS = (
            PROCESS_TERMINATE | PROCESS_CREATE_THREAD | PROCESS_SET_SESSIONID |
            PROCESS_VM_OPERATION | PROCESS_VM_READ | PROCESS_VM_WRITE | PROCESS_DUP_HANDLE |
            PROCESS_CREATE_PROCESS | PROCESS_SET_QUOTA | PROCESS_SET_INFORMATION |
            PROCESS_QUERY_INFORMATION | STANDARD_RIGHTS_REQUIRED | SYNCHRONIZE
        );

        [NonSerialized]
        private const uint MEM_COMMIT = 0x00001000u;

        [NonSerialized]
        private const uint MEM_RELEASE = 0x8000u;

        [NonSerialized]
        private const uint PAGE_READWRITE = 0x004u;

        [NonSerialized]
        private const uint NIM_DELETE = 0x00000002u;

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr FindWindow(
            string lpClassName,
            string lpWindowName);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        private static extern IntPtr FindWindowEx(
            IntPtr hwndParent,
            IntPtr hwndChildAfter,
            string lpszClass,
            string lpszWindow);

        [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = false)]
        private static extern IntPtr SendMessage(
            IntPtr hWnd,
            uint Msg,
            IntPtr wParam,
            IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern uint GetWindowThreadProcessId(
            IntPtr hWnd,
            out uint lpdwProcessId);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr OpenProcess(
            uint dwDesiredAccess,
            int bInheritHandle,
            uint dwProcessId);

        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool CloseHandle(IntPtr hHandle);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern IntPtr VirtualAllocEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            uint dwSize,
            uint flAllocationType,
            uint flProtect);

        [DllImport("kernel32.dll", SetLastError = true, ExactSpelling = true)]
        private static extern bool VirtualFreeEx(
            IntPtr hProcess,
            IntPtr lpAddress,
            IntPtr dwSize,
            uint dwFreeType);

        [DllImport("kernel32.dll")]
        private static unsafe extern bool ReadProcessMemory(
            IntPtr hProcess,
            IntPtr baseAddress,
            void* buffer,
            uint dwSize,
            out uint numberOfBytesRead);

        [DllImport("shell32.dll")]
        private static extern bool Shell_NotifyIcon(
            uint dwMessage,
           [In] ref NOTIFYICONDATA pnid);

        private static IntPtr FindTrayToolbarWindow()
        {
            // 작업 표시줄 검색
            IntPtr hShellTrayWnd = FindWindow("Shell_TrayWnd", null);

            if (hShellTrayWnd.Equals(IntPtr.Zero))
                return IntPtr.Zero;

            // 트레이 영역 검색
            IntPtr hTrayNotifyWnd = FindWindowEx(hShellTrayWnd, IntPtr.Zero, "TrayNotifyWnd", null);

            if (hTrayNotifyWnd.Equals(IntPtr.Zero))
                return IntPtr.Zero;

            // Windows XP 이상의 시스템에는 SysPager 창 검색 (아이콘 자동 숨기기 기능 수행)
            IntPtr hSysPager = FindWindowEx(hTrayNotifyWnd, IntPtr.Zero, "SysPager", null);

            return FindWindowEx(
                (hSysPager.Equals(IntPtr.Zero) ? hTrayNotifyWnd : hSysPager),
                IntPtr.Zero, "ToolbarWindow32", null);
        }

        public static unsafe bool Cleanup(out Exception throwedException)
        {
            throwedException = null;

            uint temp = 0u;
            IntPtr lpData = IntPtr.Zero;
            IntPtr hProcess = IntPtr.Zero;

            try
            {
                TBBUTTON tButton = new TBBUTTON();
                TRAYDATA trayData = new TRAYDATA();
                IntPtr hTrayWindow = FindTrayToolbarWindow();

                if (hTrayWindow.Equals(IntPtr.Zero))
                    return false;

                int trayCount = SendMessage(hTrayWindow, TB_BUTTONCOUNT, IntPtr.Zero, IntPtr.Zero).ToInt32();
                uint trayProcessId;

                GetWindowThreadProcessId(hTrayWindow, out trayProcessId);

                // 프로세스 열기 시도
                hProcess = OpenProcess(PROCESS_ALL_ACCESS, 0, trayProcessId);

                if (hProcess.Equals(IntPtr.Zero))
                    return false;

                // 프로세스 내부 메모리 할당 시도
                lpData = VirtualAllocEx(
                    hProcess, IntPtr.Zero, (uint)Marshal.SizeOf(tButton),
                    MEM_COMMIT, PAGE_READWRITE);

                if (lpData.Equals(IntPtr.Zero))
                    return false;

                for (int i = 0; i < trayCount; i++)
                {
                    SendMessage(hTrayWindow, TB_GETBUTTON, new IntPtr(i), lpData);
                    ReadProcessMemory(hProcess, lpData, &tButton, (uint)Marshal.SizeOf(tButton), out temp);
                    ReadProcessMemory(hProcess, new IntPtr(tButton.dwData), &trayData, (uint)Marshal.SizeOf(trayData), out temp);
                    GetWindowThreadProcessId(trayData.hwnd, out temp);
                    IntPtr test = IntPtr.Zero;

                    try
                    {
                        // HWP IME의 경우 종료 후에도 다른 코드 값을 내보내므로 추가 확인 작업 필요
                        test = OpenProcess(PROCESS_ALL_ACCESS, 0, temp);

                        // Process가 없을 경우 해당 Tray Icon을 제거합니다.
                        if (temp.Equals(0u) || test.Equals(IntPtr.Zero))
                        {
                            NOTIFYICONDATA icon = new NOTIFYICONDATA();
                            icon.cbSize = (uint)Marshal.SizeOf(icon);
                            icon.hIcon = trayData.hIcon;
                            icon.hWnd = trayData.hwnd;
                            icon.uCallbackMessage = trayData.uCallbackMessage;
                            icon.uID = trayData.uID;
                            Shell_NotifyIcon(NIM_DELETE, ref icon);
                        }
                    }
                    catch { }
                    finally
                    {
                        if (!test.Equals(IntPtr.Zero))
                        {
                            CloseHandle(test);
                            test = IntPtr.Zero;
                        }
                    }
                }
            }
            catch (Exception e)
            {
                throwedException = e;
            }
            finally
            {
                if (!lpData.Equals(IntPtr.Zero))
                {
                    VirtualFreeEx(hProcess, lpData, IntPtr.Zero, MEM_RELEASE);
                    lpData = IntPtr.Zero;
                }

                if (!hProcess.Equals(IntPtr.Zero))
                {
                    CloseHandle(hProcess);
                    hProcess = IntPtr.Zero;
                }
            }

            return (throwedException == null);
        }

        public static bool Cleanup()
        {
            Exception temp;
            return Cleanup(out temp);
        }
    }
}

