<commit_before>﻿using OutlookOkan.CsvTools;
using OutlookOkan.Properties;
using OutlookOkan.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookOkan.Models
{
    public class GenerateCheckList
    {
        private readonly CheckList _checkList = new CheckList();

        private readonly Dictionary<string, string> _displayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _toDisplayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _ccDisplayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _bccDisplayNameAndRecipient = new Dictionary<string, string>();

        private readonly List<Whitelist> _whitelist = new List<Whitelist>();

        /// <summary>
        /// メール送信の確認画面を表示。
        /// </summary>
        /// <param name="mail">送信するメールアイテム</param>
        public CheckList GenerateCheckListFromMail(Outlook._MailItem mail)
        {
            //This methods must run first.
            GetGeneralMailInfomation(mail);

            MakeDisplayNameAndRecipient(mail);

            CheckForgotAttach(mail);

            CheckKeyword();

            AutoAddCcAndBcc(mail);

            //TODO Temporary processing. It will be improved.
            MakeDisplayNameAndRecipient(mail);

            GetRecipient();

            GetAttachmentsInfomation(mail);

            CheckMailbodyAndRecipient();

            _checkList.RecipientExternalDomainNum = CountRecipientExternalDomains();

            _checkList.DeferredMinutes = CalcDeferredMinutes();

            return _checkList;
        }

        /// <summary>
        /// 一般的なメールの情報を取得して格納する。
        /// </summary>
        /// <param name="mail"></param>
        private void GetGeneralMailInfomation(Outlook._MailItem mail)
        {
            if (string.IsNullOrEmpty(mail.SentOnBehalfOfName))
            {
                //代理送信ではない場合。
                _checkList.Sender = mail.SenderEmailAddress ?? Resources.FailedToGetInformation;

                //mail.SenderEmailAddressがExchangeのアカウントだとそのまま使えないので、メールアドレスに変換する。
                if (!_checkList.Sender.Contains("@"))
                {
                    var tempOutlookApp = new Outlook.Application();
                    var tempRecipient = tempOutlookApp.Session.CreateRecipient(mail.SenderEmailAddress);

                    var exchangeUser = tempRecipient.AddressEntry.GetExchangeUser();

                    _checkList.Sender = exchangeUser.PrimarySmtpAddress ?? Resources.FailedToGetInformation;

                    if (!_checkList.Sender.Contains("@"))
                    {
                        //ここまでやって見つからなければ送信者のメールアドレスの検索を諦めてそのまま入れる。
                        _checkList.Sender = mail.Sender.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x39FE001E").ToString();
                    }
                }
                _checkList.SenderDomain = _checkList.Sender == Resources.FailedToGetInformation ? "------------------" : _checkList.Sender.Substring(_checkList.Sender.IndexOf("@", StringComparison.Ordinal));
            }
            else
            {
                //代理送信の場合
                _checkList.Sender = mail.Sender.Address ?? Resources.FailedToGetInformation;

                if (_checkList.Sender.Contains("@"))
                {
                    //メールアドレスが取得できる場合はExchangeではないのでそのままでよい。
                    _checkList.Sender = $@"{_checkList.Sender} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                    _checkList.SenderDomain = _checkList.Sender.Substring(_checkList.Sender.IndexOf("@", StringComparison.Ordinal));
                }
                else
                {
                    //代理送信の場合かつExchange利用
                    var tempOutlookApp = new Outlook.Application();
                    var tempRecipient = tempOutlookApp.Session.CreateRecipient(mail.Sender.Address);

                    try
                    {
                        //ユーザの代理送信
                        var exchangeUser = tempRecipient.AddressEntry.GetExchangeUser();
                        _checkList.Sender = $@"{exchangeUser.PrimarySmtpAddress} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                        _checkList.SenderDomain = exchangeUser.PrimarySmtpAddress.Substring(exchangeUser.PrimarySmtpAddress.IndexOf("@", StringComparison.Ordinal));
                    }
                    catch (Exception)
                    {
                        try
                        {
                            //配布リストの代理送信
                            var exchangeDistributionList = tempRecipient.AddressEntry.GetExchangeDistributionList();
                            _checkList.Sender = $@"{exchangeDistributionList.PrimarySmtpAddress} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                            _checkList.SenderDomain = exchangeDistributionList.PrimarySmtpAddress.Substring(exchangeDistributionList.PrimarySmtpAddress.IndexOf("@", StringComparison.Ordinal));
                        }
                        catch (Exception)
                        {
                            _checkList.Sender = $@"[{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf}";
                            _checkList.SenderDomain = @"------------------";
                        }
                    }
                }
            }
            
            _checkList.Subject = mail.Subject ?? Resources.FailedToGetInformation;
            _checkList.MailType = GetMailBodyFormat(mail) ?? Resources.FailedToGetInformation;
            _checkList.MailBody = mail.Body ?? Resources.FailedToGetInformation;

            //改行が2行になる問題を回避するため、HTML形式の場合にのみ2行の改行を1行に置換する。
            if (_checkList.MailType == Resources.HTML)
            {
                _checkList.MailBody = _checkList.MailBody.Replace("\r\n\r\n", "\r\n");
            }

            _checkList.MailHtmlBody = mail.HTMLBody ?? Resources.FailedToGetInformation;
        }

        /// <summary>
        /// 送信者ドメインを除く宛先のドメイン数を数える。
        /// </summary>
        /// <returns>送信者ドメインを除く宛先のドメイン数</returns>
        private int CountRecipientExternalDomains()
        {
            var domainList = new HashSet<string>();
            foreach (var mail in _displayNameAndRecipient)
            {
                var recipient = mail.Key;
                if (recipient != Resources.FailedToGetInformation && recipient.Contains("@"))
                {
                    domainList.Add(recipient.Substring(recipient.IndexOf("@", StringComparison.Ordinal)));
                }
            }

            //外部ドメインの数のため、送信者のドメインが含まれていた場合それをマイナスする。
            if (domainList.Contains(_checkList.SenderDomain))
            {
                return domainList.Count - 1;
            }

            return domainList.Count;
        }
        }

        /// <summary>
        /// 送信先の表示名と表示名とメールアドレスを対応させる。(Outlookの仕様上、表示名にメールアドレスが含まれない事がある。)
        /// </summary>
        /// <param name="mail"></param>
        private void MakeDisplayNameAndRecipient(Outlook._MailItem mail)
        {
            //TODO Temporary processing. It will be improved.
            //暫定的にこのメソッドを複数回実行する可能性があるため、実行のたびに以下の3つは初期化する。
            _toDisplayNameAndRecipient.Clear();
            _ccDisplayNameAndRecipient.Clear();
            _bccDisplayNameAndRecipient.Clear();

            foreach (Outlook.Recipient recip in mail.Recipients)
            {
                // Exchangeの連絡先に登録された情報を取得。
                Outlook.ExchangeUser exchangeUser = null;
                try
                {
                    exchangeUser = recip.AddressEntry.GetExchangeUser();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                // Exchangeの配布リスト(ML)として登録された情報を取得。
                Outlook.ExchangeDistributionList exchangeDistributionList = null;
                try
                {
                    exchangeDistributionList = recip.AddressEntry.GetExchangeDistributionList();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                // ローカルの連絡先に登録された情報を取得。
                Outlook.ContactItem registeredUser = null;
                try
                {
                    registeredUser = recip.AddressEntry.GetContact();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                //宛先メールアドレスを取得
                string mailAddress = recip.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001E").ToString() ?? Resources.FailedToGetInformation;

                // 登録されたメールアドレスの場合、登録名のみが表示されるため、メールアドレスと共に表示されるよう表示用テキストを生成。
                var nameAndMailAddress = exchangeUser != null
                    ? exchangeUser.Name + $@" ({mailAddress})"
                    : exchangeDistributionList != null
                        ? exchangeDistributionList.Name + $@" ({mailAddress})"
                        : registeredUser != null
                            ? registeredUser.FullName + $@" ({mailAddress})"
                            : mailAddress ?? Resources.FailedToGetInformation;
                
                //ケースによってメールアドレスのみを正しく取得できない恐れがあるため、その場合は、表示名称をメールアドレスとして登録する。
                if (!mailAddress.Contains("@"))
                {
                    mailAddress = nameAndMailAddress;
                }

                _displayNameAndRecipient[mailAddress] = nameAndMailAddress;

                //TODO Temporary processing. It will be improved.
                //名称を差出人とメールアドレスの紐づけをTo/CC/BCCそれぞれに格納
                switch (recip.Type)
                {
                    case 1:
                        _toDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    case 2:
                        _ccDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    case 3:
                        _bccDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// ファイルの添付忘れを確認。
        /// </summary>
        /// <param name="mail"></param>
        private void CheckForgotAttach(Outlook._MailItem mail)
        {
            if (mail.Attachments.Count >= 1) return;

            var generalSetting = new List<GeneralSetting>();
            var readCsv = new ReadAndWriteCsv("GeneralSetting.csv");
            foreach (var data in readCsv.GetCsvRecords<GeneralSetting>(readCsv.LoadCsv<GeneralSettingMap>()))
            {
                generalSetting.Add((data));
            }

            string attachmentsKeyword;

            if (generalSetting.Count == 0)
            {
                attachmentsKeyword = "添付";
            }
            else
            {
                if (!generalSetting[0].EnableForgottenToAttachAlert) return;

                switch (generalSetting[0].LanguageCode)
                {
                    case "ja-JP":
                        attachmentsKeyword = "添付";
                        break;
                    case "en-US":
                        attachmentsKeyword = "attached file";
                        break;
                    default:
                        return;
                }
            }

            if (_checkList.MailBody.Contains(attachmentsKeyword))
            {
                _checkList.Alerts.Add(new Alert { AlertMessage = Resources.ForgottenToAttachAlert, IsImportant = true, IsWhite = false, IsChecked = false });
            }
        }

        /// <summary>
        /// メールの形式を取得し、表示用の文字列を返す。
        /// </summary>
        /// <param name="mail"></param>
        /// <returns>メールの形式</returns>
        private string GetMailBodyFormat(Outlook._MailItem mail)
        {
            switch (mail.BodyFormat)
            {
                case Outlook.OlBodyFormat.olFormatUnspecified:
                    return Resources.Unknown;
                case Outlook.OlBodyFormat.olFormatPlain:
                    return Resources.Text;
                case Outlook.OlBodyFormat.olFormatHTML:
                    return Resources.HTML;
                case Outlook.OlBodyFormat.olFormatRichText:
                    return Resources.RichText;
                default:
                    return Resources.Unknown;
            }
        }

        /// <summary>
        /// 本文に登録したキーワードがある場合、登録した警告文を表示する。
        /// </summary><commit_msg>メールの形式取得処理を微修正。<commit_after>﻿using OutlookOkan.CsvTools;
using OutlookOkan.Properties;
using OutlookOkan.Types;
using System;
using System.Collections.Generic;
using System.Linq;
using Outlook = Microsoft.Office.Interop.Outlook;

namespace OutlookOkan.Models
{
    public class GenerateCheckList
    {
        private readonly CheckList _checkList = new CheckList();

        private readonly Dictionary<string, string> _displayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _toDisplayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _ccDisplayNameAndRecipient = new Dictionary<string, string>();
        private readonly Dictionary<string, string> _bccDisplayNameAndRecipient = new Dictionary<string, string>();

        private readonly List<Whitelist> _whitelist = new List<Whitelist>();

        /// <summary>
        /// メール送信の確認画面を表示。
        /// </summary>
        /// <param name="mail">送信するメールアイテム</param>
        public CheckList GenerateCheckListFromMail(Outlook._MailItem mail)
        {
            //This methods must run first.
            GetGeneralMailInfomation(mail);

            MakeDisplayNameAndRecipient(mail);

            CheckForgotAttach(mail);

            CheckKeyword();

            AutoAddCcAndBcc(mail);

            //TODO Temporary processing. It will be improved.
            MakeDisplayNameAndRecipient(mail);

            GetRecipient();

            GetAttachmentsInfomation(mail);

            CheckMailbodyAndRecipient();

            _checkList.RecipientExternalDomainNum = CountRecipientExternalDomains();

            _checkList.DeferredMinutes = CalcDeferredMinutes();

            return _checkList;
        }

        /// <summary>
        /// 一般的なメールの情報を取得して格納する。
        /// </summary>
        /// <param name="mail"></param>
        private void GetGeneralMailInfomation(Outlook._MailItem mail)
        {
            if (string.IsNullOrEmpty(mail.SentOnBehalfOfName))
            {
                //代理送信ではない場合。
                _checkList.Sender = mail.SenderEmailAddress ?? Resources.FailedToGetInformation;

                //mail.SenderEmailAddressがExchangeのアカウントだとそのまま使えないので、メールアドレスに変換する。
                if (!_checkList.Sender.Contains("@"))
                {
                    var tempOutlookApp = new Outlook.Application();
                    var tempRecipient = tempOutlookApp.Session.CreateRecipient(mail.SenderEmailAddress);

                    var exchangeUser = tempRecipient.AddressEntry.GetExchangeUser();

                    _checkList.Sender = exchangeUser.PrimarySmtpAddress ?? Resources.FailedToGetInformation;

                    if (!_checkList.Sender.Contains("@"))
                    {
                        //ここまでやって見つからなければ送信者のメールアドレスの検索を諦めてそのまま入れる。
                        _checkList.Sender = mail.Sender.PropertyAccessor.GetProperty("https://schemas.microsoft.com/mapi/proptag/0x39FE001E").ToString();
                    }
                }
                _checkList.SenderDomain = _checkList.Sender == Resources.FailedToGetInformation ? "------------------" : _checkList.Sender.Substring(_checkList.Sender.IndexOf("@", StringComparison.Ordinal));
            }
            else
            {
                //代理送信の場合
                _checkList.Sender = mail.Sender.Address ?? Resources.FailedToGetInformation;

                if (_checkList.Sender.Contains("@"))
                {
                    //メールアドレスが取得できる場合はExchangeではないのでそのままでよい。
                    _checkList.Sender = $@"{_checkList.Sender} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                    _checkList.SenderDomain = _checkList.Sender.Substring(_checkList.Sender.IndexOf("@", StringComparison.Ordinal));
                }
                else
                {
                    //代理送信の場合かつExchange利用
                    var tempOutlookApp = new Outlook.Application();
                    var tempRecipient = tempOutlookApp.Session.CreateRecipient(mail.Sender.Address);

                    try
                    {
                        //ユーザの代理送信
                        var exchangeUser = tempRecipient.AddressEntry.GetExchangeUser();
                        _checkList.Sender = $@"{exchangeUser.PrimarySmtpAddress} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                        _checkList.SenderDomain = exchangeUser.PrimarySmtpAddress.Substring(exchangeUser.PrimarySmtpAddress.IndexOf("@", StringComparison.Ordinal));
                    }
                    catch (Exception)
                    {
                        try
                        {
                            //配布リストの代理送信
                            var exchangeDistributionList = tempRecipient.AddressEntry.GetExchangeDistributionList();
                            _checkList.Sender = $@"{exchangeDistributionList.PrimarySmtpAddress} ([{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf})";
                            _checkList.SenderDomain = exchangeDistributionList.PrimarySmtpAddress.Substring(exchangeDistributionList.PrimarySmtpAddress.IndexOf("@", StringComparison.Ordinal));
                        }
                        catch (Exception)
                        {
                            _checkList.Sender = $@"[{mail.SentOnBehalfOfName}] {Resources.SentOnBehalf}";
                            _checkList.SenderDomain = @"------------------";
                        }
                    }
                }
            }
            
            _checkList.Subject = mail.Subject ?? Resources.FailedToGetInformation;
            _checkList.MailType = GetMailBodyFormat(mail.BodyFormat) ?? Resources.FailedToGetInformation;
            _checkList.MailBody = mail.Body ?? Resources.FailedToGetInformation;

            //改行が2行になる問題を回避するため、HTML形式の場合にのみ2行の改行を1行に置換する。
            if (_checkList.MailType == Resources.HTML)
            {
                _checkList.MailBody = _checkList.MailBody.Replace("\r\n\r\n", "\r\n");
            }

            _checkList.MailHtmlBody = mail.HTMLBody ?? Resources.FailedToGetInformation;
        }

        /// <summary>
        /// 送信者ドメインを除く宛先のドメイン数を数える。
        /// </summary>
        /// <returns>送信者ドメインを除く宛先のドメイン数</returns>
        private int CountRecipientExternalDomains()
        {
            var domainList = new HashSet<string>();
            foreach (var mail in _displayNameAndRecipient)
            {
                var recipient = mail.Key;
                if (recipient != Resources.FailedToGetInformation && recipient.Contains("@"))
                {
                    domainList.Add(recipient.Substring(recipient.IndexOf("@", StringComparison.Ordinal)));
                }
            }

            //外部ドメインの数のため、送信者のドメインが含まれていた場合それをマイナスする。
            if (domainList.Contains(_checkList.SenderDomain))
            {
                return domainList.Count - 1;
            }

            return domainList.Count;
        }
        }

        /// <summary>
        /// 送信先の表示名と表示名とメールアドレスを対応させる。(Outlookの仕様上、表示名にメールアドレスが含まれない事がある。)
        /// </summary>
        /// <param name="mail"></param>
        private void MakeDisplayNameAndRecipient(Outlook._MailItem mail)
        {
            //TODO Temporary processing. It will be improved.
            //暫定的にこのメソッドを複数回実行する可能性があるため、実行のたびに以下の3つは初期化する。
            _toDisplayNameAndRecipient.Clear();
            _ccDisplayNameAndRecipient.Clear();
            _bccDisplayNameAndRecipient.Clear();

            foreach (Outlook.Recipient recip in mail.Recipients)
            {
                // Exchangeの連絡先に登録された情報を取得。
                Outlook.ExchangeUser exchangeUser = null;
                try
                {
                    exchangeUser = recip.AddressEntry.GetExchangeUser();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                // Exchangeの配布リスト(ML)として登録された情報を取得。
                Outlook.ExchangeDistributionList exchangeDistributionList = null;
                try
                {
                    exchangeDistributionList = recip.AddressEntry.GetExchangeDistributionList();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                // ローカルの連絡先に登録された情報を取得。
                Outlook.ContactItem registeredUser = null;
                try
                {
                    registeredUser = recip.AddressEntry.GetContact();
                }
                catch (Exception)
                {
                    //Do Nothing.
                }

                //宛先メールアドレスを取得
                string mailAddress = recip.PropertyAccessor.GetProperty("http://schemas.microsoft.com/mapi/proptag/0x39FE001E").ToString() ?? Resources.FailedToGetInformation;

                // 登録されたメールアドレスの場合、登録名のみが表示されるため、メールアドレスと共に表示されるよう表示用テキストを生成。
                var nameAndMailAddress = exchangeUser != null
                    ? exchangeUser.Name + $@" ({mailAddress})"
                    : exchangeDistributionList != null
                        ? exchangeDistributionList.Name + $@" ({mailAddress})"
                        : registeredUser != null
                            ? registeredUser.FullName + $@" ({mailAddress})"
                            : mailAddress ?? Resources.FailedToGetInformation;
                
                //ケースによってメールアドレスのみを正しく取得できない恐れがあるため、その場合は、表示名称をメールアドレスとして登録する。
                if (!mailAddress.Contains("@"))
                {
                    mailAddress = nameAndMailAddress;
                }

                _displayNameAndRecipient[mailAddress] = nameAndMailAddress;

                //TODO Temporary processing. It will be improved.
                //名称を差出人とメールアドレスの紐づけをTo/CC/BCCそれぞれに格納
                switch (recip.Type)
                {
                    case 1:
                        _toDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    case 2:
                        _ccDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    case 3:
                        _bccDisplayNameAndRecipient[mailAddress] = nameAndMailAddress;
                        break;
                    default:
                        break;
                }
            }
        }

        /// <summary>
        /// ファイルの添付忘れを確認。
        /// </summary>
        /// <param name="mail"></param>
        private void CheckForgotAttach(Outlook._MailItem mail)
        {
            if (mail.Attachments.Count >= 1) return;

            var generalSetting = new List<GeneralSetting>();
            var readCsv = new ReadAndWriteCsv("GeneralSetting.csv");
            foreach (var data in readCsv.GetCsvRecords<GeneralSetting>(readCsv.LoadCsv<GeneralSettingMap>()))
            {
                generalSetting.Add((data));
            }

            string attachmentsKeyword;

            if (generalSetting.Count == 0)
            {
                attachmentsKeyword = "添付";
            }
            else
            {
                if (!generalSetting[0].EnableForgottenToAttachAlert) return;

                switch (generalSetting[0].LanguageCode)
                {
                    case "ja-JP":
                        attachmentsKeyword = "添付";
                        break;
                    case "en-US":
                        attachmentsKeyword = "attached file";
                        break;
                    default:
                        return;
                }
            }

            if (_checkList.MailBody.Contains(attachmentsKeyword))
            {
                _checkList.Alerts.Add(new Alert { AlertMessage = Resources.ForgottenToAttachAlert, IsImportant = true, IsWhite = false, IsChecked = false });
            }
        }

        /// <summary>
        /// メールの形式を取得し、表示用の文字列を返す。
        /// </summary>
        /// <param name="bodyFormat"></param>
        /// <returns>メールの形式</returns>
        private string GetMailBodyFormat(Outlook.OlBodyFormat bodyFormat)
        {
            switch (bodyFormat)
            {
                case Outlook.OlBodyFormat.olFormatUnspecified:
                    return Resources.Unknown;
                case Outlook.OlBodyFormat.olFormatPlain:
                    return Resources.Text;
                case Outlook.OlBodyFormat.olFormatHTML:
                    return Resources.HTML;
                case Outlook.OlBodyFormat.olFormatRichText:
                    return Resources.RichText;
                default:
                    return Resources.Unknown;
            }
        }

        /// <summary>
        /// 本文に登録したキーワードがある場合、登録した警告文を表示する。
        /// </summary>
{'fraction_non_alphanumeric': 0.09131368338863947, 'fraction_numerical': 0.0018739020105406987, 'mean_word_length': 1.4944980037004576, 'pattern_counts': {'":': 4, '<': 87, '<?xml version=': 0, '>': 89, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3817641', 'n_tokens_mistral': 7449, 'n_tokens_neox': 6834, 'n_words': 923}