module.exports = RequestParser =
	VALID_COMPILERS: ["pdflatex", "latex", "xelatex", "lualatex"]
	MAX_TIMEOUT: 300

	parse: (body, callback = (error, data) ->) ->
		response = {}

		if !body.compile?
			return callback "top level object should have a compile attribute"

		compile = body.compile
		compile.options ||= {}
		
		try
			response.compiler = @_parseAttribute "compiler",
				compile.options.compiler,
				validValues: @VALID_COMPILERS
				default: "pdflatex"
				type: "string"
			response.timeout = @_parseAttribute "timeout",
				compile.options.timeout
				default: RequestParser.MAX_TIMEOUT
				type: "number"
			response.imageName = @_parseAttribute "imageName",
				compile.options.imageName,
				type: "string"
			response.draft = @_parseAttribute "draft",
				compile.options.draft,
				default: false,
				type: "boolean"
			response.check = @_parseAttribute "check",
				compile.options.check,
				type: "string"

			# The syncType specifies whether the request contains all
			# resources (full) or only those resources to be updated
			# in-place (incremental).
			response.syncType = @_parseAttribute "syncType",
				compile.options.syncType,
				validValues: ["full", "incremental"]
				type: "string"

			# The syncState is an identifier passed in with the request
			# which has the property that it changes when any resource is
			# added, deleted, moved or renamed.
			#
			# on syncType full the syncState identifier is passed in and
			# stored
			#
			# on syncType incremental the syncState identifier must match
			# the stored value
			response.syncState = @_parseAttribute "syncState",
				compile.options.syncState,
				type: "string"

			if response.timeout > RequestParser.MAX_TIMEOUT
				response.timeout = RequestParser.MAX_TIMEOUT
			response.timeout = response.timeout * 1000 # milliseconds

			response.resources = (@_parseResource(resource) for resource in (compile.resources or []))

			rootResourcePath = @_parseAttribute "rootResourcePath",
				compile.rootResourcePath
				default: "main.tex"
				type: "string"
			originalRootResourcePath = rootResourcePath
			sanitizedRootResourcePath = RequestParser._sanitizePath(rootResourcePath)
			response.rootResourcePath = RequestParser._checkPath(sanitizedRootResourcePath)
			
			for resource in response.resources
				if resource.path == originalRootResourcePath
					resource.path = sanitizedRootResourcePath
		catch error
			return callback error

		callback null, response

	_parseResource: (resource) ->
		if !resource.path? or typeof resource.path != "string"
			throw "all resources should have a path attribute"

		if resource.modified?
			modified = new Date(resource.modified)
			if isNaN(modified.getTime())
				throw "resource modified date could not be understood: #{resource.modified}"

		if !resource.url? and !resource.content?
		   	throw "all resources should have either a url or content attribute"
		if resource.content? and typeof resource.content != "string"
			throw "content attribute should be a string"
		if resource.url? and typeof resource.url != "string"
			throw "url attribute should be a string"

		return {
			path: resource.path
			modified: modified
			url: resource.url
			content: resource.content
		}

	_parseAttribute: (name, attribute, options) ->
		if attribute?
			if options.validValues?
				if options.validValues.indexOf(attribute) == -1
					throw "#{name} attribute should be one of: #{options.validValues.join(", ")}"
			if options.type?
				if typeof attribute != options.type
					throw "#{name} attribute should be a #{options.type}"
		else
			return options.default if options.default?
		return attribute

	_sanitizePath: (path) ->
		# See http://php.net/manual/en/function.escapeshellcmd.php
		path.replace(/[\#\&\;\`\|\*\?\~\<\>\^\(\)\[\]\{\}\$\\\x0A\xFF\x00]/g, "")

	_checkPath: (path) ->
		# check that the request does not use a relative path
		for dir in path.split('/')
			if dir == '..'
				throw "relative path in root resource"
		return path