Path = require "path"
fs = require "fs"
logger = require "logger-sharelatex"
settings = require("settings-sharelatex")
Errors = require "./Errors"
SafeReader = require "./SafeReader"

module.exports = ResourceStateManager =

	# The sync state is an identifier which must match for an
	# incremental update to be allowed.
	#
	# The initial value is passed in and stored on a full
	# compile, along with the list of resources..
	#
	# Subsequent incremental compiles must come with the same value - if
	# not they will be rejected with a 409 Conflict response. The
	# previous list of resources is returned.
	#
	# An incremental compile can only update existing files with new
	# content.  The sync state identifier must change if any docs or
	# files are moved, added, deleted or renamed.

	SYNC_STATE_FILE: ".project-sync-state"
	SYNC_STATE_MAX_SIZE: 128*1024

	saveProjectState: (state, resources, basePath, callback = (error) ->) ->
		stateFile = Path.join(basePath, @SYNC_STATE_FILE)
		if not state? # remove the file if no state passed in
			logger.log state:state, basePath:basePath, "clearing sync state"
			fs.unlink stateFile, (err) ->
				if err? and err.code isnt 'ENOENT'
					return callback(err)
				else
					return callback()
		else
			logger.log state:state, basePath:basePath, "writing sync state"
			resourceList = (resource.path for resource in resources)
			fs.writeFile stateFile, [resourceList..., "stateHash:#{state}"].join("\n"), callback

	checkProjectStateMatches: (state, basePath, callback = (error, resources) ->) ->
		stateFile = Path.join(basePath, @SYNC_STATE_FILE)
		size = @SYNC_STATE_MAX_SIZE
		SafeReader.readFile stateFile, size, 'utf8', (err, result, bytesRead) ->
			return callback(err) if err?
			if bytesRead is size
				logger.error file:stateFile, size:size, bytesRead:bytesRead, "project state file truncated"
			[resourceList..., oldState] = result?.toString()?.split("\n") or []
			newState = "stateHash:#{state}"
			logger.log state:state, oldState: oldState, basePath:basePath, stateMatches: (newState is oldState), "checking sync state"
			if newState isnt oldState
				return callback new Errors.FilesOutOfSyncError("invalid state for incremental update")
			else
				resources = ({path: path} for path in resourceList)
				callback(null, resources)

	checkResourceFiles: (resources, allFiles, basePath, callback = (error) ->) ->
		# check the paths are all relative to current directory
		for file in resources or []
			for dir in file?.path?.split('/')
				if dir == '..'
					return callback new Error("relative path in resource file list")
		# check if any of the input files are not present in list of files
		seenFile = {}
		for file in allFiles
			seenFile[file] = true
		missingFiles = (resource.path for resource in resources when not seenFile[resource.path])
		if missingFiles?.length > 0
			logger.err missingFiles:missingFiles, basePath:basePath, allFiles:allFiles, resources:resources, "missing input files for project"
			return callback new Errors.FilesOutOfSyncError("resource files missing in incremental update")
		else
			callback()
