using System;
using Quik.CommandMachine;

namespace Quik.Controls
{
    public class Control
    {
        public string Name { get; set; } = null;

        public Control Parent { get; set; } = null;

        protected RootControl Root { get; set; } = null;

        public QRectangle Bounds { get; set; }

        public bool Focused { get => Root.FocusedControl == this; }

        public QRectangle AbsoluteBounds
        {
            get => Parent is null
                ? Bounds
                : new QRectangle(Parent.Bounds.Min + Bounds.Max, Parent.Bounds.Min + Bounds.Min);
            set => Bounds = Parent is null
                ? value
                : new QRectangle(value.Min - Parent.Bounds.Min, value.Max - Parent.Bounds.Min);
        }

        private MouseButton DownButtons;

        // Hierarchy events.

        public event EventHandler Update;
        public event EventHandler<CommandQueue> Paint;
        public event EventHandler<RootChangedEventArgs> RootChanging;
        public event EventHandler<ParentChangedEventArgs> ParentChanging;
        public event EventHandler<FocusChangedEventArgs> FocusLost;
        public event EventHandler<FocusChangedEventArgs> FocusAcquired;

        // Mouse events.

        public event EventHandler<MouseButtonEventArgs> Clicked;
        public event EventHandler<MouseButtonEventArgs> MouseDown;
        public event EventHandler<MouseButtonEventArgs> MouseUp;
        public event EventHandler<MouseMoveEventArgs> MouseEnter;
        public event EventHandler<MouseMoveEventArgs> MouseMove;
        // public event EventHandler<MouseMoveEventArgs> MouseHover;
        public event EventHandler<MouseMoveEventArgs> MouseLeave;

        protected virtual void OnUpdate() => Update?.Invoke(this, EventArgs.Empty);
        protected virtual void OnPaint(CommandQueue draw) => Paint?.Invoke(this, draw);
        protected virtual void OnParentChanging(ParentChangedEventArgs args) => ParentChanging?.Invoke(this, args);
        protected virtual void OnRootChanging(RootChangedEventArgs args) => RootChanging?.Invoke(this, args);
        protected virtual void OnFocusLost(FocusChangedEventArgs args) => FocusLost?.Invoke(this, args);
        protected virtual void OnFocusAcquired(FocusChangedEventArgs args) => FocusAcquired?.Invoke(this, args);

        protected virtual void OnClicked(MouseButtonEventArgs args) => Clicked?.Invoke(this, args);
        protected virtual void OnMouseDown(MouseButtonEventArgs args) => MouseDown?.Invoke(this, args);
        protected virtual void OnMouseUp(MouseButtonEventArgs args) => MouseUp?.Invoke(this, args);
        protected virtual void OnMouseEnter(MouseMoveEventArgs args) => MouseEnter?.Invoke(this, args);
        protected virtual void OnMouseMove(MouseMoveEventArgs args) => MouseMove?.Invoke(this, args);
        // protected virtual void OnMouseHover(MouseMoveEventArgs args) => MouseHover?.Invoke(this, args);
        protected virtual void OnMouseLeave(MouseMoveEventArgs args) => MouseLeave?.Invoke(this, args);

        public void Focus()
        {
            Root?.Focus(this);
        }

        internal virtual void NotifyUpdate()
        {
            OnUpdate();
        }

        internal virtual void NotifyPaint(CommandQueue draw)
        {
            OnPaint(draw);
        }

        internal void NotifyFocusChanged(FocusChangedEventArgs args)
        {
            if (args.Focused == this)
            {
                OnFocusAcquired(args);
            }
            else
            {
                OnFocusLost(args);
            }
        }

        internal virtual void NotifyParentChanged(ParentChangedEventArgs args)
        {
            OnParentChanging(args);
            Parent = args.NewParent;
        }

        internal virtual void NotifyRootChanged(RootChangedEventArgs args)
        {
            OnRootChanging(args);
            Root = args.NewRoot;
        }

        internal virtual void NotifyMouseMove(MouseMoveEventArgs args)
        {
            QRectangle bounds = AbsoluteBounds;

            if (bounds.Contains(args.AbsolutePosition))
            {
                if (!bounds.Contains(args.AbsolutePosition - args.Motion))
                {
                    OnMouseEnter(args);
                }

                OnMouseMove(args);
            }
            else if (bounds.Contains(args.AbsolutePosition - args.Motion))
            {
                OnMouseLeave(args);
            }
        }

        internal virtual void NotifyMouseDown(MouseButtonEventArgs args)
        {
            if (AbsoluteBounds.Contains(args.AbsolutePosition))
            {
                OnMouseDown(args);
                DownButtons = args.Buttons;
            }
        }

        internal virtual void NotifyMouseUp(MouseButtonEventArgs args)
        {
            if (AbsoluteBounds.Contains(args.AbsolutePosition))
            {
                MouseButton mask;

                OnMouseUp(args);

                if ((mask = DownButtons & args.Buttons) > 0)
                {
                    MouseButtonEventArgs nargs = new MouseButtonEventArgs(args.AbsolutePosition, mask);
                    Focus();
                    OnClicked(nargs);
                }

                DownButtons &= ~mask;
            }
            else
            {
                DownButtons = 0;
            }
        }
    }
}