using System.ComponentModel;
using Windows.UI.Core;
using Windows.UI.ViewManagement;

namespace CharacterMap.Services;

// A custom event that fires whenever the secondary view is ready to be closed. You should
// clean up any state (including deregistering for events) then close the window in this handler
public delegate void ViewReleasedHandler(Object sender, EventArgs e);

// A ViewLifetimeControl is instantiated for every secondary view. ViewLifetimeControl's reference count
// keeps track of when the secondary view thinks it's in use and when the main view is interacting with the secondary view (about to show
// it to the user, etc.) When the reference count drops to zero, the secondary view is closed.
public sealed class ViewLifetimeManager : INotifyPropertyChanged
{
    // Dispatcher for this view. Kept here for sending messages between this view and the main view.
    readonly CoreDispatcher dispatcher;

    // Window for this particular view. Used to register and unregister for events
    readonly CoreWindow window;

    // The title for the view shown in the list of recently used apps (by setting the title on 
    // ApplicationView)
    string title;

    // This class uses references counts to make sure the secondary views isn't closed prematurely.
    // Whenever the main view is about to interact with the secondary view, it should take a reference
    // by calling "StartViewInUse" on this object. When finished interacting, it should release the reference
    // by calling "StopViewInUse". You can see examples of this throughout the sample, especially in
    // scenario 1.
    int refCount = 0;

    // Each view has a unique Id, found using the ApplicationView.Id property or
    // ApplicationView.GetApplicationViewIdForCoreWindow method. This id is used in all of the ApplicationViewSwitcher
    // and ProjectionManager APIs. 
    readonly int viewId;

    // Tracks if this ViewLifetimeControl object is still valid. If this is true, then the view is in the process
    // of closing itself down
    bool released = false;

    // Keeps track of if the consolidated event has fired yet. A view is consolidated with other views
    // when there's no way for the user to get to it (it's not in the list of recently used apps, cannot be
    // launched from Start, etc.) A view stops being consolidated when it's visible--at that point
    // the user can interact with it, move it on or off screen, etc. 
    //bool consolidated = true;

    // Used to store publicly registered events under the protection of a lock
    event ViewReleasedHandler InternalReleased;

    // Instantiate views using "CreateForCurrentView"
    private ViewLifetimeManager(CoreWindow newWindow)
    {
        dispatcher = newWindow.Dispatcher;
        window = newWindow;
        viewId = ApplicationView.GetApplicationViewIdForWindow(window);

        // This class will automatically tell the view when its time to close
        // or stay alive in a few cases
        RegisterForEvents();
    }

    // Register for events on the current view
    private void RegisterForEvents()
    {
        // A view is consolidated with other views when there's no way for the user to get to it (it's not in the list of recently used apps, cannot be
        // launched from Start, etc.) A view stops being consolidated when it's visible--at that point the user can interact with it, move it on or off screen, etc.
        // It's generally a good idea to close a view after it has been consolidated, but keep it open while it's visible.
        ApplicationView.GetForCurrentView().Consolidated += ViewConsolidated;
    }

    // Unregister for events. Call this method before closing the view to prevent leaks.
    private void UnregisterForEvents()
    {
        ApplicationView.GetForCurrentView().Consolidated -= ViewConsolidated;
    }

    // A view is consolidated with other views hen there's no way for the user to get to it (it's not in the list of recently used apps, cannot be
    // launched from Start, etc.) A view stops being consolidated when it's visible--at that point the user can interact with it, move it on or off screen, etc. 
    // It's generally a good idea to close a view after it has been consolidated, but keep it open while it's visible.
    private void ViewConsolidated(ApplicationView sender, ApplicationViewConsolidatedEventArgs e)
    {
        StopViewInUse();
    }

    // Called when a view has been "consolidated" (no longer accessible to the user) 
    // and no other view is trying to interact with it. This should only be closed after the reference
    // count goes to 0 (including being consolidated). At the end of this, the view is closed. 
    private void FinalizeRelease()
    {
        bool justReleased = false;
        lock (this)
        {
            if (refCount == 0)
            {
                justReleased = true;
                released = true;
            }
        }

        // This assumes that released will never be made false after it
        // it has been set to true
        if (justReleased)
        {
            UnregisterForEvents();
            InternalReleased(this, null);
        }
    }

    // Creates ViewLifetimeControl for the particular view.
    // Only do this once per view.
    public static ViewLifetimeManager CreateForCurrentView()
    {
        return new ViewLifetimeManager(CoreWindow.GetForCurrentThread());
    }

    // For purposes of this sample, the collection of views
    // is bound to a UI collection. This property is available for binding
    public string Title
    {
        get => title;
        set
        {
            if (title != value)
            {
                title = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Title)));
            }
        }
    }

    // Necessary to communicate with the window
    // This property never changes, so there's no need to lock
    public CoreDispatcher Dispatcher => dispatcher;

    // Each view has a unique Id, found using the ApplicationView.Id property or
    // ApplicationView.GetApplicationViewIdForCoreWindow method. This id is used in all of the ApplicationViewSwitcher
    // and ProjectionManager APIs. 
    // This property never changes, so there's no need to lock
    public int Id => viewId;

    // Signals that the view is being interacted with by another view,
    // so it shouldn't be closed even if it becomes "consolidated"
    public int StartViewInUse()
    {
        bool releasedCopy = false;
        int refCountCopy = 0;

        // This method is called from several different threads
        // (each view lives on its own thread)
        lock (this)
        {
            releasedCopy = this.released;
            if (!released)
            {
                refCountCopy = ++refCount;
            }
        }

        if (releasedCopy)
        {
            throw new InvalidOperationException("This view is being disposed");
        }

        return refCountCopy;
    }

    // Should come after any call to StartViewInUse
    // Signals that the another view has finished interacting with the view tracked
    // by this object
    public int StopViewInUse()
    {
        int refCountCopy = 0;
        bool releasedCopy = false;

        lock (this)
        {
            releasedCopy = this.released;
            if (!released)
            {
                refCountCopy = --refCount;
                if (refCountCopy == 0)
                {
                    // If no other view is interacting with this view, and
                    // the view isn't accessible to the user, it's appropriate
                    // to close it
                    //
                    // Before actually closing the view, make sure there are no
                    // other important events waiting in the queue (this low-priority item
                    // will run after other events)
                    var task = dispatcher.RunAsync(CoreDispatcherPriority.Low, FinalizeRelease);
                }
            }
        }

        if (releasedCopy)
        {
            throw new InvalidOperationException("This view is being disposed");
        }
        return refCountCopy;
    }

    // Signals to consumers that its time to close the view so that
    // they can clean up (including calling Window.Close() when finished)
    public event PropertyChangedEventHandler PropertyChanged;
    public event ViewReleasedHandler Released
    {
        add
        {
            bool releasedCopy = false;
            lock (this)
            {
                releasedCopy = released;
                if (!released)
                {
                    InternalReleased += value;
                }
            }

            if (releasedCopy)
            {
                throw new InvalidOperationException("This view is being disposed");
            }
        }

        remove
        {
            lock (this)
            {
                InternalReleased -= value;
            }
        }
    }
}
