//
// context.cs: Various compiler contexts.
//
// Author:
//   Marek Safar (marek.safar@gmail.com)
//   Miguel de Icaza (miguel@ximian.com)
//
// Copyright 2001, 2002, 2003 Ximian, Inc.
// Copyright 2004-2009 Novell, Inc.
// Copyright 2011 Xamarin Inc.
//

using System;
using System.Collections.Generic;
using System.IO;
using System.Security.Cryptography;

namespace Mono.CSharp
{
	public enum LookupMode
	{
		Normal = 0,
		Probing = 1,
		IgnoreAccessibility = 2
	}

	//
	// Implemented by elements which can act as independent contexts
	// during resolve phase. Used mostly for lookups.
	//
	public interface IMemberContext : IModuleContext
	{
		//
		// A scope type context, it can be inflated for generic types
		//
		TypeSpec CurrentType { get; }

		//
		// A scope type parameters either VAR or MVAR
		//
		TypeParameters CurrentTypeParameters { get; }

		//
		// A member definition of the context. For partial types definition use
		// CurrentTypeDefinition.PartialContainer otherwise the context is local
		//
		// TODO: Obsolete it in this context, dynamic context cannot guarantee sensible value
		//
		MemberCore CurrentMemberDefinition { get; }

		bool IsObsolete { get; }
		bool IsUnsafe { get; }
		bool IsStatic { get; }

		string GetSignatureForError ();

		ExtensionMethodCandidates LookupExtensionMethod (TypeSpec extensionType, string name, int arity);
		FullNamedExpression LookupNamespaceOrType (string name, int arity, LookupMode mode, Location loc);
		FullNamedExpression LookupNamespaceAlias (string name);
	}

	public interface IModuleContext
	{
		ModuleContainer Module { get; }
	}

	//
	// Block or statement resolving context
	//
	public class BlockContext : ResolveContext
	{
		FlowBranching current_flow_branching;

		readonly TypeSpec return_type;

		public int FlowOffset;

		public BlockContext (IMemberContext mc, ExplicitBlock block, TypeSpec returnType)
			: base (mc)
		{
			if (returnType == null)
				throw new ArgumentNullException ("returnType");

			this.return_type = returnType;

			// TODO: check for null value
			CurrentBlock = block;
		}

		public BlockContext (ResolveContext rc, ExplicitBlock block, TypeSpec returnType)
			: this (rc.MemberContext, block, returnType)
		{
			if (rc.IsUnsafe)
				flags |= ResolveContext.Options.UnsafeScope;

			if (rc.HasSet (ResolveContext.Options.CheckedScope))
				flags |= ResolveContext.Options.CheckedScope;

			if (rc.IsInProbingMode)
				flags |= ResolveContext.Options.ProbingMode;

			if (rc.HasSet (ResolveContext.Options.FieldInitializerScope))
				flags |= ResolveContext.Options.FieldInitializerScope;

			if (rc.HasSet (ResolveContext.Options.ExpressionTreeConversion))
				flags |= ResolveContext.Options.ExpressionTreeConversion;

			if (rc.HasSet (ResolveContext.Options.BaseInitializer))
				flags |= ResolveContext.Options.BaseInitializer;
		}

		public override FlowBranching CurrentBranching {
			get { return current_flow_branching; }
		}

		public TypeSpec ReturnType {
			get { return return_type; }
		}

		public bool IsUnreachable {
			get {
				return HasSet (Options.UnreachableScope);
			}
			set {
				flags = value ? flags | Options.UnreachableScope : flags & ~Options.UnreachableScope;
			}
		}

		public bool UnreachableReported {
			get {
				return HasSet (Options.UnreachableReported);
			}
			set {
				flags = value ? flags | Options.UnreachableReported : flags & ~Options.UnreachableScope;
			}
		}

		// <summary>
		//   Starts a new code branching.  This inherits the state of all local
		//   variables and parameters from the current branching.
		// </summary>
		public FlowBranching StartFlowBranching (FlowBranching.BranchingType type, Location loc)
		{
			current_flow_branching = FlowBranching.CreateBranching (CurrentBranching, type, null, loc);
			return current_flow_branching;
		}

