using System;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Windows.Threading;
using GitCommands;
using GitUI.Properties;
using GitUI.UserControls;
using JetBrains.Annotations;
using Microsoft.WindowsAPICodePack.Taskbar;

namespace GitUI
{
    public partial class FormStatus : GitExtensionsForm
    {
        private readonly bool _useDialogSettings;

        private DispatcherFrameModalController _modalController;

        public FormStatus() : this(true)
        {
        }

        public FormStatus(bool useDialogSettings)
            : this(null, useDialogSettings)
        {
        }

        public FormStatus([CanBeNull] ConsoleOutputControl consoleOutput, bool useDialogSettings)
            : base(true)
        {
            _useDialogSettings = useDialogSettings;

            ConsoleOutput = consoleOutput ?? ConsoleOutputControl.CreateInstance();
            ConsoleOutput.Dock = DockStyle.Fill;
            ConsoleOutput.Terminated += delegate { Close(); }; // This means the control is not visible anymore, no use in keeping. Expected scenario: user hits ESC in the prompt after the git process exits

            InitializeComponent();

            if (_useDialogSettings)
            {
                KeepDialogOpen.Checked = !AppSettings.CloseProcessDialog;
            }
            else
            {
                KeepDialogOpen.Hide();
            }

            InitializeComplete();
        }

        public FormStatus(Action<FormStatus> process, string text)
            : this(new EditboxBasedConsoleOutputControl(), true)
        {
            ProcessCallback = process;
            Text = text;
        }

        protected readonly ConsoleOutputControl ConsoleOutput;
        public Action<FormStatus> ProcessCallback;
        public Action<FormStatus> AbortCallback;
        private bool _errorOccurred;
        private bool _showOnError;

        /// <summary>
        /// Gets the logged output text. Note that this is a separate string from what you see in the console output control.
        /// For instance, progress messages might be skipped; other messages might be added manually.
        /// </summary>
        [NotNull]
        public readonly FormStatusOutputLog OutputLog = new FormStatusOutputLog();

        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams mdiCp = base.CreateParams;
                mdiCp.ClassStyle = mdiCp.ClassStyle | NativeConstants.CP_NOCLOSE_BUTTON;
                return mdiCp;
            }
        }

        public bool ErrorOccurred()
        {
            return _errorOccurred;
        }

        public async Task SetProgressAsync(string text)
        {
            // This has to happen on the UI thread
            await this.SwitchToMainThreadAsync();

            int index = text.LastIndexOf('%');
            if (index > 4 && int.TryParse(text.Substring(index - 3, 3), out var progressValue) && progressValue >= 0)
            {
                ProgressBar.Style = ProgressBarStyle.Blocks;
                ProgressBar.Value = Math.Min(100, progressValue);
                TaskbarProgress.SetProgress(TaskbarProgressBarState.Normal, progressValue, 100);
            }

            // Show last progress message in the title, unless it's showing in the control body already
            if (!ConsoleOutput.IsDisplayingFullProcessOutput)
            {
                Text = text;
            }
        }

        /// <summary>
        /// Adds a message to the console display control ONLY, <see cref="GetOutputString" /> will not list it.
        /// </summary>
        public void AddMessage(string text)
        {
            ConsoleOutput.AppendMessageFreeThreaded(text);
        }

        /// <summary>
        /// Adds a message line to the console display control ONLY, <see cref="GetOutputString" /> will not list it.
        /// </summary>
        public void AddMessageLine(string text)
        {
            AddMessage(text + Environment.NewLine);
        }

        public void Done(bool isSuccess)
        {
            try
            {
                AppendMessageCrossThread("Done");
                ProgressBar.Visible = false;
                Ok.Enabled = true;
                Ok.Focus();
                AcceptButton = Ok;
                Abort.Enabled = false;
                TaskbarProgress.SetProgress(
                    isSuccess
                        ? TaskbarProgressBarState.Normal
                        : TaskbarProgressBarState.Error,
                    100,
                    100);

                picBoxSuccessFail.Image = isSuccess
                    ? Images.StatusBadgeSuccess
                    : Images.StatusBadgeError;

                _errorOccurred = !isSuccess;

                if (isSuccess && !_showOnError && (_useDialogSettings && AppSettings.CloseProcessDialog))
                {
                    Close();
                }
            }
            finally
            {
                _modalController?.EndModal(isSuccess);
            }
        }

        public void AppendMessageCrossThread(string text)
        {
            ConsoleOutput.AppendMessageFreeThreaded(text);
        }

        public void Reset()
        {
            ConsoleOutput.Reset();
            OutputLog.Clear();
            ProgressBar.Visible = true;
            Ok.Enabled = false;
            ActiveControl = null;
        }

        public void Retry()
        {
            Reset();
            ProcessCallback(this);
        }

        public void ShowDialogOnError(IWin32Window owner = null)
        {
            KeepDialogOpen.Visible = false;
            Abort.Visible = false;
            _showOnError = true;
            _modalController = new DispatcherFrameModalController(this, owner);
            _modalController.BeginModal();
        }

        private void Ok_Click(object sender, EventArgs e)
        {
            Close();
            DialogResult = DialogResult.OK;
        }

        private void FormStatus_Load(object sender, EventArgs e)
        {
            if (DesignMode)
            {
                return;
            }

            if (_modalController != null)
            {
                return;
            }

            Start();
        }

        private void FormStatus_FormClosed(object sender, FormClosedEventArgs e)
        {
            AfterClosed();
        }

        internal void Start()
        {
            if (ProcessCallback == null)
            {
                throw new InvalidOperationException("You can't load the form without a ProcessCallback");
            }

            if (AbortCallback == null)
            {
                Abort.Visible = false;
            }

            StartPosition = FormStartPosition.CenterParent;

            TaskbarProgress.SetIndeterminate();

            Reset();
            ProcessCallback(this);
        }

        private void Abort_Click(object sender, EventArgs e)
        {
            try
            {
                AbortCallback(this);
                OutputLog.Append(Environment.NewLine + "Aborted");  // TODO: write to display control also, if we pull the function up to this base class
                Done(false);
                DialogResult = DialogResult.Abort;
            }
            catch
            {
            }
        }

        public string GetOutputString()
        {
            return OutputLog.GetString();
        }

        private void KeepDialogOpen_CheckedChanged(object sender, EventArgs e)
        {
            AppSettings.CloseProcessDialog = !KeepDialogOpen.Checked;

            // Maintain the invariant: if changing to "don't keep" and conditions are such that the dialog would have closed in dont-keep mode, then close it
            // Not checking for UseDialogSettings because checkbox is only visible with True
            if ((!KeepDialogOpen.Checked /* keep off */) && Ok.Enabled /* done */ && (!_errorOccurred /* and successful */))
            {
                Close();
            }
        }

        internal void AfterClosed()
        {
            TaskbarProgress.Clear();
        }
    }

    internal class DispatcherFrameModalController
    {
        private readonly DispatcherFrame _dispatcherFrame = new DispatcherFrame();
        private readonly FormStatus _formStatus;
        private readonly IWin32Window _owner;

        public DispatcherFrameModalController(FormStatus formStatus, IWin32Window owner)
        {
            _formStatus = formStatus;
            _owner = owner;
        }

        public void BeginModal()
        {
            _formStatus.Start();
            Dispatcher.PushFrame(_dispatcherFrame);
        }

        public void EndModal(bool success)
        {
            if (!success)
            {
                _formStatus.ShowDialog(_owner);
            }
            else
            {
                _formStatus.AfterClosed();
            }

            _dispatcherFrame.Continue = false;
        }
    }
}
