/*
 * DrFx - FxCop Report Translator and Visualizer.
 * Copyright (C) 2010 Sasa Yuan
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */



using System;
using System.Collections.Generic;
using System.IO;

namespace Sasa.QualityTools.DrFx.Console
{
    /// <summary>
    /// ファイルシステム上のファイル、ディレクトリの検索機能を提供します。
    /// </summary>
    internal static class FileSystemSearcher
    {
        /// <summary>
        /// 指定された検索パターンを使用してファイルシステム上のディレクトリを検索します。
        /// </summary>
        /// <remarks>
        /// このメソッドは以下のような特徴を持っています。
        /// <list>
        /// <item>検索パターンとして存在しないパスを指定した場合は空の検索結果を返します。</item>
        /// <item>検索パターンとしてファイルを直接指定した場合はそのファイルのみを返します。</item>
        /// <item>検索パターンとしてディレクトリを指定した場合は空の検索結果を返します。</item>
        /// <item>ワイルドカードを使用できます。</item>
        /// <item>検索パスの途中にワイルドカードを使用できます (例 : dir1\*\dir2\*.txt)。</item>
        /// <item>絶対パス指定でも相対パス指定でも検索できます。</item>
        /// </list>
        /// </remarks>
        /// <param name="pattern">検索パターン。</param>
        /// <returns>検索して得られたディレクトリの配列。</returns>
        internal static string[] SearchDirectories(string pattern)
        {
            return Search(pattern, (path, searchPattern) => { return Directory.GetDirectories(path, searchPattern); });
        }

        /// <summary>
        /// 指定された検索パターンを使用してファイルシステム上のファイルを検索します。
        /// </summary>
        /// <remarks>
        /// このメソッドは以下のような特徴を持っています。
        /// <list>
        /// <item>検索パターンとして存在しないパスを指定した場合は空の検索結果を返します。</item>
        /// <item>検索パターンとしてファイルを直接指定した場合は空の検索結果を返します</item>
        /// <item>検索パターンとしてディレクトリを指定した場合はそのディレクトリのみを返します。</item>
        /// <item>ワイルドカードを使用できます。</item>
        /// <item>検索パスの途中にワイルドカードを使用できます (例 : dir1\*\dir2\*)。</item>
        /// <item>絶対パス指定でも相対パス指定でも検索できます。</item>
        /// </list>
        /// </remarks>
        /// <param name="pattern">検索パターン。</param>
        /// <returns>検索して得られたファイルの配列。</returns>
        internal static string[] SearchFiles(string pattern)
        {
            return Search(pattern, (path, searchPattern) => { return Directory.GetFiles(path, searchPattern); });
        }

        /// <summary>
        /// 指定された検索アクションを使用してファイルシステム上のファイル、もしくは
        /// ディレクトリを検索します。
        /// </summary>
        /// <param name="pattern">検索パターン。</param>
        /// <param name="searchAction">検索基点ディレクトリと検索パターンを受け取って、
        /// 検索結果の配列を返す検索アクション。</param>
        /// <returns>検索結果のパスからなる配列。</returns>
        private static string[] Search(string pattern, Func<string, string, string[]> searchAction)
        {
            List<string> result = new List<string>();

            if (Path.IsPathRooted(pattern))
            {
                string root = Path.GetPathRoot(pattern);
                pattern = pattern.Replace(root, "");
                result.AddRange(Search(root, pattern, searchAction));
            }
            else
            {
                result.AddRange(Search(".", pattern, searchAction));
            }

            result.Sort();
            for (int i = 0; i < result.Count; i++)
            {
                if (result[i].StartsWith("." + Path.DirectorySeparatorChar))
                {
                    result[i] = result[i].Substring(2);
                }
            }

            return result.ToArray();
        }

        /// <summary>
        /// 指定された検索パターンを使用してファイルシステム上のファイル、ディレクトリを
        /// 検索します。
        /// </summary>
        /// <param name="current">検索の基点となるディレクトリ。</param>
        /// <param name="pattern">検索パターン。</param>
        /// <param name="searchAction">検索基点ディレクトリと検索パターンを受け取って、
        /// 検索結果の配列を返す検索アクション。</param>
        /// <returns>検索結果のパスからなるリスト。</returns>
        private static List<string> Search(string current, string pattern, Func<string, string, string[]> searchAction)
        {
            List<string> paths = new List<string>();

            if (pattern.Contains(Path.DirectorySeparatorChar.ToString()))
            {
                string[] tokens = pattern.Split(new char[] { Path.DirectorySeparatorChar }, 2, StringSplitOptions.RemoveEmptyEntries);
                foreach (string path in Directory.GetDirectories(current, tokens[0]))
                {
                    paths.AddRange(Search(path, tokens[1], searchAction));
                }
            }
            else
            {
                paths.AddRange(searchAction(current, pattern));
            }

            return paths;
        }
    }
}
