using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using UnityEngine;

namespace TwitchInGames {
    public class ClientAuth : IDisposable {
        public string AccessToken { get; private set; } = "";

        private IntPtr clientAuth;

        public Coroutine<string> StartAuth(string clientId, string redirectUri, IEnumerable<string> scopes) {
            return new Coroutine<string>(InternalStartAuth(clientId, redirectUri, scopes));
        }

        public void Cancel() {
            if(clientAuth != IntPtr.Zero) {
                NativeMethods.CancelClientAuth(clientAuth);
            }
        }

        private IEnumerator InternalStartAuth(string clientId, string redirectUri, IEnumerable<string> scopes) {
            Utility.CheckArgument(nameof(clientId), clientId);
            Utility.CheckArgument(nameof(redirectUri), redirectUri);
            if(clientAuth != IntPtr.Zero) {
                throw new InvalidOperationException("client auth already started");
            }
            var scope = scopes != null ? String.Join(" ", scopes.ToArray()) : "";
            yield return DoClientAuth(clientId, redirectUri, scope);
        }

        private IEnumerator DoClientAuth(string clientId, string redirectUri, string scope) {
            clientAuth = NativeMethods.CreateClientAuth();
            AccessToken = null;
            void fetchToken() {
                AccessToken = NativeMethods.DoClientAuth(clientAuth, clientId, redirectUri, scope);
            }
            using(var thread = new SingleThread()) {
                thread.EnqueueTask(fetchToken);
                yield return new WaitUntil(() => AccessToken != null);
                NativeMethods.DeleteClientAuth(ref clientAuth);
            }
            yield return AccessToken;
        }

        #region IDisposable Support

        private bool isDisposed = false;

        protected virtual void Dispose(bool disposing) {
            if(!isDisposed) {
                if(disposing) {
                    // Dispose of managed state.
                }

                // Free unmanaged resources.
                NativeMethods.DeleteClientAuth(ref clientAuth);

                isDisposed = true;
            }
        }

        ~ClientAuth() {
            Dispose(false);
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        private static class NativeMethods {
            [DllImport("UnityWin32", CallingConvention = CallingConvention.Cdecl)]
            internal static extern IntPtr CreateClientAuth();

            [DllImport("UnityWin32", CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Unicode)]
            internal static extern string DoClientAuth(IntPtr clientAuth, string clientId, string redirectUri, string scope);

            [DllImport("UnityWin32", CallingConvention = CallingConvention.Cdecl)]
            internal static extern void CancelClientAuth(IntPtr clientAuth);

            [DllImport("UnityWin32", CallingConvention = CallingConvention.Cdecl)]
            internal static extern void DeleteClientAuth(ref IntPtr clientAuth);
        }
    }
}
