using InABox.Core;
using Syncfusion.Pdf.Parsing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Xml.Linq;
using ColorHelper;
using Color = System.Drawing.Color;
using Pen = System.Drawing.Pen;
using PixelFormat = System.Drawing.Imaging.PixelFormat;
using Point = System.Drawing.Point;
using Size = System.Drawing.Size;
using System.Windows.Controls;
using System.Drawing;
using System.Collections.Generic;
using System;
using System.Linq;
using Syncfusion.Pdf.Graphics;
using Syncfusion.Pdf;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
using System.Runtime;
using System.Text;
using SharpVectors.Converters;
using SharpVectors.Renderers.Wpf;
using Encoder = System.Drawing.Imaging.Encoder;

namespace InABox.WPF
{
    public static class ImageUtils
    {
        
        // https://en.wikipedia.org/wiki/List_of_file_signatures
        /* Bytes in c# have a range of 0 to 255 so each byte can be represented as
         * a two digit hex string. */
        private static readonly Dictionary<ImageFormat, string[][]> SignatureTable = new()
        {
            {
                ImageFormat.Jpeg,
                new[]
                {
                    new[] { "FF", "D8", "FF", "DB" },
                    new[] { "FF", "D8", "FF", "EE" },
                    new[] { "FF", "D8", "FF", "E0", "00", "10", "4A", "46", "49", "46", "00", "01" }
                }
            },
            {
                ImageFormat.Gif,
                new[]
                {
                    new[] { "47", "49", "46", "38", "37", "61" },
                    new[] { "47", "49", "46", "38", "39", "61" }
                }
            },
            {
                ImageFormat.Png,
                new[]
                {
                    new[] { "89", "50", "4E", "47", "0D", "0A", "1A", "0A" }
                }
            },
            {
                ImageFormat.Bmp,
                new[]
                {
                    new[] { "42", "4D" }
                }
            }
        };

        public static Size Adjust(this Size src, double maxWidth, double maxHeight, bool enlarge = false)
        {
            maxWidth = enlarge ? maxWidth : Math.Min(maxWidth, src.Width);
            maxHeight = enlarge ? maxHeight : Math.Min(maxHeight, src.Height);

            var rnd = Math.Min((decimal)maxWidth / src.Width, (decimal)maxHeight / src.Height);
            return new Size((int)Math.Round(src.Width * rnd), (int)Math.Round(src.Height * rnd));
        }

        public static Bitmap AsGrayScale(this Bitmap source)
        {
            //create a blank bitmap the same size as original
            var newBitmap = new Bitmap(source.Width, source.Height);

            //get a graphics object from the new image
            var g = Graphics.FromImage(newBitmap);

            //create the grayscale ColorMatrix
            var colorMatrix = new ColorMatrix(
                new[]
                {
                    new[] { .3f, .3f, .3f, 0, 0 },
                    new[] { .59f, .59f, .59f, 0, 0 },
                    new[] { .11f, .11f, .11f, 0, 0 },
                    new float[] { 0, 0, 0, 1, 0 },
                    new float[] { 0, 0, 0, 0, 1 }
                });

            //create some image attributes
            var attributes = new ImageAttributes();

            //set the color matrix attribute
            attributes.SetColorMatrix(colorMatrix);

            //draw the original image on the new image
            //using the grayscale color matrix
            g.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height),
                0, 0, source.Width, source.Height, GraphicsUnit.Pixel, attributes);

