using System;
using System.Collections.Generic;
using System.Text;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Diagnostics;
using System.Threading;

namespace Foo.Platform.Interacters
{
    /// <summary>
    /// BHInteracter - ButtonHook Interacter.
    /// Use this class abstraction to send/communicate with ButtonHook.
    /// </summary>
    public class BHInteracter
    {
        ////
        // Our ButtonHook.Dll Entry Points (Delegates)
        ////
        internal delegate bool MyStartButtonHook();
        internal static MyStartButtonHook _MyStartButtonHook = null;

        internal delegate bool MyStopButtonHook();
        internal static MyStopButtonHook _MyStopButtonHook = null;

        internal delegate bool MyIsButtonHookSet();
        internal static MyIsButtonHookSet _MyIsButtonHookSet = null;

        internal delegate int MyGetAllHookedWindowHandles(IntPtr pBuf);
        internal static MyGetAllHookedWindowHandles _MyGetAllHookedWindowHandles = null;

        /// <summary>
        /// Use this to send down to Buttonhook so that it knows what to show
        /// </summary>
        public enum BUTTON_HOOK_STATE
        {
            BUTTON_NONE = 1,
            BUTTON_ADD = 2,
            BUTTON_DELETE = 3
        }

        // Declare the Log4net Variable
        private static log4net.ILog Log = Logger.GetLog4NetInterface(MethodBase.GetCurrentMethod().DeclaringType);

        ////
        // Custom ButtonHook Message Handlers
        ////
        private const int WM_W32PINGISALIVE = (Platform.Win32.Win32_Constants.WM_USER + 700);
        private const int WM_W32SETBUTTONSTATE = (Platform.Win32.Win32_Constants.WM_USER + 701);
        private const int WM_W32GETBUTTONSTATE = (Platform.Win32.Win32_Constants.WM_USER + 702);
        private const int WM_W32SETPARENTWINDOWTRANSPERANCY = (Platform.Win32.Win32_Constants.WM_USER + 703);
        private const int WM_W32SETPARENTWINDOWACTIVE = (Platform.Win32.Win32_Constants.WM_USER + 704);
        private const int WM_W32POSTMESSAGETOPARENTWINDOW = (Platform.Win32.Win32_Constants.WM_USER + 705);

        ////
        // Custom OogyCaptionButton Message Handlers
        ////
        public const int WM_CAPTIONBUTTON_START = (Platform.Win32.Win32_Constants.WM_USER + 1200);
        public const int WM_CAPTIONBUTTON_STOP = (Platform.Win32.Win32_Constants.WM_USER + 1201);

        ////
        // OogyCaptionButton Spec
        ////
        private const int CAPTION_START_STOP_MAX_TRY_COUNT = 5;
        public const string CAPTIONBUTTON_CMDLINE = @"/App:StartThis4Real639";
        private const string CAPTIONBUTTON_PROCESSNAME = "CaptionButton";        
        private static string CAPTIONBUTTON_EXE = CAPTIONBUTTON_PROCESSNAME + ".exe";
        private static string CAPTIONBUTTON_EXE_FULLPATH = InstallationSpec.InstallPath + CAPTIONBUTTON_EXE;
        private static IntPtr s_CaptionButtonHandle = IntPtr.Zero;

        /// <summary>
        /// Validity internal State checking for OogyCaptionButton.exe
        /// </summary>
        private enum CAPTIONBUTTON_VALIDITY_STATE
        {
            VALIDITY_NONE = 1,
            VALIDITY_CREATEDSUCCESS = 2,
            VALIDITY_CREATEDFAILED = 3,
            VALIDITY_STOPPEDSUCCESS = 4,
            VALIDITY_STOPPEDFAILED = 5,
            VALIDITY_INTEGRITYCHECK_FAILED = 6
        }

        // State Success / Error Checking
        private static CAPTIONBUTTON_VALIDITY_STATE s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_NONE;

