using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using EQNetwork;
using EQNetwork.Objects;
using EQNetwork.Services;
using EQNetwork.Utility;

namespace EQNetwork.Controllers
{
    public class Upload_Controller
    {
        private EQProcess _eq;

        private bool _cancelled = false;
        public bool Running = true;
        public EQException EQException;
        public int MaxUploadsInProgress = 4;

        List<UploadContext> _uploadContexts = new List<UploadContext>();

        public Upload_Controller(EQProcess eq)
        {
            _eq = eq;

            new System.Threading.Thread(() =>
            {
                while (!_cancelled)
                {
                    try
                    {
                        CheckUploads(TimeSpan.FromMilliseconds(100));
                        System.Threading.Thread.Sleep(100);
                    }
                    catch (Exception ex)
                    {
                        EQException = new EQException(ex);
                        _cancelled = true;
                    }
                }

                Running = false;
            }).Start();
        }

        public void Start(string name, string path, Action<UploadContext, UploadContext.UploadState> onStateChange, Action<EQException> onError)
        {
            try
            {
                UploadContext uploadContext = new UploadContext();
                uploadContext.OnError = onError;
                uploadContext.OnStateChange = onStateChange;
                uploadContext.Name = name;
                uploadContext.Path = path;
                uploadContext.State = UploadContext.UploadState.GET_FILE_DATA;
                lock (_uploadContexts)
                {
                    _uploadContexts.Add(uploadContext);
                }
            }
            catch (Exception ex)
            {
                onError(new EQException(ex));
            }
        }

        public void Cancel()
        {
            _cancelled = true;
        }

        private int _uploadsInProgress = 0;
        private int _inARowBad = 0;
        private int _inARowGood = 0;
        private int _preferredChunkSize = 0x1000;
        private int _minChunkSize = 0x400;
        private int _maxChunkSize = 0x40000;
        private TimeSpan _maxDuration = TimeSpan.FromSeconds(10);

        void GoodChunk() {
            _inARowBad = 0;
            _inARowGood++;
            if (_inARowGood > 2) {
                _preferredChunkSize *= 2;
                if (_preferredChunkSize > _maxChunkSize)
                    _preferredChunkSize = _maxChunkSize;
                _inARowGood = 0;
            }
        }
        void BadChunk() {
            _inARowGood = 0;
            _inARowBad++;
            if (_inARowBad > 3)
            {
                _preferredChunkSize /= 2;
                if (_preferredChunkSize < _minChunkSize)
                    _preferredChunkSize = _minChunkSize;
                _inARowBad = 0;
            }
        }
        int ChunksInProgress(List<UploadContext> uploadContexts)
        {
            int n = 0;
            foreach (var uploadContext in uploadContexts)
            {
                List<UploadChunk> uploadChunksToDelete = new List<UploadChunk>();
                foreach (var chunk in uploadContext.UploadChunksInProgress)
                {
                    if (chunk.UploadStarted == true && chunk.UploadFinished != true)
                    {
                        if ((TimeSpan)chunk.UploadDuration > _maxDuration) 
                        {
                            chunk.UploadFinished = true;
                            uploadChunksToDelete.Add(chunk);
                            BadChunk();
                        }
                    }
                    n++;
                }
                foreach (var chunk in uploadChunksToDelete)
                    uploadContext.UploadChunksInProgress.Remove(chunk);
            }

            return n;
        }

        void CheckUploads(TimeSpan duration)
        {
            List<UploadContext> uploadContexts;
            lock (_uploadContexts)
            {
                uploadContexts = new List<UploadContext>(_uploadContexts);
            }

            _uploadsInProgress = ChunksInProgress(uploadContexts);

            foreach (var uploadContext in uploadContexts)
            {
                if (uploadContext.Paused) continue;
                if (uploadContext.AutoPaused) continue;

                try
                {
                    uploadContext.PumpTimeSinceLastStateChange(duration);
                    CheckUpload(uploadContext);
                }
                catch (Exception ex)
                {
                    uploadContext.EQException = new EQException(ex);
                    uploadContext.State = UploadContext.UploadState.CANCELLED;
                    uploadContext.Paused = true;
                    uploadContext.OnError(uploadContext.EQException);
                }
            }

            List<UploadContext> itemsToDelete = new List<UploadContext>();
            foreach (var uploadContext in uploadContexts)
            {
                if (uploadContext.State == UploadContext.UploadState.CANCELLED ||
                    uploadContext.State == UploadContext.UploadState.FINISHED)
                {
                    itemsToDelete.Add(uploadContext);
                }
            }
            foreach (var uploadContext in itemsToDelete)
            {
                lock (_uploadContexts)
                {
                    _uploadContexts.Remove(uploadContext);
                }
            }
        }

