#!/usr/local/bin/python

import ConfigParser
from os import path, symlink, listdir, remove, stat
from hashlib import sha1
from threading import Lock
from shutil import move
import glob
import json, datetime, time

# output constants
_OK = "OK"
_ERROR = "ERROR"
_STATUS = "status"
_VALUE = "value"

# tool dict consts
_T_QUEUE = "queue"
_T_PROGRESS = "progress"
_T_DONE = "done"
_T_ERROR = "error"
_T_LOCK = "lock"
_T_STOP = "stop"

# key dict consts
_K_ROOT = "root"
_K_USERS = "users"
_K_QUEUE = "queue"
_K_PROGRESS = "progress"
_K_DONE = "done"
_K_ERROR = "error"
_K_OPTIONS = "options"

def checkJSON(jsonData, keyList=[]):
	#	check if JSON contains key and if this key is not empty
	for key in keyList:
		if key not in jsonData:
			return {_STATUS: _ERROR, _VALUE: "Key " + key + " not found"}
		if not jsonData[key]:
			return {_STATUS: _ERROR, _VALUE: "Value of " + key + " is empty"}
	return 	{_STATUS: _OK}

def checkUsername(userName, keys):
	#	check that user exsits
	#	user exists only if his director exists
	#	for example for default user: /mnt/users/default
	return checkPath(keys[_K_USERS], userName)

def checkToolname(toolName, keys):
	#	check that tool exsits
	#	tool exists only if his director exists
	#	for example for wcrft2 tool: /mnt/requests/wcrft2
	return checkPath(keys[_K_ROOT], toolName)

def checkMultipliePaths(paths):
	#	check that multiplie paths exists
	#	returns error for first bad path
	#	returns ok if all paths exists
	for path in paths:
		tempResult = checkPath(path)
		if checkStatusError(tempResult):
			return tempResult
	return {_STATUS: _OK}

def checkPath(*args):
	#	atomic function
	#	check if path exsits
	if not path.exists(path.join(*args)):
		return {_STATUS: _ERROR, _VALUE: "Path " + path.join(*args) + " not exists"}
	return 	{_STATUS: _OK}

def checkStatusError(jsonData):
	#	atomic function
	#	check if status key have value of error
	return jsonData[_STATUS] == _ERROR

def loadConfig(configPath):
	#	load config from file
	#	returns config as dictionary
	tempKeyValue = {}
	config = ConfigParser.ConfigParser()
	config.read(configPath)
	for section in config.sections():
		for option in config.options(section):
			tempKeyValue[option] = config.get(section, option)
	return tempKeyValue

def timeNow():
	#	atomic function
	#	returns the exact time with an accuracy of millisecond
	now = datetime.datetime.now()
	return now.strftime("%Y-%m-%d %H:%M:%S:%f")

def createHashToken(value):
	#	generate token from given value and current time
	#	returns token
	return sha1(value + timeNow()).hexdigest()

def createOptionsPath(path, tool):
	#	returns path for option file
	return createPath(path, tool + "_options_" + timeNow())

def createTaskPath(userName, keys):
	#	returns path for new task of specify user
	return createPath(keys[_K_USERS], userName, createHashToken(userName))

def createPath(*args):
	#	atomic function
	#	returns the path crate from args
	return str(path.join(*args))

def generateOptionsFile(path, tool, options):
	#	create file in specify path
	#	write options to file
	#	return path to file
	optionPath = createOptionsPath(path, tool)
	f = open(optionPath, "w")
	f.write(json.dumps(options))
	f.close()
	return optionPath

def generateTokens(files, optionPath, keys, tool):
	#	for all files crate tokens
	#	and make symlinks
	#	return all tokens
	tokens = []
	for f in files:
		task = createHashToken(f)
		token = createPath(keys[_K_ROOT], tool, keys[_K_QUEUE], task)
		cfg = createPath(keys[_K_ROOT], tool, keys[_K_OPTIONS] + task + ".cfg")
		generateSymlinks(f, token)
		generateSymlinks(optionPath, cfg)
		tokens.append(task)
	return tokens

def generateSymlinks(source, target):
	#	atomic function
	#	create symbolik link
	symlink(source, target)

def getValuesFromDict(dicts, *args):
	#	atomic function
	#	get multiplie values from dictionary and return it as a tupel
	#	TIPS: if you need to get only one value use it like this (value,) = getValuesFromDict(dicts, 'value')
	return tuple([dicts[arg] for arg in args])

def checkTask(toolName, token, toolList, keys):
	#	TODO: need to be rebuild for memory queue - rebuild done test required and maybe some improvements
	#	check where is a task with token
	#	return dictionery with status and value keys
	(tool,) = getValuesFromDict(toolList, toolName)
	with tool[_T_LOCK]:
		if token in tool[_T_QUEUE]:
			 return {_STATUS: "QUEUE", _VALUE: "1"}
		if token in tool[_T_PROGRESS]:
			return {_STATUS: "PROCESSING", _VALUE: "null"}
		if token in tool[_T_DONE]:
			return {_STATUS: "DONE", _VALUE: createPath(keys[_K_ROOT], toolName, keys[_K_DONE], token)}
		if token in tool[_T_ERROR]:
			return {_STATUS: "ERROR", _VALUE: getErrorFile(tokenPath, keys)}
		return {_STATUS: "NONE", _VALUE: "404 file not found"}

def getErrorFile(token, keys):
	errorFile = open(token.replace(keys[_K_QUEUE], keys[_K_ERROR]), 'r')
	error = errorFile.read()
	errorFile.close()
	return error

def makeToolMap(keys):
	#	generate dictionery of tools dictionerys
	locklist = {}
	for tool in listdir(keys[_K_ROOT]):
		if path.isdir(keys[_K_ROOT] + tool):
			locklist[tool] = newToolDict(keys, tool)
	return locklist

