'use strict'

module.exports = function*(loader)
{
	class StateService extends loader.Service
	{
		get serviceErrors() { return [
			'instanceNotFound',
			'collectionNotFound',
			'changeNotFound',
			'changeDenied',
			'changeFailed',
			'changeContention',
		]}
		
		*init()
		{
			this.db = yield this.createMongoConnection('state')
			this.dataConfig = loader.require(this.config.state.data)
		}
		
		*getCollectionAndConfig(req)
		{
			const name = req.params.collection
			const collectionConfig = this.dataConfig.collections[name]
			if (!collectionConfig)
			{
				return this.errors.collectionNotFound(name)
			}
			
			req.collection = yield this.db.collectionAsync(name)
			req.collectionConfig = collectionConfig
		}
		
		getRoutes()
		{
			const middleware = [ this.authenticated, this.getCollectionAndConfig ]
			
			return [
				[ 'collection', this.getCollection, middleware ],
				[ 'advertised', this.getAdvertised, middleware ],
				[ 'instance', this.getInstance, middleware ],
				[ 'modify', this.modify, middleware ],
			]
		}
		
		*getCollection(req)
		{
			const findResult = yield req.collection.findAsync()
			return findResult.toArray()
		}
		
		*getAdvertised(req)
		{
			return { advertised: req.collectionConfig.advertised || {} }
		}
		
		*getInstance(req)
		{
			const result = yield req.collection.findOneAsync({ _id: req.params.id })
			if (!result)
			{
				return this.errors.instanceNotFound()
			}
			
			return result
		}
		
		*modify(req)
		{
			if (!(req.params.changes instanceof Array) || req.params.changes.length === 0)
			{
				return this.errors.messageParsingFailed('changes')
			}
			
			const changeRequests = []
			for (const changeRequest of req.params.changes)
			{
				const change = req.collectionConfig.changes[changeRequest.name]
				if (!change)
				{
					return this.errors.changeNotFound()
				}
				
				if (change.test)
				{
					const testResult = yield change.test(req.user, req.params.id)
					if (!testResult)
					{
						return this.errors.changeDenied()
					}
				}
				
				changeRequests.push({ change, params: changeRequest.params })
			}
			
			for (let attempt = 0; attempt < this.config.state.maxRetries; attempt++)
			{
				const InstanceType = req.collectionConfig.InstanceType
				let instance = yield req.collection.findOneAsync({ _id: req.params.id })
				if (!instance)
				{
					instance = new InstanceType()
					instance.v = 1
					instance._id = req.params.id
				}
				else
				{
					instance.__proto__ = InstanceType.prototype
				}
				
				for (const changeRequest of changeRequests)
				{
					let changeResult
					try
					{
						changeResult = yield changeRequest.change.apply(instance, changeRequest.params)
					}
					catch (err)
					{
						this.log.error(err, 'change application error')
						return this.errors.internalError()
					}
					
					if (changeResult !== undefined)
					{
						return this.errors.changeFailed({ changeResult })
					}
				}
				
				const requiredVersion = instance.v
				instance.v++
				
				if (requiredVersion === 1)
				{
					try
					{
						yield req.collection.insertAsync(instance)
					}
					catch (err)
					{
						if (err.code === 11000)
						{
							continue
						}
						else
						{
							return this.errors.internalError()
						}
					}
				}
				else
				{
					const write = yield req.collection.updateAsync({ _id: req.params.id, v: requiredVersion }, instance)
					if (write.result.n === 0)
					{
						continue
					}
				}
				
				return { instance }
			}
			
			return this.errors.changeContention()
		}
	}
	
	return StateService
}

@@ -2408,17 +2408,17 @@
 nce.v = 
-1
+0
 %0A%09%09%09%09%09in
@@ -3025,301 +3025,8 @@
 %09%09%09%0A
-%09%09%09%09if (requiredVersion === 1)%0A%09%09%09%09%7B%0A%09%09%09%09%09try%0A%09%09%09%09%09%7B%0A%09%09%09%09%09%09yield req.collection.insertAsync(instance)%0A%09%09%09%09%09%7D%0A%09%09%09%09%09catch (err)%0A%09%09%09%09%09%7B%0A%09%09%09%09%09%09if (err.code === 11000)%0A%09%09%09%09%09%09%7B%0A%09%09%09%09%09%09%09continue%0A%09%09%09%09%09%09%7D%0A%09%09%09%09%09%09else%0A%09%09%09%09%09%09%7B%0A%09%09%09%09%09%09%09return this.errors.internalError()%0A%09%09%09%09%09%09%7D%0A%09%09%09%09%09%7D%0A%09%09%09%09%7D%0A%09%09%09%09else%0A%09%09%09%09%7B%0A%09
 %09%09%09%09
@@ -3124,19 +3124,36 @@
 instance
+, %7B upsert: true %7D
 )%0A
-%09
 %09%09%09%09if (
@@ -3174,28 +3174,26 @@
  === 0)%0A%09%09%09%09
-%09
 %7B%0A
-%09
 %09%09%09%09%09continu
@@ -3194,23 +3194,16 @@
 ontinue%0A
-%09%09%09%09%09%7D%0A
 %09%09%09%09%7D%0A%09%09

Simplify state insertion using upsert