using System;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Printing;
using System.Reflection;
using System.Runtime.InteropServices;

namespace FaxPrint
{
    /// <summary>
    /// Implementiert einen PrintController der kein ResetDC durchführt.
    /// Verwendung auf eigene Gefahr!
    /// </summary>
    /// <remarks>
    /// Basiert auf dem StandardController und verwendet interne Strukturen.
    /// Da PrintController und StandardPrintController interne Felder und Eigenschaften verwenden,
    /// wird über Reflection darauf zugegriffen.
    /// Getestet (kurz) mit .NET 3.5 SP1 und 4.0 
    /// </remarks>
    public class FaxPrintController : StandardPrintController
    {
        const string StandardController_Graphics_Field = "graphics";
        const string PrintController_modeHandle_Field = "modeHandle";
        
        const string StandardPrintController_dc_Field = "dc";
        const string DeviceContext_Hdc_Property = "Hdc";

        // modeHandle Field aus PrintController (DEVMODE)
        private IntPtr _devModeHandle = IntPtr.Zero;
        // graphics Field aus StandardPrintController
        private FieldInfo _graphicsField = null;

        public FaxPrintController()
        {
            this.SuppressResetDC = true;
        }

        public bool SuppressResetDC { get; set; }

        public override void OnStartPrint(PrintDocument document, PrintEventArgs e)
        {
            base.OnStartPrint(document, e);
            this.StartPrint(document, e);
        }

        public override System.Drawing.Graphics OnStartPage(PrintDocument document, PrintPageEventArgs e)
        {
            if (this.SuppressResetDC)
            {
                var graphics = this.StartPage(document, e);
                return graphics;
            }
            return base.OnStartPage(document, e);
        }

        /// <summary>
        /// Ruft Felddaten der Basisklassen über Reflektion ab.
        /// </summary>
        /// <exception cref="InvalidOperationException">Fehler beim Zugriff.</exception>
        private void StartPrint(PrintDocument document, PrintEventArgs e)
        {
            // Graphics Field aus StandardPrintController ermitteln (für StartPage)
            this._graphicsField = this.GetType().BaseType.GetField(StandardController_Graphics_Field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);

            // aus PrintController 
            FieldInfo modeHandleField = this.GetType().BaseType.BaseType.GetField(PrintController_modeHandle_Field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (modeHandleField != null)
                this._devModeHandle = (IntPtr)modeHandleField.GetValue(this);

            // Falsche Version?
            if (this._graphicsField == null || this._devModeHandle == IntPtr.Zero)
            {
                e.Cancel = true;
                throw new InvalidOperationException("Couldn't access DevMode field.");
            }
        }

        /// <summary>
        /// Initialisiert eine Druckseite ohne jedoch ResetDC wie der StandardPrintController auszuführen.
        /// </summary>
        /// <exception cref="InvalidOperationException">Fehler beim Zugriff.</exception>
        private Graphics StartPage(PrintDocument document, PrintPageEventArgs e)
        {

            object dc = null; // DeviceContext Field (internal class)
            IntPtr hDC = IntPtr.Zero;
            var baseType = this.GetType().BaseType;
            FieldInfo dcField = baseType.GetField(StandardPrintController_dc_Field, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
            if (dcField != null)
            {
                dc = dcField.GetValue(this);
                if (dc != null)
                {
                    // HDC des DeviceContext
                    PropertyInfo hdcProperty = dc.GetType().GetProperty(DeviceContext_Hdc_Property, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
                    if (hdcProperty != null)
                    {
                        hDC = (IntPtr)hdcProperty.GetValue(dc, null);
                    }
                }
            }

            // Falsche Version?
            if (hDC == IntPtr.Zero)
            {
                e.Cancel = true;
                throw new InvalidOperationException("Couldn't access printer DeviceContext.");
            }

            e.PageSettings.CopyToHdevmode(this._devModeHandle);

            // StandardController ruft hier ResetDC auf

            // Graphics erzeugen und intern setzen 
            // wird von StandardPrintController.OnEndPage freigegeben 
            var hdcGraphics = Graphics.FromHdcInternal(hDC);
            this._graphicsField.SetValue(this, hdcGraphics);

            if (hdcGraphics != null && document.OriginAtMargins)
            {
                OriginAtMargins(hdcGraphics, document);
            }

            // EndPage erfolgt über StandardPrintController
            if (NativeMethods.StartPage(new HandleRef(dc, hDC)) <= 0)
                throw new Win32Exception();

            return hdcGraphics;
        }

        /// <summary>Implementiert den Versatz für OriginAtMargins.</summary>
        private void OriginAtMargins(Graphics graphics, PrintDocument document)
        {
            IntPtr hDC = graphics.GetHdc();
            int logPixelsX = NativeMethods.GetDeviceCaps(hDC, NativeMethods.LOGPIXELSX);
            int logPixelsY = NativeMethods.GetDeviceCaps(hDC, NativeMethods.LOGPIXELSY);
            int marginPixelsX = NativeMethods.GetDeviceCaps(hDC, NativeMethods.PHYSICALOFFSETX);
            int marginPixelsY = NativeMethods.GetDeviceCaps(hDC, NativeMethods.PHYSICALOFFSETY);
            float dx = (float)(marginPixelsX * 100 / logPixelsX);
            float dy = (float)(marginPixelsY * 100 / logPixelsY);
            graphics.ReleaseHdc(hDC);

            graphics.TranslateTransform(-dx, -dy);

            var margins = document.DefaultPageSettings.Margins;
            graphics.TranslateTransform(margins.Left, margins.Top);
        }

        internal static class NativeMethods
        {
            internal const int LOGPIXELSX = 88;    // Logical pixels/inch in X
            internal const int LOGPIXELSY = 90;    // Logical pixels/inch in Y
            internal const int PHYSICALOFFSETX = 112;    //  Physical Printable Area x margin
            internal const int PHYSICALOFFSETY = 113;    // Physical Printable Area y margin

            [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
            internal static extern int StartPage(HandleRef hDC);
            [DllImport("gdi32.dll", CharSet = CharSet.Auto, ExactSpelling = true, SetLastError = true)]
            internal static extern int GetDeviceCaps(IntPtr hDC, int nIndex);
        }
    }
}
