/* Copyright (c) 2012-2017 The ANTLR Project. All rights reserved.
 * Use of this file is governed by the BSD 3-clause license that
 * can be found in the LICENSE.txt file in the project root.
 */
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Antlr4.Runtime.Misc;
using Antlr4.Runtime.Sharpen;

namespace Antlr4.Runtime.Atn
{
	public class ATNConfigSet
	{


		/** Indicates that the set of configurations is read-only. Do not
		 *  allow any code to manipulate the set; DFA states will point at
		 *  the sets and they must not change. This does not protect the other
		 *  fields; in particular, conflictingAlts is set after
		 *  we've made this readonly.
		 */
		protected bool readOnly = false;

		/**
		 * All configs but hashed by (s, i, _, pi) not including context. Wiped out
		 * when we go readonly as this set becomes a DFA state.
		 */
		public ConfigHashSet configLookup;

		/** Track the elements as they are added to the set; supports get(i) */
		public ArrayList<ATNConfig> configs = new ArrayList<ATNConfig>(7);

		// TODO: these fields make me pretty uncomfortable but nice to pack up info together, saves recomputation
		// TODO: can we track conflicts as they are added to save scanning configs later?
		public int uniqueAlt;
		/** Currently this is only used when we detect SLL conflict; this does
		 *  not necessarily represent the ambiguous alternatives. In fact,
		 *  I should also point out that this seems to include predicated alternatives
		 *  that have predicates that evaluate to false. Computed in computeTargetState().
		 */
		public BitSet conflictingAlts;

		// Used in parser and lexer. In lexer, it indicates we hit a pred
		// while computing a closure operation.  Don't make a DFA state from this.
		public bool hasSemanticContext;
		public bool dipsIntoOuterContext;

		/** Indicates that this configuration set is part of a full context
		 *  LL prediction. It will be used to determine how to merge $. With SLL
		 *  it's a wildcard whereas it is not for LL context merge.
		 */
		public readonly bool fullCtx;

		private int cachedHashCode = -1;

		public ATNConfigSet(bool fullCtx)
		{
			configLookup = new ConfigHashSet();
			this.fullCtx = fullCtx;
		}

		public ATNConfigSet()
		: this(true)
		{
		}

		public ATNConfigSet(ATNConfigSet old)
			: this(old.fullCtx)
		{
			AddAll(old.configs);
			this.uniqueAlt = old.uniqueAlt;
			this.conflictingAlts = old.conflictingAlts;
			this.hasSemanticContext = old.hasSemanticContext;
			this.dipsIntoOuterContext = old.dipsIntoOuterContext;
		}

		public bool Add(ATNConfig config)
		{
			return Add(config, null);
		}

		/**
		 * Adding a new config means merging contexts with existing configs for
		 * {@code (s, i, pi, _)}, where {@code s} is the
		 * {@link ATNConfig#state}, {@code i} is the {@link ATNConfig#alt}, and
		 * {@code pi} is the {@link ATNConfig#semanticContext}. We use
		 * {@code (s,i,pi)} as key.
		 *
		 * <p>This method updates {@link #dipsIntoOuterContext} and
		 * {@link #hasSemanticContext} when necessary.</p>
		 */
		public bool Add(ATNConfig config, MergeCache mergeCache)
		{
			if (readOnly)
				throw new Exception("This set is readonly");
			if (config.semanticContext != SemanticContext.Empty.Instance)
			{
				hasSemanticContext = true;
			}
			if (config.OuterContextDepth > 0)
			{
				dipsIntoOuterContext = true;
			}
			ATNConfig existing = configLookup.GetOrAdd(config);
			if (existing == config)
			{ // we added this new one
				cachedHashCode = -1;
				configs.Add(config);  // track order here
				return true;
			}
			// a previous (s,i,pi,_), merge with it and save result
			bool rootIsWildcard = !fullCtx;
			PredictionContext merged = PredictionContext.Merge(existing.context, config.context, rootIsWildcard, mergeCache);
			 // no need to check for existing.context, config.context in cache
			 // since only way to create new graphs is "call rule" and here. We
			 // cache at both places.
			existing.reachesIntoOuterContext = Math.Max(existing.reachesIntoOuterContext, config.reachesIntoOuterContext);

			// make sure to preserve the precedence filter suppression during the merge
			if (config.IsPrecedenceFilterSuppressed)
			{
				existing.SetPrecedenceFilterSuppressed(true);
			}

			existing.context = merged; // replace context; no need to alt mapping
			return true;
		}

		/** Return a List holding list of configs */
		public List<ATNConfig> Elements
		{
			get
			{
				return configs;
			}
		}

		public HashSet<ATNState> GetStates()
		{
			HashSet<ATNState> states = new HashSet<ATNState>();
			foreach (ATNConfig c in configs)
			{
				states.Add(c.state);
			}
			return states;
		}

		/**
		 * Gets the complete set of represented alternatives for the configuration
		 * set.
		 *
		 * @return the set of represented alternatives in this configuration set
		 *
		 * @since 4.3
		 */

		public BitSet GetAlts()
		{
			BitSet alts = new BitSet();
			foreach (ATNConfig config in configs)
			{
				alts.Set(config.alt);
			}
			return alts;
		}

		public List<SemanticContext> GetPredicates()
		{
			List<SemanticContext> preds = new List<SemanticContext>();
			foreach (ATNConfig c in configs)
			{
				if (c.semanticContext != SemanticContext.Empty.Instance)
				{
					preds.Add(c.semanticContext);
				}
			}
			return preds;
		}

		public ATNConfig Get(int i) { return configs[i]; }

