using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Linq;
using System.Reflection;
using System.Windows.Forms;

namespace Pilz.UI;


[Serializable]
public class PaintingObject : ICloneable, IPaintingObjectContainer
{

    private PaintingObjectResizing resizeEngine = null;
    private bool _Selected = false;

    private PaintingControl _Parent = null;
    public Color FillColor { get; set; } = Color.Blue;
    public Color OutlineColor { get; set; } = Color.DarkBlue;
    public float OutlineThicknes { get; set; } = 1f;
    public DashStyle OutlineDashStyle { get; set; } = DashStyle.Solid;
    public LineCapProps LineStartCap { get; set; } = null;
    public LineCapProps LineEndCap { get; set; } = null;
    [JsonProperty]
    private string _Text = "";
    public TextPosition TextPosition { get; set; } = TextPosition.FullCenter;
    public StringAlignment VerticalTextAlignment { get; set; } = StringAlignment.Center;
    public StringAlignment HorizontalTextAlignment { get; set; } = StringAlignment.Center;
    public Font TextFont { get; set; } = new Font(FontFamily.GenericSansSerif, 8.25f);
    public Color TextColor { get; set; } = Color.Black;
    [JsonProperty]
    private PointF _Location = new PointF(50f, 50f);
    [JsonProperty]
    private SizeF _Size = new SizeF(50f, 80f);
    public bool EnableFill { get; set; } = true;
    public bool EnableOutline { get; set; } = true;
    public Color SelectionColor { get; set; } = Color.CornflowerBlue;
    public DashStyle SelectionDashStyle { get; set; } = DashStyle.Dot;
    [JsonProperty]
    private bool _EnableSelection = true;
    public Image Image { get; set; } = null;
    [JsonIgnore]
    public Image BufferedImage { get; set; } = null;
    public ImageSizeMode ImageSizeMode { get; set; }
    public PaintingObjectImageProperties ImageProperties { get; set; } = new PaintingObjectImageProperties();
    [JsonIgnore]
    public object Tag { get; set; } = null;
    public string Name { get; set; } = "";
    public List<PaintingObject> PinnedObjects { get; private set; } = new List<PaintingObject>();
    [JsonIgnore]
    public List<DelegateDrawPaintingObjectMethode> DrawMethodes { get; private set; } = new List<DelegateDrawPaintingObjectMethode>();
    [JsonIgnore]
    public DelegateDrawPaintingObjectMethode DrawSelectionMethode { get; private set; } = DefaultDrawMethodes.DrawSelection;
    public Cursor Cursor { get; set; } = Cursors.Default;
    public bool HardcodedSize { get; set; } = false;
    public bool HardcodedLocation { get; set; } = false;
    [JsonProperty]
    private bool _Visible = true;
    [JsonProperty]
    private bool _AutoAlignToGrid = false;
    public bool MouseTransparency { get; set; } = false;
    public PaintingObjectLayering Layering { get; private set; }
    public PaintingObjectList PaintingObjects { get; private set; }
    [JsonIgnore]
    public ulong ErrorsAtDrawing { get; private set; } = 0UL;

    public event MouseClickEventHandler MouseClick;

    public delegate void MouseClickEventHandler(PaintingObject sender, MouseEventArgs e);
    public event MouseDownEventHandler MouseDown;

    public delegate void MouseDownEventHandler(PaintingObject sender, MouseEventArgs e);
    public event MouseUpEventHandler MouseUp;

    public delegate void MouseUpEventHandler(PaintingObject sender, MouseEventArgs e);
    public event MouseMoveEventHandler MouseMove;

    public delegate void MouseMoveEventHandler(PaintingObject sender, MouseEventArgs e);
    public event SelectedChangedEventHandler SelectedChanged;

    public delegate void SelectedChangedEventHandler(PaintingObject sender, EventArgs e);
    public event PaintEventHandler Paint;

    public delegate void PaintEventHandler(PaintingObject sender, PaintEventArgs e);
    public event ParentChangedEventHandler ParentChanged;

