using FeMM.Common.Models;
using FeMM.Grasshopper.DataTypes.FeMM;
using FeMM.Grasshopper.Forms;
using FeMM.Grasshopper.Helpers;
using GH_IO.Serialization;
using Grasshopper;
using Grasshopper.GUI;
using Grasshopper.GUI.Canvas;
using Grasshopper.Kernel;
using Grasshopper.Kernel.Attributes;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Runtime.Versioning;
using System.Windows.Forms;

namespace FeMM.Grasshopper.Components.Definitions
{
    public class CombinationsTableComponent : GH_Component
    {
        private List<GH_Combination> _combos;
        private List<CaseModel> _caseModels;
        private List<FreedomCaseModel> _freedomCaseModels;

        public CombinationsTableComponent()
          : base("Combinations Table", "ComboTable", "Allows to create and store a list of combinations based on the load cases given as input", CategoryNameConstants.CATEGORY_FEMM, CategoryNameConstants.SUBCATEGORY_DEFINITIONS)
        {
            _combos = [];
            _caseModels = [];
            _freedomCaseModels = [];
        }

        protected override void RegisterInputParams(GH_InputParamManager pManager)
        {
            pManager.AddGenericParameter("Load Cases", "LCS", "List of load cases", GH_ParamAccess.list);
            pManager.AddGenericParameter("Freedom Cases", "LCS", "List of load cases", GH_ParamAccess.list);
            pManager[1].Optional = true;
        }

        protected override void RegisterOutputParams(GH_OutputParamManager pManager)
        {
            pManager.AddGenericParameter("Load Combinations", "LCombos", "The list of load combinations generated", GH_ParamAccess.list);
        }

        protected override void SolveInstance(IGH_DataAccess DA)
        {
            List<GH_Case> loadCases = [];
            List<GH_FreedomCase> freedomCases = [];

            if (!DA.GetDataList(0, loadCases))
                return;
            DA.GetDataList(1, freedomCases);

            // checks wheter load cases input list changed
            bool loadCasesChanged = loadCases.Count != _caseModels.Count;
            var cmIterator = _caseModels.GetEnumerator();
            var lcIterator = loadCases.GetEnumerator();
            while (cmIterator.MoveNext() && lcIterator.MoveNext() && !loadCasesChanged)
                if (cmIterator.Current.Name != lcIterator.Current.Value.Name)
                    loadCasesChanged = true;

            // checks wheter freedom cases input list changed
            freedomCases ??= [];
            bool freedomCasesChanged = freedomCases.Count != _freedomCaseModels.Count;
            var fcmIterator = _freedomCaseModels.GetEnumerator();
            var fcIterator = freedomCases.GetEnumerator();
            while (fcmIterator.MoveNext() && fcIterator.MoveNext() && !freedomCasesChanged)
                if (fcmIterator.Current.Name != fcIterator.Current.Value.Name)
                    freedomCasesChanged = true;

            if (loadCasesChanged || freedomCasesChanged)
            {
                _combos.Clear();
                _caseModels = [.. loadCases.Select(ghc => ghc.Value)];
                _freedomCaseModels = [.. freedomCases.Select(ghfc => ghfc.Value)];
                List<GH_Combination> loadCombination = [];
                DA.SetDataList(0, loadCombination);
            }
            else
            {
                DA.SetDataList(0, _combos.Select(combo => new GH_Combination(combo)));
            }
        }

        public override void CreateAttributes()
        {
            m_attributes = new LoadCombinationsTableAttributes(this);
        }

        public override bool Write(GH_IWriter writer)
        {
            bool success = base.Write(writer);
            if (!success)
                return false;

            writer.SetString("Combos", JsonConvert.SerializeObject(_combos));
            writer.SetString("CaseModels", JsonConvert.SerializeObject(_caseModels));
            writer.SetString("FreedomCaseModels", JsonConvert.SerializeObject(_freedomCaseModels));

            return true;
        }

        public override bool Read(GH_IReader reader)
        {
            bool success = base.Read(reader);
            if (!success)
                return false;

            string combos = string.Empty;
            if (!reader.TryGetString("Combos", ref combos) || string.IsNullOrWhiteSpace(combos))
                return false;
            string caseModels = string.Empty;
            if (!reader.TryGetString("CaseModels", ref caseModels) || string.IsNullOrWhiteSpace(caseModels))
                return false;

            try
            {
                _combos = JsonConvert.DeserializeObject<List<GH_Combination>>(combos);
                _caseModels = [.. JsonConvert.DeserializeObject<List<LoadCaseModel>>(caseModels).Cast<CaseModel>()];
                _freedomCaseModels = [.. JsonConvert.DeserializeObject<List<FreedomCaseModel>>(caseModels).Cast<FreedomCaseModel>()];
            }
            catch
            {
                _combos = [];
                _caseModels = [];
                _freedomCaseModels = [];
            }

            return true;
        }

