using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Types;
using SAP2000v1;
using System;
using System.Collections.Generic;
using System.Runtime.Versioning;

namespace FeMM.Grasshopper.Components.SAPExtra
{
#if NETCOREAPP
    [SupportedOSPlatform("windows")]
#endif
    public class SAPSetFrameExternalForceComponent : GH_Component
    {
        private bool _run = false;

        public SAPSetFrameExternalForceComponent()
          : base(
              "SAP2000 Set Frame External Forces",
              "SFF",
              "Apply external forces to SAP2000 frame elements. Click the button to run.",
              CategoryNameConstants.CATEGORY_CHECKS,
              CategoryNameConstants.SUBCATEGORY_SAPEXTRA)
        {
            _run = false;
        }

        public override void CreateAttributes()
        {
            // Create button attributes with button in the component header
            var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Run");
            attr.ButtonPressed += () =>
            {
                // Flash the _run value and expire solution
                _run = true;
                ExpireSolution(true);
            };
            m_attributes = attr;
        }

        protected override void RegisterInputParams(GH_InputParamManager p)
        {
            p.AddBooleanParameter("Attach to Instance", "AI", "Attach to running SAP2000 instance", GH_ParamAccess.item, true);
            p[0].Optional = true;

            p.AddBooleanParameter("Specify Path", "SP", "Launch SAP2000 from specified path", GH_ParamAccess.item, false);
            p[1].Optional = true;

            p.AddTextParameter("Program Path", "PP", "Full path to SAP2000.exe", GH_ParamAccess.item, "");
            p[2].Optional = true;

            p.AddTextParameter("Model Path", "MP", "SAP2000 model file path", GH_ParamAccess.item, "");
            p[3].Optional = true;

            p.AddTextParameter("Cases", "LC", "Tree branches have to match the frames structure, for every frame all the load cases have to be included in the tree branch", GH_ParamAccess.tree);
            p[4].Optional = true;

            p.AddTextParameter("Frames", "F", "Tree with each frame in its own branch", GH_ParamAccess.tree);
            p[5].Optional = true;

            p.AddIntegerParameter("Step Number", "SN", "Tree branches have to match the frames structure, one step number per load case", GH_ParamAccess.tree);
            p[6].Optional = true;

            p.AddNumberParameter("Stations", "S", "Tree branches have to match the frames structure", GH_ParamAccess.tree);
            p[7].Optional = true;

            p.AddNumberParameter("Forces", "Frc", "Tree branches have to match the frames structure, one sublist for every station", GH_ParamAccess.tree);
            p[8].Optional = true;
        }

        protected override void RegisterOutputParams(GH_OutputParamManager p)
        {
            p.AddTextParameter("Log", "L", "Operation log", GH_ParamAccess.item);
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            if (!_run)
            {
                DA.SetData(0, "Click 'Run' button to execute");
                return;
            }

            bool attach = true;
            bool specifyPath = false;
            string programPath = "";
            string modelPath = "";

            var casesTree = new GH_Structure<GH_String>();
            var framesTree = new GH_Structure<GH_String>();
            var stepTree = new GH_Structure<GH_Integer>();
            var stationsTree = new GH_Structure<GH_Number>();
            var forcesTree = new GH_Structure<GH_Number>();

            DA.GetData(0, ref attach);
            DA.GetData(1, ref specifyPath);
            DA.GetData(2, ref programPath);
            DA.GetData(3, ref modelPath);

            DA.GetDataTree(4, out casesTree);
            DA.GetDataTree(5, out framesTree);
            DA.GetDataTree(6, out stepTree);
            DA.GetDataTree(7, out stationsTree);
            DA.GetDataTree(8, out forcesTree);

            List<string> log = [];

            try
            {
                // Validate input data
                if (framesTree.IsEmpty)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "No frames provided");
                    DA.SetData(0, "No frames provided");
                    _run = false;
                    return;
                }

                // ---------------- SAP START ----------------
                cHelper helper = new Helper();
                cOAPI sapObj = null;

