'''
Created on Sep 4, 2017

@author: tskthilak
'''
import logging

from wf.model import WfSpecification, WfJob, WfTask
from wf.worker import cb_run_job
from wf.util import encode, decode
from wf.error import WfJobError, TaskNotFoundError
from cPickle import dumps

from rq import Queue
from wf.store import Store
from rq.job import Job, NoSuchJobError
from time import time

LOG = logging.getLogger(__name__)
logging.basicConfig(level=logging.DEBUG)
logging.basicConfig(format="%(asctime)s;%(levelname)s;%(message)s")

class QueueName():
    A1 = 'high'
    B1 = 'normal'
    C1 = 'low'

class Processor(object):
    '''
    classdocs
    '''
    def __init__(self, db_conn):
        self._db_conn = db_conn
        self._queue = {}
    
    def worker(self, name):
        if not self._queue.has_key(name):
            self._queue[name] = Queue(connection=self._db_conn)
        return self._queue[name]
    
    def deserialize_spec(self, mime, data):
        retobj = WfSpecification.deserialize(mime, data)
        #LOG.debug("data : %s" % (data))
        #LOG.debug("object : %s" % (dumps(retobj)))
        return retobj
    
    def serialize_spec(self, mime, spec):
        retdata = spec.serialize(mime)
        #LOG.debug("object : %s" % (dumps(spec.wfspec)))
        #LOG.debug("data : %s" % (retdata))
        return retdata
    
    def deserialize_job(self, mime, data):
        retobj = WfJob.deserialize(mime, data)
        #LOG.debug("data : %s" % (data))
        #LOG.debug("object : %s" % (dumps(retobj.wfinst)))
        return retobj
    
    def serialize_job(self, mime, job):
        retdata = job.serialize(mime)
        #LOG.debug("object : %s" % (dumps(job.wfinst)))
        #LOG.debug("data : %s" % (retdata))
        return retdata

    def job_run(self, jid, data):
        '''
        current state    operation    next state
        started            [auto]     running
        started            cancel     cancelled
        started            restart    started
        
        running            [auto]     waiting | completed
        running            cancel     cancelled
        running            restart    started
        
        waiting            [auto]     running | (timeout) cancelled
        waiting            cancel     cancelled
        waiting            restart    started
        
        cancelled and completed are terminal states,
        any operation would result in error.
        '''
        meta = {}
        meta['debug'] = False
        meta['submitted_at'] = time()
        meta['data'] = data
        LOG.debug("job {0} has meta_data as {1}".format(jid, data))
        qjob = self.worker(QueueName.B1).enqueue(cb_run_job, encode(jid), job_id=str(jid), meta=meta)
        return qjob.id
    
    def _retreive_qjob(self, jid):
        LOG.debug("jid=%s" % (jid))
        job = Job(id=jid, connection=self._db_conn)
        if job is None: raise ValueError("invalid job id")
        try:
            job.refresh()
        except NoSuchJobError:
            raise ValueError("invalid job id")
        return job
    
    def job_load(self, jid):
        LOG.debug("loading jid=%s" % (jid))
        #TODO - if the state is not eventually updated by worker, this will load the initial state
        job_details = self._db_conn.load_job(jid)
        job = WfJob.deserialize('application/json', job_details)
        job.set_data("id", jid)
        return job
    
    def job_status(self, jid):
        '''
        ' for the given job-id
        ' reports the rq job status
        ' reports the wf job tasks' status
        ' {
        '   "status" : <rq_job_status>,
        '   "tasks" :
        '   [
        '     {"id":<task_id>,"name":<task_name>,"status":<task_status>},
        '     ...
        '   ]
        ' }
        '''
        status = {}
        status['status'] = self._retreive_qjob(jid).status
        #TODO: see the bug mentioned in job_load()
        wfjob = self.job_load(jid)
        task_list = wfjob.task_list()
        status['tasks'] = []
        for tid in task_list:
            try:
                task = WfTask.deserialize(wfjob, self._db_conn.load_task(tid))
                status['tasks'].append({
                    'id':task.id,
                    'name':task.get_name(),
                    'status':task.status})
            except TaskNotFoundError:
                LOG.debug("task {0} of job {1} is not found.".format(tid, jid))
        return status
    
    def job_result(self, jid):
        '''
        ' for the given job-id
        ' reports the rq job result
        '  that was returned by the rq job callback method
        '  which usually is the wf job task tree
        '''
        return self._retreive_qjob(jid).result

    def job_cancel(self, jid):
        qjob = self._retreive_qjob(jid)
        if qjob.get_status() in ["finished", "failed"]:
            raise WfJobError("invalid operation")
        qjob.meta['cancel_requested'] = True
        qjob.meta['cancel_requested_at'] = time()
        qjob.save_meta()
        qjob.save()
        return jid
    
    def job_restart(self, jid):
        qjob = self._retreive_qjob(jid)
        qjob.cancel()
        qjob.delete()
        return self.job_run(jid)
