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

using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities;

#nullable disable

namespace Microsoft.Build.Evaluation
{
    /// <summary>
    /// This class represents a token in the Complex Conditionals grammar.  It's
    /// really just a bag that contains the type of the token and the string that
    /// was parsed into the token.  This isn't very useful for operators, but
    /// is useful for strings and such.
    /// </summary>
    internal sealed class Token
    {
        internal static readonly Token Comma = new Token(TokenType.Comma);
        internal static readonly Token LeftParenthesis = new Token(TokenType.LeftParenthesis);
        internal static readonly Token RightParenthesis = new Token(TokenType.RightParenthesis);
        internal static readonly Token LessThan = new Token(TokenType.LessThan);
        internal static readonly Token GreaterThan = new Token(TokenType.GreaterThan);
        internal static readonly Token LessThanOrEqualTo = new Token(TokenType.LessThanOrEqualTo);
        internal static readonly Token GreaterThanOrEqualTo = new Token(TokenType.GreaterThanOrEqualTo);
        internal static readonly Token And = new Token(TokenType.And);
        internal static readonly Token Or = new Token(TokenType.Or);
        internal static readonly Token EqualTo = new Token(TokenType.EqualTo);
        internal static readonly Token NotEqualTo = new Token(TokenType.NotEqualTo);
        internal static readonly Token Not = new Token(TokenType.Not);
        internal static readonly Token EndOfInput = new Token(TokenType.EndOfInput);

        /// <summary>
        /// Valid tokens
        /// </summary>
        internal enum TokenType
        {
            Comma,
            LeftParenthesis,
            RightParenthesis,

            LessThan,
            GreaterThan,
            LessThanOrEqualTo,
            GreaterThanOrEqualTo,

            And,
            Or,

            EqualTo,
            NotEqualTo,
            Not,

            Property,
            String,
            Numeric,
            ItemList,
            ItemMetadata,
            Function,

            EndOfInput,
        }

        private TokenType _tokenType;
        private string _tokenString;

        /// <summary>
        /// Constructor for types that don't have values
        /// </summary>
        /// <param name="tokenType"></param>
        private Token(TokenType tokenType)
        {
            _tokenType = tokenType;
            _tokenString = null;
        }

        /// <summary>
        /// Constructor takes the token type and the string that
        /// represents the token
        /// </summary>
        /// <param name="type"></param>
        /// <param name="tokenString"></param>
        internal Token(TokenType type, string tokenString)
            : this(type, tokenString, false /* not expandable */)
        { }

        /// <summary>
        /// Constructor takes the token type and the string that
        /// represents the token.
        /// If the string may contain content that needs expansion, expandable is set.
        /// </summary>
        internal Token(TokenType type, string tokenString, bool expandable)
        {
            ErrorUtilities.VerifyThrow(
                type == TokenType.Property ||
                type == TokenType.String ||
                type == TokenType.Numeric ||
                type == TokenType.ItemList ||
                type == TokenType.ItemMetadata ||
                type == TokenType.Function,
                "Unexpected token type");

            ErrorUtilities.VerifyThrowInternalNull(tokenString);

            _tokenType = type;
            _tokenString = tokenString;
            this.Expandable = expandable;
        }

        /// <summary>
        /// Whether the content potentially has expandable content,
        /// such as a property expression or escaped character.
        /// </summary>
        internal bool Expandable
        {
            get;
            set;
        }

        /// <summary>
        ///
        /// </summary>
        /// <param name="type"></param>
        /// <returns></returns>
        internal bool IsToken(TokenType type)
        {
            return _tokenType == type;
        }

        internal string String
        {
            get
            {
                if (_tokenString != null)
                {
                    return _tokenString;
                }

                // Return a token string for
                // an error message.
                switch (_tokenType)
                {
                    case TokenType.Comma:
                        return ",";
                    case TokenType.LeftParenthesis:
                        return "(";
                    case TokenType.RightParenthesis:
                        return ")";
                    case TokenType.LessThan:
                        return "<";
                    case TokenType.GreaterThan:
                        return ">";
                    case TokenType.LessThanOrEqualTo:
                        return "<=";
                    case TokenType.GreaterThanOrEqualTo:
                        return ">=";
                    case TokenType.And:
                        return "and";
                    case TokenType.Or:
                        return "or";
                    case TokenType.EqualTo:
                        return "==";
                    case TokenType.NotEqualTo:
                        return "!=";
                    case TokenType.Not:
                        return "!";
                    case TokenType.EndOfInput:
                        return null;
                    default:
                        ErrorUtilities.ThrowInternalErrorUnreachable();
                        return null;
                }
            }
        }
    }
}
