﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

#nullable disable

using System;
using System.Runtime.InteropServices;
using Microsoft.CodeAnalysis;
using Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.Collections;
using Microsoft.VisualStudio.LanguageServices.Implementation.Interop;
using Microsoft.VisualStudio.LanguageServices.Implementation.Utilities;

namespace Microsoft.VisualStudio.LanguageServices.Implementation.CodeModel.InternalElements
{
    [ComVisible(true)]
    [ComDefaultInterface(typeof(EnvDTE80.CodeAttribute2))]
    public sealed class CodeAttribute : AbstractCodeElement, ICodeElementContainer<CodeAttributeArgument>, EnvDTE.CodeAttribute, EnvDTE80.CodeAttribute2
    {
        internal static EnvDTE.CodeAttribute Create(
            CodeModelState state,
            FileCodeModel fileCodeModel,
            AbstractCodeElement parent,
            string name,
            int ordinal)
        {
            var newElement = new CodeAttribute(state, fileCodeModel, parent, name, ordinal);
            return (EnvDTE.CodeAttribute)ComAggregate.CreateAggregatedObject(newElement);
        }

        // CodeAttributes are tracked by name+ordinal within their parent node, or FileCodeModel if parent is null.
        private readonly AbstractCodeElement _parent; // NOTE: Ref'd
        private string _name;
        private readonly int _ordinal;

        private CodeAttribute(CodeModelState state, FileCodeModel fileCodeModel, AbstractCodeElement parent, string name, int ordinal)
            : base(state, fileCodeModel)
        {
            _parent = parent;
            _name = name;
            _ordinal = ordinal;
        }

        EnvDTE.CodeElements ICodeElementContainer<CodeAttributeArgument>.GetCollection()
            => this.Arguments;

        protected override EnvDTE.CodeElements GetCollection()
            => GetCollection<CodeAttribute>(Parent);

        internal override bool TryLookupNode(out SyntaxNode node)
        {
            node = null;

            var parentNode = _parent != null
                ? _parent.LookupNode()
                : FileCodeModel.GetSyntaxRoot();

            if (parentNode == null)
            {
                return false;
            }

            if (!CodeModelService.TryGetAttributeNode(parentNode, _name, _ordinal, out var attributeNode))
            {
                return false;
            }

            node = attributeNode;
            return node != null;
        }

        public override EnvDTE.vsCMElement Kind
        {
            get { return EnvDTE.vsCMElement.vsCMElementAttribute; }
        }

        public override object Parent => _parent ?? (object)this.FileCodeModel;

        public EnvDTE.CodeElements Arguments
        {
            get { return AttributeArgumentCollection.Create(this.State, this); }
        }

        public override EnvDTE.CodeElements Children
        {
            get { return Arguments; }
        }

        protected override void SetName(string value)
        {
            // Defer to the base type to do the heavy lifting...
            base.SetName(value);

            // ...and then update the name we use to track this attribute.
            _name = value;
        }

        public string Target
        {
            get
            {
                return CodeModelService.GetAttributeTarget(LookupNode());
            }

            set
            {
                UpdateNode(FileCodeModel.UpdateAttributeTarget, value);
            }
        }

        public string Value
        {
            get
            {
                return CodeModelService.GetAttributeValue(LookupNode());
            }

            set
            {
                UpdateNode(FileCodeModel.UpdateAttributeValue, value);
            }
        }

        public EnvDTE80.CodeAttributeArgument AddArgument(string value, object nameObj, object position)
        {
            string name;

            if (nameObj == Type.Missing ||
                nameObj == null)
            {
                name = null;
            }
            else if (nameObj is string s)
            {
                name = s;
            }
            else
            {
                throw Exceptions.ThrowEInvalidArg();
            }

            return FileCodeModel.EnsureEditor(() =>
            {
                return FileCodeModel.AddAttributeArgument(LookupNode(), name, value, position);
            });
        }

        public new void Delete()
            => base.Delete();
    }
}
