/*
 * 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.Diagnostics;
using System.IO;
using System.Text;

namespace Sasa.QualityTools.DrFx.Core
{
    /// <summary>
    /// FxCopCmd.exe 実行用のプロセスを生成する機能を提供します。
    /// </summary>
    public static class FxCopProcessGenerator
    {
        /// <summary>
        /// CUI 版 FxCop の実行ファイル名。
        /// </summary>
        private const string FXCOPCMD_FILE_NAME = "FxCopCmd.exe";

        /// <summary>
        /// FxCop のデフォルトルールディレクトリ名。
        /// </summary>
        private const string RULES_DIRECTORY_NAME = "Rules";



        /// <summary>
        /// 指定された設定値 <paramref name="configuration"/> を使用して、FxCopCmd.exe 実行のための
        /// プロセスオブジェクトを作成します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        /// <returns>生成されたプロセスオブジェクト。</returns>
        public static Process GenerateFxCopCmdProcess(FxCopProcessConfiguration configuration)
        {
            ValidateTargetFiles(configuration);
            ValidateOutputFile(configuration);
            ValidateRuleFiles(configuration);
            ValidateDependencyDirectories(configuration);
            ValidateDictionaryFile(configuration);
            ValidateFxCopInstallDirectory(configuration);

            string directory = (String.IsNullOrEmpty(configuration.FxCopInstallDirectory)) ? "." : configuration.FxCopInstallDirectory;
            string command = Quote(Path.Combine(directory, FXCOPCMD_FILE_NAME));
            string arguments = GenerateFxCopCmdArguments(configuration);

            Process process = new Process();
            process.StartInfo = new ProcessStartInfo(command, arguments);

            return process;
        }

        /// <summary>
        /// FxCopCmd.exe のコマンドライン引数を生成します。
        /// </summary>
        /// <param name="configuration">コマンドライン引数生成のための設定値。</param>
        /// <returns>生成された FxCopCmd.exe のコマンドライン引数。</returns>
        internal static string GenerateFxCopCmdArguments(FxCopProcessConfiguration configuration)
        {
            StringBuilder command = new StringBuilder("/c");

            command.Append(" /o:" + Quote(configuration.OutputFile));

            foreach (string file in configuration.TargetFiles)
            {
                command.Append(" /f:" + Quote(file));
            }

            string fxcopInstallDirectory = (String.IsNullOrEmpty(configuration.FxCopInstallDirectory)) ? "." : configuration.FxCopInstallDirectory;
            command.Append(" /r:" + Quote(Path.Combine(fxcopInstallDirectory, RULES_DIRECTORY_NAME)));
            foreach (string file in configuration.RuleFiles)
            {
                command.Append(" /r:" + Quote(file));
            }

            foreach (string directory in configuration.DependencyDirectories)
            {
                command.Append(" /d:" + Quote(directory));
            }

            if (String.IsNullOrEmpty(configuration.DictionaryFile) == false)
            {
                command.Append(" /dic:" + Quote(configuration.DictionaryFile));
            }

            if (configuration.EnableSearchGac)
            {
                command.Append(" /gac");
            }

            return command.ToString();
        }

        /// <summary>
        /// 指定された文字列をダブルクォーテーションで囲みます。
        /// </summary>
        /// <param name="word">処理対象の文字列。</param>
        /// <returns>ダブルクォーテーションで囲まれた <paramref name="word"/>。</returns>
        private static string Quote(string word)
        {
            return '"' + word + '"';
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定されたコード分析対象アセンブリが１つ以上
        /// 指定されており、それらが存在することを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateTargetFiles(FxCopProcessConfiguration configuration)
        {
            if (configuration.TargetFiles.Count == 0)
            {
                string message = MessageProvider.TargetFileWasNotSpecified();
                throw new ValidationException(message);
            }

            foreach (string file in configuration.TargetFiles)
            {
                if (File.Exists(file) == false)
                {
                    string message = MessageProvider.TargetFileWasNotFound(file);
                    throw new ValidationException(message);
                }
            }
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定された出力先としてディレクトリが指定されていないこと、
        /// およびパスの一部に存在しないディレクトリが含まれていないことを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateOutputFile(FxCopProcessConfiguration configuration)
        {
            string file = configuration.OutputFile;
            if (String.IsNullOrEmpty(file))
            {
                return;
            }

            if (File.Exists(file) == false && Directory.Exists(file))
            {
                string message = MessageProvider.OutputFileWasDirectory(file);
                throw new ValidationException(message);
            }

            string directory = Path.GetDirectoryName(file);
            if (String.IsNullOrEmpty(directory) == false && Directory.Exists(directory) == false)
            {
                string message = MessageProvider.OutputDirectoryWasNotFound(directory);
                throw new ValidationException(message);
            }
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定されたルール定義アセンブリが存在することを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateRuleFiles(FxCopProcessConfiguration configuration)
        {
            foreach (string file in configuration.RuleFiles)
            {
                if (File.Exists(file) == false)
                {
                    string message = MessageProvider.RuleFileWasNotFound(file);
                    throw new ValidationException(message);
                }
            }
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定された依存アセンブリ検索ディレクトリが存在することを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateDependencyDirectories(FxCopProcessConfiguration configuration)
        {
            foreach (string directory in configuration.DependencyDirectories)
            {
                if (Directory.Exists(directory) == false)
                {
                    string message = MessageProvider.DependencyDirectoryWasNotFound(directory);
                    throw new ValidationException(message);
                }
            }
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定されたカスタムディクショナリファイルとして、
        /// 存在しないファイルが指定されていないことを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateDictionaryFile(FxCopProcessConfiguration configuration)
        {
            string file = configuration.DictionaryFile;
            if (String.IsNullOrEmpty(file))
            {
                return;
            }

            if (File.Exists(file) == false)
            {
                string message = MessageProvider.DictionaryFileWasNotFound(file);
                throw new ValidationException(message);
            }
        }

        /// <summary>
        /// <paramref name="configuration"/> に設定された FxCop インストールディレクトリが存在し、
        /// かつそれが FxCop インストールディレクトリとして妥当であることを確認します。
        /// </summary>
        /// <param name="configuration">FxCopCmd.exe プロセス生成のための設定値。</param>
        public static void ValidateFxCopInstallDirectory(FxCopProcessConfiguration configuration)
        {
            string directory = configuration.FxCopInstallDirectory;
            if (Directory.Exists(directory) == false)
            {
                string message = MessageProvider.FxCopIntallDirectoryWasNotFound(directory);
                throw new ValidationException(message);
            }

            string fxcopcmdPath = Path.Combine(directory, FXCOPCMD_FILE_NAME);
            if (File.Exists(fxcopcmdPath) == false)
            {
                string message = MessageProvider.FxCopCmdWasNotFound(fxcopcmdPath);
                throw new ValidationException(message);
            }

            string ruleDirectory = Path.Combine(directory, RULES_DIRECTORY_NAME);
            if (Directory.Exists(ruleDirectory) == false)
            {
                string message = MessageProvider.FxCopRuleDirectoryWasNotFound(ruleDirectory);
                throw new ValidationException(message);
            }
        }
    }
}
