#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.DB.Structure;
using Autodesk.Revit.UI;
using Autodesk.Revit.UI.Selection;

#endregion

namespace RevitAddinInstallWindow
{
    [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 windowTypeName = "w1600h0800";
            //壁を選択してもらう
            Reference wallRef = uidoc.Selection.PickObject(ObjectType.Element, "窓を設置する壁を選択してください");
            Wall wall = doc.GetElement(wallRef) as Wall;

            try
            {
                // 現在開いている平面ビューのレベルを取得する
                Level level = null;
                View activeView = uidoc.ActiveView;
                //平面ビューか？
                if (activeView is ViewPlan viewPlan)
                {
                    // 平面ビューのレベルを取得
                    level = doc.GetElement(viewPlan.GenLevel.Id) as Level;
                }
                else
                {
                    TaskDialog.Show("エラー", "現在のビューは平面ビューではありません。");
                    return Result.Failed;
                }
                //窓の設置位置を計算する
                // 窓の配置位置（例: 壁の中心座標を取得し、zにlevel.Elevationを設定）
                LocationCurve wallCurve = wall.Location as LocationCurve;
                XYZ wallMidPoint = null;
                XYZ pos = null;
                if (wallCurve != null)
                {
                    wallMidPoint = (wallCurve.Curve.GetEndPoint(0) + wallCurve.Curve.GetEndPoint(1)) / 2;
                    //高さはレベルの高さに設定
                    double z = level.Elevation;                  
                    pos = new XYZ(wallMidPoint.X, wallMidPoint.Y, z);
                }
                else
                {
                    TaskDialog.Show("エラー", "壁のカーブが取得できません");
                }
                //プロジェクトに登録されている上記に対応したタイプ（シンボル）を検索して取得する
                FamilySymbol windowType = new FilteredElementCollector(doc).OfClass(typeof(FamilySymbol))
                    .OfCategory(BuiltInCategory.OST_Windows).FirstOrDefault(q => q.Name == windowTypeName) as FamilySymbol;

                //窓タイプパラメータの「既定の腰高」を取得して位置の変数posのZ座標（高さ成分）に加算する
                double koshidaka = 0;
                if (double.TryParse(GetParameter(windowType, "既定の腰高"), out double v)) koshidaka = v;
                pos = pos.Add(new XYZ(0,0, koshidaka));

                //窓を配置する
                if (windowType != null && level != null && wall != null)
                {
                    using (Transaction tx = new Transaction(doc))
                    {
                        tx.Start("Install Window");
                        //窓タイプが非アクティブならアクティブにする
                        if (!windowType.IsActive) windowType.Activate();
                        //配置する
                        FamilyInstance window = doc.Create.NewFamilyInstance(pos, windowType, wall, level, StructuralType.NonStructural);
                        tx.Commit();
                    }
                }
                else
                {
                    TaskDialog.Show("エラー", "窓タイプ、レベル、壁のいずれかがnullです");
                }
            }
            catch (Exception ex)
            {
                // 例外が発生した場合のエラーメッセージ
                TaskDialog.Show("エラー", $"窓の配置に失敗しました。エラーメッセージ：{ex.Message}");
                return Result.Failed;
            }
            return Result.Succeeded;
        }
        //１つの要素の指定パラメータを取得する関数
        public string GetParameter(Element e, string pName)
        {
            //パラメータの入れ物を作る
            IList<Parameter> parameters = new List<Parameter>();
            //全パラメータを取得する（Revitのプロパティウィンドウに表示されるもの）
            parameters = e.GetOrderedParameters();
            foreach (Parameter param in parameters)
            {
                //検索しているパラメータ名と一致するか
                string name = param.Definition.Name;
                if (name.Equals(pName))
                {
                    //一致したらそれを返す
                    return ParameterToString(param);
                }
            }
            return null;
        }
        //パラメータをstringに変換する関数
        public static string ParameterToString(Parameter param)
        {
            string val = null;
            if (param == null)
            {
                return val;
            }
            switch (param.StorageType)
            {
                case StorageType.Double:
                    double dVal = param.AsDouble();
                    val = dVal.ToString();
                    break;
                case StorageType.Integer:
                    int iVal = param.AsInteger();
                    val = iVal.ToString();
                    break;
                case StorageType.String:
                    string sVal = param.AsString();
                    val = sVal;
                    break;
                case StorageType.ElementId:
                    ElementId idVal = param.AsElementId();
                    val = idVal.ToString();
                    break;
                case StorageType.None:
                    break;
                default:
                    break;
            }
            return val;
        }
    }
}
