using ComPDFKit.Import;
using ComPDFKit.PDFPage;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Drawing.Imaging;
using System.Drawing.Printing;
using System.Linq;
using System.Printing;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace ComPDFKit.Controls.Printer
{
    public enum DuplexingStage
    {
        None,
        FontSide,
        BackSide
    }

    public static class PrintHelper
    {

        public static readonly PrintServer printServer = new PrintServer();
        private static PrintSettingsInfo printSettingsInfo;
        private static PrintQueue printQueue;
        private static int MaxCopies = 1;
        private static bool FinishedFrontSide = false;
        private static bool isManualDuplex = false;
        private static List<OutputColor> outputColors;
        private static List<Duplexing> duplexings;
        private static Bitmap blankPageBitmapForPrint;
        private static int PrintIndex;

        private static void SetPrinterLimits()
        {
            MaxCopies = printQueue.GetPrintCapabilities().MaxCopyCount.HasValue ? printQueue.GetPrintCapabilities().MaxCopyCount.Value : 1;
            outputColors = new List<OutputColor>(printQueue.GetPrintCapabilities().OutputColorCapability);
            duplexings = new List<Duplexing>(printQueue.GetPrintCapabilities().DuplexingCapability);
        }

        public static void PrintDocument(PrintSettingsInfo printSettings)
        {
            printSettingsInfo = printSettings;
            printQueue = printServer.GetPrintQueue(printSettings.PrinterName);

            SetPrinterLimits();

            if (printSettings.DuplexPrintMod == DuplexPrintMod.None ||
                (printSettings.PrintMode is BookletModeInfo bookletModeInfo && bookletModeInfo.Subset != BookletSubset.BothSides))
            {
                HandlePrintQueue(DuplexingStage.None);
            }
            else if (printSettings.DuplexPrintMod > 0 ||
                (printSettings.PrintMode is BookletModeInfo bookletModeInfo1 && bookletModeInfo1.Subset == BookletSubset.BothSides))
            {
                if (duplexings.Contains(Duplexing.TwoSidedShortEdge) || duplexings.Contains(Duplexing.TwoSidedLongEdge))
                {
                    HandlePrintQueue(DuplexingStage.None);
                }
                else
                {
                    if (OnlyOnePage())
                    {
                        HandlePrintQueue(DuplexingStage.None);
                    }
                    else
                    {
                        HandlePrintQueue(DuplexingStage.FontSide);
                        if (FinishedFrontSide)
                        {
                            DialogResult dialogResult = MessageBox.Show("Do you want to print the back side?", "Print", MessageBoxButtons.YesNo);
                            if (dialogResult == DialogResult.OK)
                            {
                                HandlePrintQueue(DuplexingStage.BackSide);
                            }
                        }
                    }
                }
            }

            bool OnlyOnePage()
            {
                return ((printSettings.PrintMode is SizeModeInfo || printSettings.PrintMode is PosterModeInfo) &&
                    printSettings.PageRangeList.Count == 1) ||
                    (printSettings.PrintMode is MultipleModeInfo multipleModeInfo &&
                    (int)Math.Ceiling(printSettings.PageRangeList.Count / (double)(multipleModeInfo.Sheet.TotalPageNumber)) == 1);
            }
        }

        private static void HandlePrintQueue(DuplexingStage stage)
        {
            if (printQueue != null)
            {
                using (var printDocument = new PrintDocument())
                {
                    var printTicket = new PrintTicket();

                    printDocument.PrinterSettings.PrinterName = printQueue.Name;

                    printDocument.DefaultPageSettings.Color = !printSettingsInfo.IsGrayscale && outputColors.Contains(OutputColor.Color);

                    if (printSettingsInfo.DuplexPrintMod > 0 || printSettingsInfo.PrintMode is BookletModeInfo)
                    {
                        if (stage == DuplexingStage.None)
                        {
                            printDocument.DefaultPageSettings.Landscape = (printSettingsInfo.PrintOrientation == PrintOrientation.Landscape);
                        }
                        else
                        {
                            printDocument.PrinterSettings.Duplex = Duplex.Simplex;
                            isManualDuplex = true;
                            PrintIndex = stage == DuplexingStage.FontSide ? 0 : 1;
                        }
                    }
                    else
                    {
                        printDocument.PrinterSettings.Duplex = Duplex.Simplex;
                    }

                    printQueue.DefaultPrintTicket = printTicket;
                    printDocument.DefaultPageSettings.PaperSize = printSettingsInfo.PaperSize;

                    List<PaperSource> paperSources = Enumerable.Range(0, printDocument.PrinterSettings.PaperSources.Count).
                        Select(i => printDocument.PrinterSettings.PaperSources[i]).ToList();
                    printDocument.DefaultPageSettings.Margins = printSettingsInfo.Margins;
                    printDocument.DefaultPageSettings.PaperSource = paperSources.FirstOrDefault();
                    if (printSettingsInfo.Copies >= printQueue.GetPrintCapabilities().MaxCopyCount)
                    {
                        printDocument.PrinterSettings.Copies = (short)MaxCopies;
                    }
                    else
                    {
                        printDocument.PrinterSettings.Copies = (short)printSettingsInfo.Copies;
                    }

                    printDocument.PrintPage += PrintDocument_PrintPage;

                    printDocument.PrinterSettings.PrintFileName = printSettingsInfo.Document.FileName;

                    try
                    {
                        printDocument.Print();
                    }
                    catch (Exception ex)
                    {
                        MessageBox.Show(ex.Message);
                    }
                }
            }
        }

        public static Bitmap ToGray(Bitmap bmp, int mode)
        {
            if (bmp == null)
            {
                return null;
            }

            int w = bmp.Width;
            int h = bmp.Height;
            try
            {
                byte newColor = 0;
                BitmapData srcData = bmp.LockBits(new Rectangle(0, 0, w, h), ImageLockMode.ReadWrite, System.Drawing.Imaging.PixelFormat.Format24bppRgb);
                unsafe
                {
                    byte* p = (byte*)srcData.Scan0.ToPointer();
                    for (int y = 0; y < h; y++)
                    {
                        for (int x = 0; x < w; x++)
                        {
                            if (mode == 0) // 加权平均
                            {
                                newColor = (byte)((float)p[0] * 0.114f + (float)p[1] * 0.587f + (float)p[2] * 0.299f);
                            }
                            else    // 算数平均
                            {
                                newColor = (byte)((float)(p[0] + p[1] + p[2]) / 3.0f);
                            }
                            p[0] = newColor;
                            p[1] = newColor;
                            p[2] = newColor;

                            p += 3;
                        }
                        p += srcData.Stride - w * 3;
                    }
                    bmp.UnlockBits(srcData);
                    return bmp;
                }
            }
            catch
            {
                return null;
            }
        }

        public static Bitmap BuildBmp(int width, int height, byte[] imgBytes)
        {
            // Check if the byte array length matches the expected size
            int expectedLength = width * height * 4;
            if (imgBytes.Length != expectedLength)
            {
                throw new ArgumentException("The length of imgBytes does not match the expected size.");
            }

            // Create a new bitmap with the specified width, height, and pixel format
            Bitmap bitmap = new Bitmap(width, height, System.Drawing.Imaging.PixelFormat.Format32bppArgb);

            // Lock the bitmap's bits for writing
            BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, width, height), ImageLockMode.WriteOnly, bitmap.PixelFormat);

            try
            {
                // Copy the byte array to the bitmap's scan0 pointer
                Marshal.Copy(imgBytes, 0, bmpData.Scan0, imgBytes.Length);
            }
            finally
            {
                // Ensure that the bitmap is always unlocked, even if an exception occurs
                bitmap.UnlockBits(bmpData);
            }

            return bitmap;
        }

        public static int CaculatePrintedPageCount(PrintSettingsInfo printSettingsInfo)
        {
            int tempCount = printSettingsInfo.PageRangeList.Count;
            if (printSettingsInfo.PrintMode is MultipleModeInfo multipleModeInfo)
            {
                tempCount = (int)Math.Ceiling((double)tempCount /
                    multipleModeInfo.Sheet.TotalPageNumber);
            }
            else if (printSettingsInfo.PrintMode is BookletModeInfo bookletInfo)
            {
                if (tempCount != 1)
                {
                    tempCount = (bookletInfo.EndPageIndex - bookletInfo.BeginPageIndex + 1) / 2;
                    if (bookletInfo.Subset != BookletSubset.BothSides)
                    {
                        tempCount /= 2;
                    }
                }
            }
            return tempCount;
        }

        public static Bitmap CombineBitmap(Bitmap background, Bitmap foreground, System.Drawing.Point point)
        {
            if (background == null)
            {
                return null;
            }
            int bgWidth = background.Width;
            int bgHeight = background.Height;
            Bitmap newMap = new Bitmap(bgWidth, bgHeight);
            Graphics graphics = Graphics.FromImage(newMap);
            graphics.DrawImage(background, new System.Drawing.Point(0, 0));
            graphics.DrawImage(foreground, point);
            graphics.Dispose();
            return newMap;
        }

        private static void PrintDocumentModSize(PrintPageEventArgs e)
        {
            int PrintedPageCount = CaculatePrintedPageCount(printSettingsInfo);
            int widthDpiRatio = (int)e.Graphics.DpiX / 100;
            int heightDpiRatio = (int)e.Graphics.DpiY / 100;

            if (printSettingsInfo.PrintMode is SizeModeInfo sizeMode)
            {
                CPDFPage page = printSettingsInfo.Document.PageAtIndex(printSettingsInfo.PageRangeList[PrintIndex]);
                Rectangle realBound = e.PageBounds;
                Point point = new Point(0, 0);

                if (page != null)
                {
                    CSize cSize = page.PageSize;
                    System.Drawing.Size pageSize = new System.Drawing.Size((int)cSize.width * widthDpiRatio, (int)cSize.height * heightDpiRatio);

                    byte[] bmpData = new byte[(int)(pageSize.Width * pageSize.Height * 4)];

                    page.RenderPageBitmap(0, 0, pageSize.Width, pageSize.Height, 0xFFFFFFFF, bmpData, printSettingsInfo.IsPrinAnnot ? 1 : 0, printSettingsInfo.IsPrintForm);
                    Bitmap bitmap = BuildBmp((int)pageSize.Width, (int)pageSize.Height, bmpData);

                    if (printSettingsInfo.IsGrayscale)
                    {
                        bitmap = ToGray(bitmap, 0);
                    }

                    if (sizeMode.SizeType == SizeType.Adaptive)
                    {
                        int resizeHeight;
                        int resizeWidth;

                        if (bitmap.Height / bitmap.Width >= printSettingsInfo.PaperSize.Height / printSettingsInfo.PaperSize.Width)
                        {
                            resizeHeight = printSettingsInfo.PaperSize.Height * heightDpiRatio;
                            resizeWidth = bitmap.Width * printSettingsInfo.PaperSize.Height / bitmap.Height * widthDpiRatio;
                        }
                        else
                        {
                            resizeWidth = printSettingsInfo.PaperSize.Width * widthDpiRatio;
                            resizeHeight = bitmap.Height * printSettingsInfo.PaperSize.Width / bitmap.Width * heightDpiRatio;
                        }

                        using (Bitmap resizedBitmap = new Bitmap(bitmap, resizeWidth, resizeHeight))
                        {
                            if (isManualDuplex && PrintIndex % 2 == 1 && printSettingsInfo.DuplexPrintMod == DuplexPrintMod.FlipShortEdge)
                            {
                                resizedBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                            }
                            float aspectRatioResizedBitmap = (float)resizedBitmap.Width / resizedBitmap.Height;
                            float aspectRatioRealBound = (float)realBound.Width / realBound.Height;

                            if (aspectRatioResizedBitmap != aspectRatioRealBound)
                            {
                                if (realBound.Width / aspectRatioResizedBitmap <= realBound.Height)
                                {
                                    realBound.Height = (int)(realBound.Width / aspectRatioResizedBitmap);
                                }
                                else
                                {
                                    realBound.Width = (int)(realBound.Height * aspectRatioResizedBitmap);
                                }
                            }
                            e.Graphics.DrawImage(resizedBitmap, realBound, new Rectangle(0, 0, resizedBitmap.Width, resizedBitmap.Height), GraphicsUnit.Pixel);
                        }
                    }
                    else if (sizeMode.SizeType == SizeType.Actural)
                    {
                        using (Bitmap resizedBitmap = ResizeBitmap(bitmap, 100))
                        {
                            if (isManualDuplex && PrintIndex % 2 == 1 && printSettingsInfo.DuplexPrintMod == DuplexPrintMod.FlipShortEdge)
                            {
                                resizedBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                            }
                            float aspectRatioResizedBitmap = (float)resizedBitmap.Width / resizedBitmap.Height;
                            float aspectRatioRealBound = (float)realBound.Width / realBound.Height;

                            if (aspectRatioResizedBitmap != aspectRatioRealBound)
                            {
                                if (realBound.Width / aspectRatioResizedBitmap <= realBound.Height)
                                {
                                    realBound.Height = (int)(realBound.Width / aspectRatioResizedBitmap);
                                }
                                else
                                {
                                    realBound.Width = (int)(realBound.Height * aspectRatioResizedBitmap);
                                }
                            }
                            e.Graphics.DrawImage(resizedBitmap, realBound, new Rectangle(0, 0, resizedBitmap.Width, resizedBitmap.Height), GraphicsUnit.Pixel);
                        }
                    }
                    else if (sizeMode.SizeType == SizeType.Customized)
                    {
                        using (Bitmap resizedBitmap = ResizeBitmap(bitmap, (printSettingsInfo.PrintMode as SizeModeInfo).Scale))
                        {
                            if (isManualDuplex && PrintIndex % 2 == 1 && printSettingsInfo.DuplexPrintMod == DuplexPrintMod.FlipShortEdge)
                            {
                                resizedBitmap.RotateFlip(RotateFlipType.RotateNoneFlipY);
                            }
                            float aspectRatioResizedBitmap = (float)resizedBitmap.Width / resizedBitmap.Height;
                            float aspectRatioRealBound = (float)realBound.Width / realBound.Height;

                            realBound.Width = (int)(realBound.Width * (printSettingsInfo.PrintMode as SizeModeInfo).Scale / 100.0); 
                            realBound.Height = (int)(realBound.Height * (printSettingsInfo.PrintMode as SizeModeInfo).Scale / 100.0);

                            if (aspectRatioResizedBitmap != aspectRatioRealBound)
                            {
                                if (realBound.Width / aspectRatioResizedBitmap <= realBound.Height)
                                {
                                    realBound.Height = (int)(realBound.Width / aspectRatioResizedBitmap);
                                }
                                else
                                {
                                    realBound.Width = (int)(realBound.Height * aspectRatioResizedBitmap);
                                }
                            }

                            realBound.X = (e.PageBounds.Width - realBound.Width) / 2;
                            realBound.Y = (e.PageBounds.Height - realBound.Height) / 2;

                            e.Graphics.DrawImage(resizedBitmap, realBound,  new Rectangle(0, 0, resizedBitmap.Width, resizedBitmap.Height), GraphicsUnit.Pixel);
                        }
                    }

                    bitmap.Dispose();
                    GC.Collect();

                    if (isManualDuplex && PrintedPageCount != 1)
                    {
                        if (PrintIndex < PrintedPageCount - 2)
                        {
                            PrintIndex += 2;
                            e.HasMorePages = true;
                        }
                        else
                        {
                            e.HasMorePages = false;
                            if (PrintIndex % 2 == 0)
                            {

                            }
                        }
                    }
                    else
                    {
                        if (PrintIndex < PrintedPageCount - 1)
                        {
                            PrintIndex++;
                            e.HasMorePages = true;
                        }
                        else
                        {
                            e.HasMorePages = false;
                        }
                    }
                }
            }
        }

        private static void PrintDocument_PrintPage(object sender, PrintPageEventArgs e)
        {
            switch (printSettingsInfo.PrintMode)
            {
                case SizeModeInfo _:
                    PrintDocumentModSize(e);
                    break;
                case PosterModeInfo _:
                    break;
                case MultipleModeInfo _:

                    break;
                case BookletModeInfo _:

                    break;
            }
        }

        internal static Bitmap ResizeBitmap(Bitmap bitmap, float scale)
        {
            int newWidth = (int)(bitmap.Width * scale / 72);
            int newHeight = (int)(bitmap.Height * scale / 72);
            Bitmap newBitmap = new Bitmap(newWidth, newHeight);
            using (Graphics g = Graphics.FromImage(newBitmap))
            {
                g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
                g.DrawImage(bitmap, 0, 0, newWidth, newHeight);
            }
            return newBitmap;
        }
    }
}
