// 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;
using System.Diagnostics;
using System.Reflection;
using Microsoft.CSharp.RuntimeBinder.Syntax;

namespace Microsoft.CSharp.RuntimeBinder.Semantics
{
    // Name used for AGGDECLs in the symbol table.

    // AggregateSymbol - a symbol representing an aggregate type. These are classes,
    // interfaces, and structs. Parent is a namespace or class. Children are methods,
    // properties, and member variables, and types (including its own AGGTYPESYMs).

    internal class AggregateSymbol : NamespaceOrAggregateSymbol
    {
        public Type AssociatedSystemType;
        public Assembly AssociatedAssembly;

        // The instance type. Created when first needed.
        private AggregateType _atsInst;

        private AggregateType _pBaseClass;     // For a class/struct/enum, the base class. For iface: unused.
        private AggregateType _pUnderlyingType; // For enum, the underlying type. For iface, the resolved CoClass. Not used for class/struct.

        private TypeArray _ifaces;         // The explicit base interfaces for a class or interface.
        private TypeArray _ifacesAll;      // Recursive closure of base interfaces ordered so an iface appears before all of its base ifaces.

        private TypeArray _typeVarsThis; // Type variables for this generic class, as declarations.
        private TypeArray _typeVarsAll;     // The type variables for this generic class and all containing classes.

        // First UD conversion operator. This chain is for this type only (not base types).
        // The hasConversion flag indicates whether this or any base types have UD conversions.
        private MethodSymbol _pConvFirst;

        // ------------------------------------------------------------------------
        // 
        // Put members that are bits under here in a contiguous section.
        //
        // ------------------------------------------------------------------------

        private AggKindEnum _aggKind;

        // Where this came from - fabricated, source, import
        // Fabricated AGGs have isSource == true but hasParseTree == false.
        // N.B.: in incremental builds, it is quite possible for
        // isSource==TRUE and hasParseTree==FALSE. Be
        // sure you use the correct variable for what you are trying to do!

        // Predefined
        private bool _isPredefined;    // A special predefined type.
        private PredefinedType _iPredef;        // index of the predefined type, if isPredefined.

        // Flags
        private bool _isAbstract;      // Can it be instantiated?
        private bool _isSealed;        // Can it be derived from?

        // Constructors
        private bool _hasPubNoArgCtor; // Whether it has a public instance constructor taking no args

        // User defined operators

        private bool _isSkipUDOps; // Never check for user defined operators on this type (eg, decimal, string, delegate).

        // When this is unset we don't know if we have conversions.  When this 
        // is set it indicates if this type or any base type has user defined 
        // conversion operators
        private bool? _hasConversion;

        // ----------------------------------------------------------------------------
        // AggregateSymbol
        // ----------------------------------------------------------------------------

        public AggregateSymbol GetBaseAgg()
        {
            return _pBaseClass?.OwningAggregate;
        }

        public AggregateType getThisType()
        {
            if (_atsInst == null)
            {
                Debug.Assert(GetTypeVars() == GetTypeVarsAll() || isNested());

                AggregateType pOuterType = isNested() ? GetOuterAgg().getThisType() : null;

                _atsInst = TypeManager.GetAggregate(this, pOuterType, GetTypeVars());
            }

            //Debug.Assert(GetTypeVars().Size == atsInst.GenericArguments.Count);
            return _atsInst;
        }

        public bool FindBaseAgg(AggregateSymbol agg)
        {
            for (AggregateSymbol aggT = this; aggT != null; aggT = aggT.GetBaseAgg())
            {
                if (aggT == agg)
                    return true;
            }
            return false;
        }

        public NamespaceOrAggregateSymbol Parent => parent as NamespaceOrAggregateSymbol;

        public bool isNested() => parent is AggregateSymbol;

        public AggregateSymbol GetOuterAgg() => parent as AggregateSymbol;

        public bool isPredefAgg(PredefinedType pt)
        {
            return _isPredefined && (PredefinedType)_iPredef == pt;
        }

        // ----------------------------------------------------------------------------
        // The following are the Accessor functions for AggregateSymbol.
        // ----------------------------------------------------------------------------

        public AggKindEnum AggKind()
        {
            return (AggKindEnum)_aggKind;
        }

        public void SetAggKind(AggKindEnum aggKind)
        {
            // NOTE: When importing can demote types:
            //  - enums with no underlying type go to struct
            //  - delegates which are abstract or have no .ctor/Invoke method goto class
            _aggKind = aggKind;

            //An interface is always abstract
            if (aggKind == AggKindEnum.Interface)
            {
                SetAbstract(true);
            }
        }

        public bool IsClass()
        {
            return AggKind() == AggKindEnum.Class;
        }

