
__author__ = 'Miroslav Bauer'


import os
import saga
from constants import JOB_EXTERNAL_STATE
from os.path import basename


class JobParam:
	''' Job's command-line argument, which contains some extra information.
	    pos:       position on the command-line. Special positions are stdout,stderr
	    ptype:     file or string
	    direction: input or output
	    value:     string value or file path
	    name:      used for output arguments as a key in results map
	'''
	
	def __init__(self, pos, ptype, direction, value=None, name=None):
		self.pos       = pos
		self.name      = name
		self.ptype     = ptype
		self.value     = value
		self.direction = direction
	
	def positional(self):
		return self.pos.isdigit()
	

class Job:
	''' Class carrying all neccessary info about a job
	    executable:      path to a script to be run
	    params:          list of JobParam instances
	    dependencies:    list of another needed files/supportive scripts
	    environment:     env variables for the job
	    envRequirements: requirements on the resource attributes like OS,arch
	    prefered:        preferred resource name
	'''

	def __init__(self, executable, params, dependencies=[],
			environment={}, envRequirements={}, preferred=None):
		self.executable      = executable
		self.params          = params
		self.dependencies    = dependencies if dependencies is list else [dependencies]
		self.environment     = environment
		self.envRequirements = envRequirements
		self.preferred       = preferred

		self.state    = None
		files = [ p for f, p in self.params.items() if p.ptype == 'file' ]
		self.infiles  = [ f.value for f in files if f.direction == 'in']
		self.infiles.append(executable)
		self.infiles += dependencies
		self.outfiles = [ f.value for f in files if f.direction == 'out']
		self.size     = self.getJobSize()
	
	def setId(self, jid):
		''' Sets id of a job to JID given '''
		self.jid = jid

	def getSagaDescription(self, resource):
		''' Given a resource attributes dict, this method returns
		    a resource specific description of a job to be used
		    with SAGA backend for job's submission onto a resource.
		'''
		desc = saga.job.Description()
		if resource['jobbackend'] == 'pbs':
			# This feature works with pbs backend only
			desc.name = 'Datamorph-SAGA-Job-#%s' % self.jid
		desc.executable = './%s' % basename(self.executable)
		if resource['scratchdir']:
			workdir = '%sjobs/%s/' % (resource['scratchdir'], self.jid)
		else:
			workdir = 'jobs/%s/' % self.jid
			
		desc.working_directory = workdir
		argv = []
		for pos, arg in self.params.iteritems():
			if arg.positional:
				value = basename(arg.value) if arg.ptype == 'file' else arg.value
				argv.append((pos, value))

		argv = [ val for pos, val in sorted(argv) ]
		desc.arguments  = argv
		if 'stdout' in self.params and self.params['stdout'].value:
			desc.output = '%s%s' % (workdir, basename(self.params['stdout'].value))
		else:
			desc.output = '%s%s' % (workdir,'stout')
		if 'error' in self.params and self.params['error'].value:
			desc.error = '%s%s' % (workdir, basename(self.params['stderr'].value))
		else:
			desc.error = '%s%s' % (workdir, 'sterr')
		if 'cpus' in self.envRequirements:
			desc.total_cpu_count = self.envRequirements['cpus']
		else:
			desc.total_cpu_count = 1
		if self.environment: desc.environment = self.environment
		env = dict(resource['environment'].items() + self.environment.items())
		if env: desc.environment = env
		# NOTICE: These features aren't supported by saga-python job adaptors yet
		# For currently supported set of features, refer to (e.g):
		# https://github.com/saga-project/saga-python/blob/master/saga/adaptors/pbs/pbsjob.py#L280
		#
		#if 'arch' in self.envRequirements:
		#	desc.cpu_architecture = self.envRequirements['arch']
		#if 'os' in self.envRequirements:
		#	desc.operating_system_type = self.envRequirements['os']
		#desc.cleanup = True
		return desc
	
	def getJobSize(self):
		''' Returns size of all input files needed in kB '''
		return self._getFilesSize(self.infiles)/1024
                	
	def _getFilesSize(self, fileList):
		fsize = 0
		for f in fileList:
                        if not os.path.exists(f): continue
                        if os.path.isdir(f):
				fsize += self._getFilesSize(['%s%s' % (f,fn) for fn in os.listdir(f)])
                        else:
                                fsize += os.path.getsize(f)
		return fsize
