using FeMM.Common.Models;
using FeMM.Grasshopper.DataTypes.FeMM;
using FeMM.Grasshopper.Helpers;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Data;
using Grasshopper.Kernel.Special;
using Grasshopper.Kernel.Types;
using MeChecksApi;
using Rhino.Geometry;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.Versioning;

namespace FeMM.Grasshopper.Components.MeCheck
{
#if NETCOREAPP
    [SupportedOSPlatform("windows")]
#endif
    public class SuperElementCheckComponent : GH_Component
    {
        protected bool _run;
        protected List<string> _casesNames;

        public List<string> CasesNames => _casesNames;

        public bool IsRunning { get; set; }

        /// <summary>
        /// Initializes a new instance of the ChecksComponent class.
        /// </summary>
        public SuperElementCheckComponent()
          : base("Super Element Check", "SEC", "Perform the check of a Super-Element through the external MeCheck software",
               CategoryNameConstants.CATEGORY_CHECKS, CategoryNameConstants.SUBCATEGORY_MECHECK)
        {
            _run = false;
            _casesNames = [];
        }

        public override void CreateAttributes()
        {
            var attr = new ComponentAttributes.ComponentOneButtonAttributes(this, "Run");
            attr.ButtonPressed += () =>
            {
                _run = true;
                ExpireSolution(true);
            };
            m_attributes = attr;
        }

        /// <summary>
        /// Registers all the input parameters for this component.
        /// </summary>
        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddGenericParameter("Models", "M", "The FEM models (more than one for stages)", GH_ParamAccess.list);
            pManager.AddTextParameter("Super-Element", "SE", "The name of the Super-Element to filter", GH_ParamAccess.item);
            pManager[1].Optional = true;
            pManager.AddGenericParameter("Load Combinations", "LCB", "The load cases combinations", GH_ParamAccess.tree);
            pManager[2].Optional = true;
            pManager.AddTextParameter("Output Path", "O", "The full path of the file MeCheck to create", GH_ParamAccess.item);
        }