        /// <summary>
        /// Static Constructor Loads the ButtonHook.dll dynamically from the correct path
        /// </summary>
        static BHInteracter()
        {
            try
            {
                IntPtr procaddr = IntPtr.Zero;
                string strButtonHookDll = InstallationSpec.InstallPath + "ButtonHook.dll";

                Log.Info(string.Format("{0}() - BHInteracter() Trying to load ButtonHook.dll {1}", MethodBase.GetCurrentMethod().Name, strButtonHookDll));
                IntPtr ButtonHook = Win32.Win32Functions.LoadLibrary(strButtonHookDll);                
                if(ButtonHook != IntPtr.Zero)
                    Log.Info(string.Format("{0}() - BHInteracter() loading ButtonHook.dll succeeded", MethodBase.GetCurrentMethod().Name));
                else
                    Log.Error(string.Format("{0}() - BHInteracter() loading ButtonHook.dll failed!", MethodBase.GetCurrentMethod().Name));

                // StartButtonHook            
                procaddr = Win32.Win32Functions.GetProcAddress(ButtonHook, "_StartButtonHook@0");
                if(procaddr != null)
                    _MyStartButtonHook = (MyStartButtonHook)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyStartButtonHook));
                else
                    Log.Info(string.Format("{0}() - BHInteracter() GetProcAddress for StartButtonHook Failed", MethodBase.GetCurrentMethod().Name));

