using System;
using System.Collections.Generic;

using UnityEngine;
using BestHTTP;

namespace BestHTTP.Examples
{
    public sealed class LargeFileDownloadSample : MonoBehaviour
    {
        /// <summary>
        /// The url of the resource to download
        /// </summary>
        const string URL = "http://uk3.testmy.net/dl-102400";

        #region Private Fields

        /// <summary>
        /// Cached request to be able to abort it
        /// </summary>
        HTTPRequest request;

        /// <summary>
        /// Debug status of the request
        /// </summary>
        string status = string.Empty;

        /// <summary>
        /// Download(processing) progress. Its range is between [0..1]
        /// </summary>
        float progress;

        /// <summary>
        /// The fragment size that we will set to the request
        /// </summary>
        int fragmentSize = HTTPResponse.MinBufferSize;

        #endregion

        #region Unity Events

        void Awake()
        {
            // If we have a non-finished download, set the progress to the value where we left it
            if (PlayerPrefs.HasKey("DownloadLength"))
                progress = PlayerPrefs.GetInt("DownloadProgress") / (float)PlayerPrefs.GetInt("DownloadLength");
        }

        void OnDestroy()
        {
            // Stop the download if we are leaving this example
            if (request != null && request.State < HTTPRequestStates.Finished)
            {
                request.OnProgress = null;
                request.Callback = null;
                request.Abort();
            }
        }

        void OnGUI()
        {
            GUIHelper.DrawArea(GUIHelper.ClientArea, true, () =>
                {
                    // Draw the current status
                    GUILayout.Label("Request status: " + status);

                    GUILayout.Space(5);

                    // Draw the current progress
                    GUILayout.Label(string.Format("Progress: {0:P2} of {1:N0}Mb", progress, PlayerPrefs.GetInt("DownloadLength") / 1048576 /*1 Mb*/));
                    GUILayout.HorizontalSlider(progress, 0, 1);

                    GUILayout.Space(50);

                    if (request == null)
                    {
                        // Draw a slider to be able to change the fragment size
                        GUILayout.Label(string.Format("Desired Fragment Size: {0:N} KBytes", fragmentSize / 1024f));
                        fragmentSize = (int)GUILayout.HorizontalSlider(fragmentSize, HTTPResponse.MinBufferSize, 10 * 1024 * 1024);

                        GUILayout.Space(5);

                        string buttonStr = PlayerPrefs.HasKey("DownloadProgress") ? "Continue Download" : "Start Download";
                        if (GUILayout.Button(buttonStr))
                            StreamLargeFileTest();
                    }
                    else if (request.State == HTTPRequestStates.Processing && GUILayout.Button("Abort Download"))
                    {
                        // Simulate a connection lost
                        request.Abort();
                    }
                });
        }

        #endregion

        #region Private Helper Functions

        // Calling this function again when the "DownloadProgress" key in the PlayerPrefs present will
        //	continue the download
        void StreamLargeFileTest()
        {
            request = new HTTPRequest(new Uri(URL), (req, resp) =>
            {
                switch (req.State)
                {
                    // The request is currently processed. With UseStreaming == true, we can get the streamed fragments here
                    case HTTPRequestStates.Processing:

                        // Set the DownloadLength, so we can display the progress
                        if (!PlayerPrefs.HasKey("DownloadLength"))
                        {
                            string value = resp.GetFirstHeaderValue("content-length");
                            if (!string.IsNullOrEmpty(value))
                                PlayerPrefs.SetInt("DownloadLength", int.Parse(value));
                        }

                        // Get the fragments, and save them
                        ProcessFragments(resp.GetStreamedFragments());

                        status = "Processing";
                        break;

                    // The request finished without any problem.
                    case HTTPRequestStates.Finished:
                        if (resp.IsSuccess)
                        {
                            // Save any remaining fragments
                            ProcessFragments(resp.GetStreamedFragments());

                            // Completely finished
                            if (resp.IsStreamingFinished)
                            {
                                status = "Streaming finished!";

                                // We are done, delete the progress key
                                PlayerPrefs.DeleteKey("DownloadProgress");
                                PlayerPrefs.Save();

                                request = null;
                            }
                            else
                                status = "Processing";
                        }
                        else
                        {
                            status = string.Format("Request finished Successfully, but the server sent an error. Status Code: {0}-{1} Message: {2}",
                                                            resp.StatusCode,
                                                            resp.Message,
                                                            resp.DataAsText);
                            Debug.LogWarning(status);

                            request = null;
                        }
                        break;

                    // The request finished with an unexpected error. The request's Exception property may contain more info about the error.
                    case HTTPRequestStates.Error:
                        status = "Request Finished with Error! " + (req.Exception != null ? (req.Exception.Message + "\n" + req.Exception.StackTrace) : "No Exception");
                        Debug.LogError(status);

                        request = null;
                        break;

                    // The request aborted, initiated by the user.
                    case HTTPRequestStates.Aborted:
                        status = "Request Aborted!";
                        Debug.LogWarning(status);

                        request = null;
                        break;

                    // Connecting to the server is timed out.
                    case HTTPRequestStates.ConnectionTimedOut:
                        status = "Connection Timed Out!";
                        Debug.LogError(status);

                        request = null;
                        break;

                    // The request didn't finished in the given time.
                    case HTTPRequestStates.TimedOut:
                        status = "Processing the request Timed Out!";
                        Debug.LogError(status);

                        request = null;
                        break;
                }
            });

            // Are there any progress, that we can continue?
            if (PlayerPrefs.HasKey("DownloadProgress"))
                // Set the range header
                request.SetRangeHeader(PlayerPrefs.GetInt("DownloadProgress"));
            else
                // This is a new request
                PlayerPrefs.SetInt("DownloadProgress", 0);

#if !BESTHTTP_DISABLE_CACHING && (!UNITY_WEBGL || UNITY_EDITOR)
            // If we are writing our own file set it true(disable), so don't duplicate it on the file-system
            request.DisableCache = true;
#endif

            // We want to access the downloaded bytes while we are still downloading
            request.UseStreaming = true;

            // Set a reasonable high fragment size. Here it is 5 megabytes.
            request.StreamFragmentSize = fragmentSize;

            // Start Processing the request
            request.Send();
        }

        /// <summary>
        /// In this function we can do whatever we want with the downloaded bytes. In this sample we will do nothing, just set the metadata to display progress.
        /// </summary>
        void ProcessFragments(List<byte[]> fragments)
        {
            if (fragments != null && fragments.Count > 0)
            {
                /*string dir = "TODO!";
                string filename = "TODO!";

                using (System.IO.FileStream fs = new System.IO.FileStream(System.IO.Path.Combine(dir, filename), System.IO.FileMode.Append))
                    for (int i = 0; i < fragments.Count; ++i)
                        fs.Write(fragments[i], 0, fragments[i].Length);*/

                for (int i = 0; i < fragments.Count; ++i)
                {
                    // Save how many bytes we wrote successfully
                    int downloaded = PlayerPrefs.GetInt("DownloadProgress") + fragments[i].Length;
                    PlayerPrefs.SetInt("DownloadProgress", downloaded);
                }

                PlayerPrefs.Save();

                // Set the progress to the actually processed bytes
                progress = PlayerPrefs.GetInt("DownloadProgress") / (float)PlayerPrefs.GetInt("DownloadLength");
            }
        }

        #endregion
    }
}