// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

namespace DemoConsole;

public class DesignSurfaceExtended : DesignSurface, IDesignSurfaceExtended
{
    private const string Name = "DesignSurfaceExt";

    #region IDesignSurfaceExt Members

    public void SwitchTabOrder()
    {
        if (!IsTabOrderMode)
        {
            InvokeTabOrder();
        }
        else
        {
            DisposeTabOrder();
        }
    }

    public void UseSnapLines()
    {
        IServiceContainer serviceProvider = GetService(typeof(IServiceContainer)) as IServiceContainer;
        DesignerOptionService opsService = serviceProvider.GetService(typeof(DesignerOptionService)) as DesignerOptionService;
        if (opsService is not null)
        {
            serviceProvider.RemoveService(typeof(DesignerOptionService));
        }

        DesignerOptionService opsService2 = new DesignerOptionService4SnapLinesExtension();
        serviceProvider.AddService(typeof(DesignerOptionService), opsService2);
    }

    public void UseGrid(Size gridSize)
    {
        IServiceContainer serviceProvider = GetService(typeof(IServiceContainer)) as IServiceContainer;
        DesignerOptionService opsService = serviceProvider.GetService(typeof(DesignerOptionService)) as DesignerOptionService;
        if (opsService is not null)
        {
            serviceProvider.RemoveService(typeof(DesignerOptionService));
        }

        DesignerOptionService opsService2 = new DesignerOptionService4GridExtension(gridSize);
        serviceProvider.AddService(typeof(DesignerOptionService), opsService2);
    }

    public void UseGridWithoutSnapping(Size gridSize)
    {
        IServiceContainer serviceProvider = GetService(typeof(IServiceContainer)) as IServiceContainer;
        DesignerOptionService opsService = serviceProvider.GetService(typeof(DesignerOptionService)) as DesignerOptionService;
        if (opsService is not null)
        {
            serviceProvider.RemoveService(typeof(DesignerOptionService));
        }

        DesignerOptionService opsService2 = new DesignerOptionService4GridWithoutSnappingExtension(gridSize);
        serviceProvider.AddService(typeof(DesignerOptionService), opsService2);
    }

    public void UseNoGuides()
    {
        IServiceContainer serviceProvider = GetService(typeof(IServiceContainer)) as IServiceContainer;
        DesignerOptionService opsService = serviceProvider.GetService(typeof(DesignerOptionService)) as DesignerOptionService;
        if (opsService is not null)
        {
            serviceProvider.RemoveService(typeof(DesignerOptionService));
        }

        DesignerOptionService opsService2 = new DesignerOptionService4NoGuidesExtension();
        serviceProvider.AddService(typeof(DesignerOptionService), opsService2);
    }

    public UndoEngineExtended GetUndoEngineExt()
    {
        return _undoEngine;
    }

