using System;
using System.Collections.Generic;
using System.IO;
using System.IO.Abstractions;
using System.Linq;

namespace Quadarax.Foundation.Core.IO
{
    public class FileSearch
    {
        #region *** Private fields ***
        private IFileSystem _fs;
        #endregion

        #region *** Properties ***
        public string Path { get; }
        public bool IsDirctoryRecursive { get; }
        #endregion

        #region *** Constructors ***
        public FileSearch(IFileSystem fileSystemAbstraction, string initialPath, bool includeSubdirs = true)
        {
            _fs = fileSystemAbstraction ?? throw new ArgumentNullException(nameof(fileSystemAbstraction));

            if (string.IsNullOrEmpty(initialPath))
                throw new ArgumentNullException(nameof(initialPath));

            if (!_fs.Directory.Exists(initialPath))
                throw new FileNotFoundException($"Initial path '{initialPath}' doesn't exists.");

            Path = initialPath;
            IsDirctoryRecursive = includeSubdirs;

        }

        public FileSearch(string initialPath, bool includeSubdirs = true) : this(new FileSystem(),initialPath, includeSubdirs)
        {
        }
        #endregion

        #region *** Public operations ***
        public string[] Search()
        {
            return Search(new[] {"*"});
        }
        public string[] Search(IEnumerable<string> includePatterns, FileSearchComparer? comparer = null)
        {
            return Search(includePatterns, new List<string>(), comparer);
        }

        public string[] Search(IEnumerable<string> includePatterns, IEnumerable<string> excludePatterns, FileSearchComparer? comparer = null)
        {
            if (includePatterns == null)
                throw new ArgumentNullException(nameof(includePatterns));
            if (excludePatterns == null)
                throw new ArgumentNullException(nameof(excludePatterns));

            if (!includePatterns.Any())
                includePatterns = new List<string>(new[] {"*"});

            comparer ??= new FileSearchComparer(includePatterns, excludePatterns);

            return SearchFiles(Path,comparer);
        }
        #endregion

        #region *** Private Operations ***

        private string[] SearchFiles(string initialDirPath, FileSearchComparer comparer)
        {
            var result = new List<string>();
            // iterate files
            foreach (var file in _fs.Directory.GetFiles(initialDirPath))
            {
                // check if file match condition
                if (comparer.Match(file))
                    result.Add(file);
            }

            if (IsDirctoryRecursive)
            {
                // iterate sub-dirs
                foreach (var currentDir in _fs.Directory.GetDirectories(initialDirPath))
                {
                    // check if directory match condition
                    if (comparer.Match(currentDir))
                        result.AddRange(SearchFiles(currentDir, comparer));
                }
            }
            return result.ToArray();
        }

        #endregion
    }
}