    public delegate void ParentChangedEventHandler(PaintingObject sender, EventArgs e);
    public event VisibleChangedEventHandler VisibleChanged;

    public delegate void VisibleChangedEventHandler(PaintingObject sender, EventArgs e);
    public event MovedEventHandler Moved;

    public delegate void MovedEventHandler(PaintingObject sender, EventArgs e);
    public event MovingEventHandler Moving;

    public delegate void MovingEventHandler(PaintingObject sender, EventArgs e);
    public event MovingBeforePositionUpdatedEventHandler MovingBeforePositionUpdated;

    public delegate void MovingBeforePositionUpdatedEventHandler(PaintingObject sender, CancelEventArgs e);

    public PaintingObject()
    {
        Layering = new(this);
        PaintingObjects = new PaintingObjectList(_Parent) { EnableRaisingEvents = false };
    }

    public PaintingObject(PaintingObjectType @type) : this()
    {
        Type = type;
    }

    public PaintingObject(PaintingObjectType @type, DelegateDrawPaintingObjectMethode[] drawMethodes) : this(type)
    {
        DrawMethodes.AddRange(drawMethodes);
    }

    internal void RaiseMouseClick(MouseEventArgs e)
    {
        MouseClick?.Invoke(this, e);
    }
    internal void RaiseMouseDown(MouseEventArgs e)
    {
        MouseDown?.Invoke(this, e);
    }
    internal void RaiseMouseUp(MouseEventArgs e)
    {
        MouseUp?.Invoke(this, e);
    }
    internal void RaiseMouseMove(MouseEventArgs e)
    {
        MouseMove?.Invoke(this, e);
    }
    private void RaisePaint(PaintEventArgs e)
    {
        Paint?.Invoke(this, e);
    }
    internal void RaiseMoved(EventArgs e)
    {
        Moved?.Invoke(this, e);
    }
    internal void RaiseMoving(EventArgs e)
    {
        Moving?.Invoke(this, e);
    }
    internal void RaiseMovingBeforePositionUpdated(EventArgs e)
    {
        MovingBeforePositionUpdated?.Invoke(this, (CancelEventArgs)e);
    }

    public PaintingObjectType Type
    {
        get
        {
            var tt = PaintingObjectType.Custom;

            foreach (DelegateDrawPaintingObjectMethode d in DrawMethodes)
            {
                if (ReferenceEquals(d.Method.DeclaringType, typeof(DefaultDrawMethodes)))
                {
                    switch (d.Method.Name ?? "")
                    {
                        case "DrawPicture":
                            {
                                tt = tt | PaintingObjectType.Picture;
                                break;
                            }
                        case "DrawText":
                            {
                                tt = tt | PaintingObjectType.Text;
                                break;
                            }
                        case "DrawRectangle":
                            {
                                tt = tt | PaintingObjectType.Rectangle;
                                break;
                            }
                        case "DrawEllipse":
                            {
                                tt = tt | PaintingObjectType.Elipse;
                                break;
                            }
                        case "DrawTriangle":
                            {
                                tt = tt | PaintingObjectType.Triangle;
                                break;
                            }
                        case "DrawLine":
                            {
                                tt = tt | PaintingObjectType.Line;
                                break;
                            }
                    }
                }
            }

            return tt;
        }
        set
        {
            DrawMethodes.Clear();

            if ((value & PaintingObjectType.Picture) == PaintingObjectType.Picture)
                DrawMethodes.Add(DefaultDrawMethodes.DrawPicture);

            if ((value & PaintingObjectType.Rectangle) == PaintingObjectType.Rectangle)
                DrawMethodes.Add(DefaultDrawMethodes.DrawRectangle);

            if ((value & PaintingObjectType.Elipse) == PaintingObjectType.Elipse)
                DrawMethodes.Add(DefaultDrawMethodes.DrawEllipse);

            if ((value & PaintingObjectType.Triangle) == PaintingObjectType.Triangle)
                DrawMethodes.Add(DefaultDrawMethodes.DrawTriangle);

            if ((value & PaintingObjectType.Line) == PaintingObjectType.Line)
                DrawMethodes.Add(DefaultDrawMethodes.DrawLine);

            if ((value & PaintingObjectType.Text) == PaintingObjectType.Text)
                DrawMethodes.Add(DefaultDrawMethodes.DrawText);
        }
    }