        /// <summary>
        /// Registers all the output parameters for this component.
        /// </summary>
        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddGenericParameter("Model", "M", "The FEM model", GH_ParamAccess.item);
        }

        /// <summary>
        /// This is the method that actually does the work.
        /// </summary>
        /// <param name="DA">The DA object is used to retrieve from inputs and store in outputs.</param>
        protected override void SolveInstance(IGH_DataAccess DA)
        {
            Message = "";
            var models = new List<GH_Model>();
            string super_element = "";
            string outputPath = "";
            if (!DA.GetDataList("Models", models))
                return;
            if (!DA.GetData("Output Path", ref outputPath))
                return;

            if (Path.GetExtension(outputPath).ToLower() != ".mec")
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Invalid extension in file name");
                return;
            }

            // Retrieve all te super elements used in the model
            Common.Helpers.CommonModelHelper.GetSuperElements(models[0].Value, out List<SuperElementAttributeModel> superElements);

            // Connect the ValueList component on the analysis results input item
            IEnumerable<IGH_Param> keys_analysisResultLists = Params.Input[1].Sources.
                Where(s => s.GetType() == typeof(GH_ValueList));
            foreach (GH_ValueList vallist in keys_analysisResultLists)
            {
                if (vallist.ListMode != GH_ValueListMode.DropDown)
                    vallist.ListMode = GH_ValueListMode.DropDown;

                List<string> names = [.. superElements.Select(se => se.Value).Distinct()];
                ComponentsHelper.SetValueList(vallist, names);
                vallist.NickName = "Super-Element";
            }

            if (DA.GetData(1, ref super_element))
            {
                if (superElements.Count(a => a.Value == super_element) == 0)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Error, $"The super element {super_element} does not exists.");
                    return;
                }
            }
            else
            {
                AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Input parameter Super-Element failed to collect data");
            }

            bool has_combinations = DA.GetDataTree(2, out GH_Structure<IGH_Goo> combinations);


            _casesNames.Clear();
            int m = 1;
            for (int i = 0; i < models.Count; i++)
            {
                GH_Model model = models[i];
                // Add the model load and combination cases from results
                var modelLoadCases = new List<CaseModel>();
                modelLoadCases.AddRange(model.Value.Elements.SelectMany(e => e.Results).Select(l => l.LoadCase).Distinct());
                foreach (CaseModel loadCase in modelLoadCases)
                {
                    string lcase_name = $"{m}-{loadCase.Name}";
                    _casesNames.Add(lcase_name);
                }
                m++;
            }

            if (_run)
            {
                // Check the super beam
                List<BeamModel> super_beam = [.. models[0].Value.Beams.Where(b => b.HasAttribute(typeof(SuperElementAttributeModel)) &&
                    SuperElementAttributeModel.GetSuperElement(b) == super_element)];
                // First find the endnodes
                double tol = ModelHelper.GetTolerance();
                var endbeams = new List<string>();
                for (int i = 0; i < super_beam.Count; i++)
                {
                    BeamModel beam = super_beam[i];
                    int n = super_beam.Count(b => b.BeamId != beam.BeamId && (b.PointFrom.DistanceTo(beam.PointFrom) < tol ||
                        b.PointFrom.DistanceTo(beam.PointTo) < tol || b.PointTo.DistanceTo(beam.PointFrom) < tol ||
                        b.PointTo.DistanceTo(beam.PointTo) < tol));
                    if (n == 1)
                        endbeams.Add(beam.BeamId);
                }
                // No breaks alloewd
                if (endbeams.Count != 2)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "The super-beam must be continuous and without breaks.");
                    return;
                }

                int lu, fu;
                switch (models[0].Value.Units)
                {
                    case ModelModel.ModelUnits.kNm:
                        lu = MeChecksConsts.LenghtUnit_Metre;
                        fu = MeChecksConsts.ForceUnit_KNewton;
                        break;
                    case ModelModel.ModelUnits.SI:
                        lu = MeChecksConsts.LenghtUnit_Metre;
                        fu = MeChecksConsts.ForceUnit_Newton;
                        break;
                    case ModelModel.ModelUnits.Nmm:
                        lu = MeChecksConsts.LenghtUnit_Millimetre;
                        fu = MeChecksConsts.ForceUnit_Newton;
                        break;
                    default:
                        return;
                }

                IMeChecksApi api = ApiHelper.Connect();
                if (api == null)
                {
                    AddRuntimeMessage(GH_RuntimeMessageLevel.Error, "Unable to connect with meChecks. Please check in the program is running");
                    return;
                }

                if (api.NewJob(models[0].Value.Name, lu, fu, MeChecksConsts.TempUnit_Celsius) != MeChecksConsts.Error_Success)
                    throw new Exception("Failed to create new model");


                // Add geometri from the first model
                foreach (NodeModel node in models[0].Value.Nodes)
                {
                    api.AddJoint(node.NodeId, node.Position.X, node.Position.Y, node.Position.Z);
                }

                foreach (BeamModel beam in models[0].Value.Beams)
                {
                    IEnumerable<NodeModel> fromNodes = models[0].Value.Nodes.Where(n => n.Position.DistanceTo(beam.PointFrom) < tol);
                    IEnumerable<NodeModel> toNodes = models[0].Value.Nodes.Where(n => n.Position.DistanceTo(beam.PointTo) < tol);

                    NodeModel node1 = fromNodes.First();
                    NodeModel node2 = toNodes.First();
                    if (node1 == null || node2 == null)
                    {
                        throw new Exception(string.Format("Unreconized node(s) at coordinates {0} and/or {1}", beam.PointFrom, beam.PointTo));
                    }
                    api.AddFrame(beam.BeamId, node1.NodeId, node2.NodeId, beam.PointFrom.DistanceTo(beam.PointTo));
                }

                // Add load cases and the tensions for the models                
                m = 1;
                //_casesNames.Clear();
                foreach (GH_Model model in models)
                {
                    // Check if model not exists 
                    if (api.IsModelExisting(model.Value.Name) == MeChecksConsts.False)
                        api.AddModel(model.Value.Name);

                    // Add the model load and combination cases
                    /*ModelHelper.GetLoadCases(model.Value, out List<LoadCaseModel> modelLoadCases);
                    foreach (LoadCaseModel loadCase in modelLoadCases)
                    {
                        string lcase_name = $"{m}-{loadCase.Name}";
                        api.AddModelLoadCase(model.Value.Name, lcase_name);
                        api.AddLoadCaseToComboTable(lcase_name);
                        //_casesNames.Add(lcase_name);
                    }
                    foreach (LoadCombinationModel combo in model.Value.LoadCombinations)
                    {
                        string combo_name = $"{m}-{combo.Name}";
                        api.AddModelLoadCase(model.Value.Name, combo_name);
                        api.AddLoadCaseToComboTable(combo_name);
                        //_casesNames.Add(combo_name);
                    }*/
                    foreach (string case_name in _casesNames.Where(n => n.StartsWith($"{m}-")))
                    {
                        api.AddModelLoadCase(model.Value.Name, case_name);
                        api.AddLoadCaseToComboTable(case_name);
                    }

                    // Add the frame forces
                    foreach (BeamModel beam in model.Value.Beams)
                    {
                        Guid guid = model.Guids[beam];
                        BeamModel ref_beam = (BeamModel)models[0].Guids.First(g => g.Value == guid).Key;

                        foreach (BeamForceResultModel force in beam.Results)
                        {
                            api.AddFrameForce(model.Value.Name, ref_beam.BeamId, force.Station, $"{m}-{force.LoadCase.Name}", "",
                                force.AxialForce, -force.ShearForce.Y, force.ShearForce.X, force.Torque, -force.BendingMoment.X,
                                force.BendingMoment.Y);
                        }
                    }

                    m++;
                }

                // Find the Starting beam
                double min_x = Math.Min(super_beam.Min(b => b.PointFrom.X), super_beam.Min(b => b.PointTo.X));
                double min_y = Math.Min(super_beam.Min(b => b.PointFrom.Y), super_beam.Min(b => b.PointTo.Y));
                var min_pt = new Point3d(min_x, min_y, 0);
                BeamModel starting_beam = endbeams.Select(id => models[0].Value.GetBeam(id))
                    .OrderBy(b => Math.Min(min_pt.DistanceTo(b.PointFrom), min_pt.DistanceTo(b.PointTo))).First();

                // Finally set the Frames To Check
                api.AddFrameToCheck(starting_beam.BeamId);
                BeamModel last_beam = starting_beam;
                for (int n = 1; n < super_beam.Count; n++)
                {
                    api.GetFramesToCheck(out string[] framesToCheck);
                    BeamModel beam = super_beam.Single(b => !framesToCheck.Contains(b.BeamId) && (
                        last_beam.PointFrom.DistanceToSquared(b.PointFrom) < tol ||
                        last_beam.PointFrom.DistanceToSquared(b.PointTo) < tol ||
                        last_beam.PointTo.DistanceToSquared(b.PointFrom) < tol ||
                        last_beam.PointTo.DistanceToSquared(b.PointTo) < tol));
                    api.AddFrameToCheck(beam.BeamId);
                    last_beam = beam;
                }

                // Set the load combinations
                var load_combinations = new List<LoadCaseCombinationModel>();
                if (has_combinations)
                {
                    var cnames = new List<string>();
                    for (int i = 0; i < combinations.Branches.Count; i++)
                    {
                        List<IGH_Goo> row = (List<IGH_Goo>)combinations.get_Branch(i);
                        if (i == 0) // First rows: check cases name
                        {
                            cnames.AddRange(row.Cast<GH_String>().Where(c => c.Value != "").Select(c => c.Value));
                            IEnumerable<string> invalid_names = cnames.Where(c => !_casesNames.Contains(c));
                            if (invalid_names.Count() > 0)
                            {
                                AddRuntimeMessage(GH_RuntimeMessageLevel.Warning, "Invalid cases name: " + string.Join(", ", invalid_names));
                                break;
                            }
                        }
                        else
                        {
                            string name = ((GH_String)row[0]).Value;
                            IEnumerable<double> incrms = row.Cast<GH_String>()
                                .Where(c => double.TryParse(c.Value, out double d))
                                .Select(c => Convert.ToDouble(c.Value));
                            api.AddLoadCombination(name, [.. cnames], [.. incrms]);
                        }
                    }
                }

                api.UpdateUI();
                api.BringToTop();
                api.SetNumberOfDivisions(8);
                api.CalculateStresses();
                api.SaveJob(outputPath);

                IsRunning = true;

                // Create a new model with the super element items
                api.GetFramesToCheck(out string[] sortedFrames);
                var new_elements = new List<ElementModel>();
                foreach (string frame in sortedFrames)
                {
                    var beam = new BeamModel(models[0].Value.GetBeam(frame));
                    beam.Attributes.Clear();
                    beam.Loads.Clear();
                    beam.Results.Clear();

                    if (api.GetFrameStresses(frame, out string[] lc, out double[] d, out double[] n, out double[] mt,
                        out double[] t2, out double[] t3, out double[] m2, out double[] m3) == MeChecksConsts.Error_Success)
                    {
                        for (int i = 0; i < lc.Length; i++)
                        {
                            Common.Helpers.CsiHelper.ConvertStressesFromCSI(beam, ref m2[i], ref m3[i], ref t2[i], ref t3[i]);

                            var f = new BeamForceResultModel(new LoadCaseModel(i + 1, lc[i]), d[i], 0, "")
                            {
                                AxialForce = n[i],
                                Torque = mt[i],
                                ShearForce = new Vector2d(t2[i], t3[i]),
                                BendingMoment = new Vector2d(m2[i], m3[i])
                            };

                            beam.Results.Add(f);
                        }
                    }
                    new_elements.Add(beam);

                    //NodeModel node = beam. ///
                    //new_elements.Add(node);
                }

                var new_model = new ModelModel(models[0].Value.Units);
                new_model.AddElements(new_elements);
                new_model.AddLoadCombinations(load_combinations);
                var new_ghmodel = new GH_Model()
                {
                    Value = new_model
                };

                ModelHelper.RestoreModelGuids(models[0], ref new_ghmodel);

                DA.SetData(0, new_ghmodel);

                _run = false;
            }

        }

        /// <summary>
        /// Provides an Icon for the component.
        /// </summary>
        protected override System.Drawing.Bitmap Icon => Properties.Resources.SuperElementCheckIcon;

        /// <summary>
        /// Gets the unique ID for this component. Do not change this ID after release.
        /// </summary>
        public override Guid ComponentGuid => new("0dfc8d9c-1eeb-4368-8900-6c87ee319d2a");
    }
}
303 files24 directories