/*
 * 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;
using System.Xml;

namespace Sasa.QualityTools.DrFx.Explorer.Report
{
    /// <summary>
    /// FxCop レポートを表すクラスです。
    /// </summary>
    public class FxCopReport
    {
        /// <summary>
        /// FxCop ルールに違反したアセンブリの情報のリスト。
        /// </summary>
        private List<AssemblyWarning> assemblies;

        /// <summary>
        /// FxCop ルールに違反した名前空間の情報のリスト。
        /// </summary>
        private List<NamespaceWarning> namespaces;

        /// <summary>
        /// FxCop レポートの中に埋め込まれていた (つまり、違反が検出された) ルール情報。
        /// </summary>
        private Dictionary<string, FxCopRule> rules;



        /// <summary>
        /// レポートファイルのパス。
        /// </summary>
        public string ReportFile { get; private set; }

        /// <summary>
        /// FxCop ルールに違反したアセンブリの情報のリスト。
        /// </summary>
        public IEnumerable<AssemblyWarning> Assemblies
        {
            get
            {
                return this.assemblies;
            }
        }

        /// <summary>
        /// FxCop ルールに違反した名前空間の情報のリスト。
        /// </summary>
        public IEnumerable<NamespaceWarning> Namespaces
        {
            get
            {
                return this.namespaces;
            }
        }

        /// <summary>
        /// FxCop レポートの中に埋め込まれていた (つまり、違反が検出された) ルール情報。
        /// </summary>
        public IDictionary<string, FxCopRule> Rules
        {
            get
            {
                return this.rules;
            }
        }



        /// <summary>
        /// <see cref="Sasa.QualityTools.DrFx.Explorer.Report.FxCopReport"/> クラスの新しいインスタンスを初期化します。
        /// </summary>
        private FxCopReport()
        {
            this.assemblies = new List<AssemblyWarning>();
            this.namespaces = new List<NamespaceWarning>();
            this.rules = new Dictionary<string, FxCopRule>();
        }



        /// <summary>
        /// 指定された FxCop レポートを解析し、インスタンス化します。
        /// </summary>
        /// <param name="file">FxCop レポートファイルのパス。</param>
        /// <returns>指定された FxCop レポートを表すインスタンス。</returns>
        /// <exception cref="System.ArgumentException"><paramref name="file"/> として null や空文字列、ファイルシステム上に存在しないパスが指定された場合に発生します。</exception>
        public static FxCopReport Load(string file)
        {
            if (String.IsNullOrEmpty(file))
            {
                throw new ArgumentException("この引数には、ファイルシステム上に存在するファイルのパスを指定してください。", "file");
            }

            if (File.Exists(file) == false)
            {
                throw new ArgumentException("この引数には、ファイルシステム上に存在するファイルのパスを指定してください。", "file");
            }

            FxCopReport report = new FxCopReport();
            report.ReportFile = Path.GetFullPath(file);

            XmlDocument document = new XmlDocument();
            document.Load(file);
            report.assemblies.AddRange(CreateAssemblyWarnings(document.DocumentElement["Targets"]));
            report.namespaces.AddRange(CreateNamespaceWarnings(document.DocumentElement["Namespaces"]));

            foreach (FxCopRule rule in CreateRules(document.DocumentElement["Rules"]))
            {
                report.Rules.Add(rule.CheckId, rule);
            }

            return report;
        }

        /// <summary>
        /// FxCop レポートの Targets ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.AssemblyWarning"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="assembliesNode">FxCop レポートの Targets ノード。</param>
        /// <returns>FxCop レポートの Targets ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.AssemblyWarning"/> オブジェクトのコレクション。</returns>
        internal static List<AssemblyWarning> CreateAssemblyWarnings(XmlNode assembliesNode)
        {
            if (assembliesNode == null)
            {
                return new List<AssemblyWarning>();
            }

            List<AssemblyWarning> assemblies = new List<AssemblyWarning>();
            foreach (XmlNode assemblyNode in assembliesNode.ChildNodes)
            {
                AssemblyWarning assembly = new AssemblyWarning();
                assembly.FileName = assemblyNode.Attributes["Name"].Value;
                assembly.BaseName = Path.GetFileName(assembly.FileName);
                foreach (XmlNode namespacesNode in assemblyNode.SelectNodes("Modules/Module/Namespaces"))
                {
                    foreach (NamespaceWarning nameSpace in CreateNamespaceWarnings(namespacesNode))
                    {
                        assembly.Namespaces.Add(nameSpace);
                    }
                }
                foreach (XmlNode messagesNode in assemblyNode.SelectNodes("Modules/Module/Messages"))
                {
                    foreach (FxCopIssue issue in CreateIssues(assemblyNode.SelectSingleNode("Modules/Module/Messages")))
                    {
                        assembly.Issues.Add(issue);
                    }
                }
                assemblies.Add(assembly);
            }

            return assemblies;
        }

        /// <summary>
        /// FxCop レポートの Namespaces ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.NamespaceWarning"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="namespacesNode">FxCop レポートの Namespaces ノード。</param>
        /// <returns>FxCop レポートの Namespaces ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.NamespaceWarning"/> オブジェクトのコレクション。</returns>
        internal static List<NamespaceWarning> CreateNamespaceWarnings(XmlNode namespacesNode)
        {
            if (namespacesNode == null)
            {
                return new List<NamespaceWarning>();
            }

            List<NamespaceWarning> nameSpaces = new List<NamespaceWarning>();
            foreach (XmlNode namespaceNode in namespacesNode.ChildNodes)
            {
                NamespaceWarning nameSpace = new NamespaceWarning();
                nameSpace.Name = namespaceNode.Attributes["Name"].Value;
                foreach (TypeWarning type in CreateTypeWarnings(namespaceNode["Types"]))
                {
                    nameSpace.Types.Add(type);
                }
                foreach (FxCopIssue issue in CreateIssues(namespaceNode["Messages"]))
                {
                    nameSpace.Issues.Add(issue);
                }
                nameSpaces.Add(nameSpace);
            }

            return nameSpaces;
        }

        /// <summary>
        /// FxCop レポートの Types ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.TypeWarning"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="typesNode">FxCop レポートの Types ノード。</param>
        /// <returns>FxCop レポートの Types ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.TypeWarning"/> オブジェクトのコレクション。</returns>
        internal static List<TypeWarning> CreateTypeWarnings(XmlNode typesNode)
        {
            if (typesNode == null)
            {
                return new List<TypeWarning>();
            }

            List<TypeWarning> types = new List<TypeWarning>();
            foreach (XmlNode typeNode in typesNode.ChildNodes)
            {
                TypeWarning type = new TypeWarning();
                type.Name = typeNode.Attributes["Name"].Value;
                type.Kind = (typeNode.Attributes["Kind"] != null) ? typeNode.Attributes["Kind"].Value : null;
                foreach (MemberWarning member in CreateMemberWarnings(typeNode["Members"]))
                {
                    type.Members.Add(member);
                }
                foreach (FxCopIssue issue in CreateIssues(typeNode["Messages"]))
                {
                    type.Issues.Add(issue);
                }
                types.Add(type);
            }

            return types;
        }

        /// <summary>
        /// FxCop レポートの Members ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.MemberWarning"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="membersNode">FxCop レポートの Members ノード。</param>
        /// <returns>FxCop レポートの Members ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.MemberWarning"/> オブジェクトのコレクション。</returns>
        internal static List<MemberWarning> CreateMemberWarnings(XmlNode membersNode)
        {
            if (membersNode == null)
            {
                return new List<MemberWarning>();
            }

            List<MemberWarning> members = new List<MemberWarning>();
            foreach (XmlNode memberNode in membersNode.ChildNodes)
            {
                MemberWarning member = new MemberWarning();
                member.Name = memberNode.Attributes["Name"].Value;
                member.Kind = (memberNode.Attributes["Kind"] != null) ? memberNode.Attributes["Kind"].Value : null;
                foreach (FxCopIssue issue in CreateIssues(memberNode["Messages"]))
                {
                    member.Issues.Add(issue);
                }
                members.Add(member);
            }

            return members;
        }

        /// <summary>
        /// FxCop レポートの Messages ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.FxCopIssue"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="messagesNode">FxCop レポートの Messages ノード。</param>
        /// <returns>FxCop レポートの Messages ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.FxCopIssue"/> オブジェクトのコレクション。</returns>
        internal static List<FxCopIssue> CreateIssues(XmlNode messagesNode)
        {
            if (messagesNode == null)
            {
                return new List<FxCopIssue>();
            }

            List<FxCopIssue> issues = new List<FxCopIssue>();
            foreach (XmlNode messageNode in messagesNode.ChildNodes)
            {
                foreach (XmlNode issueNode in messageNode.ChildNodes)
                {
                    FxCopIssue issue = new FxCopIssue();
                    issue.TypeName = messageNode.Attributes["TypeName"].Value;
                    issue.Category = messageNode.Attributes["Category"].Value;
                    issue.CheckId = messageNode.Attributes["CheckId"].Value;
                    issue.Status = messageNode.Attributes["Status"].Value;
                    issue.Level = issueNode.Attributes["Level"].Value;
                    if (issueNode.Attributes["Path"] != null && issueNode.Attributes["File"] != null)
                    {
                        issue.FilePath = Path.Combine(issueNode.Attributes["Path"].Value, issueNode.Attributes["File"].Value);
                    }
                    if (issueNode.Attributes["Line"] != null)
                    {
                        issue.Line = Int32.Parse(issueNode.Attributes["Line"].Value);
                    }
                    issue.Content = issueNode.InnerText;
                    issues.Add(issue);
                }
            }

            return issues;
        }

        /// <summary>
        /// FxCop レポートの Rules ノードを基に、<see cref="Sasa.QualityTools.DrFx.Explorer.Report.FxCopRule"/> オブジェクトの
        /// コレクションを作成します。
        /// </summary>
        /// <param name="rulesNode">FxCop レポートの Rules ノード。</param>
        /// <returns>FxCop レポートの Rules ノードを基に作成した <see cref="Sasa.QualityTools.DrFx.Explorer.Report.FxCopRule"/> オブジェクトのコレクション。</returns>
        internal static List<FxCopRule> CreateRules(XmlNode rulesNode)
        {
            if (rulesNode == null)
            {
                return new List<FxCopRule>();
            }

            List<FxCopRule> rules = new List<FxCopRule>();
            foreach (XmlNode ruleNode in rulesNode.ChildNodes)
            {
                FxCopRule rule = new FxCopRule();
                rule.CheckId = ruleNode.Attributes["CheckId"].Value;
                rule.Category = ruleNode.Attributes["Category"].Value;
                rule.Name = ruleNode["Name"].InnerText;
                rule.Description = ruleNode["Description"].InnerText;
                rules.Add(rule);
            }
            
            return rules;
        }
    }
}