		// <summary>
		//   Starts a new code branching for block `block'.
		// </summary>
		public FlowBranching StartFlowBranching (Block block)
		{
			Set (Options.DoFlowAnalysis);

			current_flow_branching = FlowBranching.CreateBranching (
				CurrentBranching, FlowBranching.BranchingType.Block, block, block.StartLocation);
			return current_flow_branching;
		}

		public FlowBranchingTryCatch StartFlowBranching (TryCatch stmt)
		{
			FlowBranchingTryCatch branching = new FlowBranchingTryCatch (CurrentBranching, stmt);
			current_flow_branching = branching;
			return branching;
		}

		public FlowBranchingTryFinally StartFlowBranching (TryFinallyBlock stmt)
		{
			FlowBranchingTryFinally branching = new FlowBranchingTryFinally (CurrentBranching, stmt);
			current_flow_branching = branching;
			return branching;
		}

		public FlowBranchingLabeled StartFlowBranching (LabeledStatement stmt)
		{
			FlowBranchingLabeled branching = new FlowBranchingLabeled (CurrentBranching, stmt);
			current_flow_branching = branching;
			return branching;
		}

		public FlowBranchingIterator StartFlowBranching (Iterator iterator, FlowBranching parent)
		{
			FlowBranchingIterator branching = new FlowBranchingIterator (parent, iterator);
			current_flow_branching = branching;
			return branching;
		}

		public FlowBranchingAsync StartFlowBranching (AsyncInitializer asyncBody, FlowBranching parent)
		{
			var branching = new FlowBranchingAsync (parent, asyncBody);
			current_flow_branching = branching;
			return branching;
		}

		public FlowBranchingToplevel StartFlowBranching (ParametersBlock stmt, FlowBranching parent)
		{
			FlowBranchingToplevel branching = new FlowBranchingToplevel (parent, stmt);
			current_flow_branching = branching;
			return branching;
		}

		// <summary>
		//   Ends a code branching.  Merges the state of locals and parameters
		//   from all the children of the ending branching.
		// </summary>
		public bool EndFlowBranching ()
		{
			FlowBranching old = current_flow_branching;
			current_flow_branching = current_flow_branching.Parent;

			FlowBranching.UsageVector vector = current_flow_branching.MergeChild (old);
			return vector.IsUnreachable;
		}

		// <summary>
		//   Kills the current code branching.  This throws away any changed state
		//   information and should only be used in case of an error.
		// </summary>
		// FIXME: this is evil
		public void KillFlowBranching ()
		{
			current_flow_branching = current_flow_branching.Parent;
		}

#if !STATIC
		public void NeedReturnLabel ()
		{
		}
#endif
	}

	//
	// Expression resolving context
	//
	public class ResolveContext : IMemberContext
	{
		[Flags]
		public enum Options
		{
			/// <summary>
			///   This flag tracks the `checked' state of the compilation,
			///   it controls whether we should generate code that does overflow
			///   checking, or if we generate code that ignores overflows.
			///
			///   The default setting comes from the command line option to generate
			///   checked or unchecked code plus any source code changes using the
			///   checked/unchecked statements or expressions.   Contrast this with
			///   the ConstantCheckState flag.
			/// </summary>
			CheckedScope = 1 << 0,

			/// <summary>
			///   The constant check state is always set to `true' and cant be changed
			///   from the command line.  The source code can change this setting with
			///   the `checked' and `unchecked' statements and expressions. 
			/// </summary>
			ConstantCheckState = 1 << 1,

			AllCheckStateFlags = CheckedScope | ConstantCheckState,

			//
			// unsafe { ... } scope
			//
			UnsafeScope = 1 << 2,
			CatchScope = 1 << 3,
			FinallyScope = 1 << 4,
			FieldInitializerScope = 1 << 5,
			CompoundAssignmentScope = 1 << 6,
			FixedInitializerScope = 1 << 7,
			BaseInitializer = 1 << 8,

			//
			// Inside an enum definition, we do not resolve enumeration values
			// to their enumerations, but rather to the underlying type/value
			// This is so EnumVal + EnumValB can be evaluated.
			//
			// There is no "E operator + (E x, E y)", so during an enum evaluation
			// we relax the rules
			//
			EnumScope = 1 << 9,

			ConstantScope = 1 << 10,

			ConstructorScope = 1 << 11,

