// 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.Collections.Generic;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Xaml.Schema;

namespace MS.Internal.Xaml.Parser
{
    class GenericTypeNameParser
    {
        [Serializable]
        class TypeNameParserException : Exception
        {
            public TypeNameParserException(string message)
                : base(message)
            {
            }
#pragma warning disable SYSLIB0051 // Type or member is obsolete
            protected TypeNameParserException(SerializationInfo si, StreamingContext sc) : base(si, sc)
            {
            }
#pragma warning restore SYSLIB0051 // Type or member is obsolete
        }

        private GenericTypeNameScanner _scanner;
        private string _inputText;
        private Func<string, string> _prefixResolver;
        Stack<TypeNameFrame> _stack;

        public GenericTypeNameParser(Func<string, string> prefixResolver)
        {
            _prefixResolver = prefixResolver;
        }

        public static XamlTypeName ParseIfTrivalName(string text, Func<string, string> prefixResolver, out string error)
        {
            int parenIdx = text.IndexOf('(');
            int bracketIdx = text.IndexOf('[');
            if (parenIdx != -1 || bracketIdx != -1)
            {
                error = string.Empty;
                return null;
            }

            string prefix;
            string simpleName;

            error = string.Empty;
            if (!XamlQualifiedName.Parse(text, out prefix, out simpleName))
            {
                error = SR.Format(SR.InvalidTypeString, text);
                return null;
            }

            string ns = prefixResolver(prefix);
            if (String.IsNullOrEmpty(ns))
            {
                error = SR.Format(SR.PrefixNotFound, prefix);
                return null;
            }
            XamlTypeName xamlTypeName = new XamlTypeName(ns, simpleName);
            return xamlTypeName;
        }

        public XamlTypeName ParseName(string text, out string error)
        {
            error = string.Empty;
            _scanner = new GenericTypeNameScanner(text);
            _inputText = text;

            StartStack();

            try
            {
                _scanner.Read();
                P_XamlTypeName();
                if (_scanner.Token != GenericTypeNameScannerToken.NONE)
                {
                    ThrowOnBadInput();
                }
            }
            catch (TypeNameParserException ex)
            {
                error = ex.Message;
            }

            XamlTypeName typeName = null;
            if (String.IsNullOrEmpty(error))
            {
                typeName = CollectNameFromStack();
            }
            return typeName;
        }

        public IList<XamlTypeName> ParseList(string text, out string error)
        {
            _scanner = new GenericTypeNameScanner(text);
            _inputText = text;
            StartStack();

            error = string.Empty;
            try
            {
                _scanner.Read();
                P_XamlTypeNameList();
                if (_scanner.Token != GenericTypeNameScannerToken.NONE)
                {
                    ThrowOnBadInput();
                }
            }
            catch (TypeNameParserException ex)
            {
                error = ex.Message;
            }

            IList<XamlTypeName> typeNameList = null;
            if (String.IsNullOrEmpty(error))
            {
                typeNameList = CollectNameListFromStack();
            }
            return typeNameList;
        }

        // XamlTypeName     ::= SimpleTypeName TypeParameters? Subscript*
        // SimpleTypeName   ::= (Prefix ‘:’)? TypeName
        // TypeParameters   ::= ‘(‘ XamlTypeNameList ‘)’
        // XamlTypeNameList ::= XamlTypeName NameListExt*
        // NameListExt      ::= ‘,’ XamlTypeName
        // Subscript        ::= ‘[’ ‘,’* ‘]’

        // XamlTypeName     ::= SimpleTypeName TypeParameters? Subscript*
        //
        private void P_XamlTypeName()
        {
            // Required
            if (_scanner.Token != GenericTypeNameScannerToken.NAME)
            {
                ThrowOnBadInput();
            }
            P_SimpleTypeName();

            // Optional
            if (_scanner.Token == GenericTypeNameScannerToken.OPEN)
            {
                P_TypeParameters();
            }

            // Optional
            if (_scanner.Token == GenericTypeNameScannerToken.SUBSCRIPT)
            {
                P_RepeatingSubscript();
            }

            Callout_EndOfType();
        }


