using System;
using System.Collections;

using SemWeb;
using SemWeb.Stores;
using SemWeb.Util;

namespace SemWeb {
	public class MemoryStore : Store, SupportsPersistableBNodes, IEnumerable {
		StatementList statements;
		
		Hashtable statementsAboutSubject = new Hashtable();
		Hashtable statementsAboutObject = new Hashtable();
		
		bool isIndexed = false;
		internal bool allowIndexing = true;
		internal bool checkForDuplicates = false;
		bool distinct = true;
		
		string guid = null;
		Hashtable pbnodeToId = null;
		Hashtable pbnodeFromId = null;
		
		public MemoryStore() {
			statements = new StatementList();
		}
		
		public MemoryStore(StatementSource source) : this() {
			Import(source);
		}
		
		public MemoryStore(Statement[] statements) {
			this.statements = new StatementList(statements);
		}

		public Statement[] ToArray() {
			return (Statement[])statements.ToArray(typeof(Statement));
		}

		public IList Statements { get { return statements.ToArray(); } }
		
		public override bool Distinct { get { return distinct; } }
		
		public override int StatementCount { get { return statements.Count; } }
		
		public Statement this[int index] {
			get {
				return statements[index];
			}
		}
		
		IEnumerator IEnumerable.GetEnumerator() {
			return statements.GetEnumerator();
		}
		
		public override void Clear() {
			statements.Clear();
			statementsAboutSubject.Clear();
			statementsAboutObject.Clear();
			distinct = true;
		}
		
		private StatementList GetIndexArray(Hashtable from, Resource entity) {
			StatementList ret = (StatementList)from[entity];
			if (ret == null) {
				ret = new StatementList();
				from[entity] = ret;
			}
			return ret;
		}
		
		public override void Add(Statement statement) {
			if (statement.AnyNull) throw new ArgumentNullException();
			if (checkForDuplicates && Contains(statement)) return;
			statements.Add(statement);
			if (isIndexed) {
				GetIndexArray(statementsAboutSubject, statement.Subject).Add(statement);
				GetIndexArray(statementsAboutObject, statement.Object).Add(statement);
			}
			if (!checkForDuplicates) distinct = false;
		}
		
		public override void Import(StatementSource source) {
			bool newDistinct = checkForDuplicates || ((StatementCount==0) && source.Distinct);
			base.Import(source); // distinct set to false if !checkForDuplicates
			distinct = newDistinct;
		}
		
		public override void Remove(Statement statement) {
			if (statement.AnyNull) {
				for (int i = 0; i < statements.Count; i++) {
					Statement s = (Statement)statements[i];
					if (statement.Matches(s)) {
						statements.RemoveAt(i); i--;
						if (isIndexed) {
							GetIndexArray(statementsAboutSubject, s.Subject).Remove(s);
							GetIndexArray(statementsAboutObject, s.Object).Remove(s);
						}
					}
				}
			} else {
				statements.Remove(statement);
				if (isIndexed) {
					GetIndexArray(statementsAboutSubject, statement.Subject).Remove(statement);
					GetIndexArray(statementsAboutObject, statement.Object).Remove(statement);
				}
			}
		}
		
		public override Entity[] GetEntities() {
			Hashtable h = new Hashtable();
			foreach (Statement s in Statements) {
				if (s.Subject != null) h[s.Subject] = h;
				if (s.Predicate != null) h[s.Predicate] = h;
				if (s.Object != null && s.Object is Entity) h[s.Object] = h;
				if (s.Meta != null && s.Meta != Statement.DefaultMeta) h[s.Meta] = h;
			}
			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
		}
		
		public override Entity[] GetPredicates() {
			Hashtable h = new Hashtable();
			foreach (Statement s in Statements)
				h[s.Predicate] = h;
			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
		}