			UsingInitializerScope = 1 << 12,

			LockScope = 1 << 13,

			UnreachableScope = 1 << 14,

			UnreachableReported = 1 << 15,

			/// <summary>
			///   Whether control flow analysis is enabled
			/// </summary>
			DoFlowAnalysis = 1 << 20,

			/// <summary>
			///   Whether control flow analysis is disabled on structs
			///   (only meaningful when DoFlowAnalysis is set)
			/// </summary>
			OmitStructFlowAnalysis = 1 << 21,

			///
			/// Indicates the current context is in probing mode, no errors are reported. 
			///
			ProbingMode = 1 << 22,

			//
			// Return and ContextualReturn statements will set the ReturnType
			// value based on the expression types of each return statement
			// instead of the method return type which is initially null.
			//
			InferReturnType = 1 << 23,

			OmitDebuggingInfo = 1 << 24,

			ExpressionTreeConversion = 1 << 25,

			InvokeSpecialName = 1 << 26
		}

		// utility helper for CheckExpr, UnCheckExpr, Checked and Unchecked statements
		// it's public so that we can use a struct at the callsite
		public struct FlagsHandle : IDisposable
		{
			ResolveContext ec;
			readonly Options invmask, oldval;

			public FlagsHandle (ResolveContext ec, Options flagsToSet)
				: this (ec, flagsToSet, flagsToSet)
			{
			}

			internal FlagsHandle (ResolveContext ec, Options mask, Options val)
			{
				this.ec = ec;
				invmask = ~mask;
				oldval = ec.flags & mask;
				ec.flags = (ec.flags & invmask) | (val & mask);

//				if ((mask & Options.ProbingMode) != 0)
//					ec.Report.DisableReporting ();
			}

			public void Dispose ()
			{
//				if ((invmask & Options.ProbingMode) == 0)
//					ec.Report.EnableReporting ();

				ec.flags = (ec.flags & invmask) | oldval;
			}
		}

		protected Options flags;

		//
		// Whether we are inside an anonymous method.
		//
		public AnonymousExpression CurrentAnonymousMethod;

		//
		// Holds a varible used during collection or object initialization.
		//
		public Expression CurrentInitializerVariable;

		public Block CurrentBlock;

		public readonly IMemberContext MemberContext;

		/// <summary>
		///   If this is non-null, points to the current switch statement
		/// </summary>
		public Switch Switch;

		public ResolveContext (IMemberContext mc)
		{
			if (mc == null)
				throw new ArgumentNullException ();

			MemberContext = mc;

			//
			// The default setting comes from the command line option
			//
			if (mc.Module.Compiler.Settings.Checked)
				flags |= Options.CheckedScope;

			//
			// The constant check state is always set to true
			//
			flags |= Options.ConstantCheckState;
		}

		public ResolveContext (IMemberContext mc, Options options)
			: this (mc)
		{
			flags |= options;
		}

		#region Properties

		public BuiltinTypes BuiltinTypes {
			get {
				return MemberContext.Module.Compiler.BuiltinTypes;
			}
		}

		public virtual ExplicitBlock ConstructorBlock {
			get {
				return CurrentBlock.Explicit;
			}
		}

		public virtual FlowBranching CurrentBranching {
			get { return null; }
		}

		//
		// The current iterator
		//
		public Iterator CurrentIterator {
			get { return CurrentAnonymousMethod as Iterator; }
		}

		public TypeSpec CurrentType {
			get { return MemberContext.CurrentType; }
		}

		public TypeParameters CurrentTypeParameters {
			get { return MemberContext.CurrentTypeParameters; }
		}

		public MemberCore CurrentMemberDefinition {
			get { return MemberContext.CurrentMemberDefinition; }
		}

		public bool ConstantCheckState {
			get { return (flags & Options.ConstantCheckState) != 0; }
		}

		public bool DoFlowAnalysis {
			get { return (flags & Options.DoFlowAnalysis) != 0; }
		}

		public bool IsInProbingMode {
			get {
				return (flags & Options.ProbingMode) != 0;
			}
		}

		public bool IsObsolete {
			get {
				// Disables obsolete checks when probing is on
				return MemberContext.IsObsolete;
			}
		}

		public bool IsStatic {
			get {
				return MemberContext.IsStatic;
			}
		}

