/*
 * 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.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading;
using System.Xml;

namespace Sasa.QualityTools.DrFx.Core.Translation
{
    /// <summary>
    /// FxCop レポートの翻訳機能を提供します。
    /// </summary>
    public class Translator
    {
        /// <summary>
        /// 翻訳ルールのリスト。
        /// </summary>
        private List<DrFxRule> rules;



        /// <summary>
        /// 新しいインスタンスを初期化します。
        /// </summary>
        public Translator()
        {
            this.rules = new List<DrFxRule>();
        }



        /// <summary>
        /// 翻訳ルールを追加します。
        /// </summary>
        /// <param name="rule">翻訳ルール。</param>
        public void AddRule(DrFxRule rule)
        {
            this.rules.Add(rule);
        }

        /// <summary>
        /// 翻訳ルールを追加します。
        /// </summary>
        /// <param name="rules">翻訳ルールのコレクション。</param>
        public void AddRules(IEnumerable<DrFxRule> rules)
        {
            this.rules.AddRange(rules);
        }

        /// <summary>
        /// カテゴリーが <paramref name="category"/>、チェック ID が<paramref name="checkId"/> の違反メッセージを翻訳します。
        /// </summary>
        /// <param name="category">FxCop ルールのカテゴリー。</param>
        /// <param name="checkId">FxCop ルールのチェック ID。</param>
        /// <param name="issue">翻訳対象の違反メッセージ。</param>
        /// <returns>翻訳されたメッセージ。翻訳ルールが見つからない場合は元のメッセージ。</returns>
        public string TranslateIssue(string category, string checkId, string issue)
        {
            foreach (DrFxRule rule in this.rules)
            {
                if (rule.Category == category && rule.CheckId == checkId)
                {
                    return TranslateIssue(rule, issue);
                }
            }
            return issue;
        }

        /// <summary>
        /// 指定された FxCop レポートコンテンツを翻訳します。
        /// </summary>
        /// <param name="content">翻訳したい FxCop レポートコンテンツ。</param>
        /// <returns>翻訳された FxCop レポートコンテンツ。</returns>
        public string Translate(string content)
        {
            XmlDocument report = new XmlDocument();
            report.LoadXml(content);

            foreach (DrFxRule rule in this.rules)
            {
                string xpath = "//Message[@CheckId='" + rule.CheckId + "' and @Category='" + rule.Category + "']";
                foreach (XmlNode node in report.SelectNodes(xpath))
                {
                    TranslateIssues(rule, node);
                }

                xpath = "/FxCopReport/Rules/Rule[@CheckId='" + rule.CheckId + "' and @Category='" + rule.Category + "']";
                foreach (XmlNode node in report.SelectNodes(xpath))
                {
                    node["Name"].InnerText = rule.Name;
                }
            }

            return ConvertXmlDocumentToString(report);
        }

        /// <summary>
        /// 指定された Message ノード内の各 Issue ノードを、指定されたルールに従って翻訳します。
        /// </summary>
        /// <param name="messageNode">翻訳対象の Issue ノードを含む Message ノード。</param>
        /// <param name="rule">翻訳ルール。</param>
        private static void TranslateIssues(DrFxRule rule, XmlNode messageNode)
        {
            foreach (XmlNode node in messageNode.SelectNodes("Issue"))
            {
                string content = node.InnerText.Replace(Environment.NewLine, String.Empty);
                node.InnerText = TranslateIssue(rule, content);
            }
        }

        /// <summary>
        /// 指定された翻訳ルールで FxCop 違反メッセージを翻訳します。
        /// </summary>
        /// <param name="rule">翻訳ルール。</param>
        /// <param name="issue">FxCop 違反メッセージ。</param>
        /// <returns>翻訳されたメッセージ。翻訳ルールが見つからない場合は元のメッセージ。</returns>
        private static string TranslateIssue(DrFxRule rule, string issue)
        {
            foreach (Resolution resolution in rule.Resolutions)
            {
                Match match = Regex.Match(issue, resolution.Pattern);
                if (match.Success)
                {
                    List<Group> groups = match.Groups.Cast<Group>().ToList();
                    groups.RemoveAt(0);
                    return String.Format(Thread.CurrentThread.CurrentUICulture, resolution.Content, groups.ToArray());
                }
            }
            return issue;
        }

        /// <summary>
        /// 指定された XML ドキュメントオブジェクトを文字列化します。
        /// </summary>
        /// <param name="document">文字列化したい XML ドキュメントオブジェクト。</param>
        /// <returns>指定された XML ドキュメントオブジェクトを変換して得られた文字列。</returns>
        internal static string ConvertXmlDocumentToString(XmlDocument document)
        {
            XmlDeclaration declaration = (XmlDeclaration)document.FirstChild;
            Encoding encoding = Encoding.GetEncoding(declaration.Encoding);

            using (MemoryStream stream = new MemoryStream())
            {
                using (XmlTextWriter writer = new XmlTextWriter(stream, encoding))
                {
                    writer.Formatting = Formatting.Indented;
                    document.WriteTo(writer);
                    writer.Flush();
                    byte[] preamble = encoding.GetPreamble();
                    int offset = preamble.Length;
                    return encoding.GetString(stream.ToArray(), offset, (int)(stream.Length - offset));
                }
            }
        }
    }
}