    [JsonIgnore]
    public PointF Location
    {
        get
        {
            if (Parent is not null)
                return new PointF(_Location.X * Parent.ZoomFactor.Width, _Location.Y * Parent.ZoomFactor.Height);
            else
            {
                return _Location;
            }
        }
        set
        {
            if (Parent is not null)
                _Location = new PointF(value.X / Parent.ZoomFactor.Width, value.Y / Parent.ZoomFactor.Height);
            else
            {
                _Location = value;
            }
        }
    }

    [JsonIgnore]
    public PointF LocationDirect
    {
        get
        {
            return _Location;
        }
        set
        {
            _Location = value;
        }
    }

    [JsonIgnore]
    public SizeF Size
    {
        get
        {
            if (Parent is not null)
                return new SizeF(_Size.Width * Parent.ZoomFactor.Width, _Size.Height * Parent.ZoomFactor.Height);
            else
            {
                return _Size;
            }
        }
        set
        {
            if (Parent is not null)
                _Size = new SizeF(value.Width / Parent.ZoomFactor.Width, value.Height / Parent.ZoomFactor.Height);
            else
            {
                _Size = value;
            }
            ResetImageBuffer();
        }
    }

    [JsonIgnore]
    public SizeF SizeDirect
    {
        get
        {
            return _Size;
        }
        set
        {
            _Size = value;
            ResetImageBuffer();
        }
    }

    [JsonIgnore]
    public bool AutoAlignToGrid
    {
        get
        {
            return _AutoAlignToGrid;
        }
        set
        {
            _AutoAlignToGrid = value;
            if (value)
                ArrangeToGrid();
        }
    }

    [JsonIgnore]
    public bool IsResizing
    {
        get
        {
            if (resizeEngine is null)
                return false;
            else
            {
                return (bool)resizeEngine?.IsResizing;
            }
        }
    }

    [JsonIgnore]
    public PaintingControl Parent
    {
        get
        {
            return _Parent;
        }
        set
        {
            bool re = !ReferenceEquals(value, _Parent);
            _Parent = value;
            if (re)
                ParentChanged?.Invoke(this, new EventArgs());
        }
    }

    [JsonIgnore]
    public bool Visible
    {
        get
        {
            return _Visible;
        }
        set
        {
            if (value != _Visible)
            {
                _Visible = value;
                if (!value && !_EnableSelection)
                    EnableResize = false;
                VisibleChanged?.Invoke(this, new EventArgs());
            }
        }
    }

    [JsonIgnore]
    public bool Selected
    {
        get
        {
            return _Selected;
        }
        set
        {
            SetSelection(value, true);
        }
    }

    [JsonIgnore]
    public bool SelectedDirect
    {
        get
        {
            return Selected;
        }
        set
        {
            SetSelection(value, false);
        }
    }

    private void SetSelection(bool value, bool raiseEventOnParent)
    {
        if (EnableSelection)
        {
            if (_Selected != value)
            {
                _Selected = value;
                SelectedChanged?.Invoke(this, new EventArgs());
                if (raiseEventOnParent)
                    Parent.RaiseSelectionChanged();
            }
        }
        else
        {
            _Selected = false;
        }
    }

    [JsonIgnore]
    public float Width
    {
        get
        {
            return Size.Width;
        }
        set
        {
            Size = new SizeF(value, Size.Height);
        }
    }
    [JsonIgnore]
    public float Height
    {
        get
        {
            return Size.Height;
        }
        set
        {
            Size = new SizeF(Size.Width, value);
        }
    }

    [JsonIgnore]
    public float X
    {
        get
        {
            return Location.X;
        }
        set
        {
            Location = new PointF(value, Location.Y);
        }
    }
    [JsonIgnore]
    public float Y
    {
        get
        {
            return Location.Y;
        }
        set
        {
            Location = new PointF(Location.X, value);
        }
    }

