﻿using System;
using System.IO;
using System.Net;
using System.Threading.Tasks;

namespace Division42.DownloadVerifier.Download
{
    public class DownloadManager : InstrumentableBase, IDownloadManager
    {
        public override Task Start()
        {
            String fileName = Path.GetFileName(SourceUri.LocalPath);
            String destinationPathAndFileName = Path.Combine(DestinationUri.LocalPath, fileName);

            this.StartTime = DateTimeOffset.Now;
            this.EndTime = DateTimeOffset.MaxValue;
            this.IsComplete = false;
            this.IsRunning = true;
            this.PercentComplete = 0;
            this.StatusMessage = "Download initializing...";
            this.TimeElapsed = DateTimeOffset.Now.Subtract(this.StartTime);
            this.TimeRemaining = TimeSpan.MaxValue;
            this.BytesReceived = 0;
            this.BytesTotal = 0;

            using (WebClient client = new WebClient())
            {
                _webClient = client;

                client.DownloadProgressChanged += (sender, e) =>
                {
                    InstrumentationProgressChangedEventArgs eventArgs = 
                        new InstrumentationProgressChangedEventArgs(this);

                    this.BytesTotal = e.TotalBytesToReceive;
                    this.BytesReceived = e.BytesReceived;
                    this.PercentComplete = e.ProgressPercentage;

                    OnInstrumentationProgressChanged(eventArgs);
                };
                client.DownloadFileCompleted += (sender, e) =>
                {
                    InstrumentationCompleteEventArgs eventArgs = new InstrumentationCompleteEventArgs(this);
                    OnInstrumentationComplete(eventArgs);
                };

                return client.DownloadFileTaskAsync(SourceUri, destinationPathAndFileName);
            }
        }

        public override void Cancel()
        {
            _webClient?.CancelAsync();
        }

        public Uri SourceUri
        {
            get { return _sourceUri; }
            set
            {
                _sourceUri = value;
                OnPropertyChanged();
            }
        } private Uri _sourceUri;

        public Uri DestinationUri
        {
            get { return _destinationUri; }
            set
            {
                _destinationUri = value;
                OnPropertyChanged();
            }
        } private Uri _destinationUri;

        public override void OnInstrumentationProgressChanged(InstrumentationProgressChangedEventArgs eventArgs)
        {
            RecalculateStatistics();
            base.OnInstrumentationProgressChanged(eventArgs);
        }

        public override void OnInstrumentationComplete(InstrumentationCompleteEventArgs eventArgs)
        {
            RecalculateStatistics();
            base.OnInstrumentationComplete(eventArgs);
        }

        public override String ToString()
        {
            return
                String.Format(
                    "PercentComplete={4}; TimeElapsed={5}; TimeRemaining={6}; StatusMessage={7}; Received={8}; StartTime={0}; EndTime={1}; IsComplete={2}; IsRunning={3}; ",
                    this.StartTime, this.EndTime, this.IsComplete, this.IsRunning, this.PercentComplete,
                    this.TimeElapsed, this.TimeRemaining, this.StatusMessage, this.BytesReceived);
        }

        protected virtual void RecalculateStatistics()
        {
            lock (_recalculateLock)
            {
                if (this.IsComplete)
                    return;

                this.TimeElapsed = DateTimeOffset.Now.Subtract(this.StartTime);

                if (this.BytesReceived >= this.BytesTotal)
                {
                    // Done.
                    this.EndTime = DateTimeOffset.Now;
                    this.IsComplete = true;
                    this.IsRunning = false;
                    this.PercentComplete = 100;
                    this.StatusMessage = "Download complete.";
                    this.TimeRemaining = new System.TimeSpan();
                }
                else
                {
                    // Still going...
                    Double percentPerSecond = TimeElapsed.TotalSeconds/PercentComplete;
                    Int32 percentLeft = 100 - (Int32) PercentComplete;

                    if (percentLeft == 100)
                        TimeRemaining = TimeSpan.MaxValue;
                    else
                        TimeRemaining = new TimeSpan(0, 0, 0, (Int32) ((Double) percentLeft*percentPerSecond));

                    this.StatusMessage = "Downloading...";
                }
            }
        }

        private WebClient _webClient = null;
        private Object _recalculateLock = new Object();
    }
}