                try
                {
                    if (attach)
                    {
                        sapObj = helper.GetObject("CSI.SAP2000.API.SapObject");
                    }
                    else if (specifyPath && !string.IsNullOrEmpty(programPath))
                    {
                        sapObj = helper.CreateObject(programPath);
                        sapObj.ApplicationStart();
                    }
                    else
                    {
                        sapObj = helper.CreateObjectProgID("CSI.SAP2000.API.SapObject");
                        sapObj.ApplicationStart();
                    }

                    if (sapObj == null)
                        throw new Exception("Failed to initialize SAP2000 object");

                    cSapModel sap = sapObj.SapModel ?? throw new Exception("Failed to get SAP model");

                    if (!attach && !string.IsNullOrEmpty(modelPath))
                    {
                        if (sap.File.OpenFile(modelPath) != 0)
                            throw new Exception($"Failed to open SAP2000 model at: {modelPath}");
                    }

                    // ---------------- PROCESS ----------------
                    int totalFramesProcessed = 0;
                    int totalCasesApplied = 0;

                    foreach (GH_Path path in framesTree.Paths)
                    {
                        try
                        {
                            // Get frame name
                            if (framesTree[path].Count == 0)
                                continue;

                            string frame = framesTree[path][0].Value;

                            if (string.IsNullOrWhiteSpace(frame))
                            {
                                log.Add("Frame name is empty");
                                continue;
                            }

                            totalFramesProcessed++;

                            // Validate all required data exists for this frame
                            if (!casesTree.PathExists(path))
                            {
                                log.Add($"Missing load cases for frame: {frame}");
                                continue;
                            }

                            if (!stationsTree.PathExists(path))
                            {
                                log.Add($"Missing stations for frame: {frame}");
                                continue;
                            }

                            if (!stepTree.PathExists(path) || stepTree[path].Count == 0)
                            {
                                log.Add($"Missing step number for frame: {frame}");
                                continue;
                            }

                            // Extract data
                            string[] cases = [.. casesTree[path].ConvertAll(x => x.Value)];
                            double[] stations = [.. stationsTree[path].ConvertAll(x => x.Value)];
                            int step = stepTree[path][0].Value;

                            if (cases.Length == 0 || stations.Length == 0)
                            {
                                log.Add($"Empty cases or stations for frame: {frame}");
                                continue;
                            }

                            // Setup SAP for this frame
                            sap.Results.Setup.DeselectAllCasesAndCombosForOutput();
                            foreach (var c in cases)
                            {
                                if (!string.IsNullOrWhiteSpace(c))
                                    sap.LoadCases.ExternalResults.SetCase(c);
                            }

                            sap.ExternalAnalysisResults.PresetFrameCases(ref frame, cases.Length, ref cases);
                            sap.ExternalAnalysisResults.SetFrameStations(ref frame, ref stations);

                            // Process forces for each case
                            int casesApplied = 0;
                            for (int c = 0; c < cases.Length; c++)
                            {
                                var forcePath = path.AppendElement(c);
                                if (!forcesTree.PathExists(forcePath) || forcesTree[forcePath].Count == 0)
                                {
                                    log.Add($"Missing forces for frame: {frame}, case: {cases[c]}");
                                    continue;
                                }

                                var f = forcesTree[forcePath];
                                int n = stations.Length;

                                // Validate force data
                                if (f.Count < n * 6)
                                {
                                    log.Add($"Insufficient force data for frame: {frame}, case: {cases[c]} (expected {n * 6} values, got {f.Count})");
                                    continue;
                                }

                                double[] P = new double[n];
                                double[] V2 = new double[n];
                                double[] V3 = new double[n];
                                double[] T = new double[n];
                                double[] M2 = new double[n];
                                double[] M3 = new double[n];

                                for (int i = 0; i < n; i++)
                                {
                                    int idx = i * 6;
                                    P[i] = f[idx + 0].Value;
                                    V2[i] = f[idx + 1].Value;
                                    V3[i] = f[idx + 2].Value;
                                    T[i] = f[idx + 3].Value;
                                    M2[i] = f[idx + 4].Value;
                                    M3[i] = f[idx + 5].Value;
                                }

                                int ret = sap.ExternalAnalysisResults.SetFrameForce(
                                    frame, cases[c], step,
                                    ref P, ref V2, ref V3, ref T, ref M2, ref M3
                                );

                                if (ret == 0)
                                {
                                    casesApplied++;
                                    totalCasesApplied++;
                                }
                                else
                                    log.Add($"Failed to apply forces for frame: {frame}, case: {cases[c]}");
                            }

                            if (casesApplied > 0)
                                log.Add($"✓ Frame '{frame}': {casesApplied} load case(s) applied");
                        }
                        catch (Exception ex)
                        {
                            log.Add($"✗ Frame error: {ex.Message}");
                        }
                    }

                    // Summary
                    log.Insert(0, $"=== OPERATION COMPLETE ===");
                    log.Insert(1, $"Frames processed: {totalFramesProcessed}");
                    log.Insert(2, $"Load cases applied: {totalCasesApplied}");
                    log.Insert(3, "");
                }
                finally
                {
                    // Note: Do not close SAP2000 here unless explicitly required
                    // as it may be a running instance
                }
            }
            catch (Exception ex)
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"Component Error: {ex.Message}");
                log.Insert(0, $"FATAL ERROR: {ex.Message}");
            }

            DA.SetData(0, string.Join("\n", log));
            _run = false;  // Reset after execution
        }

        protected override System.Drawing.Bitmap Icon => Properties.Resources.SAPBeamLoaderIcon;

        public override Guid ComponentGuid =>
            new("f4c1d3a5-1f8b-4c9b-9db6-0e3c0e8bbf41");
    }
}
303 files24 directories