/// <summary>
        /// 再帰検索処理.
        /// </summary>
        /// <param name="resultData">結果を格納</param>
        /// <param name="uppserSet">上位検索セット</param>
        /// <param name="remainingPartList">残未確定部位リスト</param>
        /// <param name="abstractArmorMode">仮想防具モードかどうか</param>
        /// <param name="upperReport">上位検索状況情報</param>
        /// <param name="nest">再帰階層</param>
        /// <param name="pBegin">この処理の開始時点の進捗</param>
        /// <param name="pEnd">この処理の終了時点の進捗</param>
        /// <returns>この処理で確定したセットの数(マイナスの場合は処理中断)</returns>
        private int RecursionSearch(ResultData resultData, SearchSet uppserSet, SearchReport upperReport, List <PartDataBase> remainingPartList, bool abstractArmorMode, int nest, int pBegin, int pEnd)
        {
            // 呼び出され回数をインクリメント
            this.TotalCalled++;

            // 階層をインクリメント.
            var curNest = nest + 1;

            // キャンセルされたか調べる
            if (this.IsCancel())
            {
                // キャンセルされたとき
                return(-1);
            }

            // ネストで見つかった確定セット数を格納する.
            var determined = 0;

            // この処理での進捗幅.
            var partUnit = pEnd - pBegin;

            // このネストで検索する部位.
            var part = remainingPartList.ElementAt(0);

            // 残りの部位.
            var unsettledPartList = remainingPartList.ToList();

            unsettledPartList.RemoveAt(0);

            // 候補装備リスト管理データを作成.
            var candidateListCtrl = abstractArmorMode
                ? this.CandidateSet.GetArmorAbstract(part.Part, upperReport)
                : this.CandidateSet.GetCandidate(part.Part, upperReport);

            // 候補装備数.
            var allCount = candidateListCtrl.GetCount();

            // 検索セットを複製.
            var searchSet = new SearchSet(uppserSet);

            // 装備毎に走査.
            // 進捗管理の為に i が必要.
            for (var i = 0; i < allCount; i++)
            {
                var candidateData = candidateListCtrl.GetAt(i);

                // 無効装備は無視.
                if (null == candidateData || !candidateData.Enable)
                {
                    continue;
                }

                // 候補装備.
                var candidate = candidateData.PartData;

                // 進捗の最小単位を計算.
                var begin = pBegin;
                var end   = pBegin;
                if (0 < partUnit)
                {
                    // 候補装備毎の進捗単位を計算.
                    // 要素数を取得.
                    var candidateUnit = partUnit / allCount;
                    if (0 < candidateUnit)
                    {
                        begin = pBegin + (i * candidateUnit);
                        end   = begin + candidateUnit;
                    }
                }

                // 新たな候補をセットして.
                searchSet.SetPart(part.Part, candidate);

                // 検索数上限に到達しているかどうかをチェック.
                if (0 < Ssm.Config.SerchLimitCount &&
                    Ssm.Config.SerchLimitCount <= resultData.GetSatisfiedCount())
                {
                    Log.Write("検索結果上限[{0}件]に到達したので処理中断".Fmt(Ssm.Config.SerchLimitCount));
                    break;
                }

                // 検索状況リポートを生成.
                var searchReport = this.CreateSearchReport(searchSet);

                // 現状のセットが検索条件を満たすかどうかチェック.
                // スキルが要求を満たしている場合.
                if (!searchReport.UnsatisfyList.Any())
                {
                    ////////////////
                    // スキル満足 //
                    ////////////////

                    // スロットが必要数を満たしている場合.
                    if (!searchReport.LackSlotList.Any())
                    {
                        //////////
                        // 完成 //
                        //////////
                        resultData.AddSatisfiedSet(new ResultSet(searchSet));
                        determined += 1;

                        // 上位互換装備を候補リストから除く.
                        candidateListCtrl.RemoveUpwardCompatibility(candidate, searchReport);
                        continue;
                    }

                    // スロットが不足している場合、下記条件のいずれかにヒットしたら検索終了
                    // ・未確定防具が残っていない場合.
                    // ・抽象化防具を使用しない場合.
                    var abstractPartList = searchSet.GetUnsettledArmorList();
                    if (!abstractPartList.Any() || !Ssm.Config.UseArmorAbstract)
                    {
                        //////////////////////////
                        // スロット不足 → 終了 //
                        //////////////////////////

                        // 解析を実施する場合は解析結果に追加.
                        if (this.AnalyzeFactors)
                        {
                            // スロット不足で不十分な組み合わせ.
                            resultData.AddShortageSlot(searchReport.GetLackSlotKey(), new ResultSet(searchSet));
                        }

                        // これ以上の検索は不要.
                        continue;
                    }

                    //////////////////////////////////////////
                    // スロット不足 → 抽象化防具検索モード //
                    //////////////////////////////////////////
                    // >>>>再帰呼び出し<<<< //
                    // 検索処理を再帰呼び出し.
                    var result = this.RecursionSearch(resultData, searchSet, searchReport, abstractPartList, true, curNest, begin, end);
                    // >>>>再帰呼び出し<<<<

                    // 条件を満たす組み合わせが見つかった場合.
                    if (0 < result)
                    {
                        // 結果に登録.
                        determined += result;
                    }
                    else if (result < 0)
                    {
                        // 処理中断の場合.
                        return(result);
                    }
                }
                else
                {
                    ////////////////
                    // スキル不足 //
                    ////////////////

                    // 残りの部位が残っていない場合はスキル不足で不十分な組み合わせ.
                    if (!unsettledPartList.Any())
                    {
                        // 下記の条件を全て満たしている場合は解析結果に登録.
                        // ・不足しているスキルが一つ
                        // ・スロットは満たしている
                        // ・解析を実施する場合.
                        if (this.AnalyzeFactors &&
                            !searchReport.LackSlotList.Any() &&
                            1 == searchReport.UnsatisfyList.Count)
                        {
                            // スキル不足で不十分な組み合わせ.
                            resultData.AddShortageSkill(searchReport.GetUnsatisfyKey(), new ResultSet(searchSet));
                        }

                        // これ以上の検索は不要.
                        continue;
                    }

                    //////////////////////////////////
                    // スキル不足 → 通常検索モード //
                    //////////////////////////////////
                    // >>>>再帰呼び出し<<<<
                    // 検索処理を再帰呼び出し.
                    var result = this.RecursionSearch(resultData, searchSet, searchReport, unsettledPartList, false, curNest, begin, end);
                    // >>>>再帰呼び出し<<<<

                    // 条件を満たす組み合わせが見つかった場合.
                    if (0 < result)
                    {
                        // 結果に登録.
                        determined += result;
                    }
                    else if (result < 0)
                    {
                        // 処理中断の場合.
                        return(result);
                    }
                }
            }

            // 進捗を更新.
            if (0 < partUnit)
            {
                this.SetProgress((int)pEnd);
            }

            return(determined);
        }
        /// <summary>
        /// 再帰検索処理.
        /// </summary>
        /// <param name="resultData">結果を格納</param>
        /// <param name="searchSet">現在までの検索状態</param>
        /// <param name="lastFixedPart">最終確定部位</param>
        /// <param name="nest">再帰階層</param>
        /// <param name="pBegin">この処理の開始時点の進捗</param>
        /// <param name="pEnd">この処理の終了時点の進捗</param>
        /// <returns>この処理で確定したセットの数(マイナスの場合は処理中断)</returns>
        private int RecursionSearch(ResultData resultData, SearchSet searchSet, Part lastFixedPart, int nest, int pBegin, int pEnd)
        {
            // 呼び出され回数をインクリメント
            this.TotalCalled++;

            // 階層をインクリメント.
            var curNest = nest + 1;

            // キャンセルされたか調べる
            if (this.IsCancel())
            {
                // キャンセルされたとき
                return(-1);
            }

            // この組み合わせで見つかった確定セット数.
            var determined = 0;

            // 進捗幅.
            var progressUnit = pEnd - pBegin;

            // 重複チェック.
            if (Ssm.Config.EnableDuplicateCheck && this.CheckDuplicate(searchSet))
            {
                // 既に検索済みのパターンの場合はこれ以上の検索不要.
                this.OverlapCount++;
                return(0);
            }

            // 検索数上限に到達しているかどうかをチェック.
            if (0 < Ssm.Config.SerchLimitCount && Ssm.Config.SerchLimitCount <= resultData.GetSatisfiedCount())
            {
                Log.Write("検索結果上限[{0}]件に到達したので中断".Fmt(Ssm.Config.SerchLimitCount));
                return(-1);
            }

            // 未確定部位リストを格納
            // ・通常再帰処理の場合は未確定部位・・防具か護石
            // ・スロットのみ不足している場合は抽象化防具を割り当て可能な未確定防具部位のリストを格納.
            var abstractArmorMode = false;
            List <PartDataBase> unsettledPartList;

            // 検索状況リポートを生成..
            var searchReport = this.CreateSearchReport(searchSet);

            // 現状のセットが検索条件を満たすかどうかチェック.
            // スキルが要求を満たしているかどうかチェック.
            if (!searchReport.UnsatisfyList.Any())
            {
                // スロットが必要数を満たしているかチェック.
                if (!searchReport.LackSlotList.Any())
                {
                    // スロットも満たしていれば完成.
                    resultData.AddSatisfiedSet(new ResultSet(searchSet));
                    return(1);
                }

                // スロットが不足している場合、下記条件のいずれかにヒットしたら検索終了
                // ・未確定防具が残っていない場合.
                // ・抽象化防具を使用しない場合.
                unsettledPartList = searchSet.GetUnsettledArmorList();
                if (!unsettledPartList.Any() || !Ssm.Config.UseArmorAbstract)
                {
                    // 解析を実施する場合.
                    if (this.AnalyzeFactors)
                    {
                        // スロット不足で不十分な組み合わせ.
                        resultData.AddShortageSlot(searchReport.GetLackSlotKey(), new ResultSet(searchSet));
                    }

                    // これ以上の検索は不要.
                    return(0);
                }

                // 未確定防具が残っている場合.
                // 抽象化防具検索モードへ移行.
                abstractArmorMode = true;
            }
            else
            {
                // スキルが不足している場合は未確定部位リストを取得.
                unsettledPartList = this.GetUnsettledPartList(searchSet, lastFixedPart);

                // 未確定防具が残っていない場合はスキル不足で不十分な組み合わせ.
                if (!unsettledPartList.Any())
                {
                    // 下記の条件を全て満たしている場合.
                    // ・不足しているスキルが一つ
                    // ・スロットは満たしている
                    // ・解析を実施する場合.
                    if (this.AnalyzeFactors &&
                        !searchReport.LackSlotList.Any() &&
                        1 == searchReport.UnsatisfyList.Count)
                    {
                        // スキル不足で不十分な組み合わせ.
                        resultData.AddShortageSkill(searchReport.GetUnsatisfyKey(), new ResultSet(searchSet));
                    }

                    // これ以上の検索は不要.
                    return(0);
                }
            }

            // 部位毎の進捗単位を計算.
            var partUnit = 0;

            if (0 < progressUnit && 0 < unsettledPartList.Count)
            {
                partUnit = progressUnit / unsettledPartList.Count;
            }

            // 未確定部位毎に走査.
            // 進捗計算のために i が必要
            for (var i = 0; i < unsettledPartList.Count; i++)
            {
                // 未確定部位.
                var unsettledPart = unsettledPartList[i];

                // 未確定部位の候補装備のリスト管理データを格納.
                var candidatePart = abstractArmorMode
                    ? this.CandidateSet.GetArmorAbstract(unsettledPart.Part, searchReport)
                    : this.CandidateSet.GetCandidate(unsettledPart.Part, searchReport);

                // 候補装備数.
                var allCount = candidatePart.GetCount();

                // 最後の未確定部位でかつ、候補装備が存在しない場合.
                if ((i + 1) == unsettledPartList.Count && 0 == allCount)
                {
                    // 進捗の最小単位を計算.
                    var begin = pBegin;
                    var end   = pBegin + (i * partUnit);

                    // 部位を取得.
                    var part = searchSet.GetPart(unsettledPart.Part);

                    // 候補なしにセット.
                    part.State = PartState.NotExist;

                    // >>>>再帰呼び出し<<<<
                    // 検索処理を再帰呼び出し.
                    var result = this.RecursionSearch(resultData, searchSet, unsettledPart.Part, curNest, begin, end);
                    // >>>>再帰呼び出し<<<<

                    // 未確定に戻して処理継続.
                    part.State = PartState.Unsettled;

                    // 条件を満たす組み合わせが見つかった場合.
                    if (0 < result)
                    {
                        // 結果に登録.
                        determined += result;
                    }
                    else if (result < 0)
                    {
                        // 処理中断の場合.
                        return(-1);
                    }

                    continue;
                }

                // 装備毎に走査.
                // 進捗管理の為に j が必要.
                for (var j = 0; j < allCount; j++)
                {
                    var candidateData = candidatePart.GetAt(j);

                    // 無効装備は無視.
                    if (null == candidateData || !candidateData.Enable)
                    {
                        continue;
                    }

                    // 候補装備.
                    var candidate = candidateData.PartData;

                    // 進捗の最小単位を計算.
                    var begin = pBegin;
                    var end   = pBegin;
                    if (0 < partUnit)
                    {
                        // 候補装備毎の進捗単位を計算.
                        // 要素数を取得.
                        var candidateUnit = partUnit / allCount;
                        if (0 < candidateUnit)
                        {
                            begin = pBegin + (i * partUnit) + (j * candidateUnit);
                            end   = begin + candidateUnit;
                        }
                    }

                    // 新たな候補をセットして.
                    searchSet.SetPart(unsettledPart.Part, candidate);

                    // >>>>再帰呼び出し<<<<
                    // 検索処理を再帰呼び出し.
                    var result = this.RecursionSearch(resultData, searchSet, unsettledPart.Part, curNest, begin, end);
                    // >>>>再帰呼び出し<<<<

                    // セットした候補を外す.
                    searchSet.RemovePart(unsettledPart.Part);

                    // 条件を満たす組み合わせが見つかった場合.
                    if (0 < result)
                    {
                        // 結果に登録.
                        determined += result;

                        // 上位互換装備を候補リストから除く.
                        candidatePart.RemoveUpwardCompatibility(candidate, searchReport);
                    }
                    else if (result < 0)
                    {
                        // 処理中断の場合.
                        return(-1);
                    }
                }
            }

            // 進捗を更新.
            if (1 <= (pEnd - pBegin))
            {
                this.SetProgress((int)pEnd);
            }

            return(determined);
        }