        public override GH_Exposure Exposure => GH_Exposure.septenary;

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

        public override Guid ComponentGuid => new("44711be2-1a9e-4f78-a3cd-62068bb500f5");

#if NETCOREAPP
        [SupportedOSPlatform("windows")]
#endif
        internal class LoadCombinationsTableAttributes : GH_ResizableAttributes<CombinationsTableComponent>
        {
            private Rectangle _internalRect;

            protected override Size MinimumSize => new(280, 100);
            protected override Size MaximumSize { get { return new Size(810, 240); } }
            protected override Padding SizingBorders { get { return new Padding(10); } }

            public LoadCombinationsTableAttributes(CombinationsTableComponent owner)
                : base(owner)
            {
                Bounds = new RectangleF(Pivot, MinimumSize);
            }

            public override void AppendToAttributeTree(List<IGH_Attributes> attributes)
            {
                attributes.Add(this);

                // make sure parameter attributes participate in hit-testing
                foreach (var p in Owner.Params.Input)
                    attributes.Add(p.Attributes);
                foreach (var p in Owner.Params.Output)
                    attributes.Add(p.Attributes);
            }

            protected override void Layout()
            {
                base.Layout();

                _internalRect = GH_Convert.ToRectangle(Bounds);
                _internalRect.X += 80;
                _internalRect.Width -= 80;
                _internalRect.Inflate(-5, -5);

                // define input grips position (distributing the space evenly)
                int inputCount = Owner.Params.Input.Count;
                if (inputCount > 0)
                {
                    float availableHeight = Bounds.Height - 20;
                    float spacing = availableHeight / inputCount;

                    for (int i = 0; i < inputCount; i++)
                    {
                        var param = Owner.Params.Input[i];
                        float yLoc = Bounds.Y + (i * spacing) + (spacing / 2) + 10;

                        var pivot = new PointF(Bounds.Left, yLoc);
                        var paramBounds = new RectangleF(Bounds.Left, yLoc - 10, 15, 20);

                        param.Attributes.Pivot = pivot;
                        param.Attributes.Bounds = paramBounds;
                    }
                }

                // define output grips position (distributing the space evenly)
                int outputCount = Owner.Params.Output.Count;
                if (outputCount > 0)
                {
                    float availableHeight = Bounds.Height - 20;
                    float spacing = availableHeight / outputCount;

                    for (int i = 0; i < outputCount; i++)
                    {
                        var param = Owner.Params.Output[i];
                        float yLoc = Bounds.Y + (i * spacing) + (spacing / 2) + 10;

                        var pivot = new PointF(Bounds.Right, yLoc);
                        var paramBounds = new RectangleF(Bounds.Right - 15, yLoc - 10, 15, 20);

                        param.Attributes.Pivot = pivot;
                        param.Attributes.Bounds = paramBounds;
                    }
                }
            }