        public bool IsDelegate()
        {
            return AggKind() == AggKindEnum.Delegate;
        }

        public bool IsInterface()
        {
            return AggKind() == AggKindEnum.Interface;
        }

        public bool IsStruct()
        {
            return AggKind() == AggKindEnum.Struct;
        }

        public bool IsEnum()
        {
            return AggKind() == AggKindEnum.Enum;
        }

        public bool IsValueType()
        {
            return AggKind() == AggKindEnum.Struct || AggKind() == AggKindEnum.Enum;
        }

        public bool IsRefType()
        {
            return AggKind() == AggKindEnum.Class ||
                AggKind() == AggKindEnum.Interface || AggKind() == AggKindEnum.Delegate;
        }

        public bool IsStatic()
        {
            return (_isAbstract && _isSealed);
        }

        public bool IsAbstract()
        {
            return _isAbstract;
        }

        public void SetAbstract(bool @abstract)
        {
            _isAbstract = @abstract;
        }

        public bool IsPredefined()
        {
            return _isPredefined;
        }

        public void SetPredefined(bool predefined)
        {
            _isPredefined = predefined;
        }

        public PredefinedType GetPredefType()
        {
            return (PredefinedType)_iPredef;
        }

        public void SetPredefType(PredefinedType predef)
        {
            _iPredef = predef;
        }

        public bool IsSealed()
        {
            return _isSealed == true;
        }

        public void SetSealed(bool @sealed)
        {
            _isSealed = @sealed;
        }

        ////////////////////////////////////////////////////////////////////////////////

        public bool HasConversion()
        {
            SymbolTable.AddConversionsForType(AssociatedSystemType);

            if (!_hasConversion.HasValue)
            {
                // ok, we tried defining all the conversions, and we didn't get anything
                // for this type.  However, we will still think this type has conversions
                // if it's base type has conversions.
                _hasConversion = GetBaseAgg() != null && GetBaseAgg().HasConversion();
            }

            return _hasConversion.Value;
        }

        ////////////////////////////////////////////////////////////////////////////////

        public void SetHasConversion()
        {
            _hasConversion = true;
        }

        ////////////////////////////////////////////////////////////////////////////////

        public bool HasPubNoArgCtor()
        {
            return _hasPubNoArgCtor == true;
        }

        public void SetHasPubNoArgCtor(bool hasPubNoArgCtor)
        {
            _hasPubNoArgCtor = hasPubNoArgCtor;
        }

        public bool IsSkipUDOps()
        {
            return _isSkipUDOps == true;
        }

        public void SetSkipUDOps(bool skipUDOps)
        {
            _isSkipUDOps = skipUDOps;
        }

        public TypeArray GetTypeVars()
        {
            return _typeVarsThis;
        }

        public void SetTypeVars(TypeArray typeVars)
        {
            if (typeVars == null)
            {
                _typeVarsThis = null;
                _typeVarsAll = null;
            }
            else
            {
                TypeArray outerTypeVars;
                if (GetOuterAgg() != null)
                {
                    Debug.Assert(GetOuterAgg().GetTypeVars() != null);
                    Debug.Assert(GetOuterAgg().GetTypeVarsAll() != null);

                    outerTypeVars = GetOuterAgg().GetTypeVarsAll();
                }
                else
                {
                    outerTypeVars = TypeArray.Empty;
                }

                _typeVarsThis = typeVars;
                _typeVarsAll = TypeArray.Concat(outerTypeVars, typeVars);
            }
        }

        public TypeArray GetTypeVarsAll()
        {
            return _typeVarsAll;
        }

        public AggregateType GetBaseClass()
        {
            return _pBaseClass;
        }

        public void SetBaseClass(AggregateType baseClass)
        {
            _pBaseClass = baseClass;
        }

        public AggregateType GetUnderlyingType()
        {
            return _pUnderlyingType;
        }

        public void SetUnderlyingType(AggregateType underlyingType)
        {
            _pUnderlyingType = underlyingType;
        }

        public TypeArray GetIfaces()
        {
            return _ifaces;
        }

        public void SetIfaces(TypeArray ifaces)
        {
            _ifaces = ifaces;
        }

        public TypeArray GetIfacesAll()
        {
            return _ifacesAll;
        }

        public void SetIfacesAll(TypeArray ifacesAll)
        {
            _ifacesAll = ifacesAll;
        }

        public MethodSymbol GetFirstUDConversion()
        {
            return _pConvFirst;
        }

        public void SetFirstUDConversion(MethodSymbol conv)
        {
            _pConvFirst = conv;
        }

        public bool InternalsVisibleTo(Assembly assembly) => TypeManager.InternalsVisibleTo(AssociatedAssembly, assembly);
    }
}
