//
// enum.cs: Enum handling.
//
// Author: Miguel de Icaza (miguel@gnu.org)
//         Ravi Pratap     (ravi@ximian.com)
//         Marek Safar     (marek.safar@seznam.cz)
//
// Dual licensed under the terms of the MIT X11 or GNU GPL
//
// Copyright 2001 Ximian, Inc (http://www.ximian.com)
// Copyright 2003-2003 Novell, Inc (http://www.novell.com)
// Copyright 2011 Xamarin Inc
//

using System;

#if STATIC
using MetaType = IKVM.Reflection.Type;
using IKVM.Reflection;
#else
using MetaType = System.Type;
using System.Reflection;
#endif

namespace ICSharpCode.NRefactory.MonoCSharp {

	public class EnumMember : Const
	{
		class EnumTypeExpr : TypeExpr
		{
			public override TypeSpec ResolveAsType (IMemberContext ec, bool allowUnboundTypeArguments)
			{
				type = ec.CurrentType;
				eclass = ExprClass.Type;
				return type;
			}
		}

		public EnumMember (Enum parent, MemberName name, Attributes attrs)
			: base (parent, new EnumTypeExpr (), Modifiers.PUBLIC, name, attrs)
		{
		}

		static bool IsValidEnumType (TypeSpec t)
		{
			switch (t.BuiltinType) {
			case BuiltinTypeSpec.Type.Int:
			case BuiltinTypeSpec.Type.UInt:
			case BuiltinTypeSpec.Type.Long:
			case BuiltinTypeSpec.Type.Byte:
			case BuiltinTypeSpec.Type.SByte:
			case BuiltinTypeSpec.Type.Short:
			case BuiltinTypeSpec.Type.UShort:
			case BuiltinTypeSpec.Type.ULong:
			case BuiltinTypeSpec.Type.Char:
				return true;
			default:
				return t.IsEnum;
			}
		}

		public override Constant ConvertInitializer (ResolveContext rc, Constant expr)
		{
			if (expr is EnumConstant)
				expr = ((EnumConstant) expr).Child;

			var en = (Enum)Parent;
			var underlying = en.UnderlyingType;
			if (expr != null) {
				expr = expr.ImplicitConversionRequired (rc, underlying);
				if (expr != null && !IsValidEnumType (expr.Type)) {
					en.Error_UnderlyingType (Location);
					expr = null;
				}
			}

			if (expr == null)
				expr = New.Constantify (underlying, Location);

			return new EnumConstant (expr, MemberType);
		}

		public override bool Define ()
		{
			if (!ResolveMemberType ())
				return false;

			const FieldAttributes attr = FieldAttributes.Public | FieldAttributes.Static | FieldAttributes.Literal;
			FieldBuilder = Parent.TypeBuilder.DefineField (Name, MemberType.GetMetaInfo (), attr);
			spec = new ConstSpec (Parent.Definition, this, MemberType, FieldBuilder, ModFlags, initializer);

			Parent.MemberCache.AddMember (spec);
			return true;
		}
		
		public override void Accept (StructuralVisitor visitor)
		{
			visitor.Visit (this);
		}

	}

	/// <summary>
	///   Enumeration container
	/// </summary>
	public class Enum : TypeDefinition
	{
		//
		// Implicit enum member initializer, used when no constant value is provided
		//
		sealed class ImplicitInitializer : Expression
		{
			readonly EnumMember prev;
			readonly EnumMember current;

			public ImplicitInitializer (EnumMember current, EnumMember prev)
			{
				this.current = current;
				this.prev = prev;
			}

			public override bool ContainsEmitWithAwait ()
			{
				return false;
			}

			public override Expression CreateExpressionTree (ResolveContext ec)
			{
				throw new NotSupportedException ("Missing Resolve call");
			}

			protected override Expression DoResolve (ResolveContext rc)
			{
				// We are the first member
				if (prev == null) {
					return New.Constantify (current.Parent.Definition, Location);
				}

				var c = ((ConstSpec) prev.Spec).GetConstant (rc) as EnumConstant;
				try {
					return c.Increment ();
				} catch (OverflowException) {
					rc.Report.Error (543, current.Location,
						"The enumerator value `{0}' is outside the range of enumerator underlying type `{1}'",
						current.GetSignatureForError (), ((Enum) current.Parent).UnderlyingType.GetSignatureForError ());

					return New.Constantify (current.Parent.Definition, current.Location);
				}
			}

			public override void Emit (EmitContext ec)
			{
				throw new NotSupportedException ("Missing Resolve call");
			}
		}

		public static readonly string UnderlyingValueField = "value__";

		const Modifiers AllowedModifiers =
			Modifiers.NEW |
			Modifiers.PUBLIC |
			Modifiers.PROTECTED |
			Modifiers.INTERNAL |
			Modifiers.PRIVATE;

		readonly FullNamedExpression underlying_type_expr;