    [JsonIgnore]
    public string Text
    {
        get
        {
            return _Text;
        }
        set
        {
            _Text = value;
        }
    }

    [JsonIgnore]
    public RectangleF Rectangle
    {
        get
        {
            return new RectangleF(Location, Size);
        }
        set
        {
            Location = value.Location;
            Size = value.Size;
        }
    }

    [JsonIgnore]
    public bool EnableSelection
    {
        get
        {
            return _EnableSelection;
        }
        set
        {
            _EnableSelection = value;
            if (!value && !_Visible)
                EnableResize = false;
            if (!value)
                Selected = false;
        }
    }

    [JsonIgnore]
    public Rectangle RectangleExtended
    {
        get
        {
            return new Rectangle((int)Math.Round(X - 12f), (int)Math.Round(Y - 12f), (int)Math.Round(Width + 12f + 12f), (int)Math.Round(Height + 12f + 12f));
        }
        set
        {
            X = value.X + 12;
            Y = value.Y + 12;
            Width = value.Width - 12 - 12;
            Height = value.Height - 12 - 12;
        }
    }

    public void FitSizeToText()
    {
        if (Parent is null)
            throw new Exception("You have to put that PaintingObject to a PaintingControl before.");

        var g = Parent.CreateGraphics();
        var newSize = g.MeasureString(Text, TextFont);
        SizeDirect = newSize + new SizeF(1f, 0f);
    }

    public void SetBounds(int x, int y, int width, int height)
    {
        Location = new Point(x, y);
        Size = new Size(width, height);
    }

    [JsonIgnore]
    public int Left
    {
        get
        {
            return (int)Math.Round(X);
        }
        set
        {
            X = value;
        }
    }

    [JsonIgnore]
    public int Top
    {
        get
        {
            return (int)Math.Round(Y);
        }
        set
        {
            Y = value;
        }
    }

    [JsonIgnore]
    public int Right
    {
        get
        {
            return (int)Math.Round(X + Width);
        }
        set
        {
            X = value - Width;
        }
    }

    [JsonIgnore]
    public int Bottom
    {
        get
        {
            return (int)Math.Round(Y + Height);
        }
        set
        {
            Y = value - Height;
        }
    }

    [JsonProperty(nameof(Tag))]
    public string TagString
    {
        get
        {
            if (Tag is string)
                return Convert.ToString(Tag);
            else
            {
                return string.Empty;
            }
        }
        set
        {
            Tag = value;
        }
    }

    public bool EnableResize
    {
        get
        {
            if (resizeEngine is null)
                return false;
            else
            {
                return resizeEngine.Enabled;
            }
        }
        set
        {
            if (resizeEngine is null && value)
                resizeEngine = new PaintingObjectResizing(this);
            else if (resizeEngine is not null)
            {
                resizeEngine.Enabled = value;
            }
        }
    }

    public void Remove()
    {
        Parent?.PaintingObjects.Remove(this);
    }

    public void AutoArrangeToGrid()
    {
        if (((Parent?.GridEnabled) is var arg1 && !arg1.HasValue || arg1.Value) && AutoAlignToGrid && arg1.HasValue)
            ArrangeToGrid();
    }

    public void ArrangeToGrid()
    {
        if (Parent is not null)
        {
            Parent.ArrangeToGrid(this, true);
            if (!Parent.StopDrawing)
                Parent.Refresh();
        }
    }

    public void Draw(PaintEventArgs e)
    {
        Draw(e, e.ClipRectangle.Location);
    }

    public void Draw(PaintEventArgs e, PointF offset)
    {
        Draw(e.Graphics, offset);

        if (Visible)
            RaisePaint(e);
    }

    public void Draw(Graphics g, PointF offset)
    {
        if (Visible)
        {
            var poevargs = new PaintingObjectPaintEventArgs(this, g, offset);

            foreach (DelegateDrawPaintingObjectMethode dm in DrawMethodes)
            {
                try
                {
                    dm?.Invoke(poevargs);
                }
                catch (Exception ex)
                {
                    ErrorsAtDrawing = (ulong)Math.Round(ErrorsAtDrawing + 1m);
                }
            }

            if (Selected && DrawSelectionMethode is not null)
                DrawSelectionMethode?.Invoke(poevargs);
        }
    }