    public TControl CreateRootComponent<TControl>(Size controlSize)
        where TControl : Control, IComponent
    {
        try
        {
            // - step.1
            // - get the IDesignerHost
            // - if we are not not able to get it
            // - then rollback (return without do nothing)
            IDesignerHost host = GetIDesignerHost();
            if (host is null)
                return null;
            // - check if the root component has already been set
            // - if so then rollback (return without do nothing)
            if (host.RootComponent is not null)
                return null;
            // -
            // -
            // - step.2
            // - create a new root component and initialize it via its designer
            // - if the component has not a designer
            // - then rollback (return without do nothing)
            // - else do the initialization
            BeginLoad(typeof(TControl));
            if (LoadErrors.Count > 0)
                throw new InvalidOperationException($"the BeginLoad() failed! Some error during {typeof(TControl).FullName} loding");
            // -
            // -
            // - step.3
            // - try to modify the Size of the object just created
            IDesignerHost ihost = GetIDesignerHost();
            // - Set the BackColor and the Size
            Control ctrl = null;
            Type hostType = host.RootComponent.GetType();
            if (hostType == typeof(Form))
            {
                ctrl = View as Control;
                ctrl.BackColor = Color.LightGray;
                // - set the Size
                PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(ctrl);
                // - Sets a PropertyDescriptor to the specific property.
                PropertyDescriptor pdS = pdc.Find("Size", false);
                pdS?.SetValue(ihost.RootComponent, controlSize);
            }
            else if (hostType == typeof(UserControl))
            {
                ctrl = View as Control;
                ctrl.BackColor = Color.DarkGray;
                // - set the Size
                PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(ctrl);
                // - Sets a PropertyDescriptor to the specific property.
                PropertyDescriptor pdS = pdc.Find("Size", false);
                pdS?.SetValue(ihost.RootComponent, controlSize);
            }
            else if (hostType == typeof(Component))
            {
                ctrl = View as Control;
                ctrl.BackColor = Color.White;
                // - don't set the Size
            }
            else
            {
                throw new InvalidOperationException($"Undefined Host Type: {hostType}");
            }

            return (TControl)ihost.RootComponent;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"{Name}::CreateRootComponent() - Exception: (see Inner Exception)", ex);
        }
    }

    public TControl CreateControl<TControl>(Size controlSize, Point controlLocation)
        where TControl : Control
    {
        try
        {
            // - step.1
            IComponent newComp = CreateComponent<TControl>(out IDesignerHost host);
            if (newComp is null)
                return null;

            // -
            // -
            // - step.3
            // - try to modify the Size/Location of the object just created
            PropertyDescriptorCollection pdc = TypeDescriptor.GetProperties(newComp);
            // - Sets a PropertyDescriptor to the specific property.
            PropertyDescriptor pdS = pdc.Find("Size", false);
            pdS?.SetValue(newComp, controlSize);
            PropertyDescriptor pdL = pdc.Find("Location", false);
            pdL?.SetValue(newComp, controlLocation);
            // -
            // -
            // - step.4
            // - commit the Creation Operation
            // - adding the control to the DesignSurface's root component
            // - and return the control just created to let further initializations
            ((Control)newComp).Parent = host.RootComponent as Control;

            return (TControl)newComp;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"{Name}::CreateControl() - Exception: (see Inner Exception)", ex);
        }
    }

    public TComponent CreateComponent<TComponent>()
        where TComponent : IComponent
    {
        return CreateComponent<TComponent>(out _);
    }

    private TComponent CreateComponent<TComponent>(out IDesignerHost host)
        where TComponent : IComponent
    {
        try
        {
            // Create a new component and initialize it via its designer

            host = GetIDesignerHost();
            if (host is null
                || host.RootComponent is null
                || host.CreateComponent(typeof(TComponent)) is not IComponent newComp
                || host.GetDesigner(newComp) is not IDesigner designer)
            {
                return default;
            }

            if (designer is IComponentInitializer initializer)
            {
                // This is to address a difference in nullability annotations between the .NET and NETFX
                // See https://github.com/dotnet/winforms/pull/12996/#issuecomment-2765302289
                initializer.InitializeNewComponent(new Dictionary<object, object>());
            }

            return (TComponent)newComp;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"{Name}::{nameof(CreateComponent)} - Exception: (see Inner Exception)", ex);
        }
    }

    public IDesignerHost GetIDesignerHost() => (IDesignerHost)GetService(typeof(IDesignerHost));

    public Control GetView()
    {
        Control ctrl = View as Control;
        ctrl.Dock = DockStyle.Fill;
        return ctrl;
    }

    #endregion

    #region TabOrder

    private TabOrderHooker _tabOrder;
    private bool _tabOrderMode;

    public bool IsTabOrderMode
    {
        get { return _tabOrderMode; }
    }

    public TabOrderHooker TabOrder
    {
        get
        {
            _tabOrder ??= new TabOrderHooker();
            return _tabOrder;
        }
        set { _tabOrder = value; }
    }

    public void InvokeTabOrder()
    {
        TabOrder.HookTabOrder(GetIDesignerHost());
        _tabOrderMode = true;
    }

    public void DisposeTabOrder()
    {
        TabOrder.HookTabOrder(GetIDesignerHost());
        _tabOrderMode = false;
    }
    #endregion

    #region  UndoEngine

    private UndoEngineExtended _undoEngine;
    private NameCreationService _nameCreationService;
    private DesignerSerializationService _designerSerializationService;
    private CodeDomComponentSerializationService _codeDomComponentSerializationService;

    #endregion

    // - ctor
    public DesignSurfaceExtended()
    {
        InitServices();
    }

    // - The DesignSurface class provides several design-time services automatically.
    // - The DesignSurface class adds all of its services in its constructor.
    // - Most of these services can be overridden by replacing them in the
    // - protected ServiceContainer property.To replace a service, override the constructor,
    // - call base, and make any changes through the protected ServiceContainer property.
    private void InitServices()
    {
        // - each DesignSurface has its own default services
        // - We can leave the default services in their present state,
        // - or we can remove them and replace them with our own.
        // - Now add our own services using IServiceContainer
        // - Note
        // - before loading the root control in the design surface
        // - we must add an instance of naming service to the service container.
        // - otherwise the root component did not have a name and this caused
        // - troubles when we try to use the UndoEngine
        // - 1. NameCreationService
        _nameCreationService = new NameCreationService();
        ServiceContainer.RemoveService(typeof(INameCreationService), false);
        ServiceContainer.AddService(typeof(INameCreationService), _nameCreationService);

        // - 2. CodeDomComponentSerializationService
        _codeDomComponentSerializationService = new CodeDomComponentSerializationService(ServiceContainer);
        ServiceContainer.RemoveService(typeof(ComponentSerializationService), false);
        ServiceContainer.AddService(typeof(ComponentSerializationService), _codeDomComponentSerializationService);

        // - 3. IDesignerSerializationService
        _designerSerializationService = new DesignerSerializationService(ServiceContainer);
        ServiceContainer.RemoveService(typeof(IDesignerSerializationService), false);
        ServiceContainer.AddService(typeof(IDesignerSerializationService), _designerSerializationService);

        // - 4. UndoEngine
        _undoEngine = new UndoEngineExtended(ServiceContainer)
        {
            // Disable the UndoEngine
            Enabled = false
        };

        ServiceContainer.RemoveService(typeof(UndoEngine), false);
        ServiceContainer.AddService(typeof(UndoEngine), _undoEngine);

        // - 5. IMenuCommandService
        ServiceContainer.AddService(typeof(IMenuCommandService), new MenuCommandService(this));

        // - 6. ITypeDiscoveryService
        ServiceContainer.AddService(typeof(ITypeDiscoveryService), new TypeDiscoveryService());
    }

    // - do some Edit menu command using the MenuCommandService
    public void DoAction(string command)
    {
        if (string.IsNullOrEmpty(command))
        {
            return;
        }

        IMenuCommandService ims = GetService(typeof(IMenuCommandService)) as IMenuCommandService;

        try
        {
            CommandID commandId = command.ToUpperInvariant() switch
            {
                "CUT" => StandardCommands.Cut,
                "COPY" => StandardCommands.Copy,
                "PASTE" => StandardCommands.Paste,
                "DELETE" => StandardCommands.Delete,
                "INVOKESMARTTAG" => MenuCommands.KeyInvokeSmartTag,
                "KEYMOVEUP" => MenuCommands.KeyMoveUp,
                "KEYMOVEDOWN" => MenuCommands.KeyMoveDown,
                "KEYMOVELEFT" => MenuCommands.KeyMoveLeft,
                "KEYMOVERIGHT" => MenuCommands.KeyMoveRight,
                "KEYSELECTNEXT" => MenuCommands.KeySelectNext,
                "KEYSELECTPREVIOUS" => MenuCommands.KeySelectPrevious,
                _ => null,
            };

            if (commandId is not null)
            {
                MenuCommand menuCommand = ims?.FindCommand(commandId);
                if (menuCommand?.Enabled is true)
                {
                    menuCommand.Invoke();
                }
            }
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException($"{Name}::DoAction() - Exception: error in performing the action: {command}(see Inner Exception)", ex);
        }
    }
}