		public override Entity[] GetMetas() {
			Hashtable h = new Hashtable();
			foreach (Statement s in Statements)
				h[s.Meta] = h;
			return (Entity[])new ArrayList(h.Keys).ToArray(typeof(Entity));
		}

		private void ShorterList(ref StatementList list1, StatementList list2) {
			if (list2.Count < list1.Count)
				list1 = list2;
		}
		
		public override void Select(Statement template, StatementSink result) {
			StatementList source = statements;
			
			// The first time select is called, turn indexing on for the store.
			// TODO: Perform this index in a background thread if there are a lot
			// of statements.
			if (!isIndexed && allowIndexing) {
				isIndexed = true;
				for (int i = 0; i < StatementCount; i++) {
					Statement statement = this[i];
					GetIndexArray(statementsAboutSubject, statement.Subject).Add(statement);
					GetIndexArray(statementsAboutObject, statement.Object).Add(statement);
				}
			}
			
			if (template.Subject != null) ShorterList(ref source, GetIndexArray(statementsAboutSubject, template.Subject));
			else if (template.Object != null) ShorterList(ref source, GetIndexArray(statementsAboutObject, template.Object));
			
			if (source == null) return;
			
			for (int i = 0; i < source.Count; i++) {
				Statement statement = source[i];
				if (!template.Matches(statement))
					continue;
				if (!result.Add(statement)) return;
			}
		}

		public override void Select(SelectFilter filter, StatementSink result) {
			ResSet
				s = filter.Subjects == null ? null : new ResSet(filter.Subjects),
				p = filter.Predicates == null ? null : new ResSet(filter.Predicates),
				o = filter.Objects == null ? null : new ResSet(filter.Objects),
				m = filter.Metas == null ? null : new ResSet(filter.Metas);
				
			foreach (Statement st in statements) {
				if (s != null && !s.Contains(st.Subject)) continue;
				if (p != null && !p.Contains(st.Predicate)) continue;
				if (o != null && !o.Contains(st.Object)) continue;
				if (m != null && !m.Contains(st.Meta)) continue;
				if (filter.LiteralFilters != null && !LiteralFilter.MatchesFilters(st.Object, filter.LiteralFilters, this)) continue;
				if (!result.Add(st)) return;
			}
		}

		public override void Replace(Entity a, Entity b) {
			MemoryStore removals = new MemoryStore();
			MemoryStore additions = new MemoryStore();
			foreach (Statement statement in statements) {
				if ((statement.Subject != null && statement.Subject == a) || (statement.Predicate != null && statement.Predicate == a) || (statement.Object != null && statement.Object == a) || (statement.Meta != null && statement.Meta == a)) {
					removals.Add(statement);
					additions.Add(statement.Replace(a, b));
				}
			}
			RemoveAll(removals.ToArray());
			Import(additions);
		}
		
		public override void Replace(Statement find, Statement replacement) {
			if (find.AnyNull) throw new ArgumentNullException("find");
			if (replacement.AnyNull) throw new ArgumentNullException("replacement");
			if (find == replacement) return;
			
			foreach (Statement match in Select(find)) {
				Remove(match);
				Add(replacement);
				break; // should match just one statement anyway
			}
		}

		string SupportsPersistableBNodes.GetStoreGuid() {
			if (guid == null) guid = Guid.NewGuid().ToString("N");;
			return guid;
		}
		
		string SupportsPersistableBNodes.GetNodeId(BNode node) {
			if (pbnodeToId == null) {
				pbnodeToId = new Hashtable();
				pbnodeFromId = new Hashtable();
			}
			if (pbnodeToId.ContainsKey(node)) return (string)pbnodeToId[node];
			string id = pbnodeToId.Count.ToString();
			pbnodeToId[node] = id;
			pbnodeFromId[id] = node;
			return id;
		}
		
		BNode SupportsPersistableBNodes.GetNodeFromId(string persistentId) {
			if (pbnodeFromId == null) return null;
			return (BNode)pbnodeFromId[persistentId];
		}
	}
}
