 # Mongo.Cursor for all reactive computations. The only limitation is that all
 # result sets must share a unique identifying property (like _id in Mongo).
 
-# TODO Keep track of the cursor's output. When replacing the cursor, do a full diff.
-# TODO Allow for changing between arrays and cursors
-
-makeObject = (id, fields) ->
-	object = _.clone fields
-	object._id = id
-	object
-
 class ComputationObserver
 	constructor: (options) ->
-		@_computation = options.computation
+		@_provider = options.computation
 		@_valueField = options.valueField
 		@_events = options.events
 	
 	start: ->
-		if typeof @_computation is "function"
+		if typeof @_provider is "function"
 			# Reactive data source
 			@_computation = Tracker.autorun => @_takeSnapshot()
-		else if _.isArray @_computation
+		else if _.isArray @_provider
 			# Static data source
-			@_snapshot = @_computation
+			@_snapshot = @_provider
+			@_addAll @_snapshot
 		else
 			throw new Error "computation must be either an array or a function returning an array"
 	

	
	_takeSnapshot: ->
		@_previousSnapshot = @_snapshot
		@_snapshot = @_computation()
		
		if @_snapshot instanceof Mongo.Cursor
			# Begin receiving stream of results using live query interface
			@_ensureResultTypeUnchanged "cursor"
			@_liveQuery.stop() if @_liveQuery?
			@_liveQuery = @_observeChangesInQuery @_snapshot
		else if _.isArray @_snapshot
			# Diff current snapshot against previous snapshot, if any
			@_ensureResultTypeUnchanged "array"
			if @_previousSnapshot
				@_diffSnapshots @_previousSnapshot, @_snapshot
			else
				@_addAll @_snapshot
		else
			throw new Error "computation function must return an array or a Mongo.Cursor instance"
	
	_ensureResultTypeUnchanged: (type) ->
		# Sorry about this. :/ Changing result types are is supported because
		# this class does not keep track of what is actually being returned by
		# Mongo.Cursor's observe function. Maybe another time.
		if @_resultType?
			if type is not @_resultType
				throw new Error "computation must not change result type"
		else
			@_resultType = type
	
	_addAll: (collection) ->
		@_events.batchBegin() if @_events.batchBegin?
		if not @_events.added?
			return

			@_events.added doc
		@_events.batchEnd() if @_events.batchEnd?
	
	_objectValue: (object) ->
		if not object[@_valueField]?
			throw new Error "computation returned object without identifying property '#{@_valueField}'"
		value = object[@_valueField]
		if typeof value isnt "string"
			throw new Error "computation returned object whose identifying property is not a string"
		value
	
	_diffKeys: (previousSnapshot, currentSnapshot) ->
		# Extract keys
		previousKeys = _.pluck previousSnapshot, @_valueField
		currentKeys = _.pluck currentSnapshot, @_valueField
		
		# Calculate difference
		added: _.difference currentKeys, previousKeys
		changed: _.intersection previousKeys, currentKeys
		removed: _.difference previousKeys, currentKeys
	
	_diffSnapshots: (previousSnapshot, currentSnapshot) ->
		# Calculate difference
		diff = @_diffKeys previousSnapshot, currentSnapshot
		
		# Map objects to keys
		previous = {}
		current = {}
		for object in previousSnapshot
			previous[@_objectValue object] = object
		for object in currentSnapshot
			current[@_objectValue object] = object
		
		# Build object collections
		added: _.map diff.added, (key) ->
			current[key]
		changed: _.map diff.changed, (key) ->
			{current: current[key], prev: previous[key]}
		removed: _.map diff.removed, (key) ->
			previous[key]
	
	_applyChangeSet: (changeset) ->
		# Begin batch update
		@_events.batchBegin() if @_events.batchBegin?
		
		# Send changes
		if @_events.added?
			_.each changeset.added, @_events.added
		if @_events.changed?
			_.each changeset.changed, (x) => @_events.changed x.current, x.prev
		if @_events.removed?
			_.each changeset.removed, @_events.removed
		
		# End batch update
		@_events.batchEnd() if @_events.batchEnd?
