﻿// 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.

using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Features.RQName.Nodes;

namespace Microsoft.CodeAnalysis.Features.RQName
{
    internal static class RQNodeBuilder
    {
        /// <summary>
        /// Builds the RQName for a given symbol.
        /// </summary>
        /// <returns>The node if it could be created, otherwise null</returns>
        public static RQNode? Build(ISymbol symbol)
            => symbol switch
            {
                INamespaceSymbol namespaceSymbol => BuildNamespace(namespaceSymbol),
                INamedTypeSymbol namedTypeSymbol => BuildUnconstructedNamedType(namedTypeSymbol),
                IMethodSymbol methodSymbol => BuildMethod(methodSymbol),
                IFieldSymbol fieldSymbol => BuildField(fieldSymbol),
                IEventSymbol eventSymbol => BuildEvent(eventSymbol),
                IPropertySymbol propertySymbol => BuildProperty(propertySymbol),
                _ => null,
            };

        private static RQNamespace BuildNamespace(INamespaceSymbol @namespace)
            => new(RQNodeBuilder.GetNameParts(@namespace));

        private static IList<string> GetNameParts(INamespaceSymbol @namespace)
        {
            var parts = new List<string>();

            if (@namespace == null)
            {
                return parts;
            }

            while (!@namespace.IsGlobalNamespace)
            {
                parts.Add(@namespace.Name);
                @namespace = @namespace.ContainingNamespace;
            }

            parts.Reverse();
            return parts;
        }

        private static RQUnconstructedType? BuildUnconstructedNamedType(INamedTypeSymbol type)
        {
            // Anything that is a valid RQUnconstructed types is ALWAYS safe for public APIs

            if (type == null)
            {
                return null;
            }

            // Anonymous types are unsupported
            if (type.IsAnonymousType)
            {
                return null;
            }

            // the following types are supported for BuildType() used in signatures, but are not supported
            // for UnconstructedTypes
            if (type != type.ConstructedFrom || type.SpecialType == SpecialType.System_Void)
            {
                return null;
            }

            // make an RQUnconstructedType
            var namespaceNames = RQNodeBuilder.GetNameParts(@type.ContainingNamespace);
            var typeInfos = new List<RQUnconstructedTypeInfo>();

            for (var currentType = type; currentType != null; currentType = currentType.ContainingType)
            {
                typeInfos.Insert(0, new RQUnconstructedTypeInfo(currentType.Name, currentType.TypeParameters.Length));
            }

            return new RQUnconstructedType(namespaceNames, typeInfos);
        }

        private static RQMember? BuildField(IFieldSymbol symbol)
        {
            var containingType = BuildUnconstructedNamedType(symbol.ContainingType);

            if (containingType == null)
            {
                return null;
            }

            return new RQMemberVariable(containingType, symbol.Name);
        }

        private static RQProperty? BuildProperty(IPropertySymbol symbol)
        {
            RQMethodPropertyOrEventName name = symbol.IsIndexer
                ? RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryIndexerName()
                : RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryPropertyName(symbol.Name);

            if (symbol.ExplicitInterfaceImplementations.Any())
            {
                if (symbol.ExplicitInterfaceImplementations.Length > 1)
                {
                    return null;
                }

                var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);

                if (interfaceType != null)
                {
                    name = new RQExplicitInterfaceMemberName(
                        interfaceType,
                        (RQOrdinaryMethodPropertyOrEventName)name);
                }
            }

            var containingType = BuildUnconstructedNamedType(symbol.ContainingType);

            if (containingType == null)
            {
                return null;
            }

            var parameterList = BuildParameterList(symbol.Parameters);

            if (parameterList == null)
            {
                return null;
            }

            return new RQProperty(containingType, name, typeParameterCount: 0, parameters: parameterList);
        }

        private static IList<RQParameter>? BuildParameterList(ImmutableArray<IParameterSymbol> parameters)
        {
            var parameterList = new List<RQParameter>();

            foreach (var parameter in parameters)
            {
                var parameterType = BuildType(parameter.Type);

                if (parameterType == null)
                {
                    return null;
                }

                if (parameter.RefKind == RefKind.Out)
                {
                    parameterList.Add(new RQOutParameter(parameterType));
                }
                else if (parameter.RefKind == RefKind.Ref)
                {
                    parameterList.Add(new RQRefParameter(parameterType));
                }
                else
                {
                    parameterList.Add(new RQNormalParameter(parameterType));
                }
            }

            return parameterList;
        }

