<commit_before>﻿using FlatRedBall.Glue.CodeGeneration.CodeBuilder;
using FlatRedBall.Glue.Managers;
using Gum.DataTypes;
using Gum.Managers;
using GumPlugin.CodeGeneration;
using GumPlugin.Managers;
using System;
using System.Collections.Generic;
using System.Text;

namespace GumPluginCore.CodeGeneration
{
    public class FormsClassCodeGenerator : Singleton<FormsClassCodeGenerator>
    {
        public static string FormsRuntimeNamespace =>
            FlatRedBall.Glue.ProjectManager.ProjectNamespace + ".FormsControls";

        public string GenerateCodeFor(ElementSave elementSave)
        {
            if (elementSave == null)
            {
                throw new ArgumentNullException(nameof(elementSave));
            }

            bool generated = false;

            var isScreen = elementSave is ScreenSave;
            var isComponent = elementSave is ComponentSave;
            if (isScreen)
            {
                generated = true;
            }
            else if(isComponent)
            {
                var component = elementSave as ComponentSave;

                var behaviors = component?.Behaviors;

                string controlType = null;

                if (behaviors != null)
                {
                    controlType = GueDerivingClassCodeGenerator.GetFormsControlTypeFrom(behaviors);
                }

                // todo - see if there are any Forms controls here? Or always generate? Not sure...
                generated = controlType == null;
            }
            // don't do anything with standards

            if (generated)
            {
                var topBlock = new CodeBlockBase();
                var fullNamespace = GetFullRuntimeNamespaceFor(elementSave);
                var currentBlock = topBlock.Namespace(fullNamespace);
                GenerateScreenAndComponentCodeFor(elementSave, currentBlock);
                return topBlock.ToString();
            }
            else
            {
                return null;
            }
        }

        private void GenerateScreenAndComponentCodeFor(ElementSave elementSave, ICodeBlock codeBlock)
        {
            ICodeBlock currentBlock = GenerateClassHeader(codeBlock, elementSave);

            GenerateProperties(elementSave, currentBlock);

            string runtimeClassName = GetUnqualifiedRuntimeTypeFor(elementSave);

            GenerateConstructor(elementSave, currentBlock, runtimeClassName);

            currentBlock.Line("partial void CustomInitialize();");
        }

        private void GenerateConstructor(ElementSave elementSave, ICodeBlock currentBlock, string runtimeClassName)
        {
            string baseCall = null;
            var constructor = currentBlock.Constructor("public", runtimeClassName, "Gum.Wireframe.GraphicalUiElement visual", baseCall);

            if(elementSave is ScreenSave || elementSave is ComponentSave)
            {
                foreach (var instance in elementSave.Instances)
                {
                    string type = GetQualifiedRuntimeTypeFor(instance, elementSave);

                    if (!string.IsNullOrEmpty(type))
                    {
                        var line = 
                            $"{instance.MemberNameInCode()} = ({type})visual.GetGraphicalUiElementByName(\"{instance.Name}\").FormsControlAsObject;";
                        constructor.Line(line);
                    }
                }
            }

            constructor.Line("CustomInitialize();");
        }

        private void GenerateProperties(ElementSave elementSave, ICodeBlock currentBlock)
        {
            var rfs = GumProjectManager.Self.GetRfsForGumProject();

            var makePublic =
                true;
                //rfs?.Properties.GetValue<bool>(nameof(GumViewModel.MakeGumInstancesPublic)) == true;

            string publicOrPrivate;
            if (elementSave is Gum.DataTypes.ScreenSave || makePublic)
            {
                // make these public for screens because the only time this will be accessed is in the Glue screen that owns it
                publicOrPrivate = "public";
            }
            else
            {
                publicOrPrivate = "private";
            }
            foreach (var instance in elementSave.Instances)
            {
                string type = GetQualifiedRuntimeTypeFor(instance, elementSave);

                if(!string.IsNullOrEmpty(type))
                {
                    ICodeBlock property = currentBlock.AutoProperty($"{publicOrPrivate} " + type, instance.MemberNameInCode());
                }
            }
        }

