// Copyright © 2020 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.

using System;
using System.Threading.Tasks;

namespace CefSharp.Internals
{
    /// <summary>
    /// To access the CEF threads we expose a TaskFactory, as this requires managed vc++ this
    /// exists in CefSharp.Core it cannot be directly accessed in CefSharp.dll. When
    /// Cef.Initialized is called we pass a reference to the TaskFactory here so we
    /// can write methods (typically extension methods) in this assembly.
    /// </summary>
    /// TODO: This can likely be removed and code that depends on this can be moved
    /// to CefSharp.Core and interact directly with the C++ api
    public static class CefThread
    {
        private static readonly object LockObj = new object();

        /// <summary>
        /// TaskFactory will be null before Cef.Initialize is called
        /// and null after Cef.Shutdown is called.
        /// </summary>
        public static TaskFactory UiThreadTaskFactory { get; private set; }

        /// <summary>
        /// Event fired after Cef.Initialze has been called, we can now start
        /// posting Tasks to the CEF UI Thread.
        /// </summary>
        public static event EventHandler Initialized;

        /// <summary>
        /// Delegate used to wrap the native call to CefCurrentlyOn(CefThreadId::TID_UI).
        /// </summary>
        public static Func<bool> CurrentOnUiThreadDelegate { get; private set; }

        /// <summary>
        /// true if we have a reference to the UiThreadTaskFactory
        /// TaskFactory, otherwise false
        /// </summary>
        /// <remarks>
        /// The current implementation isn't thread safe, generally speaking this shouldn't be a problem
        /// </remarks>
        public static bool CanExecuteOnUiThread
        {
            get
            {
                return UiThreadTaskFactory != null;
            }
        }

        /// <summary>
        /// Currently on the CEF UI Thread
        /// </summary>
        public static bool CurrentlyOnUiThread
        {
            get
            {
                var d = CurrentOnUiThreadDelegate;
                if (d == null)
                {
                    return false;
                }

                return d();
            }
        }

        /// <summary>
        /// returns true if Cef.Shutdown been called, otherwise false.
        /// </summary>
        public static bool HasShutdown { get; private set; }

        /// <summary>
        /// Execute the provided function on the CEF UI Thread
        /// </summary>
        /// <typeparam name="TResult">result</typeparam>
        /// <param name="function">function</param>
        /// <returns>Task{Result}</returns>
        public static Task<TResult> ExecuteOnUiThread<TResult>(Func<TResult> function)
        {
            lock (LockObj)
            {
                if (HasShutdown)
                {
                    throw new Exception("Cef.Shutdown has already been called, it's no longer possible to execute on the CEF UI Thread. Check CefThread.HasShutdown to guard against this execption");
                }

                var taskFactory = UiThreadTaskFactory;

                if (taskFactory == null)
                {
                    //We don't have a task factory yet, so we'll queue for execution.
                    return QueueForExcutionWhenUiThreadCreated(function);
                }

                return taskFactory.StartNew(function);
            }
        }

        /// <summary>
        /// Execute the provided action on the CEF UI Thread
        /// </summary>
        /// <param name="action">action</param>
        /// <returns>Task</returns>
        public static Task ExecuteOnUiThread(Action action)
        {
            lock (LockObj)
            {
                if (HasShutdown)
                {
                    throw new Exception("Cef.Shutdown has already been called, it's no longer possible to execute on the CEF UI Thread. Check CefThread.HasShutdown to guard against this execption");
                }

                var taskFactory = UiThreadTaskFactory;

                if (taskFactory == null)
                {
                    //We don't have a task factory yet, so we'll queue for execution.
                    return QueueForExcutionWhenUiThreadCreated(action);
                }

                return taskFactory.StartNew(action);
            }
        }

        /// <summary>
        /// Wait for CEF to Initialize, continuation happens on
        /// the CEF UI Thraed.
        /// </summary>
        /// <returns>Task that can be awaited</returns>
        private static Task QueueForExcutionWhenUiThreadCreated(Action action)
        {
            var tcs = new TaskCompletionSource<bool>();

            EventHandler handler = null;

            handler = (s, args) =>
            {
                Initialized -= handler;

                try
                {
                    //TODO: Should this call UiThreadTaskFactory.StartNew?
                    action();

                    tcs.TrySetResult(true);
                }
                catch(Exception ex)
                {
                    tcs.TrySetException(ex);
                }
            };

            Initialized += handler;

            return tcs.Task;
        }

        /// <summary>
        /// Wait for CEF to Initialize, continuation happens on
        /// the CEF UI Thraed.
        /// </summary>
        /// <returns>Task that can be awaited</returns>
        private static Task<T> QueueForExcutionWhenUiThreadCreated<T>(Func<T> func)
        {
            var tcs = new TaskCompletionSource<T>();

            EventHandler handler = null;

            handler = (s, args) =>
            {
                Initialized -= handler;

                try
                {
                    //TODO: Should this call UiThreadTaskFactory.StartNew?
                    var result = func();

                    tcs.TrySetResult(result);
                }
                catch(Exception ex)
                {
                    tcs.TrySetException(ex);
                }
            };

            Initialized += handler;

            return tcs.Task;
        }

        /// <summary>
        /// Called when the CEF UI Thread is a
        /// </summary>
        public static void Initialize(TaskFactory uiThreadTaskFactory, Func<bool> currentOnUiThreadDelegate)
        {
            lock (LockObj)
            {
                Initialized?.Invoke(null, EventArgs.Empty);

                UiThreadTaskFactory = uiThreadTaskFactory;
                CurrentOnUiThreadDelegate = currentOnUiThreadDelegate;
            }
        }

        /// <summary>
        /// !!WARNING!! DO NOT CALL THIS YOURSELF, THIS WILL BE CALLED INTERNALLY.
        /// Called when Cef.Shutdown is called to cleanup our references
        /// and release any event handlers.
        /// </summary>
        public static void Shutdown()
        {
            lock (LockObj)
            {
                CurrentOnUiThreadDelegate = null;
                Initialized = null;
                UiThreadTaskFactory = null;
                HasShutdown = true;
            }
        }
    }
}
