using FastReport;
using FastReport.Utils;
using InABox.Core;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Design;
using System.Linq;

namespace InABox.Wpf.Reports.CustomObjects
{
    public class MultiSignatureObject : MultiItemObject
    {
        private FillBase textFill;

        private IDictionary<string, byte[]>? signatureData { get; set; }

        [Category("Data")]
        public string? DataColumn { get; set; }

        [Category("Data")]
        public Dictionary<string, Image>? Signatures { get; set; }

        [Category("Layout")]
        public float LabelHeight { get; set; }

        [Category("Appearance")]
        [Editor("FastReport.TypeEditors.FillEditor, FastReport", typeof(UITypeEditor))]
        public FillBase TextFill
        {
            get { return textFill; }
            set
            {
                if (value == null)
                    throw new ArgumentNullException("TextFill");
                textFill = value;
                if (!String.IsNullOrEmpty(Style))
                    Style = "";
            }
        }

        [Category("Appearance")]
        public Font Font { get; set; }

        public MultiSignatureObject()
        {
            LabelHeight = 20;
            textFill = new SolidFill(Color.Black);
            Font = DrawUtils.DefaultReportFont;
        }

        public override void GetData()
        {
            base.GetData();
            if (!string.IsNullOrEmpty(DataColumn))
            {
                signatureData = null;
                Signatures = null;

                object data = Report.GetColumnValueNullable(DataColumn);
                if (data is IDictionary<string, byte[]> dict)
                {
                    SetSignatureData(dict);
                }
            }
        }
        private void SetSignatureData(IDictionary<string, byte[]> signatureData)
        {
            this.signatureData = signatureData;
        }

        private class SignatureItem : Item
        {
            public string Name;

            public Image Image;

            public float Width => Image.Width;
            public float Height { get; }

            public SignatureItem(string name, Image image, float labelHeight)
            {
                Name = name;
                Image = image;
                Height = image.Height + labelHeight;
            }
        }

        #region Serialization

        public override void Serialize(FRWriter writer)
        {
            base.Serialize(writer);
            MultiSignatureObject c = writer.DiffObject as MultiSignatureObject;

            if (DataColumn != c.DataColumn)
                writer.WriteValue("DataColumn", DataColumn);
            if ((writer.SerializeTo != SerializeTo.Preview || Font != c.Font) && writer.ItemName != "inherited")
                writer.WriteValue("Font", Font);
            if(TextFill != c.TextFill)
                TextFill.Serialize(writer, "TextFill", c.TextFill);

            if (writer.SerializeTo != SerializeTo.SourcePages)
            {
                if (writer.SerializeTo == SerializeTo.Preview || String.IsNullOrEmpty(DataColumn))
                {
                    if (Signatures == null && signatureData != null)
                        writer.WriteStr("Images", Serialization.Serialize(signatureData));
                    else if (!writer.AreEqual(Signatures, c.Signatures))
                        writer.WriteValue("Images", Signatures);
                }
            }
        }

        public override void Deserialize(FRReader reader)
        {
            base.Deserialize(reader);
            if (reader.HasProperty("TextFill"))
            {
                TextFill.Deserialize(reader, "TextFill");
            }
            if (reader.HasProperty("Images"))
            {
                SetSignatureData(Serialization.Deserialize<Dictionary<string, byte[]>>(reader.ReadStr("Images")));
            }
            switch (reader.DeserializeFrom)
            {
                case SerializeTo.Undo:
                case SerializeTo.Preview:
                case SerializeTo.Clipboard:
                    // skip
                    break;
                default:
                    if (!reader.HasProperty("Font") && reader.ItemName != "inherited")
                    {
                        string creatorVersion = reader.Root.GetProp("ReportInfo.CreatorVersion");
                        if (!String.IsNullOrEmpty(creatorVersion))
                        {
                            try
                            {
                                string[] versions = creatorVersion.Split('.');
                                int major = 0;
                                if (Int32.TryParse(versions[0], out major))
                                {
                                    if (major < 2016)
                                    {
                                        Font = new Font("Arial", 10);
                                    }
                                }
                            }
                            catch
                            {

                            }
                        }
                    }
                    break;
            }
        }

        #endregion

        public override void Assign(Base source)
        {
            base.Assign(source);

            if (source is MultiSignatureObject src)
            {
                DataColumn = src.DataColumn;
                TextFill = src.TextFill;
                Font = src.Font;

                Signatures = src.Signatures == null ? null : src.Signatures.ToDictionary(x => x.Key, x => x.Value.Clone() as Image);
                if (src.Signatures == null && src.signatureData != null)
                {
                    signatureData = src.signatureData;
                }
            }
        }


        public static Image? Load(byte[] bytes)
        {
            if (bytes != null && bytes.Length > 0)
            {
                try
                {
#if CROSSPLATFORM
                    // TODO memory leaks image converter
                    return Image.FromStream(new MemoryStream(bytes));
#else
                    return new ImageConverter().ConvertFrom(bytes) as Image;
#endif

                }
                catch
                {
                    Bitmap errorBmp = new Bitmap(10, 10);
                    using (Graphics g = Graphics.FromImage(errorBmp))
                    {
                        g.DrawLine(Pens.Red, 0, 0, 10, 10);
                        g.DrawLine(Pens.Red, 0, 10, 10, 0);
                    }
                    return errorBmp;
                }
            }
            return null;
        }