        private string GetQualifiedRuntimeTypeFor(InstanceSave instance, ElementSave elementSave)
        {
            var instanceType = instance.BaseType;
            var component = ObjectFinder.Self.GetComponent(instanceType);

            var behaviors = component?.Behaviors;

            string controlType = null;

            if(behaviors != null)
            {
                controlType = GueDerivingClassCodeGenerator.GetFormsControlTypeFrom(behaviors);
            }

            return controlType;<commit_msg>Generated forms objects can now reference other generated forms objects<commit_after>﻿using FlatRedBall.Glue.CodeGeneration.CodeBuilder;
using FlatRedBall.Glue.Managers;
using Gum.DataTypes;
using Gum.Managers;
using GumPlugin.CodeGeneration;
using GumPlugin.Managers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace GumPluginCore.CodeGeneration
{
    public class FormsClassCodeGenerator : Singleton<FormsClassCodeGenerator>
    {
        public static string FormsRuntimeNamespace =>
            FlatRedBall.Glue.ProjectManager.ProjectNamespace + ".FormsControls";

        public string GenerateCodeFor(ElementSave elementSave)
        {
            if (elementSave == null)
            {
                throw new ArgumentNullException(nameof(elementSave));
            }

            bool shouldGenerate = false;

            var isScreen = elementSave is ScreenSave;
            var isComponent = elementSave is ComponentSave;
            if (isScreen)
            {
                shouldGenerate = true;
            }
            else if(isComponent)
            {
                shouldGenerate = GetIfShouldGenerate(elementSave);
            }
            // don't do anything with standards

            if (shouldGenerate)
            {
                var topBlock = new CodeBlockBase();
                var fullNamespace = GetFullRuntimeNamespaceFor(elementSave);
                var currentBlock = topBlock.Namespace(fullNamespace);
                GenerateScreenAndComponentCodeFor(elementSave, currentBlock);
                return topBlock.ToString();
            }
            else
            {
                return null;
            }
        }

        private bool GetIfShouldGenerate(ElementSave elementSave)
        {
            bool shouldGenerate;
            var component = elementSave as ComponentSave;

            var behaviors = component?.Behaviors;

            string controlType = null;

            if (behaviors != null)
            {
                controlType = GueDerivingClassCodeGenerator.GetFormsControlTypeFrom(behaviors);
            }

            shouldGenerate = controlType == null;

            // todo - see if there are any Forms controls here? Or always generate? Not sure...
            // Update 11/27/2020 - Justin's game has lots of components that aren't forms and this
            // is adding a lot of garbage to Justin's project.
            if (shouldGenerate)
            {
                var allInstances = elementSave.Instances;

                shouldGenerate = allInstances.Any(item =>
                {
                    var instanceElement = ObjectFinder.Self.GetElementSave(item);

                    if(instanceElement is ComponentSave component)
                    {

                        return GueDerivingClassCodeGenerator.GetFormsControlTypeFrom(component.Behaviors) != null ||
                            GetIfShouldGenerate(instanceElement);

                    }
                    return false;
                });
            }

            return shouldGenerate;
        }

        private void GenerateScreenAndComponentCodeFor(ElementSave elementSave, ICodeBlock codeBlock)
        {
            ICodeBlock currentBlock = GenerateClassHeader(codeBlock, elementSave);

            GenerateProperties(elementSave, currentBlock);

            string runtimeClassName = GetUnqualifiedRuntimeTypeFor(elementSave);

            GenerateConstructor(elementSave, currentBlock, runtimeClassName);

            currentBlock.Line("partial void CustomInitialize();");
        }

        private void GenerateConstructor(ElementSave elementSave, ICodeBlock currentBlock, string runtimeClassName)
        {
            string baseCall = null;
            var constructor = currentBlock.Constructor("public", runtimeClassName, "Gum.Wireframe.GraphicalUiElement visual", baseCall);

            if(elementSave is ScreenSave || elementSave is ComponentSave)
            {
                foreach (var instance in elementSave.Instances)
                {
                    string type = GetQualifiedRuntimeTypeFor(instance, out bool isStandard);

                    if (!string.IsNullOrEmpty(type))
                    {
                        string line;

                        if(isStandard)
                        {
                            line = 
                                $"{instance.MemberNameInCode()} = ({type})visual.GetGraphicalUiElementByName(\"{instance.Name}\").FormsControlAsObject;";
                        }
                        else
                        {
                            line =
                                $"{instance.MemberNameInCode()} = new {type}(visual.GetGraphicalUiElementByName(\"{instance.Name}\"));";
                        }
                        constructor.Line(line);
                    }
                }
            }

            constructor.Line("CustomInitialize();");
        }

        private void GenerateProperties(ElementSave elementSave, ICodeBlock currentBlock)
        {
            var rfs = GumProjectManager.Self.GetRfsForGumProject();

            var makePublic =
                true;
                //rfs?.Properties.GetValue<bool>(nameof(GumViewModel.MakeGumInstancesPublic)) == true;

            string publicOrPrivate;
            if (elementSave is Gum.DataTypes.ScreenSave || makePublic)
            {
                // make these public for screens because the only time this will be accessed is in the Glue screen that owns it
                publicOrPrivate = "public";
            }
            else
            {
                publicOrPrivate = "private";
            }
            foreach (var instance in elementSave.Instances)
            {
                string type = GetQualifiedRuntimeTypeFor(instance, out bool isStandardType);

                if(!string.IsNullOrEmpty(type))
                {
                    ICodeBlock property = currentBlock.AutoProperty($"{publicOrPrivate} " + type, instance.MemberNameInCode());
                }
            }
        }

        private string GetQualifiedRuntimeTypeFor(InstanceSave instance, out bool isStandardForms)
        {
            isStandardForms = false;
            var instanceType = instance.BaseType;
            var component = ObjectFinder.Self.GetComponent(instanceType);

            var behaviors = component?.Behaviors;

            string controlType = null;

            if(behaviors != null)
            {
                controlType = GueDerivingClassCodeGenerator.GetFormsControlTypeFrom(behaviors);
            }

            if(controlType != null)
            {
                isStandardForms = true;
                return controlType;
            }

            // else it may still need to be generated as a reference to a generated form type
            if(component != null && GetIfShouldGenerate(component))
            {
                return GetFullRuntimeNamespaceFor(component) + "." + GetUnqualifiedRuntimeTypeFor(component);
            }

            return null;
{'fraction_non_alphanumeric': 0.0699021207177814, 'fraction_numerical': 0.0006525285481239804, 'mean_word_length': 1.7018510356985457, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 10, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1465217', 'n_tokens_mistral': 2843, 'n_tokens_neox': 2669, 'n_words': 632}