#region Namespaces
using System;
using System.Collections.Generic;
using System.Diagnostics;
using Autodesk.Revit.ApplicationServices;
using Autodesk.Revit.Attributes;
using Autodesk.Revit.DB;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;

#endregion

namespace RevitAddinCreateRoof
{
    [Transaction(TransactionMode.Manual)]
    public class Command : IExternalCommand
    {
        public Result Execute(
          ExternalCommandData commandData,
          ref string message,
          ElementSet elements)
        {
            UIApplication uiapp = commandData.Application;
            UIDocument uidoc = uiapp.ActiveUIDocument;
            Application app = uiapp.Application;
            Document doc = uidoc.Document;

            //*******条件**********
            /* 本アドインを実行する前に屋根の輪郭のベースになる一連の壁が必要です
             * 屋階レベルの名称は以下の文字列にする必要があります。違う場合にはモデルと次の名称を一致させる変更が必要
             * アドインの使い方は、まず壁を選んで、その後にアドインを実行する手順です。
             * 屋根タイプは最初に見つかったものを使用するようになっています。
             */
            // 屋階の名称
            string rfLevelName = "Roof";
            bool isSlope = true; // true:勾配あり、false:勾配なし
            double slopeAngle = 0.5; // 勾配の角度（1/1=45度、1/2=26.57度、1/3=18.43度、1/4=14.04度、1/5=11.31度）

            // RFレベルを検索する
            FilteredElementCollector collector = new FilteredElementCollector(doc).OfClass(typeof(Level));
            var roofLevel = from element in collector where element.Name == rfLevelName select element;
            Level level = roofLevel.Cast<Level>().ElementAt<Level>(0);

            //RFレベルを検索する（別の方法）
            //Level level = new FilteredElementCollector(doc).OfClass(typeof(Level)).FirstOrDefault(q => q.Name == rfLevelName) as Level;

            if (level == null)
            {
                TaskDialog.Show("エラー", $"屋階の名称{rfLevelName}が見つかりません。確認してください。");
                return Result.Failed;
            }

            // 屋根タイプは最初に見つかったものを使用する
            RoofType roofType = new FilteredElementCollector(doc).OfClass(typeof(RoofType)).FirstOrDefault() as RoofType;

            //屋根タイプを指定する場合には、以下のコメントアウトを外して使用する。
            //ただし、モデル内に指定した名称の屋根タイプが存在する必要があります。
            //その代わり上の3行はコメントアウトする。
            //屋根タイプ名の指定
            //string roofTypeName = "コンクリート-200mm";
            //屋根タイプを検索する
            //RoofType roofType = new FilteredElementCollector(doc).OfClass(typeof(RoofType)).Where(typ => typ.Name == roofTypeName).First() as RoofType;

            if (roofType == null)
            {
                TaskDialog.Show("エラー", "屋根のタイプがありません。確認してください。");
                return Result.Failed;
            }

            // ユーザーが選んだ壁にそって屋根の輪郭ラインのCurveArrayを用意
            CurveArray footprint = app.Create.NewCurveArray();

            // 選択された要素を取得する
            ICollection<ElementId> selectedIds = uidoc.Selection.GetElementIds();
            if (selectedIds.Count != 0)
            {
                foreach (ElementId id in selectedIds)
                {
                    // 選択された要素が壁かモデル線かを確認する
                    Element element = doc.GetElement(id);
                    Wall wall = element as Wall;
                    if (wall != null)
                    {
                        // 壁である場合にはその位置（LocationCurve）を取得する
                        LocationCurve wallCurve = wall.Location as LocationCurve;
                        // その曲線を屋根の輪郭に追加する
                        if (wallCurve != null) footprint.Append(wallCurve.Curve);
                        continue;
                    }
                    // 選択された要素がモデル線である場合
                    ModelCurve modelCurve = element as ModelCurve;
                    if (modelCurve != null)
                    {
                        // その曲線を屋根の輪郭に追加する
                        footprint.Append(modelCurve.GeometryCurve);
                    }
                }
            }
            else
            {
                TaskDialog.Show("エラー", "壁など屋根の輪郭を指定するものが選択されていません。曲線ループ、壁ループ、または壁と曲線の組み合わせループを選択する必要があります。");
                return Result.Failed;
            }
            // 屋根の輪郭線（NewFootPrintRoofメソッドの実行後のモデルカーブ出力）
            ModelCurveArray footPrintToModelCurveMapping = new ModelCurveArray();
            try
            {
                //屋根を作成する
                using (Transaction tx = new Transaction(doc))
                {
                    tx.Start("Create Roof");
                    // 屋根を配置する
                    FootPrintRoof footprintRoof = doc.Create.NewFootPrintRoof(footprint, level, roofType, out footPrintToModelCurveMapping);
                    if (footprintRoof == null)
                    {
                        TaskDialog.Show("エラー", "屋根の作成のところで失敗しました。");
                        return Result.Failed;
                    }
                    // 屋根勾配を設定する
                   
                    if (isSlope)
                    {
                        // 屋根の輪郭線モデルカーブが正しくセットされているか確認する
                        if (footPrintToModelCurveMapping.Size != 0)
                        {
                            // 問題なければ屋根の輪郭線をイタレータで取得する
                            ModelCurveArrayIterator iterator = footPrintToModelCurveMapping.ForwardIterator();
                            iterator.Reset();
                            while (iterator.MoveNext())
                            {
                                // 屋根の各輪郭線（線分）に勾配を設定する
                                ModelCurve modelCurve = iterator.Current as ModelCurve;
                                // 勾配あり、なし設定
                                footprintRoof.set_DefinesSlope(modelCurve, isSlope);
                                // 勾配の角度設定
                                footprintRoof.set_SlopeAngle(modelCurve, slopeAngle);
                            }
                        }
                        else
                        {
                            TaskDialog.Show("エラー", "屋根の輪郭モデルカーブが取得できないため勾配が設定できません。");
                            return Result.Failed;
                        }
                    }
                    
                    tx.Commit();
                    return Result.Succeeded;
                }
            }
            catch (Exception ex)
            {
                // 例外が発生した場合のエラーメッセージ
                TaskDialog.Show("エラー", $"屋根が作成できません。エラーメッセージ：{ex.Message}");
                return Result.Failed;
            }
            return Result.Succeeded;
        }
    }
}