		public bool IsUnsafe {
			get {
				return HasSet (Options.UnsafeScope) || MemberContext.IsUnsafe;
			}
		}

		public bool IsRuntimeBinder {
			get {
				return Module.Compiler.IsRuntimeBinder;
			}
		}

		public bool IsVariableCapturingRequired {
			get {
				return !IsInProbingMode && (CurrentBranching == null || !CurrentBranching.CurrentUsageVector.IsUnreachable);
			}
		}

		public ModuleContainer Module {
			get {
				return MemberContext.Module;
			}
		}

		public bool OmitStructFlowAnalysis {
			get { return (flags & Options.OmitStructFlowAnalysis) != 0; }
		}

		public Report Report {
			get {
				return Module.Compiler.Report;
			}
		}

		#endregion

		public bool MustCaptureVariable (INamedBlockVariable local)
		{
			if (CurrentAnonymousMethod == null)
				return false;

			//
			// Capture only if this or any of child blocks contain yield
			// or it's a parameter
			//
			if (CurrentAnonymousMethod.IsIterator)
				return local.IsParameter || local.Block.Explicit.HasYield;

			//
			// Capture only if this or any of child blocks contain await
			// or it's a parameter
			//
			if (CurrentAnonymousMethod is AsyncInitializer)
				return local.IsParameter || local.Block.Explicit.HasAwait || CurrentBlock.Explicit.HasAwait;

			return local.Block.ParametersBlock != CurrentBlock.ParametersBlock.Original;
		}

		public bool HasSet (Options options)
		{
			return (this.flags & options) == options;
		}

		public bool HasAny (Options options)
		{
			return (this.flags & options) != 0;
		}


		// Temporarily set all the given flags to the given value.  Should be used in an 'using' statement
		public FlagsHandle Set (Options options)
		{
			return new FlagsHandle (this, options);
		}

		public FlagsHandle With (Options options, bool enable)
		{
			return new FlagsHandle (this, options, enable ? options : 0);
		}

		#region IMemberContext Members

		public string GetSignatureForError ()
		{
			return MemberContext.GetSignatureForError ();
		}

		public ExtensionMethodCandidates LookupExtensionMethod (TypeSpec extensionType, string name, int arity)
		{
			return MemberContext.LookupExtensionMethod (extensionType, name, arity);
		}

		public FullNamedExpression LookupNamespaceOrType (string name, int arity, LookupMode mode, Location loc)
		{
			return MemberContext.LookupNamespaceOrType (name, arity, mode, loc);
		}

		public FullNamedExpression LookupNamespaceAlias (string name)
		{
			return MemberContext.LookupNamespaceAlias (name);
		}

		#endregion
	}

	//
	// This class is used during the Statement.Clone operation
	// to remap objects that have been cloned.
	//
	// Since blocks are cloned by Block.Clone, we need a way for
	// expressions that must reference the block to be cloned
	// pointing to the new cloned block.
	//
	public class CloneContext
	{
		Dictionary<Block, Block> block_map = new Dictionary<Block, Block> ();

		public void AddBlockMap (Block from, Block to)
		{
			block_map.Add (from, to);
		}

		public Block LookupBlock (Block from)
		{
			Block result;
			if (!block_map.TryGetValue (from, out result)) {
				result = (Block) from.Clone (this);
			}

			return result;
		}

		///
		/// Remaps block to cloned copy if one exists.
		///
		public Block RemapBlockCopy (Block from)
		{
			Block mapped_to;
			if (!block_map.TryGetValue (from, out mapped_to))
				return from;

			return mapped_to;
		}
	}

	//
	// Main compiler context
	//
	public class CompilerContext
	{
		static readonly TimeReporter DisabledTimeReporter = new TimeReporter (false);

		readonly Report report;
		readonly BuiltinTypes builtin_types;
		readonly CompilerSettings settings;

		Dictionary<string, SourceFile> all_source_files;

		public CompilerContext (CompilerSettings settings, ReportPrinter reportPrinter)
		{
			this.settings = settings;
			this.report = new Report (this, reportPrinter);
			this.builtin_types = new BuiltinTypes ();
			this.TimeReporter = DisabledTimeReporter;
		}

		#region Properties

