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

using System.Collections.Generic;
using System.Xml;
using Microsoft.Build.Construction;

#nullable disable

namespace Microsoft.Build.ObjectModelRemoting
{
    /// <summary>
    /// This interface will allow us to share a single field between
    /// <see cref="XmlElementWithLocation"/> and <see cref="ProjectElementLink"/>
    /// for construction objects so therefore not increasing the storage size while supporting
    /// external linking.
    /// <see cref="ProjectElement.XmlElement"/> and <see cref="ProjectElement.Link"/>
    /// </summary>
    internal interface ILinkedXml
    {
        /// <summary>
        /// Not null for "external" objects, null for internal objects
        /// </summary>
        ProjectElementLink Link { get; }

        /// <summary>
        /// Null for "external" objects, not null for internal objects
        /// </summary>
        XmlElementWithLocation Xml { get; }
    }

    /// <summary>
    /// External projects support.
    /// Allow for creating a local representation to external construction objects derived from <see cref="ProjectElement"/>
    /// </summary>
    public abstract class ProjectElementLink : ILinkedXml
    {
        /// <summary>
        /// <see cref="ILinkedXml.Link"/>
        /// </summary>
        ProjectElementLink ILinkedXml.Link => this;

        /// <summary>
        /// <see cref="ILinkedXml.Xml"/>
        /// </summary>
        XmlElementWithLocation ILinkedXml.Xml => null;

        /// <summary>
        /// Access to remote <see cref="ProjectElement.Parent"/>.
        /// </summary>
        public abstract ProjectElementContainer Parent { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.ContainingProject"/>.
        /// </summary>
        public abstract ProjectRootElement ContainingProject { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.ElementName"/>.
        /// </summary>
        public abstract string ElementName { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.OuterElement"/>.
        /// </summary>
        public abstract string OuterElement { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.ExpressedAsAttribute"/>.
        /// </summary>
        public abstract bool ExpressedAsAttribute { get; set; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.PreviousSibling"/>.
        /// </summary>
        public abstract ProjectElement PreviousSibling { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.NextSibling"/>.
        /// </summary>
        public abstract ProjectElement NextSibling { get; }

        /// <summary>
        /// Access to remote <see cref="ProjectElement.Location"/>.
        /// </summary>
        public abstract ElementLocation Location { get; }

        /// <summary>
        /// Supports <see cref="ProjectElement.CopyFrom"/>.
        /// </summary>
        public abstract IReadOnlyCollection<XmlAttributeLink> Attributes { get; }

        /// <summary>
        /// Supports <see cref="ProjectElement.CopyFrom"/>.
        /// return raw xml content of the element if it has exactly 1 text child
        /// </summary>
        public abstract string PureText { get; }

        /// <summary>
        /// Required to implement Attribute access for remote element.
        /// </summary>
        public abstract ElementLocation GetAttributeLocation(string attributeName);

        /// <summary>
        /// Required to implement Attribute access for remote element.
        /// </summary>
        public abstract string GetAttributeValue(string attributeName, bool nullIfNotExists);

        /// <summary>
        /// Required to implement Attribute access for remote element.
        /// </summary>
        public abstract void SetOrRemoveAttribute(string name, string value, bool clearAttributeCache, string reason, string param);

        /// <summary>
        /// Facilitate remoting to remote <see cref="ProjectElement.CopyFrom"/>.
        /// </summary>
        public abstract void CopyFrom(ProjectElement element);

        /// <summary>
        /// Facilitate remoting to remote <see cref="ProjectElement.CreateNewInstance(ProjectRootElement)"/>.
        /// </summary>
        public abstract ProjectElement CreateNewInstance(ProjectRootElement owner);

        /// <summary>
        /// Utility function for ExternalProjects provider
        /// </summary>
        public static bool GetExpressedAsAttribute(ProjectElement xml) => xml.ExpressedAsAttribute;
        public static void SetExpressedAsAttribute(ProjectElement xml, bool value) => xml.ExpressedAsAttribute = value;
        public static ElementLocation GetAttributeLocation(ProjectElement xml, string attributeName) => xml.GetAttributeLocation(attributeName);
        public static string GetAttributeValue(ProjectElement xml, string attributeName, bool nullIfNotExists) => xml.GetAttributeValue(attributeName, nullIfNotExists);
        public static void SetOrRemoveAttribute(ProjectElement xml, string name, string value, bool clearAttributeCache, string reason, string param) => xml.SetOrRemoveAttributeForLink(name, value, clearAttributeCache, reason, param);
        public static void MarkDirty(ProjectElement xml, string reason, string param) => xml.MarkDirty(reason, param);
        public static ProjectElement CreateNewInstance(ProjectElement xml, ProjectRootElement owner) => ProjectElement.CreateNewInstance(xml, owner);

        public static string GetPureText(ProjectElement xml)
        {
            string result = null;
            if (xml.XmlElement.ChildNodes.Count == 1 && xml.XmlElement.FirstChild.NodeType == XmlNodeType.Text)
            {
                result = xml.XmlElement.FirstChild.Value;
            }

            return result;
        }
        public static IReadOnlyCollection<XmlAttributeLink> GetAttributes(ProjectElement xml)
        {
            List<XmlAttributeLink> result = new List<XmlAttributeLink>();
            foreach (XmlAttribute attribute in xml.XmlElement.Attributes)
            {
                result.Add(new XmlAttributeLink(attribute.LocalName, attribute.Value, attribute.NamespaceURI));
            }

            return result;
        }
    }

    public struct XmlAttributeLink
    {
        public XmlAttributeLink(string localName, string value, string namespaceUri)
        {
            this.LocalName = localName;
            this.Value = value;
            this.NamespaceURI = namespaceUri;
        }

        public string LocalName { get; }
        public string Value { get; }
        public string NamespaceURI { get; }
    }

    // the "equivalence" classes in cases when we don't need additional functionality,
    // but want to allow for such to be added in the future.

    /// <summary>
    /// External projects support.
    /// Allow for creating a local representation to external object of type <see cref="ProjectOnErrorElement"/>
    /// </summary>
    public abstract class ProjectOnErrorElementLink : ProjectElementLink { }

    /// <summary>
    /// External projects support.
    /// Allow for creating a local representation to external object of type <see cref="ProjectOutputElement"/>
    /// </summary>
    public abstract class ProjectOutputElementLink : ProjectElementLink { }
}