		public void OptimizeConfigs(ATNSimulator interpreter)
		{
			if (readOnly)
				throw new Exception("This set is readonly");
			if (configLookup.Count == 0)
				return;

			foreach (ATNConfig config in configs)
			{
				//			int before = PredictionContext.getAllContextNodes(config.context).size();
				config.context = interpreter.getCachedContext(config.context);
				//			int after = PredictionContext.getAllContextNodes(config.context).size();
				//			System.out.println("configs "+before+"->"+after);
			}
		}

		public bool AddAll(ICollection<ATNConfig> coll)
		{
			foreach (ATNConfig c in coll) Add(c);
			return false;
		}

		public override bool Equals(Object o)
		{
			if (o == this)
			{
				return true;
			}
			else if (!(o is ATNConfigSet))
			{
				return false;
			}

			//		System.out.print("equals " + this + ", " + o+" = ");
			ATNConfigSet other = (ATNConfigSet)o;
			bool same = configs != null &&
				configs.Equals(other.configs) &&  // includes stack context
				this.fullCtx == other.fullCtx &&
				this.uniqueAlt == other.uniqueAlt &&
				this.conflictingAlts == other.conflictingAlts &&
				this.hasSemanticContext == other.hasSemanticContext &&
				this.dipsIntoOuterContext == other.dipsIntoOuterContext;

			//		System.out.println(same);
			return same;
		}


		public override int GetHashCode()
		{
			if (IsReadOnly)
			{
				if (cachedHashCode == -1)
				{
					cachedHashCode = configs.GetHashCode();
				}

				return cachedHashCode;
			}

			return configs.GetHashCode();
		}

		public int Count
		{
			get
			{
				return configs.Count;
			}
		}

		public bool Empty
		{
			get
			{

				return configs.Count == 0;
			}
		}

		public bool Contains(Object o)
		{
			if (configLookup == null)
			{
				throw new Exception("This method is not implemented for readonly sets.");
			}

			return configLookup.ContainsKey((ATNConfig)o);
		}


		public void Clear()
		{
			if (readOnly)
				throw new Exception("This set is readonly");
			configs.Clear();
			cachedHashCode = -1;
			configLookup.Clear();
		}

		public bool IsReadOnly
		{
			get
			{
				return readOnly;
			}
			set
			{
				this.readOnly = value;
				configLookup = null; // can't mod, no need for lookup cache
			}
		}

		public override String ToString()
		{
			StringBuilder buf = new StringBuilder();
			buf.Append('[');
			List<ATNConfig> cfgs = Elements;
			if (cfgs.Count > 0)
			{
				foreach (ATNConfig c in cfgs)
				{
					buf.Append(c.ToString());
					buf.Append(", ");
				}
				buf.Length = buf.Length - 2;
			}
			buf.Append(']');
			if (hasSemanticContext)
				buf.Append(",hasSemanticContext=")
				   .Append(hasSemanticContext.ToString().ToLower());
			if (uniqueAlt != ATN.INVALID_ALT_NUMBER)
				buf.Append(",uniqueAlt=")
				   .Append(uniqueAlt);
			if (conflictingAlts != null)
				buf.Append(",conflictingAlts=")
				   .Append(conflictingAlts);
			if (dipsIntoOuterContext)
				buf.Append(",dipsIntoOuterContext");
			return buf.ToString();
		}


	}

	public class OrderedATNConfigSet : ATNConfigSet
	{

		public OrderedATNConfigSet()
		{
			this.configLookup = new LexerConfigHashSet();
		}

		public class LexerConfigHashSet : ConfigHashSet
		{
			public LexerConfigHashSet()
				: base(new ObjectEqualityComparator())
			{
			}
		}
	}

	public class ObjectEqualityComparator : IEqualityComparer<ATNConfig>
	{


		public int GetHashCode(ATNConfig o)
		{
			if (o == null)
				return 0;
			else
				return o.GetHashCode();
		}

		public bool Equals(ATNConfig a, ATNConfig b)
		{
			if (a == b) return true;
			if (a == null || b == null) return false;
			return a.Equals(b);
		}
	}

	/**
	* The reason that we need this is because we don't want the hash map to use
	* the standard hash code and equals. We need all configurations with the same
	* {@code (s,i,_,semctx)} to be equal. Unfortunately, this key effectively doubles
	* the number of objects associated with ATNConfigs. The other solution is to
	* use a hash table that lets us specify the equals/hashcode operation.
	*/
	public class ConfigHashSet : Dictionary<ATNConfig, ATNConfig>
	{
		public ConfigHashSet(IEqualityComparer<ATNConfig> comparer)
			: base(comparer)
		{
		}


		public ConfigHashSet()
			: base(new ConfigEqualityComparator())
		{
		}

		public ATNConfig GetOrAdd(ATNConfig config)
		{
			ATNConfig existing;
			if (this.TryGetValue(config, out existing))
				return existing;
			else
			{
				this.Put(config, config);
				return config;
			}
		}

	}

	public class ConfigEqualityComparator : IEqualityComparer<ATNConfig>
	{


		public int GetHashCode(ATNConfig o)
		{
			int hashCode = 7;
			hashCode = 31 * hashCode + o.state.stateNumber;
			hashCode = 31 * hashCode + o.alt;
			hashCode = 31 * hashCode + o.semanticContext.GetHashCode();
			return hashCode;
		}

		public bool Equals(ATNConfig a, ATNConfig b)
		{
			if (a == b) return true;
			if (a == null || b == null) return false;
			return a.state.stateNumber == b.state.stateNumber
				&& a.alt == b.alt
				&& a.semanticContext.Equals(b.semanticContext);
		}
	}

}