        private static RQEvent? BuildEvent(IEventSymbol symbol)
        {
            var containingType = BuildUnconstructedNamedType(symbol.ContainingType);

            if (containingType == null)
            {
                return null;
            }

            RQMethodPropertyOrEventName name = RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryEventName(symbol.Name);

            if (symbol.ExplicitInterfaceImplementations.Any())
            {
                if (symbol.ExplicitInterfaceImplementations.Length > 1)
                {
                    return null;
                }

                var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);

                if (interfaceType != null)
                {
                    name = new RQExplicitInterfaceMemberName(interfaceType, (RQOrdinaryMethodPropertyOrEventName)name);
                }
            }

            return new RQEvent(containingType, name);
        }

        private static RQMethod? BuildMethod(IMethodSymbol symbol)
        {
            if (symbol.MethodKind is MethodKind.UserDefinedOperator or
                MethodKind.BuiltinOperator or
                MethodKind.EventAdd or
                MethodKind.EventRemove or
                MethodKind.PropertySet or
                MethodKind.PropertyGet)
            {
                return null;
            }

            RQMethodPropertyOrEventName name;

            if (symbol.MethodKind == MethodKind.Constructor)
            {
                name = RQOrdinaryMethodPropertyOrEventName.CreateConstructorName();
            }
            else if (symbol.MethodKind == MethodKind.Destructor)
            {
                name = RQOrdinaryMethodPropertyOrEventName.CreateDestructorName();
            }
            else
            {
                name = RQOrdinaryMethodPropertyOrEventName.CreateOrdinaryMethodName(symbol.Name);
            }

            if (symbol.ExplicitInterfaceImplementations.Any())
            {
                if (symbol.ExplicitInterfaceImplementations.Length > 1)
                {
                    return null;
                }

                var interfaceType = BuildType(symbol.ExplicitInterfaceImplementations.Single().ContainingType);

                if (interfaceType != null)
                {
                    name = new RQExplicitInterfaceMemberName(interfaceType, (RQOrdinaryMethodPropertyOrEventName)name);
                }
            }

            var containingType = BuildUnconstructedNamedType(symbol.ContainingType);

            if (containingType == null)
            {
                return null;
            }

            var typeParamCount = symbol.TypeParameters.Length;
            var parameterList = BuildParameterList(symbol.Parameters);

            if (parameterList == null)
            {
                return null;
            }

            return new RQMethod(containingType, name, typeParamCount, parameterList);
        }

        private static RQType? BuildType(ITypeSymbol symbol)
        {
            if (symbol.IsAnonymousType)
            {
                return null;
            }

            if (symbol.SpecialType == SpecialType.System_Void)
            {
                return RQVoidType.Singleton;
            }
            else if (symbol is IPointerTypeSymbol pointerType)
            {
                var pointedAtType = BuildType(pointerType.PointedAtType);
                if (pointedAtType == null)
                {
                    return null;
                }

                return new RQPointerType(pointedAtType);
            }
            else if (symbol is IArrayTypeSymbol arrayType)
            {
                var elementType = BuildType(arrayType.ElementType);
                if (elementType == null)
                {
                    return null;
                }

                return new RQArrayType(arrayType.Rank, elementType);
            }
            else if (symbol.TypeKind == TypeKind.TypeParameter)
            {
                return new RQTypeVariableType(symbol.Name);
            }
            else if (symbol.TypeKind == TypeKind.Unknown)
            {
                return new RQErrorType(symbol.Name);
            }
            else if (symbol.TypeKind == TypeKind.Dynamic)
            {
                // NOTE: Because RQNames were defined as an interchange format before C# had "dynamic", and we didn't want 
                // all consumers to have to update their logic to crack the attributes about whether something is object or
                // not, we just erase dynamic to object here.
                return RQType.ObjectType;
            }
            else if (symbol is INamedTypeSymbol namedTypeSymbol)
            {
                var definingType = namedTypeSymbol.ConstructedFrom ?? namedTypeSymbol;

                var typeChain = new List<INamedTypeSymbol>();
                var type = namedTypeSymbol;
                typeChain.Add(namedTypeSymbol);

                while (type.ContainingType != null)
                {
                    type = type.ContainingType;
                    typeChain.Add(type);
                }

                typeChain.Reverse();

                var typeArgumentList = new List<RQType>();

                foreach (var entry in typeChain)
                {
                    foreach (var typeArgument in entry.TypeArguments)
                    {
                        var rqType = BuildType(typeArgument);
                        if (rqType == null)
                        {
                            return null;
                        }

                        typeArgumentList.Add(rqType);
                    }
                }

                var containingType = BuildUnconstructedNamedType(definingType);

                if (containingType == null)
                {
                    return null;
                }

                return new RQConstructedType(containingType, typeArgumentList);
            }
            else
            {
                return null;
            }
        }
    }
}
