using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using System.Web;

namespace Disco.Web.Extensions.MvcExtensions.Bundles
{
    public class Bundle
    {
        private DateTime? _FileLastModified { get; set; }
        private string _FileHash { get; set; }
        private string _VersionUrl { get; set; }

        public string Url { get; private set; }
        public string File { get; private set; }
        public string FileHash
        {
            get
            {
#if DEBUG
                UpdateFileHash();
#endif
                return _FileHash;
            }
        }
        public string ContentType { get; private set; }
        public string VersionUrl
        {
            get
            {
#if DEBUG
                return string.Format("{0}?v={1}", this.Url, this.FileHash);
#else
                return _VersionUrl;
#endif
            }
        }

        public Bundle(string Url, string File)
        {
            if (string.IsNullOrWhiteSpace(Url))
                throw new ArgumentNullException("Url");
            if (string.IsNullOrWhiteSpace(File))
                throw new ArgumentNullException("File");

            Uri fileUri;
            if (!Uri.TryCreate(File, UriKind.Absolute, out fileUri))
            {
                File = HttpContext.Current.Server.MapPath(File);
            }

            var fileInfo = new FileInfo(File);

            if (!fileInfo.Exists)
                throw new FileNotFoundException(string.Format("Not Found: {0}", File), File);

            this.Url = Url;
            this.File = File;

            switch (fileInfo.Extension.ToLower())
            {
                case ".css":
                    this.ContentType = "text/css";
                    break;
                case ".js":
                    this.ContentType = "text/javascript";
                    break;
                default:
                    throw new ArgumentException("Unsupported Bundle File Extension");
            }

            // Write File Hash
            if (fileInfo.Length > 0)
                UpdateFileHash();
            else
                this._FileHash = string.Empty;

            //this.Version = fileInfo.LastWriteTimeUtc.Ticks;

            this._VersionUrl = string.Format("{0}?v={1}", this.Url, this.FileHash);
        }

        private void UpdateFileHash()
        {
            if (System.IO.File.Exists(this.File))
            {
                var fileLastModified = System.IO.File.GetLastWriteTimeUtc(this.File);
                if (!this._FileLastModified.HasValue || this._FileLastModified.Value != fileLastModified)
                {
                    this._FileLastModified = fileLastModified;
                    var fileBytes = System.IO.File.ReadAllBytes(this.File);
                    if (fileBytes.Length > 0)
                    {
                        using (SHA256 sha = SHA256.Create())
                        {
                            byte[] hash = sha.ComputeHash(fileBytes);
                            this._FileHash = HttpServerUtility.UrlTokenEncode(hash);
                            return;
                        }
                    }
                }
                else
                {
                    // Already Updated
                    return;
                }
            }

            this._FileHash = string.Empty;
        }

        internal void ProcessRequest(HttpContext context)
        {
            // Write Content Type
            context.Response.ContentType = this.ContentType;

            // Write Headers
            var cache = context.Response.Cache;
            cache.SetCacheability(HttpCacheability.Public);
            cache.SetOmitVaryStar(true);
            cache.SetExpires(DateTime.Now.AddYears(1));
            cache.SetValidUntilExpires(true);
            cache.SetMaxAge(TimeSpan.FromDays(365));
            cache.SetLastModified(DateTime.Now);
            cache.VaryByHeaders["User-Agent"] = true;

            // Write File
            context.Response.WriteFile(this.File);
        }
    }
}