        // SimpleTypeName   ::= (Prefix ‘:’)? TypeName
        //
        private void P_SimpleTypeName()
        {
            // caller checks this.
            Debug.Assert(_scanner.Token == GenericTypeNameScannerToken.NAME);

            string prefix = string.Empty;
            string name = _scanner.MultiCharTokenText;
            _scanner.Read();

            // Colon is optional.
            if (_scanner.Token == GenericTypeNameScannerToken.COLON)
            {
                prefix = name;
                _scanner.Read();

                // IF there was a colon then there must be a name following.
                if (_scanner.Token != GenericTypeNameScannerToken.NAME)
                {
                    ThrowOnBadInput();
                }
                name = _scanner.MultiCharTokenText;
                _scanner.Read();

            }
            Callout_FoundName(prefix, name);
        }

        // TypeParameters   ::= ‘(‘ XamlTypeNameList ‘)’
        //
        private void P_TypeParameters()
        {
            // Required
            // caller checks this.
            Debug.Assert(_scanner.Token == GenericTypeNameScannerToken.OPEN);
            _scanner.Read();

            P_XamlTypeNameList();

            // Required
            if (_scanner.Token != GenericTypeNameScannerToken.CLOSE)
            {
                ThrowOnBadInput();
            }
            _scanner.Read();
        }

        // XamlTypeNameList ::= XamlTypeName NameListExt*
        //
        private void P_XamlTypeNameList()
        {
            P_XamlTypeName();

            // optional zero or more.
            while (_scanner.Token == GenericTypeNameScannerToken.COMMA)
            {
                P_NameListExt();
            }
        }

        // NameListExt      ::= ‘,’ XamlTypeName
        //
        private void P_NameListExt()
        {
            // Caller checked this.
            Debug.Assert(_scanner.Token == GenericTypeNameScannerToken.COMMA);
            _scanner.Read();

            P_XamlTypeName();
        }

        // Subscript        ::= ‘[’ ‘,’* ‘]’
        //
        private void P_RepeatingSubscript()
        {
            // caller checks this.
            Debug.Assert(_scanner.Token == GenericTypeNameScannerToken.SUBSCRIPT);

            do
            {
                Callout_Subscript(_scanner.MultiCharTokenText);
                _scanner.Read();
            }
            while (_scanner.Token == GenericTypeNameScannerToken.SUBSCRIPT);
        }

        private void ThrowOnBadInput()
        {
            throw new TypeNameParserException(SR.Format(SR.InvalidCharInTypeName, _scanner.ErrorCurrentChar, _inputText));
        }

        private void StartStack()
        {
            _stack = new Stack<TypeNameFrame>();
            TypeNameFrame frame;
            frame = new TypeNameFrame();
            _stack.Push(frame);
        }

        void Callout_FoundName(string prefix, string name)
        {
            TypeNameFrame frame = new TypeNameFrame();
            frame.Name = name;
            string ns = _prefixResolver(prefix);
            frame.Namespace = ns ?? throw new TypeNameParserException(SR.Format(SR.PrefixNotFound, prefix));
            _stack.Push(frame);
        }

        void Callout_EndOfType()
        {
            TypeNameFrame frame = _stack.Pop();
            XamlTypeName typeName = new XamlTypeName(frame.Namespace, frame.Name, frame.TypeArgs);

            frame = _stack.Peek();
            if (frame.TypeArgs == null)
            {
                frame.AllocateTypeArgs();
            }
            frame.TypeArgs.Add(typeName);
        }

        void Callout_Subscript(string subscript)
        {
            TypeNameFrame frame = _stack.Peek();
            frame.Name += subscript;
        }

        private XamlTypeName CollectNameFromStack()
        {
            if (_stack.Count != 1)
            {
                throw new TypeNameParserException(SR.Format(SR.InvalidTypeString, _inputText));
            }

            TypeNameFrame frame = _stack.Peek();
            if (frame.TypeArgs.Count != 1)
            {
                throw new TypeNameParserException(SR.Format(SR.InvalidTypeString, _inputText));
            }

            XamlTypeName xamlTypeName = frame.TypeArgs[0];
            return xamlTypeName;
        }

        private IList<XamlTypeName> CollectNameListFromStack()
        {
            if (_stack.Count != 1)
            {
                throw new TypeNameParserException(SR.Format(SR.InvalidTypeString, _inputText));
            }

            TypeNameFrame frame = _stack.Peek();

            List<XamlTypeName> xamlTypeNameList = frame.TypeArgs;
            return xamlTypeNameList;
        }
    }

    class TypeNameFrame
    {
        List<XamlTypeName> _typeArgs;

        public string Namespace { get; set; }
        public string Name { get; set; }
        public List<XamlTypeName> TypeArgs { get { return _typeArgs; } }

        public void AllocateTypeArgs()
        {
            _typeArgs = new List<XamlTypeName>();
        }
    }
}