		public Enum (TypeContainer parent, FullNamedExpression type, Modifiers mod_flags, MemberName name, Attributes attrs)
			: base (parent, name, attrs, MemberKind.Enum)
		{
			underlying_type_expr = type;
			var accmods = IsTopLevel ? Modifiers.INTERNAL : Modifiers.PRIVATE;
			ModFlags = ModifiersExtensions.Check (AllowedModifiers, mod_flags, accmods, Location, Report);
			spec = new EnumSpec (null, this, null, null, ModFlags);
		}

		#region Properties

		public override AttributeTargets AttributeTargets {
			get {
				return AttributeTargets.Enum;
			}
		}

		public FullNamedExpression BaseTypeExpression {
			get {
				return underlying_type_expr;
			}
		}

		protected override TypeAttributes TypeAttr {
			get {
				return base.TypeAttr | TypeAttributes.Class | TypeAttributes.Sealed;
			}
		}

		public TypeSpec UnderlyingType {
			get {
				return ((EnumSpec) spec).UnderlyingType;
			}
		}

		#endregion

		public override void Accept (StructuralVisitor visitor)
		{
			visitor.Visit (this);
		}

		public void AddEnumMember (EnumMember em)
		{
			if (em.Name == UnderlyingValueField) {
				Report.Error (76, em.Location, "An item in an enumeration cannot have an identifier `{0}'",
					UnderlyingValueField);
				return;
			}

			AddMember (em);
		}

		public void Error_UnderlyingType (Location loc)
		{
			Report.Error (1008, loc,
				"Type byte, sbyte, short, ushort, int, uint, long or ulong expected");
		}

		protected override void DoDefineContainer ()
		{
			TypeSpec ut;
			if (underlying_type_expr != null) {
				ut = underlying_type_expr.ResolveAsType (this);
				if (!EnumSpec.IsValidUnderlyingType (ut)) {
					Error_UnderlyingType (underlying_type_expr.Location);
					ut = null;
				}
			} else {
				ut = null;
			}

			if (ut == null)
				ut = Compiler.BuiltinTypes.Int;

			((EnumSpec) spec).UnderlyingType = ut;

			TypeBuilder.DefineField (UnderlyingValueField, UnderlyingType.GetMetaInfo (),
				FieldAttributes.Public | FieldAttributes.SpecialName | FieldAttributes.RTSpecialName);

			DefineBaseTypes ();
		}

		protected override bool DoDefineMembers ()
		{
			for (int i = 0; i < Members.Count; ++i) {
				EnumMember em = (EnumMember) Members[i];
				if (em.Initializer == null) {
					em.Initializer = new ImplicitInitializer (em, i == 0 ? null : (EnumMember) Members[i - 1]);
				}

				em.Define ();
			}

			return true;
		}

		public override bool IsUnmanagedType ()
		{
			return true;
		}

		protected override TypeSpec[] ResolveBaseTypes (out FullNamedExpression base_class)
		{
			base_type = Compiler.BuiltinTypes.Enum;
			base_class = null;
			return null;
		}
		
		protected override bool VerifyClsCompliance ()
		{
			if (!base.VerifyClsCompliance ())
				return false;

			switch (UnderlyingType.BuiltinType) {
			case BuiltinTypeSpec.Type.UInt:
			case BuiltinTypeSpec.Type.ULong:
			case BuiltinTypeSpec.Type.UShort:
				Report.Warning (3009, 1, Location, "`{0}': base type `{1}' is not CLS-compliant",
					GetSignatureForError (), UnderlyingType.GetSignatureForError ());
				break;
			}

			return true;
		}
	}

	class EnumSpec : TypeSpec
	{
		TypeSpec underlying;

		public EnumSpec (TypeSpec declaringType, ITypeDefinition definition, TypeSpec underlyingType, MetaType info, Modifiers modifiers)
			: base (MemberKind.Enum, declaringType, definition, info, modifiers | Modifiers.SEALED)
		{
			this.underlying = underlyingType;
		}

		public TypeSpec UnderlyingType {
			get {
				return underlying;
			}
			set {
				if (underlying != null)
					throw new InternalErrorException ("UnderlyingType reset");

				underlying = value;
			}
		}

		public static TypeSpec GetUnderlyingType (TypeSpec t)
		{
			return ((EnumSpec) t.GetDefinition ()).UnderlyingType;
		}

		public static bool IsValidUnderlyingType (TypeSpec type)
		{
			switch (type.BuiltinType) {
			case BuiltinTypeSpec.Type.Int:
			case BuiltinTypeSpec.Type.UInt:
			case BuiltinTypeSpec.Type.Long:
			case BuiltinTypeSpec.Type.Byte:
			case BuiltinTypeSpec.Type.SByte:
			case BuiltinTypeSpec.Type.Short:
			case BuiltinTypeSpec.Type.UShort:
			case BuiltinTypeSpec.Type.ULong:
				return true;
			}

			return false;
		}
	}
}