		public BuiltinTypes BuiltinTypes {
			get {
				return builtin_types;
			}
		}

		// Used for special handling of runtime dynamic context mostly
		// by error reporting but also by member accessibility checks
		public bool IsRuntimeBinder {
			get; set;
		}

		public Report Report {
			get {
				return report;
			}
		}

		public CompilerSettings Settings {
			get {
				return settings;
			}
		}

		public List<SourceFile> SourceFiles {
			get {
				return settings.SourceFiles;
			}
		}

		internal TimeReporter TimeReporter {
			get; set;
		}

		#endregion

		//
		// This is used when we encounter a #line preprocessing directive during parsing
		// to register additional source file names
		//
		public SourceFile LookupFile (CompilationSourceFile comp_unit, string name)
		{
			if (all_source_files == null) {
				all_source_files = new Dictionary<string, SourceFile> ();
				foreach (var source in SourceFiles)
					all_source_files[source.FullPathName] = source;
			}

			string path;
			if (!Path.IsPathRooted (name)) {
				string root = Path.GetDirectoryName (comp_unit.SourceFile.FullPathName);
				path = Path.Combine (root, name);
			} else
				path = name;

			SourceFile retval;
			if (all_source_files.TryGetValue (path, out retval))
				return retval;

			retval = new SourceFile (name, path, all_source_files.Count + 1);
			Location.AddFile (retval);
			all_source_files.Add (path, retval);
			return retval;
		}
	}

	//
	// Generic code emitter context
	//
	public class BuilderContext
	{
		[Flags]
		public enum Options
		{
			/// <summary>
			///   This flag tracks the `checked' state of the compilation,
			///   it controls whether we should generate code that does overflow
			///   checking, or if we generate code that ignores overflows.
			///
			///   The default setting comes from the command line option to generate
			///   checked or unchecked code plus any source code changes using the
			///   checked/unchecked statements or expressions.   Contrast this with
			///   the ConstantCheckState flag.
			/// </summary>
			CheckedScope = 1 << 0,

			AccurateDebugInfo = 1 << 1,

			OmitDebugInfo = 1 << 2,

			ConstructorScope = 1 << 3,

			AsyncBody = 1 << 4
		}

		// utility helper for CheckExpr, UnCheckExpr, Checked and Unchecked statements
		// it's public so that we can use a struct at the callsite
		public struct FlagsHandle : IDisposable
		{
			BuilderContext ec;
			readonly Options invmask, oldval;

			public FlagsHandle (BuilderContext ec, Options flagsToSet)
				: this (ec, flagsToSet, flagsToSet)
			{
			}

			internal FlagsHandle (BuilderContext ec, Options mask, Options val)
			{
				this.ec = ec;
				invmask = ~mask;
				oldval = ec.flags & mask;
				ec.flags = (ec.flags & invmask) | (val & mask);
			}

			public void Dispose ()
			{
				ec.flags = (ec.flags & invmask) | oldval;
			}
		}

		protected Options flags;

		public bool HasSet (Options options)
		{
			return (this.flags & options) == options;
		}

		// Temporarily set all the given flags to the given value.  Should be used in an 'using' statement
		public FlagsHandle With (Options options, bool enable)
		{
			return new FlagsHandle (this, options, enable ? options : 0);
		}
	}

	//
	// Parser session objects. We could recreate all these objects for each parser
	// instance but the best parser performance the session object can be reused
	//
	public class ParserSession
	{
		MD5 md5;

		public readonly char[] StreamReaderBuffer = new char[SeekableStreamReader.DefaultReadAheadSize * 2];
		public readonly Dictionary<char[], string>[] Identifiers = new Dictionary<char[], string>[Tokenizer.MaxIdentifierLength + 1];
		public readonly List<Parameter> ParametersStack = new List<Parameter> (4);
		public readonly char[] IDBuilder = new char[Tokenizer.MaxIdentifierLength];
		public readonly char[] NumberBuilder = new char[Tokenizer.MaxNumberLength];

		public LocationsBag LocationsBag { get; set; }
		public bool UseJayGlobalArrays { get; set; }
		public LocatedToken[] LocatedTokens { get; set; }

		public MD5 GetChecksumAlgorithm ()
		{
			return md5 ?? (md5 = MD5.Create ());
		}
	}
}
