using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Windows.Forms;
using System.Reflection;

using Foo.Platform;
using Foo.Platform.Win32;

namespace Foo.GUILib
{

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

        /// <summary>
        /// Saves a Window using a DC of the Full Screen, then crops the window out of the
        /// full screen capture. We have to do this because SaveWindowUsingDCNBitBlt() doesn't
        /// work for all programs that overwrite the glass area on Aero (like MS Office)
        /// </summary>
        /// <remarks>
        /// IMP! - This function will work on some multiple monitor configurations but currently NOT all!
        /// If for example, the primary display is on the left and sec on the right, and the window is on the
        /// right monitor, then all x and y values will be positive, (We currently only handle multiple monitors,
        /// where one x or y value is negative!),
        /// ~more calculations must be done for multiple x, multiple y positive configurations
        /// </remarks>
        /// <param name="hWnd">handle to a Window</param>
        public Image SaveWindowUsingFullScreenShotCrop(IntPtr hWnd)
        {
            try
            {
                // First get the Window Rect
                RECT rectWnd;
                Win32Functions.GetWindowRect(hWnd, out rectWnd);

                // Now figure out which monitor the window is in
                IntPtr hMonitor = Win32Functions.MonitorFromRect(ref rectWnd, (int)Win32_Constants.MONITOR_DEFAULTTONEAREST);

                // Get the szDevice Name for the Monitor
                MONITORINFOEX monitorinfoex = new MONITORINFOEX();
                monitorinfoex.cbSize = Marshal.SizeOf(monitorinfoex);
                if (!Win32Functions.GetMonitorInfo(hMonitor, ref monitorinfoex))
                    return null;

                // Use szDevice to create a DC
                IntPtr hDC = Win32Functions.CreateDC(monitorinfoex.szDeviceName, IntPtr.Zero, IntPtr.Zero, IntPtr.Zero);

                //Here we make a compatible device context in memory for screen device context.
                IntPtr hMemDC = Win32Functions.CreateCompatibleDC(hDC);

                // Let's get all the monitor dimensions we need
                Rectangle rectDim = monitorinfoex.rcMonitor.AsRectangle;
                int DisplayHeight = rectDim.Height;
                int DisplayWidth = rectDim.Width;

                //The .net way to retrieve dimensions            
                Screen screen = Screen.FromHandle(hWnd);

                //We create a compatible bitmap of screen size using screen device context.
                IntPtr hBitmap = Win32Functions.CreateCompatibleBitmap(hDC, DisplayWidth, DisplayHeight);

                if (hBitmap != IntPtr.Zero)
                {
                    //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
                    IntPtr hOld = (IntPtr)Win32Functions.SelectObject(hMemDC, hBitmap);

                    //We copy the Bitmap to the memory device context.
                    Win32Functions.BitBlt(hMemDC, 0, 0, DisplayWidth, DisplayHeight, hDC, 0, 0, Win32_Constants.SRCCOPY);

                    //We select the old bitmap back to the memory device context.
                    Win32Functions.SelectObject(hMemDC, hOld);

                    //We delete the memory device context.
                    Win32Functions.DeleteDC(hMemDC);

                    //Delete the screen device context.
                    Win32Functions.DeleteDC(hDC);

                    //Image is created by Image bitmap handle and stored in local variable.
                    Image image = (Image)System.Drawing.Image.FromHbitmap(hBitmap);

                    // * For Debugging *
                    //string strFileNameNPathWExtPNG = (Env.GetSnapshotDirectory() + @"\" + hWnd.ToString() + ".png");
                    //image.Save(strFileNameNPathWExtPNG, System.Drawing.Imaging.ImageFormat.Png);

                    //Release the memory for compatible bitmap.
                    Win32Functions.DeleteObject(hBitmap);
                                        
                    ////
                    // Imp! - Now we want to crop the Window out of the Screen Image
                    // NOTE! - Here we convert the screen positions into the corresponding
                    //         image position on that screen
                    ////
                    int height = rectWnd.AsRectangle.Height;
                    int width = rectWnd.AsRectangle.Width;

                    rectWnd.left =  Math.Abs(rectWnd.left - screen.Bounds.Left);
                    rectWnd.right = rectWnd.left + width;

                    rectWnd.top = Math.Abs(rectWnd.top - screen.Bounds.Top);
                    rectWnd.bottom = rectWnd.top + height;

                    // Convert the Rectangle for .Net Use
                    Rectangle rectangleWnd = rectWnd.AsRectangle;
                                        
                    Bitmap bmpImage = new Bitmap(image);                
                    Bitmap bmpCrop = bmpImage.Clone(rectangleWnd, bmpImage.PixelFormat);
                    
                    //Bitmap bmpCrop = (Bitmap)image;
                    //bmpCrop = bmpCrop.Clone(rectangleWnd, bmpCrop.PixelFormat);

                    return (Image)bmpCrop;
                }
            }
            catch (Exception e)
            {
                Log.Error(string.Format("{0}() - Error thrown", MethodBase.GetCurrentMethod().Name), e);
            }

            return null;
        }

