// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Collections;
using System.IO;

// We keep this class in Common to allow utilizing it without taking a dependency on System.CodeDom
#if CODEDOM
namespace System.CodeDom.Compiler
#else
namespace System.IO.Internal
#endif
{
    // Explicitly not [Serializable], so as to avoid accidentally deleting
    // files specified in a serialized payload.

#if CODEDOM
    public
#else
    internal sealed
#endif
    class TempFileCollection : ICollection, IDisposable
    {
        private string _basePath;
        private readonly string _tempDir;
        private readonly Hashtable _files;
        private bool _createdTempDirectory;

        public TempFileCollection() : this(null, false)
        {
        }

        public TempFileCollection(string tempDir) : this(tempDir, false)
        {
        }

        public TempFileCollection(string tempDir, bool keepFiles)
        {
            KeepFiles = keepFiles;
            _tempDir = tempDir;
            _files = new Hashtable(StringComparer.OrdinalIgnoreCase);
        }

        void IDisposable.Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

#pragma warning disable IDE0060
#if CODEDOM
        protected virtual
#else
        internal
#endif
        void Dispose(bool disposing)
        {
            SafeDelete();
        }
#pragma warning restore IDE0060

        ~TempFileCollection()
        {
            Dispose(false);
        }

        public string AddExtension(string fileExtension) => AddExtension(fileExtension, KeepFiles);

        public string AddExtension(string fileExtension, bool keepFile)
        {
            if (string.IsNullOrEmpty(fileExtension))
            {
                throw new ArgumentException(SR.Format(SR.InvalidNullEmptyArgument, nameof(fileExtension)), nameof(fileExtension));
            }

            string fileName = BasePath + "." + fileExtension;
            AddFile(fileName, keepFile);
            return fileName;
        }

        public void AddFile(string fileName, bool keepFile)
        {
            if (string.IsNullOrEmpty(fileName))
            {
                throw new ArgumentException(SR.Format(SR.InvalidNullEmptyArgument, nameof(fileName)), nameof(fileName));
            }

            if (_files[fileName] != null)
            {
                throw new ArgumentException(SR.Format(SR.DuplicateFileName, fileName), nameof(fileName));
            }

            _files.Add(fileName, keepFile);
        }

        public IEnumerator GetEnumerator() => _files.Keys.GetEnumerator();

        IEnumerator IEnumerable.GetEnumerator() => _files.Keys.GetEnumerator();

        void ICollection.CopyTo(Array array, int start) => _files.Keys.CopyTo(array, start);

        public void CopyTo(string[] fileNames, int start) => _files.Keys.CopyTo(fileNames, start);

        public int Count => _files.Count;

        int ICollection.Count => _files.Count;

        object ICollection.SyncRoot => null;

        bool ICollection.IsSynchronized => false;

        public string TempDir => _tempDir ?? string.Empty;

        public string BasePath
        {
            get
            {
                EnsureTempNameCreated();
                return _basePath;
            }
        }

        private void EnsureTempNameCreated()
        {
            if (_basePath == null)
            {
                string tempFileName;
                bool uniqueFile;
                int retryCount = 5000;
                do
                {
                    _basePath = Path.Combine(
                        string.IsNullOrEmpty(TempDir) ? GetTempDirectory() : TempDir,
                        Path.GetFileNameWithoutExtension(Path.GetRandomFileName()));
                    tempFileName = _basePath + ".tmp";

                    try
                    {
                        new FileStream(tempFileName, FileMode.CreateNew, FileAccess.Write).Dispose();
                        uniqueFile = true;
                    }
                    catch (IOException ex)
                    {
                        retryCount--;
                        if (retryCount == 0 || ex is DirectoryNotFoundException)
                        {
                            throw;
                        }
                        uniqueFile = false;
                    }
                } while (!uniqueFile);
                _files.Add(tempFileName, KeepFiles);
            }
        }

#if NET7_0_OR_GREATER
        private string GetTempDirectory()
        {
            _createdTempDirectory = true;
            return Directory.CreateTempSubdirectory().FullName;
        }
#else
        private static string GetTempDirectory()
        {
            return Path.GetTempPath();
        }
#endif

        public bool KeepFiles { get; set; }

        private bool KeepFile(string fileName)
        {
            object keep = _files[fileName];
            return keep != null ? (bool)keep : false;
        }

        public void Delete()
        {
            SafeDelete();
        }

        private static void Delete(string fileName)
        {
            try
            {
                File.Delete(fileName);
            }
            catch
            {
                // Ignore all exceptions
            }
        }

        internal void SafeDelete()
        {
            bool allFilesDeleted = true;

            if (_files != null && _files.Count > 0)
            {
                string[] fileNames = new string[_files.Count];
                _files.Keys.CopyTo(fileNames, 0);
                foreach (string fileName in fileNames)
                {
                    if (!KeepFile(fileName))
                    {
                        Delete(fileName);
                        _files.Remove(fileName);
                    }
                    else
                    {
                        allFilesDeleted = false;
                    }
                }
            }

            // if we created a temp directory, delete it and clear the basePath, so a new directory will be created for the next request.
            if (_createdTempDirectory && allFilesDeleted)
            {
                try
                {
                    Directory.Delete(Path.GetDirectoryName(BasePath));
                }
                catch
                {
                    // Ignore all exceptions
                }

                _createdTempDirectory = false;
                _basePath = null;
            }
        }
    }
}