            protected override void Render(GH_Canvas canvas, Graphics graphics, GH_CanvasChannel channel)
            {
                if (channel == GH_CanvasChannel.Objects)
                {
                    // set component color
                    GH_Palette palette = Owner.RuntimeMessageLevel switch
                    {
                        GH_RuntimeMessageLevel.Warning => GH_Palette.Warning,
                        GH_RuntimeMessageLevel.Error => GH_Palette.Error,
                        _ => GH_Palette.Normal
                    };

                    // render the capsule: component body (rectangle) and input/output grips
                    using (var capsule = GH_Capsule.CreateCapsule(Bounds, palette))
                    {
                        foreach (var param in Owner.Params.Input)
                            capsule.AddInputGrip(param.Attributes.Pivot.Y);
                        foreach (var param in Owner.Params.Output)
                            capsule.AddOutputGrip(param.Attributes.Pivot.Y);

                        capsule.Render(graphics, Selected, Owner.Locked, false);
                    }

                    // render input labels
                    StringFormat format = new()
                    {
                        Alignment = StringAlignment.Near,
                        LineAlignment = StringAlignment.Center,
                        Trimming = StringTrimming.EllipsisCharacter
                    };
                    RectangleF labelRectangle = GH_Convert.ToRectangle(Bounds);
                    labelRectangle.Width = 90;
                    labelRectangle.Height = 20;
                    labelRectangle.Inflate(-2, 0);
                    foreach (var param in Owner.Params.Input)
                    {
                        labelRectangle.Y = param.Attributes.Pivot.Y - 10;
                        graphics.DrawString(param.Name, GH_FontServer.Standard, Brushes.Black, labelRectangle, format);
                    }

                    // draw rectangle
                    using (Pen pen = new(Color.Black))
                        graphics.DrawRectangle(pen, _internalRect);

                    // define text rectangle to specify where to write
                    RectangleF textRectangle = _internalRect;
                    textRectangle.Y += 2;
                    textRectangle.Height = 20;
                    textRectangle.Inflate(-2, 0);

                    format.Alignment = StringAlignment.Center;
                    graphics.DrawString("Combinations", GH_FontServer.Standard, Brushes.Black, textRectangle, format);

                    // draw the combos
                    format.Alignment = StringAlignment.Near;
                    textRectangle.X += 2;
                    if (Owner._combos.Count != 0)
                    {
                        for (int i = 0; i < Owner._combos.Count; i++)
                        {
                            if (Owner._combos[i] == null)
                                continue;

                            textRectangle.Y += textRectangle.Height;
                            int textLen = GH_FontServer.StringWidth(Owner._combos[i].ToString(), GH_FontServer.Standard);
                            if (textLen <= textRectangle.Width)
                                textRectangle.Height = 20;
                            else
                                textRectangle.Height = GH_FontServer.Standard.Height * (float)Math.Ceiling(textLen / textRectangle.Width);

                            if (textRectangle.Y + textRectangle.Height + (i < Owner._combos.Count - 1 ? 20 : 0) > _internalRect.Y + _internalRect.Height)
                            {
                                textRectangle.Height = 20;
                                graphics.DrawString($"{Owner._combos.Count - i} more...", GH_FontServer.Standard, Brushes.Black, textRectangle, format);
                                break;
                            }

                            graphics.DrawString(Owner._combos[i].ToString(), GH_FontServer.Standard, Brushes.Black, textRectangle, format);
                        }
                    }
                    else
                    {
                        textRectangle.Y += textRectangle.Height;
                        graphics.DrawString("No combination defined...", GH_FontServer.Standard, Brushes.Black, textRectangle, format);
                    }
                }
                else if (channel == GH_CanvasChannel.Wires)
                {
                    foreach (var p in Owner.Params.Input)
                        p.Attributes?.RenderToCanvas(canvas, GH_CanvasChannel.Wires);
                    foreach (var p in Owner.Params.Output)
                        p.Attributes?.RenderToCanvas(canvas, GH_CanvasChannel.Wires);
                }
                else
                {
                    base.Render(canvas, graphics, channel);
                }
            }

            public override GH_ObjectResponse RespondToMouseDoubleClick(GH_Canvas sender, GH_CanvasMouseEvent e)
            {
                if (e.Button == MouseButtons.Left)
                {
                    List<GH_Combination> list = null;
                    if (Owner._combos.Count != 0)
                        list = Owner._combos;
                    var caseModels = Owner._caseModels.Concat(Owner._freedomCaseModels).ToArray();

                    LoadCombinationsForm form = new(caseModels, list);
                    GH_WindowsFormUtil.CenterFormOnCursor(form, true);
                    if (form.ShowDialog(Instances.DocumentEditor) == DialogResult.OK)
                    {
                        Owner._combos = form.Values;
                        Owner.ExpireSolution(true);
                    }
                    return GH_ObjectResponse.Handled;
                }
                return base.RespondToMouseDoubleClick(sender, e);
            }

            public override GH_ObjectResponse RespondToMouseDown(GH_Canvas sender, GH_CanvasMouseEvent e)
            {
                // Check if we clicked on an input/output parameter area. If that's the case, redirect the event to the parameter's own attributes
                foreach (var param in Owner.Params.Input)
                    if (param.Attributes.Bounds.Contains(e.CanvasLocation))
                        return param.Attributes.RespondToMouseDown(sender, e);
                foreach (var param in Owner.Params.Output)
                    if (param.Attributes.Bounds.Contains(e.CanvasLocation))
                        return param.Attributes.RespondToMouseDown(sender, e);

                return base.RespondToMouseDown(sender, e);
            }
        }
    }
}
303 files24 directories