        /// <summary>
        /// Saves the Window using a DC, this works well for bUseClientDC = true, but doesn't
        /// work when using the Window's DC because of Aero drawing via directx, the window frame
        /// won't be in the image
        /// </summary>
        /// <param name="hWnd">handle to a Window</param>
        /// <param name="bUseClientDC">true to use ClientDC, false to use WindowDC</param>
        public Image SaveWindowUsingDCNBitBlt(IntPtr hWnd, bool bUseClientDC)
        {
            try
            {
                SIZE size;
                IntPtr hDC;
                IntPtr hBitmap;
                Image image;
                RECT Rect = new RECT();

                if (bUseClientDC)
                    Win32Functions.GetClientRect(hWnd, out Rect);
                else
                    Win32Functions.GetWindowRect(hWnd, out Rect);

                size.cx = (Rect.right - Rect.left);
                size.cy = (Rect.bottom - Rect.top);

                // TRY USING GetDCEx() - May actually do better
                //hDC = Win32Functions.GetDCEx(hWnd);
                if (bUseClientDC)
                    hDC = Win32Functions.GetDC(hWnd);
                else
                    hDC = Win32Functions.GetWindowDC(hWnd);

                //Here we make a compatible device context in memory for screen device context.
                IntPtr hMemDC = Win32Functions.CreateCompatibleDC(hDC);

                //We create a compatible bitmap of screen size using screen device context.
                hBitmap = Win32Functions.CreateCompatibleBitmap(hDC, size.cx, size.cy);

                if (hBitmap != IntPtr.Zero)
                {
                    //Here we select the compatible bitmap in memeory device context and keeps the refrence to Old bitmap.
                    IntPtr hOld = (IntPtr)Win32Functions.SelectObject(hMemDC, hBitmap);

                    //We copy the Bitmap to the memory device context.
                    Win32Functions.BitBlt(hMemDC, 0, 0, size.cx, size.cy, hDC, 0, 0, Win32_Constants.SRCCOPY);

                    //We select the old bitmap back to the memory device context.
                    Win32Functions.SelectObject(hMemDC, hOld);

                    //We delete the memory device context.
                    Win32Functions.DeleteDC(hMemDC);

                    //We release the screen device context.
                    Win32Functions.ReleaseDC(hWnd, hDC);

                    //Image is created by Image bitmap handle and stored in local variable.
                    image = (Image)System.Drawing.Image.FromHbitmap(hBitmap);

                    // * For Debugging *
                    //string strFileNameNPathWExtPNG = (Env.GetSnapshotDirectory() + @"\" + hWnd.ToString() + ".png");
                    //image.Save(strFileNameNPathWExtPNG, System.Drawing.Imaging.ImageFormat.Png);

                    //Release the memory for compatible bitmap.
                    Win32Functions.DeleteObject(hBitmap);
                    return image;
                }
                else
                {
                    Log.Error(string.Format("{0}() - hBitmap is Null, something is wrong", MethodBase.GetCurrentMethod().Name));
                }
            }
            catch (Exception e)
            {
                Log.Error(string.Format("{0}() - Error thrown", MethodBase.GetCurrentMethod().Name), e);
            }

            return null;
        }       
    }
}
