using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Windows.Forms;

namespace OAuth
{
    internal class AccessTokenGenerator
    {
        internal static string GetAccessToken(string clientId, string scope, string username, string password)
        {
            return GetAccessToken(clientId, scope, username, password, 30000);
        }

        internal static string GetAccessToken(string clientId, string scope, string username, string password, int timeout)
        {
            var generatorThread = new GeneratorThread(clientId, scope, username, password, timeout);
            var thread = new Thread(generatorThread.GetAccessToken);
            thread.SetApartmentState(ApartmentState.STA);
            thread.Start();

            while (thread.ThreadState != ThreadState.Stopped)
            {
                Thread.Sleep(10);
            }

            if (string.IsNullOrEmpty(generatorThread.AccessToken))
            {
                throw new TimeoutException();
            }

            return generatorThread.AccessToken;
        }
    }

    internal class GeneratorThread
    {
        private string _clientId;
        private string _scope;
        private string _username;
        private string _password;
        private int _timeout;

        public string AccessToken;
        private WebBrowser _webBrowser;
        private bool _documentCompleted = false;

        public GeneratorThread(string clientId, string scope, string username, string password, int timeout)
        {
            _clientId = clientId;
            _scope = scope;
            _username = username;
            _password = password;
            _timeout = timeout;
        }

        public void GetAccessToken()
        {
            IntPtr pHandle = GetCurrentProcess();
            SetProcessWorkingSetSize(pHandle, -1, -1);

            using (_webBrowser = new WebBrowser())
            {
                AccessToken = "";
                var url = "https://login.live.com/oauth20_authorize.srf?client_id=" + _clientId + "&scope=" + _scope + "&response_type=token";
                _webBrowser.Navigated += Navigated;
                _webBrowser.DocumentCompleted += DocumentCompleted;
                _webBrowser.Navigate(url);

                var startTime = DateTime.Now;
                var endTime = DateTime.Now;
                while (string.IsNullOrEmpty(AccessToken) && (endTime - startTime).TotalMilliseconds < _timeout)
                {
                    Thread.Sleep(10);
                    Application.DoEvents();
                    endTime = DateTime.Now;

                    if (_documentCompleted)
                    {
                        _documentCompleted = false;
                        OnDocumentCompleted();
                    }
                }

                _webBrowser.Navigated -= Navigated;
                _webBrowser.DocumentCompleted -= DocumentCompleted;
            }
            GC.Collect();
        }

        private void DocumentCompleted(object sender, WebBrowserDocumentCompletedEventArgs e)
        {
            _documentCompleted = true;
        }

        private void OnDocumentCompleted()
        {
            // sleep 2000 ms.
            for (var i = 0; i < 200; i++)
            {
                Thread.Sleep(10);
                Application.DoEvents();
            }

            var script = "javascript:void(0);";
            if (_webBrowser.Url.AbsoluteUri.StartsWith("https://login.live.com/oauth20_authorize.srf"))
            {
                script = "javascript:" +
                   "if(document && document.querySelector && document.querySelector(\"input[name=loginfmt]\")){" +
                   "var ev = document.createEvent(\"HTMLEvents\");ev.initEvent(\"change\", false, true);" +
                   "document.querySelector(\"input[name=loginfmt]\").value=\"" + _username + "\";" +
                   "document.querySelector(\"input[name=loginfmt]\").dispatchEvent(ev);" +
                   "document.querySelector(\"input[name=passwd]\").value=\"" + _password + "\";" +
                   "document.querySelector(\"input[name=passwd]\").dispatchEvent(ev);" +
                   "document.querySelector(\"input[type=submit]\").click();}";
            }
            else if (_webBrowser.Url.AbsoluteUri.StartsWith("https://account.live.com"))
            {
                script = "javascript:document.all.idBtn_Accept.click();";
            }

            _webBrowser.Navigate(script);
        }

        private void Navigated(object sender, WebBrowserNavigatedEventArgs e)
        {
            var tokenIndex = _webBrowser.Url.AbsoluteUri.IndexOf("access_token=");
            if (tokenIndex != -1)
            {
                AccessToken = Uri.UnescapeDataString(_webBrowser.Url.AbsoluteUri.Substring(tokenIndex + 13, _webBrowser.Url.AbsoluteUri.IndexOf("&", tokenIndex) - tokenIndex - 13));
            }
        }

        [DllImport("KERNEL32.DLL", EntryPoint = "SetProcessWorkingSetSize", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern bool SetProcessWorkingSetSize(IntPtr pProcess, int dwMinimumWorkingSetSize, int dwMaximumWorkingSetSize);

        [DllImport("KERNEL32.DLL", EntryPoint = "GetCurrentProcess", SetLastError = true, CallingConvention = CallingConvention.StdCall)]
        internal static extern IntPtr GetCurrentProcess();

    }
}
