using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.InteropServices.WindowsRuntime;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Json;
using System.Threading;
using System.Threading.Tasks;
using Windows.ApplicationModel.Activation;
using Windows.Foundation;
using Windows.Security.Authentication.Web;
using Windows.Storage.Streams;
using Windows.Web.Http;
using Windows.Web.Http.Headers;

namespace ControlExplorer
{
    public class OneDriveHelper
    {
        #region ** fields

        private static readonly string OAuthAuthorizeUri = "https://login.live.com/oauth20_authorize.srf";
        private static readonly string ApiServiceUri = "https://apis.live.net/v5.0/";
        private static readonly string ClientId = "0000000040062F11";
        private static readonly string RedirectUri = "https://oauth.live.com/desktop";
        private string[] _scopes = new string[] { "wl.skydrive_update" };
        private TaskCompletionSource<string> _getAccessTokenCompleted;

        #endregion

        #region ** public methods
        public async Task<string> GetAccessTokenAsync()
        {
            this._getAccessTokenCompleted = new TaskCompletionSource<string>();
            var uri = BuildOAuthUri(ClientId, _scopes, RedirectUri);
            WebAuthenticationBroker.AuthenticateAndContinue(uri, new Uri(RedirectUri));
            return await _getAccessTokenCompleted.Task;
        }

        public void ContinueAuthentication(WebAuthenticationBrokerContinuationEventArgs args)
        {
            if (_getAccessTokenCompleted != null)
            {
                if (args.WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.Success)
                {
                    var responseUri = new Uri(args.WebAuthenticationResult.ResponseData);
                    var decoder = new WwwFormUrlDecoder(responseUri.Fragment.Substring(1));
                    var access_token = decoder.GetFirstValueByName("access_token");
                    _getAccessTokenCompleted.TrySetResult(access_token);
                }
                else if (args.WebAuthenticationResult.ResponseStatus == WebAuthenticationStatus.UserCancel)
                {
                    _getAccessTokenCompleted.TrySetCanceled();
                }
                else
                {
                    _getAccessTokenCompleted.TrySetException(new Exception(args.WebAuthenticationResult.ResponseErrorDetail.ToString()));
                }
            }
        }

        /// <summary>
        /// Build the OAuth URI.
        /// </summary>
        /// <param name="scopes">The requested scopes.</param>
        /// <returns>The OAuth URI.</returns>
        private static Uri BuildOAuthUri(string clientId, string[] scopes, string redirectUri)
        {
            List<string> paramList = new List<string>();
            paramList.Add("client_id=" + WebUtility.UrlEncode(ClientId));
            paramList.Add("scope=" + WebUtility.UrlEncode(String.Join(" ", scopes)));
            paramList.Add("response_type=" + WebUtility.UrlEncode("token"));
            paramList.Add("display=" + WebUtility.UrlEncode("touch"));
            paramList.Add("redirect_uri=" + WebUtility.UrlEncode(RedirectUri));

            UriBuilder authorizeUri = new UriBuilder(OAuthAuthorizeUri);
            authorizeUri.Query = String.Join("&", paramList.ToArray());
            return authorizeUri.Uri;
        }

        /// <summary>
        /// Build the API service URI.
        /// </summary>
        /// <param name="path">The relative path requested.</param>
        /// <returns>The request URI.</returns>
        private static Uri BuildApiUri(string accessToken, string path, IDictionary<string, string> parameters = null)
        {
            UriBuilder builder = new UriBuilder(ApiServiceUri);
            builder.Path += path;
            builder.Query = (parameters != null && parameters.Count() > 0 ? string.Join("&", parameters.Select(pair => pair.Key + "=" + pair.Value).ToArray()) + "&" : "") + "access_token=" + WebUtility.UrlEncode(accessToken);
            return builder.Uri;
        }

        public static async Task<IList<OneDriveFolder>> GetFoldersAsync(string accessToken)
        {
            var parameters = new Dictionary<string, string> { { "filter", "folders" } };
            var uri = BuildApiUri(accessToken, "me/skydrive/files", parameters);
            var client = WebRequest.Create(uri);
            var response = await client.GetResponseAsync();
            var ser = new DataContractJsonSerializer(typeof(OneDriveFolders));
            var folders = (OneDriveFolders)ser.ReadObject(response.GetResponseStream());
            return folders.data;
        }

        public static async Task<WLLink> GetFileInfoAsync(string accessToken, string fileId)
        {
            var uri = BuildApiUri(accessToken, string.Format("{0}/shared_read_link", fileId));
            var client = WebRequest.Create(uri);
            var response = await client.GetResponseAsync();
            var ser = new DataContractJsonSerializer(typeof(WLLink));
            var file = (WLLink)ser.ReadObject(response.GetResponseStream());
            return file;
        }

        public static async Task<OneDriveFile> UploadFileAsync(string accessToken, string fileName, string contentType, OneDriveFolder folder, IInputStream fileStream, Action<double> progress = null, CancellationToken cancel = default(CancellationToken))
        {
            var uri = BuildApiUri(accessToken, folder.Id + "/files/" + fileName);
            var client = new HttpClient();
            var content = new HttpStreamContent(fileStream);
            var response = await client.PutAsync(uri, content).AsTask(cancel, new Progress<HttpProgress>(p => { if (progress != null && p.TotalBytesToSend > 0) { progress((double)p.BytesSent / (double)p.TotalBytesToSend); } }));
            var responseBuffer = await response.Content.ReadAsBufferAsync();
            if (response.IsSuccessStatusCode)
            {
                var ser2 = new DataContractJsonSerializer(typeof(OneDriveFile));
                var uploadedFile = (OneDriveFile)ser2.ReadObject(responseBuffer.AsStream());
                return uploadedFile;
            }
            else
            {
                var ser2 = new DataContractJsonSerializer(typeof(OneDriveErrorMessage));
                var error = (OneDriveErrorMessage)ser2.ReadObject(responseBuffer.AsStream());
                throw new Exception(error.Error.Message);
            }
        }

        #endregion
    }

    /// <summary>
    /// Represents multiple albums returned in a JSON request
    /// </summary>
    public class OneDriveFolders
    {
        public List<OneDriveFolder> data { get; set; }
    }

    [DataContract]
    public class OneDriveFolder
    {
        [DataMember(Name = "id")]
        public string Id { get; set; }
        [DataMember(Name = "name")]
        public string Name { get; set; }
    }

    [DataContract]
    public class OneDriveFile
    {
        [DataMember(Name = "id")]
        public string Id { get; set; }
        [DataMember(Name = "source")]
        public string Source { get; set; }
    }

    [DataContract]
    public class WLLink
    {
        [DataMember(Name = "link")]
        public string Link { get; set; }
    }

    [DataContract]
    public class OneDriveErrorMessage
    {
        [DataMember(Name = "error")]
        public OneDriveError Error { get; set; }
    }

    [DataContract]
    public class OneDriveError
    {
        [DataMember(Name = "code")]
        public string Code { get; set; }
        [DataMember(Name = "message")]
        public string Message { get; set; }
    }
}
