// 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 Microsoft.CSharp.RuntimeBinder.Syntax;

namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
    internal static class Operators
    {
        private sealed class OperatorInfo
        {
            public readonly TokenKind TokenKind;
            public readonly PredefinedName MethodName;
            public readonly ExpressionKind ExpressionKind;

            public OperatorInfo(TokenKind kind, PredefinedName pn, ExpressionKind e)
            {
                TokenKind = kind;
                MethodName = pn;
                ExpressionKind = e;
            }
        }

        private static readonly OperatorInfo[] s_operatorInfos = {
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Equal,            PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.PlusEqual,        PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.Add               ),
            new OperatorInfo(TokenKind.MinusEqual,       PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.Subtract          ),
            new OperatorInfo(TokenKind.SplatEqual,       PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.Multiply          ),
            new OperatorInfo(TokenKind.SlashEqual,       PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.Divide            ),
            new OperatorInfo(TokenKind.PercentEqual,     PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.Modulo            ),
            new OperatorInfo(TokenKind.AndEqual,         PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.BitwiseAnd        ),
            new OperatorInfo(TokenKind.HatEqual,         PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.BitwiseExclusiveOr),
            new OperatorInfo(TokenKind.BarEqual,         PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.BitwiseOr         ),
            new OperatorInfo(TokenKind.LeftShiftEqual,   PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.LeftShirt         ),
            new OperatorInfo(TokenKind.RightShiftEqual,  PredefinedName.PN_COUNT,                ExpressionKind.MultiOffset + (int)ExpressionKind.RightShift        ),
            new OperatorInfo(TokenKind.Question,         PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.QuestionQuestion, PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.LogicalOr,        PredefinedName.PN_COUNT,                ExpressionKind.LogicalOr                                           ),
            new OperatorInfo(TokenKind.LogicalAnd,       PredefinedName.PN_COUNT,                ExpressionKind.LogicalAnd                                          ),
            new OperatorInfo(TokenKind.Bar,              PredefinedName.PN_OPBITWISEOR,          ExpressionKind.BitwiseOr                                           ),
            new OperatorInfo(TokenKind.Hat,              PredefinedName.PN_OPXOR,                ExpressionKind.BitwiseExclusiveOr                                  ),
            new OperatorInfo(TokenKind.Ampersand,        PredefinedName.PN_OPBITWISEAND,         ExpressionKind.BitwiseAnd                                          ),
            new OperatorInfo(TokenKind.EqualEqual,       PredefinedName.PN_OPEQUALITY,           ExpressionKind.Eq                                                  ),
            new OperatorInfo(TokenKind.NotEqual,         PredefinedName.PN_OPINEQUALITY,         ExpressionKind.NotEq                                               ),
            new OperatorInfo(TokenKind.LessThan,         PredefinedName.PN_OPLESSTHAN,           ExpressionKind.LessThan                                            ),
            new OperatorInfo(TokenKind.LessThanEqual,    PredefinedName.PN_OPLESSTHANOREQUAL,    ExpressionKind.LessThanOrEqual                                     ),
            new OperatorInfo(TokenKind.GreaterThan,      PredefinedName.PN_OPGREATERTHAN,        ExpressionKind.GreaterThan                                         ),
            new OperatorInfo(TokenKind.GreaterThanEqual, PredefinedName.PN_OPGREATERTHANOREQUAL, ExpressionKind.GreaterThanOrEqual                                  ),
            new OperatorInfo(TokenKind.Is,               PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.As,               PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.LeftShift,        PredefinedName.PN_OPLEFTSHIFT,          ExpressionKind.LeftShirt                                           ),
            new OperatorInfo(TokenKind.RightShift,       PredefinedName.PN_OPRIGHTSHIFT,         ExpressionKind.RightShift                                          ),
            new OperatorInfo(TokenKind.Plus,             PredefinedName.PN_OPPLUS,               ExpressionKind.Add                                                 ),
            new OperatorInfo(TokenKind.Minus,            PredefinedName.PN_OPMINUS,              ExpressionKind.Subtract                                            ),
            new OperatorInfo(TokenKind.Splat,            PredefinedName.PN_OPMULTIPLY,           ExpressionKind.Multiply                                            ),
            new OperatorInfo(TokenKind.Slash,            PredefinedName.PN_OPDIVISION,           ExpressionKind.Divide                                              ),
            new OperatorInfo(TokenKind.Percent,          PredefinedName.PN_OPMODULUS,            ExpressionKind.Modulo                                              ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Plus,             PredefinedName.PN_OPUNARYPLUS,          ExpressionKind.UnaryPlus                                           ),
            new OperatorInfo(TokenKind.Minus,            PredefinedName.PN_OPUNARYMINUS,         ExpressionKind.Negate                                              ),
            new OperatorInfo(TokenKind.Tilde,            PredefinedName.PN_OPCOMPLEMENT,         ExpressionKind.BitwiseNot                                          ),
            new OperatorInfo(TokenKind.Bang,             PredefinedName.PN_OPNEGATION,           ExpressionKind.LogicalNot                                          ),
            new OperatorInfo(TokenKind.PlusPlus,         PredefinedName.PN_OPINCREMENT,          ExpressionKind.Add                                                 ),
            new OperatorInfo(TokenKind.MinusMinus,       PredefinedName.PN_OPDECREMENT,          ExpressionKind.Subtract                                            ),
            new OperatorInfo(TokenKind.TypeOf,           PredefinedName.PN_COUNT,                ExpressionKind.TypeOf                                              ),
            new OperatorInfo(TokenKind.Checked,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unchecked,        PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.MakeRef,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.RefValue,         PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.RefType,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.ArgList,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Splat,            PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Ampersand,        PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Colon,            PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.This,             PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Base,             PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Null,             PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.True,             PredefinedName.PN_OPTRUE,               ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.False,            PredefinedName.PN_OPFALSE,              ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.PlusPlus,         PredefinedName.PN_COUNT,                ExpressionKind.Add                                                 ),
            new OperatorInfo(TokenKind.MinusMinus,       PredefinedName.PN_COUNT,                ExpressionKind.Subtract                                            ),
            new OperatorInfo(TokenKind.Dot,              PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Implicit,         PredefinedName.PN_OPIMPLICITMN,         ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Explicit,         PredefinedName.PN_OPEXPLICITMN,         ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_OPEQUALS,             ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_OPCOMPARE,            ExpressionKind.ExpressionKindCount                                 ),
            new OperatorInfo(TokenKind.Unknown,          PredefinedName.PN_COUNT,                ExpressionKind.ExpressionKindCount                                 )
        };

        private static Dictionary<Name, string> s_operatorsByName;

        private static Dictionary<Name, string> GetOperatorByName()
        {
            Dictionary<Name, string> dict = new Dictionary<Name, string>(28)
            {
                {NameManager.GetPredefinedName(PredefinedName.PN_OPEQUALS), "equals"},
                {NameManager.GetPredefinedName(PredefinedName.PN_OPCOMPARE), "compare" }
            };

            foreach (OperatorInfo opInfo in s_operatorInfos)
            {
                PredefinedName predefName = opInfo.MethodName;
                TokenKind token = opInfo.TokenKind;
                if (predefName != PredefinedName.PN_COUNT && token != TokenKind.Unknown)
                {
                    dict.Add(NameManager.GetPredefinedName(predefName), TokenFacts.GetText(token));
                }
            }

            return dict;
        }

        private static OperatorInfo GetInfo(OperatorKind op) => s_operatorInfos[(int)op];

        public static string OperatorOfMethodName(Name name) =>
            (s_operatorsByName ??= GetOperatorByName())[name];

        public static string GetDisplayName(OperatorKind op) => TokenFacts.GetText(GetInfo(op).TokenKind);

        public static ExpressionKind GetExpressionKind(OperatorKind op) => GetInfo(op).ExpressionKind;
    }
}
