using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Pilz.IO
{
    public class EmbeddedFilesContainer
    {
        [JsonProperty("CompressedFiles")]
        private readonly Dictionary<string, byte[]> compressedFiles = new Dictionary<string, byte[]>();

        /// <summary>
        /// Returns the names of all embedded files.
        /// </summary>
        [JsonIgnore]
        public IEnumerable<string> AllFileNames
        {
            get => compressedFiles.Keys;
        }

        /// <summary>
        /// Embeds a file to this container.
        /// </summary>
        /// <param name="fileName">The name how it should be called in this container.</param>
        /// <param name="filePath">The file path to the file that should be embedded.</param>
        /// <returns>Returns a <see cref="bool"/> that defines if the file as been embedded successfully.</returns>
        public Task<bool> AddFileAsync(string fileName, string filePath)
        {
            return Task.Run(() => AddFile(fileName, filePath));
        }

        /// <summary>
        /// Embeds a file to this container.
        /// </summary>
        /// <param name="fileName">The name how it should be called in this container.</param>
        /// <param name="filePath">The file path to the file that should be embedded.</param>
        /// <returns>Returns a <see cref="bool"/> that defines if the file as been embedded successfully.</returns>
        public bool AddFile(string fileName, string filePath)
        {
            bool success;
            FileStream fs = null;
            MemoryStream compressed = null;

#if !DEBUG
            try
            {
#endif
                fs = new FileStream(filePath, FileMode.Open, FileAccess.Read);
                compressed = new MemoryStream();
                using (var compressor = new DeflateStream(compressed, CompressionLevel.Optimal, true))
                    fs.CopyTo(compressor);
                success = true;
#if !DEBUG
            }
            catch (Exception)
            {
                success = false;
            }
#endif

            if (success)
            {
                var compressedBytes = compressed.ToArray();
                if (compressedFiles.ContainsKey(fileName))
                    compressedFiles[fileName] = compressedBytes;
                else
                    compressedFiles.Add(fileName, compressedBytes);
            }

            compressed?.Close();
            fs?.Close();

            return success;
        }

        /// <summary>
        /// Removes a file from this container.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        public void RemoveFile(string fileName)
        {
            if (compressedFiles.ContainsKey(fileName))
                compressedFiles.Remove(fileName);
        }

        /// <summary>
        /// Checks if the given file exists in this container.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        /// <returns>Returns if the given file exists in this container.</returns>
        public bool HasFile(string fileName)
        {
            return compressedFiles.ContainsKey(fileName);
        }

        /// <summary>
        /// Gets a file from this container as stream.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        /// <returns>Returns a stream of the file with the given name.</returns>
        public Task<Stream> GetStreamAsync(string fileName)
        {
            return Task.Run(() => GetStream(fileName));
        }

        /// <summary>
        /// Gets a file from this container as stream.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        /// <returns>Returns a stream of the file with the given name.</returns>
        public Stream GetStream(string fileName)
        {
            Stream decompressed = null;

            if (compressedFiles.ContainsKey(fileName))
            {
                decompressed = new MemoryStream();
                DecompressToStream(decompressed, compressedFiles[fileName]);
            }

            return decompressed;
        }

        /// <summary>
        /// Saves a given file to the users temp directory.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        /// <returns>Returns the file path to the temp file.</returns>
        public Task<string> GetLocalFilePathAsync(string fileName)
        {
            return Task.Run(() => GetLocalFilePath(fileName));
        }


        /// <summary>
        /// Saves a given file to the users temp directory.
        /// </summary>
        /// <param name="fileName">The name how the file is called.</param>
        /// <returns>Returns the file path to the temp file.</returns>
        public string GetLocalFilePath(string fileName)
        {
            string filePath = string.Empty;

            if (compressedFiles.ContainsKey(fileName))
            {
                filePath = Path.GetTempFileName();

                if (Path.HasExtension(fileName))
                    filePath = Path.ChangeExtension(filePath, Path.GetExtension(fileName));

                var decompressed = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite);
                DecompressToStream(decompressed, compressedFiles[fileName]);
                decompressed.Flush();
                decompressed.Close();
            }

            return filePath;
        }

        private void DecompressToStream(Stream decompressed, byte[] compressedData)
        {
            var compressed = new MemoryStream(compressedData);
            var decompressor = new DeflateStream(compressed, CompressionMode.Decompress, true);
            decompressor.CopyTo(decompressed);
            decompressor.Close();
            compressed.Close();
        }
    }
}