def addToolToMap(dicts, tool, keys):
	#	add new tool to map
	dicts[tool] = newToolDict(keys, tool)

def newToolDict(keys, tool):
	#	TODO: rebuild for memory queue - rebuild done test required
	#	generate a dictionery for new tool
	#	return dictionery
	file_lists = {elemnt: createFileList(keys, tool, elemnt)
						  for elemnt in [_T_QUEUE, _T_PROGRESS, _T_ERROR, _T_DONE]}
	return dict({_T_LOCK: Lock(), _T_STOP: False},
				**file_lists)

def createFileList(keys, tool, name):
	#	return sorted list of file dirs
	return getSortedFiles(createPath(keys[_K_ROOT], tool, keys[name]))

def getSortedFiles(pathFiles):
	#	return sorted list of files - files are sorted by file create date
	return sorted([path.join(pathFiles, f) for f in listdir(pathFiles)], key=path.getctime)

def getTask(toolList, toolName, keys):
	#	TODO: rebuild for memory queue - rebuild done test required
	#	return oldest or empty task
	(tool,) = getValuesFromDict(toolList, toolName)
	with tool[_T_LOCK]:
		if not tool[_T_STOP] and not tool[_T_QUEUE] == []:
			requestFile = getOldestFile(tool)
		        move(createPath(keys[_K_ROOT], toolName, keys[_K_QUEUE], requestFile),
		        	 createPath(keys[_K_ROOT], toolName, keys[_K_PROGRESS], requestFile))
			tool[_T_PROGRESS].append(requestFile)
		        return {"task": requestFile,
		        		"options": readOptions(createPath(keys[_K_ROOT], toolName, keys[_K_OPTIONS], requestFile+".cfg"))}
		else:
			return {"task": ""}

def readOptions(path):
	#	read file with optios
	#	delete this file
	#	return options from file
	optionsFile = open(path, 'r')
	options = optionsFile.read()
	optionsFile.close()
	remove(path)
	return options

def taskControl(command, toolList, toolName):
	#	TODO: rebuild coz i'm not proud of this part of code
	#	check control status for tool
	#	return status of tool
	(tool,) = getValuesFromDict(toolList, toolName)
	if command != 'stop' and command != 'start':
		return {_STATUS: _ERROR, _VALUE: "Unknown command use start/stop to control task flow"}
	if command == 'stop' and not tool[_T_STOP]:
		tool[_T_STOP] = True
		return {_STATUS: _OK, _VALUE: "Sending task disable for " + toolName}
	if command == 'start' and tool[_T_STOP]:
		tool[_T_STOP] = False
		return {_STATUS: _OK, _VALUE: "Sending task enabled for " + toolName}
	return {_STATUS: _ERROR, _VALUE: "Command " + command + " already in use for " + toolName}

def getOldestFile(tool):
	#	TODO: rebuild coz i'm not proud of this part of code
	#	take first element in list
	#	remove it from list
	#	return basename of this element
	task = tool[_T_QUEUE][0]
	del tool[_T_QUEUE][0]
	return path.basename(task)

def addTaskToQueue(toolList, toolName, jsonOutcome):
	#	add new task to queue
	(tool,) = getValuesFromDict(toolList, toolName)
	(tokens,) = getValuesFromDict(jsonOutcome, _VALUE)
	with tool[_T_LOCK]:
		tool[_T_QUEUE].extend(tokens)

def moveTask(toolList, toolName, task, inputList, outputList):
	(tool,) = getValuesFromDict(toolList, toolName)
	with tool[_T_LOCK]:
		tool[inputList].remove(task)
		tool[outputList].append(task)
	return {_STATUS: _OK, _VALUE: "Task moved"}

def cleanAll(toolList, keys):
	filesToRemove = []
	for tool in toolList:
		(listOfFiles, listOfPaths) = getOldFiles(tool, keys)
		removeFromList(toolList, tool, listOfFiles)
		filesToRemove.extend(listOfPaths)
	deleteFiles(*filesToRemove)
	return {_STATUS: _OK, _VALUE: str(len(filesToRemove)) + " files was deleted"}

def removeFromList(toolList, toolName, listOfFiles):
	(tool,) = getValuesFromDict(toolList, toolName)
	with tool[_T_LOCK]:
		tool[_T_DONE] = list(set(tool[_T_DONE]) - set(listOfFiles))

def getOldFiles(tool, keys):
	listOfFiles = []
	listOfPaths = []
	cutoff = time.time() - (1 * 86400)
	pathTool = createPath(keys[_K_ROOT], tool, keys[_K_DONE])
	files = listdir(pathTool)
	for xfile in files:
		filePath = createPath(pathTool, xfile)
		if path.isfile(filePath):
			c = stat(path.join(filePath)).st_ctime
			if c < cutoff:
		    		listOfFiles.append(xfile)
				listOfPaths.append(filePath)
	return tuple([listOfFiles, listOfPaths])

def deleteFiles(*args):
	for arg in args:
		remove(arg)

def checkSecret(secret):
	if secret == "g4.19":
		return {_STATUS: _OK}
	else:
		return {_STATUS: _ERROR, _VALUE: "Secret incorrect, access deined "}

def refreshFolder(path):
	#	TODO: remove this when rebuild for queue is done
	#	we need rescan directory each time coz of nfs use
	return glob.glob(path)

def checkTaskInProgress(toolList, toolName, task):
	(tool,) = getValuesFromDict(toolList, toolName)
	if task in tool[_T_PROGRESS]:
		return {_STATUS: _OK}
	else:
		return {_STATUS: _ERROR, _VALUE: "No such task " + task + " in progress for tool " + toolName}