        void CheckUpload(UploadContext uploadContext)
        {
            nextState:

            switch (uploadContext.State)
            {
                case UploadContext.UploadState.GET_FILE_DATA:
                    System.IO.FileInfo fileInfo = new System.IO.FileInfo(uploadContext.Path);                
                    uploadContext.FileSize = fileInfo.Length;
                    uploadContext.State = UploadContext.UploadState.BEGIN;
                    goto nextState;
                case UploadContext.UploadState.GET_FILE_DATA_PENDING: // if we had a long running GET_FILE_DATA (e.g. URL or device file system)
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 10)                            
                        throw new Exception("Upload timeout waiting for file data");
                    break;
                case UploadContext.UploadState.BEGIN:
                    uploadContext.State = UploadContext.UploadState.BEGIN_PENDING;
                    _eq.UploadService.Begin(uploadContext,
                        (Upload upload, EQException ex) => // onComplete
                        {
                            if (ex != null) throw ex;
                            if (uploadContext.State == UploadContext.UploadState.BEGIN_PENDING)
                            {
                                uploadContext.Upload = upload;
                                if (uploadContext.Upload.UploadChunks != null && uploadContext.Upload.UploadChunks.Count > 0)
                                    uploadContext.State = UploadContext.UploadState.UPLOAD_CHUNKS;
                                else
                                    uploadContext.State = UploadContext.UploadState.DOWNLOAD_CHUNKLIST;
                            }
                        });
                    break;
                case UploadContext.UploadState.BEGIN_PENDING:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 4)
                    {
                        if (uploadContext.RetryCount_Begin_Pending++ > 3)
                            throw new Exception("Upload timeout waiting for start");
                        uploadContext.State = UploadContext.UploadState.BEGIN;
                        goto nextState;
                    }
                    break;
                case UploadContext.UploadState.DOWNLOAD_CHUNKLIST:
                    uploadContext.State = UploadContext.UploadState.DOWNLOAD_CHUNKLIST_PENDING;
                    uploadContext.PreferredChunkSize = _preferredChunkSize;
                    _eq.UploadService.ListUpload(uploadContext,
                        (Upload upload, EQException ex) => // onComplete
                        {
                            if (ex != null) throw ex;
                            if (uploadContext.State == UploadContext.UploadState.DOWNLOAD_CHUNKLIST_PENDING)
                            {
                                uploadContext.Upload = upload;
                                if (uploadContext.Upload.UploadChunks != null && uploadContext.Upload.UploadChunks.Count > 0)
                                    uploadContext.State = UploadContext.UploadState.UPLOAD_CHUNKS;
                                else
                                    uploadContext.State = UploadContext.UploadState.COMPLETE;
                            }
                        });
                    break;
                case UploadContext.UploadState.DOWNLOAD_CHUNKLIST_PENDING:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 20)
                    {
                        if (uploadContext.RetryCount_DownloadChunklist_Pending++ > 3)
                            throw new Exception("Upload timeout waiting for chunk information");
                        uploadContext.State = UploadContext.UploadState.DOWNLOAD_CHUNKLIST;
                        goto nextState;
                    }
                    break;
                case UploadContext.UploadState.UPLOAD_CHUNKS:
                    uploadContext.RetryCount_DownloadChunklist_Pending = 0;
                    if (uploadContext.UploadChunksInProgress.Count == 0 && uploadContext.Upload.UploadChunks.Count == 0)
                    {
                        uploadContext.State = UploadContext.UploadState.DOWNLOAD_CHUNKLIST;
                        goto nextState;
                    }
                    if (_uploadsInProgress < MaxUploadsInProgress)
                        DoUploadChunk(uploadContext);
                    break;
                case UploadContext.UploadState.COMPLETE:
                    uploadContext.State = UploadContext.UploadState.COMPLETE_PENDING;
                    _eq.UploadService.Complete(uploadContext,
                        (Upload upload, EQException ex) => // onComplete
                        {
                            if (ex != null) throw ex;
                            if (uploadContext.State == UploadContext.UploadState.COMPLETE_PENDING)
                            {
                                uploadContext.Upload = upload;
                                uploadContext.State = UploadContext.UploadState.CREATE_CONTENTFILE;
                            }
                        });
                    break;
                case UploadContext.UploadState.COMPLETE_PENDING:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 10)
                    {
                        if (uploadContext.RetryCount_Complete_Pending++ > 60)
                            throw new Exception("Upload timeout waiting for completion");
                        uploadContext.State = UploadContext.UploadState.COMPLETE;
                        goto nextState;
                    }
                    break;
                case UploadContext.UploadState.CREATE_CONTENTFILE:
                    uploadContext.State = UploadContext.UploadState.CREATE_CONTENTFILE_PENDING;
                    _eq.UploadService.CreateContentFile(uploadContext.ContentFile, uploadContext,
                        (ContentFile contentFile, EQException ex) => // onComplete
                        {
                            if (ex != null) throw ex;
                            if (uploadContext.State == UploadContext.UploadState.CREATE_CONTENTFILE_PENDING)
                            {
                                uploadContext.Upload.ContentFileId = contentFile.ContentFileId;
                                uploadContext.State = UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE;
                            }
                        });
                    break;
                case UploadContext.UploadState.CREATE_CONTENTFILE_PENDING:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 10)
                    {
                        if (uploadContext.RetryCount_CreateContentFile_Pending++ > 10)
                        throw new Exception("Upload timeout waiting for completion");
                        uploadContext.State = UploadContext.UploadState.CREATE_CONTENTFILE;
                        goto nextState;
                    }
                    break;
                case UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE_DELAY:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 5)
                        uploadContext.State = UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE;
                    break;
                case UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE:
                    uploadContext.State = UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE_PENDING;
                    _eq.ContentFileService.Get((int)uploadContext.Upload.ContentFileId,
                        (ContentFile contentFile, EQException ex) => // onComplete                        
                        {
                            if (ex != null) throw ex;
                            if (uploadContext.State == UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE_PENDING)
                            {
                                if (IsVideoReady(contentFile))
                                    uploadContext.State = UploadContext.UploadState.FINISHED;
                                else
                                    uploadContext.State = UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE_DELAY;
                            }
                        }
                        );
                    break;
                case UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE_PENDING:
                    if (uploadContext.TimeSinceLastStateChange.TotalSeconds > 10)
                    {
                        if (uploadContext.RetryCount_WaitingForAllToSee_Pending++ > 3)
                            throw new Exception("Upload timeout waiting for completion");
                        uploadContext.State = UploadContext.UploadState.WAITING_FOR_ALL_TO_SEE;
                        goto nextState;
                    }
                    break;
                case UploadContext.UploadState.FINISHED:
                    break;
            }
        }

        private bool IsVideoReady(ContentFile contentFile)
        {
            string status = "";
            string muxKey = contentFile.MuxKey;
            var arr = muxKey.Split(',');
            foreach (string item in arr)
            {
                var arr2 = item.Split('~');
                if (arr2.Length > 1 && arr2[1] == "3G")
                    status = arr2[2];
            }
            if (status == "3")
                return true;

            if (status == "4")
                throw new Exception("There was a problem processing upload");

            return false;
        }

        public void DoUploadChunk(UploadContext uploadContext)
        {
            UploadChunk uploadChunk = null;
            lock (uploadContext.Upload.UploadChunks)
            {
                if (uploadContext.Upload.UploadChunks.Count > 0)
                {
                    uploadChunk = uploadContext.Upload.UploadChunks[0];
                    uploadContext.Upload.UploadChunks.RemoveAt(0);
                    uploadContext.UploadChunksInProgress.Add(uploadChunk);
                }
            }

            if (uploadChunk == null)
                return;

            _uploadsInProgress++;

            FileUploadSpec chunk = uploadContext.GetChunk(uploadChunk);

            uploadChunk.UploadStarted = true;
            uploadContext.PreferredChunkSize = _preferredChunkSize;
            _eq.UploadService.UploadChunk(uploadContext, (long)uploadChunk.ChunkOffset, (long)uploadChunk.ChunkSize, chunk,
                (long soFar, long totalSize, EQException ex) => // onComplete
                {
                    if (ex != null) // error
                    {
                        uploadContext.UploadChunksInProgress.Remove(uploadChunk);
                        uploadChunk.UploadFinished = true;
                    }
                    else
                    {
                        GoodChunk();
                        uploadContext.UploadChunksInProgress.Remove(uploadChunk);
                        uploadContext.State = UploadContext.UploadState.UPLOAD_CHUNKS;
                        uploadChunk.UploadFinished = true;
                    }
                }
                );
            return;
        }
    }
}