                // StopButtonHook            
                procaddr = Win32.Win32Functions.GetProcAddress(ButtonHook, "_StopButtonHook@0");
                if (procaddr != null)
                    _MyStopButtonHook = (MyStopButtonHook)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyStopButtonHook));
                else
                    Log.Info(string.Format("{0}() - BHInteracter() GetProcAddress for StopButtonHook Failed", MethodBase.GetCurrentMethod().Name));

                // IsButtonHookSet            
                procaddr = Win32.Win32Functions.GetProcAddress(ButtonHook, "_IsButtonHookSet@0");
                if (procaddr != null)
                    _MyIsButtonHookSet = (MyIsButtonHookSet)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyIsButtonHookSet));
                else
                    Log.Info(string.Format("{0}() - BHInteracter() GetProcAddress for IsButtonHookSet Failed", MethodBase.GetCurrentMethod().Name));

                // GetAllHookedWindowHandles
                procaddr = Win32.Win32Functions.GetProcAddress(ButtonHook, "_GetAllHookedWindowHandles@4");
                if (procaddr != null)
                    _MyGetAllHookedWindowHandles = (MyGetAllHookedWindowHandles)Marshal.GetDelegateForFunctionPointer(procaddr, typeof(MyGetAllHookedWindowHandles));
                else
                    Log.Info(string.Format("{0}() - BHInteracter() GetProcAddress for GetAllHookedWindowHandles Failed", MethodBase.GetCurrentMethod().Name));
            }
            catch (Exception e)
            {
                Log.Error(string.Format("{0}() - BHInteracter() Construction Failed", MethodBase.GetCurrentMethod().Name), e);
            }
        }

        /// <summary>
        /// Use this function wrapper to call the StartButtonHook DLLEntry Point
        /// </summary>
        public static void StartButtonHookDLLEntryW()
        {
            Log.Info(string.Format("{0}() - CaptionButton - About to call StartButtonHook()", MethodBase.GetCurrentMethod().Name));
            if (_MyStartButtonHook())
                Log.Info(string.Format("{0}() - StartButtonHook() Succeeded", MethodBase.GetCurrentMethod().Name));
            else
                Log.Error(string.Format("{0}() - StartButtonHook() Failed", MethodBase.GetCurrentMethod().Name));
        }

        /// <summary>
        /// Use this function wrapper to call the StopButtonHook DLLEntry Point
        /// </summary>
        public static void StopButtonHookDLLEntryW()
        {
            Log.Info(string.Format("{0}() - CaptionButton - About to call StopButtonHook()", MethodBase.GetCurrentMethod().Name));
            if (_MyStopButtonHook())
                Log.Info(string.Format("{0}() - StopButtonHook() Succeeded", MethodBase.GetCurrentMethod().Name));
            else
                Log.Error(string.Format("{0}() - StopButtonHook() Failed", MethodBase.GetCurrentMethod().Name));
        }

        /// <summary>
        /// Use this function wrapper to call the IsButtonHookSet DLLEntry Point
        /// </summary>
        public static bool IsButtonHookSetDLLEntryW()
        {
            return _MyIsButtonHookSet();
        }

        /// <summary>
        /// Use this function wrapper to call the GetAllHookedWindowHandles DLLEntry Point
        /// </summary>
        public static void GetAllHookedWindowHandlesDLLEntryW(ref List<IntPtr> handleList)
        {
            // Temporary ar buffer
            int[] ar = new int[Win32.Win32_Constants.MAX_PATH];

            // allocate and reset an new unmanaged buffer
            IntPtr p = Marshal.AllocHGlobal(Marshal.SizeOf(ar));
            Marshal.Copy(ar, 0, p, ar.Length);

            // get the size
            int nsize = _MyGetAllHookedWindowHandles(p);
            
            // copy data back as needed *delete unmanaged buffer*
            Marshal.Copy(p,ar,0,nsize);
            Marshal.FreeHGlobal(p);

            // Make sure the passed in list is empty * and pass out the handle list*
            handleList.Clear();
            for (int i = 0; i < nsize; ++i)
                handleList.Add((IntPtr)ar[i]);
        }

        /// <summary>
        /// CaptionButton exe is the external process that hosts ButtonHook.dll
        /// </summary>
        /// <returns>true if the CaptionButtion exe is running, false otherwise</returns>
        public static bool IsCaptionButtonRunning()
        {
            Process[] processes = Process.GetProcessesByName(CAPTIONBUTTON_PROCESSNAME);
            return (processes.Length == 1);
        }
        
        /// <summary>
        /// Start CaptionButton exe which can host the ButtonHook.dll
        /// </summary>       
        public static void StartCaptionButton()
        {
            try
            {
                if (!IsCaptionButtonRunning())
                {
                    TemporaryVal.DeleteVal();
                    ProcessStartInfo startInfo = new ProcessStartInfo(CAPTIONBUTTON_EXE_FULLPATH, CAPTIONBUTTON_CMDLINE);
                    startInfo.WindowStyle = ProcessWindowStyle.Hidden;
                    startInfo.WorkingDirectory = InstallationSpec.InstallPath;
                    Process p = Process.Start(startInfo);
                    Log.Info(string.Format("{0}() - Created a new Oogy CaptionButton Instance EXE with PID{1}", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));

                    // Wait a little before sending any messages
                    Thread.Sleep(3000);

                    ////
                    // State Checking *IMP* .exe could have not shut down correctly, and buttonhook never got stopped
                    // so let's check to see what is going on
                    ////
                    bool bIsInvalidState = ((s_CaptionButtonState != CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_NONE) &&
                                            (s_CaptionButtonState != CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_STOPPEDSUCCESS));

                    // Let's get the window handle from the child process
                    s_CaptionButtonHandle = (IntPtr)TemporaryVal.GetValI();
                    if (s_CaptionButtonHandle == IntPtr.Zero)
                        Log.Error(string.Format("{0}() - Child Handle is Zero! This means all functions below will return out and everything fails. Check into this.", MethodBase.GetCurrentMethod().Name));

                    TemporaryVal.DeleteVal();

                    // We somehow got into an invalid state, so let's send Stop Messages and see what happens,
                    // if those failed there is really not much more we can do except fail here
                    if (bIsInvalidState && !Helper_CaptionButtonMessageSender(s_CaptionButtonHandle, WM_CAPTIONBUTTON_STOP))
                        Log.Info(string.Format("{0}() - Trying to StopButtonHook gracefully failed - ignoring and trying to StartButtonHook Anyways", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));

                    // Regular starting... 
                    if (Helper_CaptionButtonMessageSender(s_CaptionButtonHandle, WM_CAPTIONBUTTON_START))
                    {
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_CREATEDSUCCESS;
                        Log.Info(string.Format("{0}() - StartCaptionButton Succeeded - Started the ButtonHook Worked Successfully", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));
                    }
                    else
                    {
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_CREATEDFAILED;
                        Log.Error(string.Format("{0}() - StartCaptionButton Failed - Started the ButtonHook Did not work - VALIDITY_CREATEDFAILED - forcefully Killing the Process", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));
                        p.Kill();
                    }
                }
                else
                {
                    // Process is already running for some reason - this should never happen, but if it does,
                    // we'll handle it by doing an integrity check
                    Log.Info(string.Format("{0}() - StartCaptionButton - CaptionButton.exe already exists - performing Complete Integrity Check...", MethodBase.GetCurrentMethod().Name));
                    bool bIntegrityIsGood = CompleteButtonHookIntegrityCheck();

                    if (bIntegrityIsGood)
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_CREATEDSUCCESS;
                    else
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_INTEGRITYCHECK_FAILED;
                }
            }
            catch (Exception e)
            {
               Log.Error(string.Format("{0}() - StartCaptionButton - Error occured", MethodBase.GetCurrentMethod().Name), e);
            }
        }

        /// <summary>
        /// Stop CaptionButton exe which can host the ButtonHook.dll
        /// </summary>       
        public static void StopCaptionButton()
        {
            Log.Info(string.Format("{0}() - StopCaptionButton Called", MethodBase.GetCurrentMethod().Name));
            if (IsCaptionButtonRunning())
            {                
                Process[] processes = Process.GetProcessesByName(CAPTIONBUTTON_PROCESSNAME);
                foreach (Process p in processes)
                {
                    Log.Info(string.Format("{0}() - Found OogyCaptionButton Pid {1}", MethodBase.GetCurrentMethod().Name, p.Id));

                    if (Helper_CaptionButtonMessageSender(s_CaptionButtonHandle, WM_CAPTIONBUTTON_STOP))
                    {
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_STOPPEDSUCCESS;
                        Log.Info(string.Format("{0}() - StopCaptionButton Succeeded - Closing the ButtonHook Worked Successfully", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));
                    }
                    else
                    {
                        s_CaptionButtonState = CAPTIONBUTTON_VALIDITY_STATE.VALIDITY_STOPPEDFAILED;
                        Log.Error(string.Format("{0}() - StopCaptionButton Failed - Closing the ButtonHook Did not work - forcefully Killing the Process", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));
                    }

                    Log.Info(string.Format("{0}() - Closing a Oogy CaptionButton Instance EXE of PID{1}", MethodBase.GetCurrentMethod().Name, p.Id.ToString()));
                    p.Kill();
                }
            }
        }

        /// <summary>
        /// Use this to send the Start/Stop Messages to Oogy CaptionButton.exe. Small Helper 
        /// function allows us to do cleaner error handling for starting and stopping (above)
        /// </summary>
        /// <param name="Message">WM_CAPTIONBUTTON_START or WM_CAPTIONBUTTON_STOP</param>
        /// <returns>returns true if successful, false otherwise</returns>
        private static bool Helper_CaptionButtonMessageSender(IntPtr hWnd, int Message)
        {
            if((hWnd != IntPtr.Zero) && 
               (Message == WM_CAPTIONBUTTON_START || Message == WM_CAPTIONBUTTON_STOP))
            {
                for (int i = 0; i < CAPTION_START_STOP_MAX_TRY_COUNT; ++i)
                {
                    // Send the Stop ButonHook Message to the Hidden window of the Process                
                    Win32.Win32Functions.SendMessage(hWnd, Message, IntPtr.Zero, IntPtr.Zero);

                    // Wait a little second before querying for the state
                    Thread.Sleep(500);

                    // If this is a start message we can stop once the button hook is started
                    if (Message == WM_CAPTIONBUTTON_START &&
                        IsButtonHookSetDLLEntryW())
                        return true;

                    // If this is a stop message we can stop once the button hook no longer set
                    if (Message == WM_CAPTIONBUTTON_STOP &&
                        !IsButtonHookSetDLLEntryW())
                        return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Check to see if buttonhook is alive for the passed in window
        /// </summary>
        /// <param name="hWnd">handle to a buttonHooked Parent Window</param>      
        public static bool IsButtonHookAliveOnWindow(IntPtr hWnd)
        {
            if (hWnd != IntPtr.Zero)
            {
                int retVal = Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32PINGISALIVE, IntPtr.Zero, IntPtr.Zero);
                if (retVal == 0)
                {
                    Log.Error(string.Format("{0}() - IsButtonHookAlive Failed for Window {1}", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));
                    return false;
                }
                else
                {
                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Call this function to run a complete ButtonHook Integrity Check
        /// </summary>
        /// <returns>true if buttonhook passed all checks, false otherwise</returns>
        public static bool CompleteButtonHookIntegrityCheck()
        {
            bool bSuccess = true;
            Log.Info(string.Format("{0}() - CompleteButtonHookIntegrityCheck got called", MethodBase.GetCurrentMethod().Name));            

            // Make first sure that the Hook Is Set
            if (!IsButtonHookSetDLLEntryW())
                bSuccess = false;

            // Now iterate through each hooked window pinging it
            // for feedback
            List<IntPtr> handleList = new List<IntPtr>();
            foreach (IntPtr hWnd in handleList)
            {
                if (!IsButtonHookAliveOnWindow(hWnd) && Win32.Win32Functions.IsWindow(hWnd))
                {
                    bSuccess = false; // buttonhook not responsive on an existing window
                    break;
                }
            }
            
            if(bSuccess)
                Log.Info(string.Format("{0}() - CompleteButtonHookIntegrityCheck passed successfully", MethodBase.GetCurrentMethod().Name));
            else
                Log.Error(string.Format("{0}() - CompleteButtonHookIntegrityCheck failed", MethodBase.GetCurrentMethod().Name));

            return bSuccess;
        }

        /// <summary>
        /// Use this to set the Transparency on a Window that is running the ButtonHook. ~Will Only work
        /// for those windows.
        /// </summary>
        /// <param name="hWnd">handle to a buttonHooked Parent Window</param>
        /// <param name="alpha">0-255 byte value indicating the Transparency</param>
        public static void SetWindowTransparency(IntPtr hWnd, byte alpha)
        {
            if (hWnd != IntPtr.Zero)
            {
                int retVal = Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32SETPARENTWINDOWTRANSPERANCY, IntPtr.Zero, (IntPtr)alpha);
                if (retVal == 0)
                    Log.Error(string.Format("{0}() - SetWindowTransparency Failed for Window {1} with alpha {2}", MethodBase.GetCurrentMethod().Name, hWnd.ToString(), alpha.ToString()));
            }
        }

        /// <summary>
        /// Use this to set the ActiveWindow on a Window that is running the ButtonHook. ~Will Only work
        /// for those windows.
        /// </summary>
        /// <param name="hWnd">handle to a buttonHooked Parent Window</param>
        public static void SetAsActiveWindow(IntPtr hWnd)
        {
            if (hWnd != IntPtr.Zero)
            {
                int retVal = Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32SETPARENTWINDOWACTIVE, IntPtr.Zero, IntPtr.Zero);
                if (retVal == 0)
                    Log.Error(string.Format("{0}() - SetAsActiveWindow Failed for Window {1}", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));
            }
        }

        /// <summary>
        /// Use this to send a message to a window to send a message to itself
        /// </summary>
        /// <param name="hWnd"></param>
        public static void PostMessageAsIfFromWindow(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam )
        {
            if (hWnd != IntPtr.Zero)
            {
                StringBuilder strAtom = new StringBuilder(hWnd.ToString() + ";" + Msg.ToString() + ";" + wParam.ToString() + ";" + lParam.ToString());
                int nAtom = Win32.Win32Functions.GlobalAddAtom(strAtom);

                if (nAtom == 0)
                    Log.Error(string.Format("{0}() - SendMessageAsIfFromWindow GlobalAddAtom Failed for Window {1}", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));

                int retVal = Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32POSTMESSAGETOPARENTWINDOW, IntPtr.Zero, (IntPtr)nAtom);
                if (retVal == 0)
                    Log.Error(string.Format("{0}() - SendMessageAsIfFromWindow ButtonHook Failed to process the String for Window {1}", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));

                Win32.Win32Functions.GlobalDeleteAtom(nAtom);
            }
        }

        /// <summary>
        /// Use this to set a new ButtonState to a buttonhooked window
        /// </summary>
        /// <param name="hWnd">handle to a buttonHooked Parent Window</param>
        /// <param name="buttonState">a new button state</param>
        public static BUTTON_HOOK_STATE SetW32ButtonToNewState(IntPtr hWnd, BUTTON_HOOK_STATE buttonState)
        {
            if (hWnd != IntPtr.Zero)
            {
                int retVal = Platform.Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32SETBUTTONSTATE, IntPtr.Zero, (IntPtr)buttonState);
                if (retVal == 0)
                    Log.Error(string.Format("{0}() - SetW32ButtonToNewState Failed for Window {1} ", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));                
            }
            return buttonState;
        }

        /// <summary>
        /// Use this to get a ButtonState from a buttonhooked window
        /// </summary>
        /// <param name="hWnd">handle to a buttonHooked Parent Window</param>
        /// <param name="buttonState">a new button state</param>
        public static BUTTON_HOOK_STATE GetW32ButtonState(IntPtr hWnd)
        {
            BUTTON_HOOK_STATE buttonState = BUTTON_HOOK_STATE.BUTTON_NONE;
            if (hWnd != IntPtr.Zero)
            {
                int retVal = Platform.Win32.Win32Functions.SendMessage((IntPtr)hWnd, WM_W32GETBUTTONSTATE, IntPtr.Zero, IntPtr.Zero);
                if (retVal == 0)
                    Log.Error(string.Format("{0}() - SetW32ButtonToNewState Failed for Window {1} ", MethodBase.GetCurrentMethod().Name, hWnd.ToString()));

                try
                {
                    buttonState = (BUTTON_HOOK_STATE)retVal;
                }
                catch (Exception e)
                {
                    Log.Error(string.Format("{0}() - Error Thrown ", MethodBase.GetCurrentMethod().Name), e);
                }
            }
            return buttonState;
        }

    }
}
