using System;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Forms;
using GitCommands;
using JetBrains.Annotations;
using ResourceManager;

namespace GitUI
{
    public sealed class GitUICommandsSourceEventArgs : EventArgs
    {
        public GitUICommandsSourceEventArgs([NotNull] IGitUICommandsSource gitUiCommandsSource)
        {
            GitUICommandsSource = gitUiCommandsSource;
        }

        [NotNull]
        public IGitUICommandsSource GitUICommandsSource { get; }
    }

    /// <summary>
    /// Base class for a <see cref="UserControl"/> requiring <see cref="GitModule"/> and <see cref="GitUICommands"/>.
    /// </summary>
    public class GitModuleControl : GitExtensionsControl
    {
        private readonly object _lock = new object();

        private int _isDisposed;

        /// <summary>
        /// Occurs after the <see cref="UICommandsSource"/> is set.
        /// Will only occur once, as the source cannot change after being set.
        /// </summary>
        [Browsable(false)]
        public event EventHandler<GitUICommandsSourceEventArgs> UICommandsSourceSet;

        [CanBeNull] private IGitUICommandsSource _uiCommandsSource;

        /// <summary>
        /// Gets a <see cref="IGitUICommandsSource"/> for this control.
        /// </summary>
        /// <remarks>
        /// If the commands source has not yet been initialised, this property's getter attempts
        /// to find a control-tree ancestor of type <see cref="IGitUICommandsSource"/>.
        /// </remarks>
        /// <exception cref="InvalidOperationException">Unable to initialise the source as
        /// no ancestor of type <see cref="IGitUICommandsSource"/> was found.</exception>
        [NotNull]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public IGitUICommandsSource UICommandsSource
        {
            get
            {
                if (_uiCommandsSource == null)
                {
                    lock (_lock)
                    {
                        // Double check locking
                        if (_uiCommandsSource == null)
                        {
                            // Search ancestors for an implementation of IGitUICommandsSource
                            UICommandsSource = this.FindAncestors().OfType<IGitUICommandsSource>().FirstOrDefault()
                                               ?? throw new InvalidOperationException("The UI Command Source is not available for this control. Are you calling methods before adding it to the parent control?");
                        }
                    }
                }

                // ReSharper disable once AssignNullToNotNullAttribute
                return _uiCommandsSource;
            }
            set
            {
                if (_uiCommandsSource != null)
                {
                    throw new ArgumentException($"{nameof(UICommandsSource)} is already set.");
                }

                _uiCommandsSource = value ?? throw new ArgumentException($"Can not assign null value to {nameof(UICommandsSource)}.");
                OnUICommandsSourceSet(_uiCommandsSource);
            }
        }

        /// <summary>Gets the <see cref="UICommandsSource"/>'s <see cref="GitUICommands"/> reference.</summary>
        [NotNull]
        [Browsable(false)]
        public GitUICommands UICommands => UICommandsSource.UICommands;

        /// <summary>
        /// Gets the UI commands, if they've initialised.
        /// </summary>
        /// <remarks>
        /// <para>This method will not attempt to initialise the commands if they have not
        /// yet been initialised.</para>
        /// <para>By contrast, the <see cref="UICommands"/> property attempts to initialise
        /// the value if not previously initialised.</para>
        /// </remarks>
        [ContractAnnotation("=>false,commands:null")]
        [ContractAnnotation("=>true,commands:notnull")]
        public bool TryGetUICommands(out GitUICommands commands)
        {
            commands = _uiCommandsSource?.UICommands;
            return commands != null;
        }

        /// <summary>Gets the <see cref="UICommands"/>' <see cref="GitModule"/> reference.</summary>
        [NotNull]
        [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
        [Browsable(false)]
        public GitModule Module => UICommands.Module;

        protected GitModuleControl()
        {
        }

        protected override void Dispose(bool disposing)
        {
            if (Interlocked.CompareExchange(ref _isDisposed, 1, 0) != 0)
            {
                return;
            }

            if (_uiCommandsSource != null)
            {
                DisposeUICommandsSource();
            }

            DisposeCustomResources();

            base.Dispose(disposing);
        }

        protected virtual void DisposeCustomResources()
        {
        }

        /// <summary>Occurs when the <see cref="UICommandsSource"/> is disposed.</summary>
        protected virtual void DisposeUICommandsSource()
        {
        }

        protected override bool ExecuteCommand(int command)
        {
            return ExecuteScriptCommand()
                || base.ExecuteCommand(command);

            bool ExecuteScriptCommand()
            {
                return Script.ScriptRunner.ExecuteScriptCommand(this, Module, command, this as RevisionGridControl);
            }
        }

        /// <summary>Raises the <see cref="UICommandsSourceSet"/> event.</summary>
        protected virtual void OnUICommandsSourceSet([NotNull] IGitUICommandsSource source)
        {
            UICommandsSourceSet?.Invoke(this, new GitUICommandsSourceEventArgs(source));
        }
    }
}