    public object Clone()
    {
        return Clone(true);
    }

    public object Clone(bool includePinnedObject)
    {
        var obj = new PaintingObject();
        var metype = GetType();
        string[] blackField = new[] { nameof(PinnedObjects), nameof(resizeEngine), nameof(_Parent), nameof(BufferedImage), nameof(ImageProperties) };

        void copyFields(object source, object dest, string[] blackFields, Type t)
        {
            var fields = new List<FieldInfo>(t.GetFields(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.IgnoreCase | BindingFlags.Instance));
            foreach (FieldInfo @field in fields)
            {
                if (!blackFields.Contains(@field.Name))
                    @field.SetValue(dest, @field.GetValue(source));
            }
        };

        copyFields(this, obj, blackField, metype);
        copyFields(ImageProperties, obj.ImageProperties, Array.Empty<string>(), ImageProperties.GetType());

        if (includePinnedObject)
            obj.PinnedObjects.AddRange(PinnedObjects);

        obj.EnableResize = EnableResize;

        return obj;
    }

    [Obsolete("Use Layering.BringToTop() instead!")]
    public void BringToFront()
    {
        Layering.BringToTop();
    }

    [Obsolete("Use Layering.SendToBack() instead!")]
    public void SendToBack()
    {
        Layering.SendToBack();
    }

    public void ResetImageBuffer()
    {
        BufferedImage = null;
    }

}

public class PaintingObjectList : List<PaintingObject>
{

    [JsonIgnore]
    internal PaintingControl MyParent { get; private set; }
    internal bool EnableRaisingEvents { get; set; } = true;
    [JsonIgnore]
    public PaintingObjectListLayering Layering { get; private set; }

    public PaintingObjectList() : this(null)
    {
    }

    public PaintingObjectList(PaintingControl parent)
    {
        Layering = new(this);
        MyParent = parent;
    }

    public new void Add(PaintingObject item)
    {
        item.Parent = MyParent;
        base.Add(item);
        item.AutoArrangeToGrid();
        if (EnableRaisingEvents)
            MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(new[] { item }));
    }

    public void AddRange(PaintingObject[] items)
    {
        foreach (PaintingObject item in items)
            item.Parent = MyParent;
        base.AddRange(items);
        foreach (PaintingObject item in items)
            item.AutoArrangeToGrid();
        if (EnableRaisingEvents)
            MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(items));
    }

    public new void Insert(int index, PaintingObject item)
    {
        item.Parent = MyParent;
        base.Insert(index, item);
        MyParent?.AutoArrangeToGrid();
        if (EnableRaisingEvents)
            MyParent?.RaisePaintingObjectAdded(new PaintingObjectEventArgs(new[] { item }));
    }

    public new void Remove(PaintingObject item)
    {
        item.Parent = null;
        base.Remove(item);
        if (EnableRaisingEvents)
            MyParent?.RaisePaintingObjectRemoved(new PaintingObjectEventArgs(new[] { item }));
    }

    public new void RemoveAt(int index)
    {
        this[index].Parent = null;
        var item = this[index];
        base.RemoveAt(index);
        if (EnableRaisingEvents)
            MyParent?.RaisePaintingObjectRemoved(new PaintingObjectEventArgs(new[] { item }));
    }

}

public enum PaintingObjectType
{
    Custom = 0,
    Text = 1,
    Picture = 2,
    Line = 4,
    Triangle = 8,
    Rectangle = 16,
    Elipse = 32
}

public enum ImageSizeMode
{
    Fit,
    Zoom,
    Original
}

public enum TextPosition
{
    HLeft = 0x1,
    HRight = 0x2,
    HCenter = 0x4,
    VUp = 0x10,
    VDown = 0x20,
    VCenter = 0x40,
    FullCenter = HCenter | VCenter
}