        private void LoadSignatures()
        {
            if (signatureData == null) return;

            Signatures = new();
            var oldData = new Dictionary<string, byte[]>();
            foreach(var (name, data) in signatureData)
            {
                var saveImageData = data;
                // FImageData will be reset after this line, keep it
                var newImage = Load(data);
                if (newImage != null)
                {
                    Signatures.Add(name, newImage);
                }
                oldData[name] = saveImageData;
            }
            signatureData = oldData;
        }

        internal StringFormat GetStringFormat(GraphicCache cache, StringFormatFlags flags, float scale)
        {
            StringAlignment align = StringAlignment.Near;
            if (HorizontalAlignment == System.Windows.HorizontalAlignment.Center)
                align = StringAlignment.Center;
            else if (HorizontalAlignment == System.Windows.HorizontalAlignment.Right)
                align = StringAlignment.Far;

            StringAlignment lineAlign = StringAlignment.Near;
            if (VerticalAlignment == System.Windows.VerticalAlignment.Center)
                lineAlign = StringAlignment.Center;
            else if (VerticalAlignment == System.Windows.VerticalAlignment.Bottom)
                lineAlign = StringAlignment.Far;

            return cache.GetStringFormat(align, lineAlign, StringTrimming.None, flags, 0f, 0f);
        }

        protected override void DrawItem(FRPaintEventArgs e, Item item, float x, float y, float w, float h)
        {
            if (item is SignatureItem signature)
            {
                var imageBoxWidth = w;
                var imageBoxHeight = h - LabelHeight;
                var aspectRatio = signature.Image.Width / signature.Image.Height;

                float imageWidth;
                float imageHeight;

                if (aspectRatio < imageBoxWidth / imageBoxHeight)
                {
                    imageHeight = imageBoxHeight;
                    imageWidth = imageHeight * aspectRatio;
                }
                else
                {
                    imageWidth = imageBoxWidth;
                    imageHeight = imageWidth / aspectRatio;
                }
                float imageX = x;
                float imageY = y;
                switch (HorizontalAlignment)
                {
                    case System.Windows.HorizontalAlignment.Center:
                        imageX += imageBoxWidth / 2 - imageWidth / 2;
                        break;
                    case System.Windows.HorizontalAlignment.Right:
                        imageX += imageBoxWidth - imageWidth;
                        break;
                    case System.Windows.HorizontalAlignment.Stretch:
                        imageWidth = imageBoxWidth;
                        break;
                }
                switch (VerticalAlignment)
                {
                    case System.Windows.VerticalAlignment.Center:
                        imageY += imageBoxHeight / 2 - imageHeight / 2;
                        break;
                    case System.Windows.VerticalAlignment.Top:
                        imageY += imageBoxHeight - imageHeight;
                        break;
                    case System.Windows.VerticalAlignment.Stretch:
                        imageHeight = imageBoxHeight;
                        break;
                }

                RectangleF textRect = new RectangleF(
                    x * e.ScaleX,
                    (y + h - LabelHeight) * e.ScaleY,
                    w * e.ScaleX,
                    h * e.ScaleY);

                Brush textBrush = null;
                if (TextFill is SolidFill)
                    textBrush = e.Cache.GetBrush((TextFill as SolidFill).Color);
                else
                    textBrush = TextFill.CreateBrush(textRect, e.ScaleX, e.ScaleY);

                Font font = e.Cache.GetFont(Font.FontFamily,
                    IsPrinting ? Font.Size : Font.Size * e.ScaleX * 96f / DrawUtils.ScreenDpi,
                    Font.Style);

                e.Graphics.DrawImage(signature.Image, imageX * e.ScaleX, imageY * e.ScaleY, imageWidth * e.ScaleX, imageHeight * e.ScaleY);

                var horz = HorizontalAlignment switch
                {
                    System.Windows.HorizontalAlignment.Right => HorzAlign.Right,
                    System.Windows.HorizontalAlignment.Center => HorzAlign.Center,
                    System.Windows.HorizontalAlignment.Stretch => HorzAlign.Justify,
                    _ => HorzAlign.Left
                };
                var vert = VerticalAlignment switch
                {
                    System.Windows.VerticalAlignment.Bottom => VertAlign.Bottom,
                    System.Windows.VerticalAlignment.Center => VertAlign.Center,
                    System.Windows.VerticalAlignment.Stretch => VertAlign.Top,
                    _ => VertAlign.Top
                };
                
                StringFormat format = GetStringFormat(e.Cache, 0, e.ScaleX);
                // use advanced rendering
                AdvancedTextRenderer advancedRenderer = new AdvancedTextRenderer(signature.Name, e.Graphics, font, textBrush,
                    null, textRect, format, horz, vert, LabelHeight * e.ScaleY, 0,
                    1, false, false, false, false,
                    e.ScaleX * 96f / DrawUtils.ScreenDpi,
                    IsPrinting ? 1 : e.ScaleX * 96f / DrawUtils.ScreenDpi, null, IsPrinting);
                advancedRenderer.Draw();
            }
        }

        protected override IList<Item>? LoadItems()
        {
            if (Signatures == null)
            {
                LoadSignatures();
            }
            if (Signatures == null)
            {
                return null;
            }
            return Signatures.Select(x => new SignatureItem(x.Key, x.Value, LabelHeight) as Item).ToList();
        }

    }
}