            //dispose the Graphics object
            g.Dispose();
            return newBitmap;
        }

        public static BitmapImage AsBitmapImage(this Bitmap src, int height, int width, bool transparent = true)
        {
            var resized = new Bitmap(src, new Size(width, height));
            return AsBitmapImage(resized, transparent);
        }

        public static BitmapImage AsBitmapImage(this Bitmap src, Color transparent)
        {
            src.MakeTransparent(transparent);
            return src.AsBitmapImage();
        }

        public static Bitmap ChangeColor(this Bitmap image, Color fromColor, Color toColor)
        {
            var attributes = new ImageAttributes();
            attributes.SetRemapTable(new ColorMap[]
            {
                new()
                {
                    OldColor = fromColor,
                    NewColor = toColor
                }
            }, ColorAdjustType.Bitmap);

            using (var g = Graphics.FromImage(image))
            {
                g.DrawImage(
                    image,
                    new Rectangle(Point.Empty, image.Size),
                    0, 0, image.Width, image.Height,
                    GraphicsUnit.Pixel,
                    attributes);
            }

            return image;
        }

        public static Bitmap Fade(this Bitmap source, float opacity)
        {
            var result = new Bitmap(source.Width, source.Height);
            //create a graphics object from the image  
            using (var gfx = Graphics.FromImage(result))
            {
                if (opacity < 1.0)
                    gfx.Clear(Color.White);
                //create a color matrix object  
                var matrix = new ColorMatrix();

                //set the opacity  
                matrix.Matrix33 = opacity;

                //create image attributes  
                var attributes = new ImageAttributes();

                //set the color(opacity) of the image  
                attributes.SetColorMatrix(matrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

                //now draw the image  
                gfx.DrawImage(source, new Rectangle(0, 0, source.Width, source.Height), 0, 0, source.Width, source.Height, GraphicsUnit.Pixel,
                    attributes);
            }

            return result;
        }

        public static BitmapImage AsBitmapImage(this Bitmap src, Color replace, Color with)
        {
            return src.ChangeColor(replace, with).AsBitmapImage(false);
        }

        public static Bitmap AsBitmap(this BitmapImage bitmapImage)
        {
            using (var outStream = new MemoryStream())
            {
                BitmapEncoder enc = new BmpBitmapEncoder();
                enc.Frames.Add(BitmapFrame.Create(bitmapImage));
                enc.Save(outStream);
                var bitmap = new Bitmap(outStream);

                return new Bitmap(bitmap);
            }
        }

        public static Bitmap AsBitmap(this BitmapSource source)
        {
            var width = source.PixelWidth;
            var height = source.PixelHeight;
            var stride = width * ((source.Format.BitsPerPixel + 7) / 8);
            var ptr = IntPtr.Zero;
            try
            {
                ptr = Marshal.AllocHGlobal(height * stride);
                source.CopyPixels(new Int32Rect(0, 0, width, height), ptr, height * stride, stride);
                using (var btm = new Bitmap(width, height, stride, PixelFormat.Format1bppIndexed, ptr))
                {
                    return new Bitmap(btm);
                }
            }
            finally
            {
                if (ptr != IntPtr.Zero)
                    Marshal.FreeHGlobal(ptr);
            }
        }

        public static Bitmap AsBitmap2(this BitmapSource source)
        {
            var bmp = new Bitmap(
                source.PixelWidth,
                source.PixelHeight,
                PixelFormat.Format32bppPArgb);
            var data = bmp.LockBits(
                new Rectangle(Point.Empty, bmp.Size),
                ImageLockMode.WriteOnly,
                PixelFormat.Format32bppPArgb);
            source.CopyPixels(
                Int32Rect.Empty,
                data.Scan0,
                data.Height * data.Stride,
                data.Stride);
            bmp.UnlockBits(data);
            return bmp;
        }

        public static BitmapImage? BitmapImageFromBase64(string base64)
        {
            return BitmapImageFromBytes(Convert.FromBase64String(base64));
        }
        public static BitmapImage? BitmapImageFromBytes(byte[]? data)
        {

            if (data?.Any() != true)
                return null;
            
            var imageSource = new BitmapImage();
            using (var ms = new MemoryStream(data))
            {
                imageSource.BeginInit();
                imageSource.StreamSource = ms;
                imageSource.CacheOption = BitmapCacheOption.OnLoad;
                imageSource.EndInit();
            }
            return imageSource;

        }
        
        public static BitmapImage? BitmapImageFromStream(Stream data)
        {
            var imageSource = new BitmapImage();
            imageSource.BeginInit();
            imageSource.StreamSource = data;
            imageSource.CacheOption = BitmapCacheOption.OnLoad;
            imageSource.EndInit();
            return imageSource;
        }

        public static BitmapImage AsBitmapImage(this Bitmap src, bool transparent = true)
        {
            if (transparent)
            {
                var pixel = src.GetPixel(0, 0);
                if(pixel.A == byte.MaxValue)
                {
                    src.MakeTransparent(pixel);
                }
            }

            var bitmapImage = new BitmapImage();
            using (var memory = new MemoryStream())
            {
                src.Save(memory, ImageFormat.Png);
                memory.Position = 0;
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memory;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();
            }

            return bitmapImage;
        }

        public static BitmapSource AsBitmapSource(this Metafile metafile, int width, int height, Color background)
        {
            var src = new Bitmap(metafile.Width, metafile.Height);
            src.SetResolution(metafile.HorizontalResolution, metafile.VerticalResolution);
            using (var g = Graphics.FromImage(src))
            {
                g.DrawImage(metafile, 0, 0, metafile.Width, metafile.Height);
            }

            var scale = Math.Min(width / (float)metafile.Width, height / (float)metafile.Height);
            var scaleWidth = src.Width * scale;
            var scaleHeight = src.Height * scale;

            var xoffset = (width - scaleWidth) / 2.0F;
            var yoffset = (height - scaleHeight) / 2.0F;

            using (var bmp = new Bitmap(width, height))
            {
                bmp.SetResolution(metafile.HorizontalResolution, metafile.VerticalResolution);
                using (var g = Graphics.FromImage(bmp))
                {
                    g.InterpolationMode = InterpolationMode.High;
                    g.CompositingQuality = CompositingQuality.HighQuality;
                    g.SmoothingMode = SmoothingMode.AntiAlias;
                    g.FillRectangle(new SolidBrush(background), new RectangleF(0, 0, width, height));
                    g.DrawImage(src, xoffset, yoffset, scaleWidth, scaleHeight);
                }

                bmp.Save("c:\\development\\emf2bmp.png");
                return Imaging.CreateBitmapSourceFromHBitmap(bmp.GetHbitmap(), IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            }
        }

        public static Bitmap BitmapSourceToBitmap(BitmapSource source)
        {
            if (source == null)
                return null;

            var pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppArgb;  //Bgr32 equiv default
            if (source.Format == PixelFormats.Bgr24)
                pixelFormat = System.Drawing.Imaging.PixelFormat.Format24bppRgb;
            else if (source.Format == PixelFormats.Pbgra32)
                pixelFormat = System.Drawing.Imaging.PixelFormat.Format32bppPArgb;
            else if (source.Format == PixelFormats.Prgba64)
                pixelFormat = System.Drawing.Imaging.PixelFormat.Format64bppPArgb;

            Bitmap bmp = new Bitmap(
                source.PixelWidth,
                source.PixelHeight,
                pixelFormat);

            BitmapData data = bmp.LockBits(
                new Rectangle(System.Drawing.Point.Empty, bmp.Size),
                ImageLockMode.WriteOnly,
                pixelFormat);

            source.CopyPixels(
                Int32Rect.Empty,
                data.Scan0,
                data.Height * data.Stride,
                data.Stride);

            bmp.UnlockBits(data);

            return bmp;
        }

        public static BitmapImage ToBitmapImage(this Bitmap bitmap)
        {
            using (var memory = new MemoryStream())
            {
                bitmap.Save(memory, ImageFormat.Png);
                memory.Position = 0;

                var bitmapImage = new BitmapImage();
                bitmapImage.BeginInit();
                bitmapImage.StreamSource = memory;
                bitmapImage.CacheOption = BitmapCacheOption.OnLoad;
                bitmapImage.EndInit();
                bitmapImage.Freeze();

                return bitmapImage;
            }
        }

        /// <summary>
        /// Load an image from image data; this will return <see langword="null"/> if <paramref name="imageData"/> is <see langword="null"/> or empty.
        /// </summary>
        /// <param name="imageData"></param>
        /// <returns></returns>
        public static BitmapImage? LoadImage(byte[]? imageData)
        {
            if(imageData is null || imageData.Length == 0)
            {
                return null;
            }
            var result = new BitmapImage();
            result.LoadImage(imageData);
            return result;
        }

        private static void LoadImage(this BitmapImage image, byte[] imageData)
        {
            using (var mem = new MemoryStream(imageData))
            {
                mem.Position = 0;
                image.BeginInit();
                image.CreateOptions = BitmapCreateOptions.PreservePixelFormat;
                image.CacheOption = BitmapCacheOption.OnLoad;
                image.UriSource = null;
                image.StreamSource = mem;
                image.EndInit();
            }

            image.Freeze();
        }
        
        public static Bitmap? MergeBitmaps(IEnumerable<Bitmap> bitmaps, int padding)
        {
            if (!bitmaps.Any())
                return null;
            
            var totalwidth = bitmaps.Aggregate(0, (total, next) => total + next.Width + (total > 0 ? padding : 0) );
            var maxheight = bitmaps.Aggregate(0, (max, next) => Math.Max(next.Height,max) );

            Bitmap result = new Bitmap(totalwidth, maxheight);  
            using (Graphics g = Graphics.FromImage(result))  
            {  
                g.Clear(Color.Transparent);
                int left = 0;
                foreach (var bitmap in bitmaps)
                {
                    g.DrawImage(bitmap, left, 0);
                    left += bitmap.Width + padding;
                }
            }
            return result;  
        }  
        

        public static byte[] ToArray<T>(this BitmapImage image) where T : BitmapEncoder, new()
        {
            byte[] data;
            var encoder = new T();
            encoder.Frames.Add(BitmapFrame.Create(image));
            using (var ms = new MemoryStream())
            {
                encoder.Save(ms);
                data = ms.ToArray();
            }

            return data;
        }

        public static BitmapImage Resize(this BitmapImage image, int height, int width)
        {
            var buffer = image.ToArray<BmpBitmapEncoder>();
            var ms = new MemoryStream(buffer);
            var result = new BitmapImage();
            result.BeginInit();
            result.CacheOption = BitmapCacheOption.None;
            result.CreateOptions = BitmapCreateOptions.IgnoreColorProfile;
            result.DecodePixelWidth = width;
            result.DecodePixelHeight = height;
            result.StreamSource = ms;
            result.Rotation = Rotation.Rotate0;
            result.EndInit();
            buffer = null;
            return result;
        }

        public static Bitmap Resize(this Bitmap bitmap, int width, int height)
        {
            if ((width == bitmap.Width) && (height == bitmap.Height))
                return bitmap;
            return new Bitmap(bitmap,new Size(width,height));
        }

        public static BitmapImage Scale(this BitmapImage image, int maxheight, int maxwidth)
        {
            var scaleHeight = maxheight / (float)image.Height;
            var scaleWidth = maxwidth / (float)image.Width;
            var scale = Math.Min(scaleHeight, scaleWidth);
            return image.Resize((int)(image.Height * scale), (int)(image.Width * scale));
        }

        public static Bitmap BitmapFromColor(Color color, int width, int height, Color frame)
        {
            var result = new Bitmap(width, height);
            var g = Graphics.FromImage(result);
            g.Clear(color);
            if (frame != Color.Transparent)
                g.DrawRectangle(new Pen(new SolidBrush(frame), 1), new Rectangle(0, 0, width-1, height-1));
            return result;
        }
        
        public static Bitmap BitmapFromColor(System.Windows.Media.Color color, int width, int height, System.Windows.Media.Color frame)
        {
            var result = new Bitmap(width, height);
            var g = Graphics.FromImage(result);
            g.Clear(Color.FromArgb(color.A,color.R,color.G,color.B));
            if (frame != Colors.Transparent)
                g.DrawRectangle(new Pen(new SolidBrush(Color.FromArgb(frame.A,frame.R,frame.G,frame.B)), 1F), new Rectangle(0, 0, width-1, height-1));
            return result;
        }

        public static Color MixColors(this Color color1, double factor, Color color2)
        {
            if (factor < 0) throw new Exception($"Factor {factor} must be >= 0.");
            if (factor > 1) throw new Exception($"Factor {factor} must be <= 1.");

            if (factor == 0) return color2;
            if (factor == 1) return color1;

            var factor1 = 1 - factor;
            return Color.FromArgb(
                (byte)(color1.A * factor + color2.A * factor1),
                (byte)(color1.R * factor + color2.R * factor1),
                (byte)(color1.G * factor + color2.G * factor1),
                (byte)(color1.B * factor + color2.B * factor1));
        }

        public static System.Windows.Media.Color MixColors(this System.Windows.Media.Color color1, double factor, System.Windows.Media.Color color2)
        {
            if (factor < 0) throw new Exception($"Factor {factor} must be >= 0.");
            if (factor > 1) throw new Exception($"Factor {factor} must be <= 1.");

            if (factor == 0) return color2;
            if (factor == 1) return color1;

            var factor1 = 1 - factor;
            return System.Windows.Media.Color.FromArgb(
                (byte)(color1.A * factor + color2.A * factor1),
                (byte)(color1.R * factor + color2.R * factor1),
                (byte)(color1.G * factor + color2.G * factor1),
                (byte)(color1.B * factor + color2.B * factor1));
        }

        public static string ColorToString(Color color)
        {
            return string.Format("#{0:X2}{1:X2}{2:X2}{3:X2}",
                color.A,
                color.R,
                color.G,
                color.B
            );
        }

        public static Color StringToColor(string colorcode)
        {
            var col = Color.Transparent;
            if (!string.IsNullOrEmpty(colorcode))
            {
                var code = colorcode.Replace("#", "");
                if (code.Length == 6)
                    col = Color.FromArgb(255,
                        byte.Parse(code.Substring(0, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(2, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(4, 2), NumberStyles.HexNumber));
                else if (code.Length == 8)
                    col = Color.FromArgb(
                        byte.Parse(code.Substring(0, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(2, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(4, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(6, 2), NumberStyles.HexNumber));
            }

            return col;
        }

        public static System.Windows.Media.Color StringToMediaColor(string colorcode)
        {
            var col = Colors.Transparent;
            if (!string.IsNullOrEmpty(colorcode))
            {
                var code = colorcode.Replace("#", "");
                if (code.Length == 6)
                    col = System.Windows.Media.Color.FromArgb(255,
                        byte.Parse(code.Substring(0, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(2, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(4, 2), NumberStyles.HexNumber));
                else if (code.Length == 8)
                    col = System.Windows.Media.Color.FromArgb(
                        byte.Parse(code.Substring(0, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(2, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(4, 2), NumberStyles.HexNumber),
                        byte.Parse(code.Substring(6, 2), NumberStyles.HexNumber));
            }

            return col;
        }

        /// <summary>
        ///     Creates color with corrected brightness.
        /// </summary>
        /// <param name="color">Color to correct.</param>
        /// <param name="correctionFactor">
        ///     The brightness correction factor. Must be between -1 and 1.
        ///     Negative values produce darker colors.
        /// </param>
        /// <returns>
        ///     Corrected <see cref="Color" /> structure.
        /// </returns>
        public static System.Windows.Media.Color AdjustBrightness(this System.Windows.Media.Color color, float correctionFactor)
        {
            float red = color.R;
            float green = color.G;
            float blue = color.B;

            if (correctionFactor < 0)
            {
                correctionFactor = 1 + correctionFactor;
                red *= correctionFactor;
                green *= correctionFactor;
                blue *= correctionFactor;
            }
            else
            {
                red = (255 - red) * correctionFactor + red;
                green = (255 - green) * correctionFactor + green;
                blue = (255 - blue) * correctionFactor + blue;
            }

            return System.Windows.Media.Color.FromArgb(color.A, (byte)red, (byte)green, (byte)blue);
        }

        public static bool TryGetImageType(byte[] imageData, [NotNullWhen(true)] out ImageFormat? format)
        {
            foreach (var signatureEntry in SignatureTable)
                foreach (var signature in signatureEntry.Value)
                {
                    var isMatch = true;
                    for (var i = 0; i < signature.Length; i++)
                    {
                        var signatureByte = signature[i];

                        // ToString("X") gets the hex representation and pads it to always be length 2
                        var imageByte = imageData[i]
                            .ToString("X2");

                        if (signatureByte == imageByte)
                            continue;
                        isMatch = false;
                        break;
                    }

                    if (isMatch)
                    {
                        format = signatureEntry.Key;
                        return true;
                    }
                }
            format = null;
            return false;
        }

        /// <summary>
        ///     Takes a byte array and determines the image file type by
        ///     comparing the first few bytes of the file to a list of known
        ///     image file signatures.
        /// </summary>
        /// <param name="imageData">Byte array of the image data</param>
        /// <returns>ImageFormat corresponding to the image file format</returns>
        /// <exception cref="ArgumentException">Thrown if the image type can't be determined</exception>
        public static ImageFormat GetImageType(byte[] imageData)
        {
            if(TryGetImageType(imageData, out var format))
            {
                return format;
            }
            throw new ArgumentException("The byte array did not match any known image file signatures.");
        }

        public static System.Drawing.Bitmap Invert(this System.Drawing.Bitmap source)
        {
            Bitmap bmpDest = new Bitmap(source.Width,source.Height);

            ColorMatrix clrMatrix = new ColorMatrix(new float[][]
            {
                new float[] {-1, 0, 0, 0, 0},
                new float[] {0, -1, 0, 0, 0},
                new float[] {0, 0, -1, 0, 0},
                new float[] {0, 0, 0, 1, 0},
                new float[] {1, 1, 1, 0, 1}
            });

            using (ImageAttributes attrImage = new ImageAttributes())
            {

                attrImage.SetColorMatrix(clrMatrix);

                using (Graphics g = Graphics.FromImage(bmpDest))
                {
                    g.DrawImage(source, new Rectangle(0, 0,
                            source.Width, source.Height), 0, 0,
                        source.Width, source.Height, GraphicsUnit.Pixel,
                        attrImage);
                }
            }

            return bmpDest;           
        }
        
        
        public static Font AdjustSize(this Font font, Graphics graphics, string text, int width)
        {
            Font result = null;
            for (int size = (int)font.Size; size > 0; size--)
            {
                result = new Font(font.Name, size, font.Style);
                SizeF adjustedSizeNew = graphics.MeasureString(text, result);
                if (width > Convert.ToInt32(adjustedSizeNew.Width))
                    return result;
            }
            return result;
            
        }

        public static Bitmap WatermarkImage(this Bitmap image, String text, System.Windows.Media.Color color, int maxfontsize = 0)
        {
            return image.WatermarkImage(text, Color.FromArgb(color.A, color.R, color.G, color.B),maxfontsize);
        }

        
        public static Bitmap WatermarkImage(this Bitmap image, String text, Color color, int maxfontsize = 0)
        {
            int w = image.Width;
            int h = image.Height;
            Bitmap result = new System.Drawing.Bitmap(w, h);
            Graphics graphics = System.Drawing.Graphics.FromImage((System.Drawing.Image)result);
            graphics.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.High;
            graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
            graphics.Clear(System.Drawing.Color.Transparent);
            graphics.DrawImage(image, 0, 0, w, h);
            
            Font drawFont = new System.Drawing.Font("Arial", 96).AdjustSize(graphics,text,(int)(image.Width * 0.9F));
            if ((maxfontsize > 0) && (drawFont.Size > maxfontsize))
                drawFont = new System.Drawing.Font("Arial", maxfontsize);
            
            SolidBrush drawBrush = new System.Drawing.SolidBrush(color);
            
            StringFormat stringFormat = new StringFormat();
            stringFormat.Alignment = StringAlignment.Center;
            stringFormat.LineAlignment = StringAlignment.Center;
            
            graphics.DrawString(text, drawFont, drawBrush, new Rectangle(0,0,w,h), stringFormat);
            
            graphics.Dispose();
            return result;
        }
        
        private static System.Windows.Media.Color AdjustColor(System.Windows.Media.Color color, Action<HSL> action)
        {
            var hsl = ColorHelper.ColorConverter.RgbToHsl(new RGB(color.R,color.G,color.B));
            action(hsl);
            var rgb = ColorHelper.ColorConverter.HslToRgb(hsl);
            return System.Windows.Media.Color.FromArgb(color.A, rgb.R, rgb.G, rgb.B);           
        }

        private static int AdjustPercentage(int original, int percentage)
        {
            int percent = Math.Min(100, Math.Max(-100, percentage));
            int newvalue = (percent < 0)
                ? (byte)((percent * original) / 100)
                : (byte)((percent * (100 - original)) / 100);
            return original + newvalue;
        }
        
        public static System.Windows.Media.Color AdjustHue(this System.Windows.Media.Color color, int degrees) => 
            AdjustColor(color, (hsl => hsl.H += degrees));

        public static System.Windows.Media.Color AdjustSaturation(this System.Windows.Media.Color color, int percentage) =>
            AdjustColor(color, (hsl =>
            {
                hsl.S = (byte)AdjustPercentage(hsl.S, percentage);
            }));        
        
        public static System.Windows.Media.Color SetSaturation(this System.Windows.Media.Color color, int percentage) =>
            AdjustColor(color, (hsl => hsl.S = (byte)percentage));
        
        public static System.Windows.Media.Color AdjustLightness(this System.Windows.Media.Color color, int percentage) =>
            AdjustColor(color, (hsl =>
            {
                hsl.L = (byte)AdjustPercentage(hsl.L, percentage);
            }));  

        public static System.Windows.Media.Color SetLightness(this System.Windows.Media.Color color, int percentage) => 
            AdjustColor(color, (hsl => hsl.L = (byte)percentage));

        public static System.Windows.Media.Color SetAlpha(this System.Windows.Media.Color color, byte alpha) =>
            System.Windows.Media.Color.FromArgb(alpha, color.R, color.G, color.B);

        public static HSL ToHSL(this System.Windows.Media.Color color)
        {
            return ColorHelper.ColorConverter.RgbToHsl(new RGB(color.R, color.G, color.B));
        }

        public static System.Windows.Media.Color ToColor(this HSL hsl)
        {
            var rgb = ColorHelper.ColorConverter.HslToRgb(hsl);
            return System.Windows.Media.Color.FromRgb(rgb.R, rgb.G, rgb.B);
        }
        
        public static HSL ToHSL(this System.Drawing.Color color)
        {
            return ColorHelper.ColorConverter.RgbToHsl(new RGB(color.R, color.G, color.B));
        }


        public static System.Windows.Media.Color GetForegroundColor(this System.Windows.Media.Color c, int threshold = 130)
        {
            var perceivedbrightness = (int)Math.Sqrt(
                    c.R * c.R * .299 +
                    c.G * c.G * .587 +
                    c.B * c.B * .114);
            return perceivedbrightness >= threshold ? Colors.Black : Colors.White;
        }
        
        public static uint ToUint(this System.Drawing.Color color) => (uint)((color.A << 24) | (color.R << 16) | (color.G << 8)  | (color.B << 0));
        public static uint ToUint(this System.Windows.Media.Color color) => (uint)((color.A << 24) | (color.R << 16) | (color.G << 8)  | (color.B << 0));

        public enum ImageEncoding
        {
            JPEG
        }

        public static ImageCodecInfo? GetEncoder(ImageFormat format)
        {
            ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
            foreach (ImageCodecInfo codec in codecs)
            {
                if (codec.FormatID == format.Guid)
                {
                    return codec;
                }
            }
            return null;
        }

        public static byte[] BitmapToPdf(Bitmap bitmap)
        {
            byte[]? _result = null;
            using (var _stream = new MemoryStream())
            {
                bitmap.Save(_stream, ImageFormat.Png);
                _result = BitmapToPdf(_stream);
            }
            return _result;
        }
        
        public static byte[] BitmapToPdf(byte[] bitmap)
        {
            byte[] _result = null;
            using (var _stream = new MemoryStream(bitmap))
                _result = BitmapToPdf(_stream);
            
            return _result;
        }

        public static byte[] BitmapToPdf(Stream stream)
        {
            byte[]? _result = null;
            PdfDocument _doc = new PdfDocument();
            PdfPage _page = _doc.Pages.Add();
            PdfGraphics _graphics = _page.Graphics;
            stream.Position = 0;
            PdfBitmap _image = new PdfBitmap(stream);
            _graphics.DrawImage(_image, 0, 0);
            using (var _out = new MemoryStream())
            {
                _doc.Save(_out);
                _out.Position = 0;
                _doc.Close(true);
                _result = _out.ToArray();
            }
            return _result;
        }
        

        
        public static bool IsPdf(byte[] data)
        {
            var pdfBytes = Encoding.ASCII.GetBytes("%PDF-");
            return data.Take(pdfBytes.Length).SequenceEqual(pdfBytes);
        }

        public static byte[] GetPDFThumbnail(byte[] pdfData, int width, int height)
        {
            PdfLoadedDocument loadeddoc = new PdfLoadedDocument(pdfData);
            Bitmap image = loadeddoc.ExportAsImage(0, new SizeF(width, height), true);
            MemoryStream stream = new MemoryStream();
            image.Save(stream, ImageFormat.Jpeg);
            return stream.ToArray();
        }
        
        public static byte[] PDFToBitmap(byte[] pdfData, int page)
        {
            PdfLoadedDocument loadeddoc = new PdfLoadedDocument(pdfData);
            Bitmap image = loadeddoc.ExportAsImage(page, new ImageExportSettings() { KeepAspectRatio = true });
            MemoryStream stream = new MemoryStream();
            image.Save(stream, ImageFormat.Jpeg);
            return stream.ToArray();
        }

        [System.Runtime.InteropServices.DllImport("gdi32.dll")]
        public static extern bool DeleteObject(IntPtr hObject);
        public static List<ImageSource> RenderPDFToImageSources(byte[] pdfData, ImageEncoding encoding = ImageEncoding.JPEG)
        {
            using var profiler = new Profiler(true);
            var rendered = new List<ImageSource>();

            var loadeddoc = new PdfLoadedDocument(pdfData);
            loadeddoc.FlattenAnnotations();
            var images = loadeddoc.ExportAsImage(0, loadeddoc.Pages.Count - 1);

            if (images != null)
                foreach (var image in images)
                {
                    var hBitmap = image.GetHbitmap();
                    try
                    {
                        var source = (ImageSource)Imaging.CreateBitmapSourceFromHBitmap(
                            hBitmap,
                            IntPtr.Zero,
                            Int32Rect.Empty,
                            BitmapSizeOptions.FromEmptyOptions());
                        source.Freeze();
                        rendered.Add(source);
                    }
                    finally
                    {
                        DeleteObject(hBitmap);
                    }
                }

            return rendered;
        }

        public static List<byte[]> RenderPDFToImageBytes(byte[] pdfData, ImageEncoding encoding = ImageEncoding.JPEG)
        {
            var rendered = new List<byte[]>();

            PdfLoadedDocument loadeddoc = new PdfLoadedDocument(pdfData);
            loadeddoc.FlattenAnnotations();
            Bitmap[] images = loadeddoc.ExportAsImage(0, loadeddoc.Pages.Count - 1);

            var jpgEncoder = GetEncoder(ImageFormat.Jpeg)!;
            var quality = Encoder.Quality;
            var encodeParams = new EncoderParameters(1);
            encodeParams.Param[0] = new EncoderParameter(quality, 100L);

            if (images != null)
                foreach (var image in images)
                {
                    using (var data = new MemoryStream())
                    {
                        image.Save(data, jpgEncoder, encodeParams);
                        rendered.Add(data.ToArray());
                    }
                }

            return rendered;
        }

        public static List<byte[]> RenderTextFileToImages(string textData)
        {
            var pdfDocument = new PdfDocument();
            var page = pdfDocument.Pages.Add();

            var font = new PdfStandardFont(PdfFontFamily.Courier, 14);
            var textElement = new PdfTextElement(textData, font);
            var layoutFormat = new PdfLayoutFormat
            {
                Layout = PdfLayoutType.Paginate,
                Break = PdfLayoutBreakType.FitPage
            };

            textElement.Draw(page, new RectangleF(0, 0, page.GetClientSize().Width, page.GetClientSize().Height), layoutFormat);

            using var docStream = new MemoryStream();
            pdfDocument.Save(docStream);

            var loadeddoc = new PdfLoadedDocument(docStream.ToArray());
            Bitmap[] bmpImages = loadeddoc.ExportAsImage(0, loadeddoc.Pages.Count - 1);

            var jpgEncoder = GetEncoder(ImageFormat.Jpeg)!;
            var quality = Encoder.Quality;
            var encodeParams = new EncoderParameters(1);
            encodeParams.Param[0] = new EncoderParameter(quality, 100L);

            var images = new List<byte[]>();
            if (bmpImages != null)
                foreach (var image in bmpImages)
                {
                    using var data = new MemoryStream();
                    image.Save(data, jpgEncoder, encodeParams);
                    images.Add(data.ToArray());
                }
            return images;
        }

        public static ContentControl CreatePreviewWindowButtonContent(string caption, Bitmap bitmap)
        {
            Frame frame = new Frame();
            frame.Padding = new Thickness(0);
            frame.Margin = new Thickness(10, 10, 10, 5);

            Grid grid = new Grid();
            grid.Margin = new Thickness(0);
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
            grid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Auto) });

            var img = new System.Windows.Controls.Image
            {
                Source = bitmap.AsBitmapImage(),
                Height = 32.0F,
                Width = 32.0F,
                Margin = new Thickness(10)
            };
            img.SetValue(Grid.RowProperty, 0);
            img.Margin = new Thickness(0);
            grid.Children.Add(img);

            var txt = new System.Windows.Controls.TextBox();
            txt.IsEnabled = false;
            txt.Text = caption;
            txt.BorderThickness = new Thickness(0);
            txt.TextWrapping = TextWrapping.WrapWithOverflow;
            txt.MaxWidth = 90;
            txt.HorizontalContentAlignment = System.Windows.HorizontalAlignment.Center;
            txt.SetValue(Grid.RowProperty, 1);

            grid.Children.Add(txt);

            frame.Content = grid;

            return frame;
        }

        public static String AsExtension(this ImageFormat format)
        {
            if (format.Equals(ImageFormat.Bmp) || format.Equals(ImageFormat.MemoryBmp))
                return "bmp";
            if (format.Equals(ImageFormat.Emf))
                return "emf";
            if (format.Equals(ImageFormat.Wmf))
                return "wmf";
            if (format.Equals(ImageFormat.Gif))
                return "gif";
            if (format.Equals(ImageFormat.Jpeg))
                return "jpeg";
            if (format.Equals(ImageFormat.Png))
                return "png";
            if (format.Equals(ImageFormat.Tiff))
                return "tiff";
            if (format.Equals(ImageFormat.Exif))
                return "exif";
            if (format.Equals(ImageFormat.Icon))
                return "ico";
            return "";
        }
        
        public static bool IsEqual(this BitmapImage? image1, BitmapImage? image2)
        {
            if (image1 == null || image2 == null)
                return false;
            return image1.ToBytes().SequenceEqual(image2.ToBytes());
        }

        public static byte[] ToBytes(this BitmapImage image)
        {
            byte[] data = new byte[] { };
            if (image != null)
            {
                try
                {
                    var encoder = new BmpBitmapEncoder();
                    encoder.Frames.Add(BitmapFrame.Create(image));
                    using (MemoryStream ms = new MemoryStream())
                    {
                        encoder.Save(ms);
                        data = ms.ToArray();
                    }

                    return data;
                }
                catch (Exception ex)
                {
                }
            }

            return data;
        }
        
        public static ImageSource? SvgToBitmapImage(Stream stream)
        {
            using (var image = new MemoryStream())
            {
                var converter = new StreamSvgConverter(new WpfDrawingSettings());
                if (converter.Convert(stream, image))
                {
                    image.Position = 0;
                    var bitmap = new BitmapImage();
                    bitmap.BeginInit();
                    bitmap.StreamSource = image;
                    bitmap.CacheOption = BitmapCacheOption.OnLoad;
                    bitmap.EndInit();
                    bitmap.Freeze();
                    
                    var drawing = new DrawingImage(converter.Drawing);
                    return drawing;
                }
            }
            return null;
        }
        

    }


}