#-*- coding: utf-8 -*-

"""
Copyright Theophile Rotoudjimaye <rotoudjimaye.theo@gmail.com>
All Rights Reserved
"""

QUARTZ_AVAILABLE = True
try:
    from org.quartz import JobBuilder, TriggerBuilder, CronScheduleBuilder
    from org.rt.jyquartz import ScheduledJob
    from org.rt.modjy.servlet import ModjyServlet

    SCHEDULER = ModjyServlet.SCHEDULER
    FN_CACHE = {}
except:
    QUARTZ_AVAILABLE = False

import logging

logger = logging.getLogger('djangojy.jyquartz')

def schedule(cron, context=False, cancel_on_failure=False):
    def decorator(task_fn):
        pymodule, pyfunction = task_fn.__module__, task_fn.__name__
        FN_CACHE[pymodule + "#" + pyfunction] = task_fn
        if ScheduledJob.isJobRunner():
            logger.warning("::JobRunner cannot submit to scheduler!! (%s.%s)", pymodule, pyfunction)
            return task_fn
        if SCHEDULER.isShutdown():
            logger.warning("::Scheduler is already shut down. Cannot submit job!")
            return task_fn
        jobBuilder = JobBuilder.newJob(ScheduledJob).withIdentity(pyfunction, pymodule)
        if SCHEDULER.checkExists(jobBuilder.build().key):
            logger.warning("::Scheduler is already shut down. Cannot submit job!")
            return task_fn
        jobBuilder.usingJobData(ScheduledJob.PY_MODULE, pymodule).usingJobData(ScheduledJob.PY_FUNCTION, pyfunction)
        if context:
            jobBuilder.usingJobData(ScheduledJob.INCLUDE_CTX, True)
        jobDetail = jobBuilder.build()
        logger.info("::Scheduling %s --> [%s]", jobDetail.key, cron)
        trigger = TriggerBuilder.newTrigger().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build()
        SCHEDULER.scheduleJob(jobDetail, trigger)
        return task_fn

    return decorator

from django.db import models
from django.core import serializers

def serialize_arg(arg):
    if isinstance(arg, models.Model):
        json_serializer = serializers.get_serializer("json")()
        return "django.db.models.Model::" + json_serializer.serialize([arg], ensure_ascii=False)
    return arg


def deserialize_arg(arg):
    if isinstance(arg, unicode) and arg.startswith("django.db.models.Model[]::"):
        json_stream = arg[arg.index("::") + 2:]
        return serializers.deserialize("json", json_stream)
    if isinstance(arg, unicode) and arg.startswith("django.db.models.Model::"):
        json_stream = arg[arg.index("::") + 2:]
        model = list(serializers.deserialize("json", json_stream))[0]
        return model
    return arg


class Launcher():
    def __init__(self, task_fn):
        self.task_fn = task_fn
        self.pymodule, self.pyfunction = task_fn.__module__, task_fn.__name__

    def __call__(self, **kwargs):
        self.kwargs = kwargs
        return self

    def execute(self):
        jobBuilder = JobBuilder.newJob(ScheduledJob)#.withIdentity(self.pyfunction, self.pymodule)
        jobBuilder.usingJobData(ScheduledJob.PY_MODULE, self.pymodule).usingJobData(ScheduledJob.PY_FUNCTION,
            self.pyfunction)
        for k in self.kwargs:
            jobBuilder.usingJobData("__kwarg__." + k, serialize_arg(self.kwargs[k]))
        jobDetail = jobBuilder.build()
        if  not (SCHEDULER.isShutdown() or SCHEDULER.checkExists(jobDetail.key)):
            logger.info("::Scheduling  %s", jobDetail.key)
            trigger = TriggerBuilder.newTrigger().build()
            SCHEDULER.scheduleJob(jobDetail, trigger)
        return self

    def schedule(self, cron, context=False, cancel_on_failure=False):
        schedule(cron, context, cancel_on_failure)(self.task_fn)
        return self


def async():
    def decorator(task_fn):
        pymodule, pyfunction = task_fn.__module__, task_fn.__name__
        FN_CACHE[pymodule + "#" + pyfunction] = task_fn
        return Launcher(task_fn)

    return decorator


import sys

if QUARTZ_AVAILABLE:
    ScheduledJob.sys_path = sys.path
    ScheduledJob.sys_modules = sys.modules

def jobRunner(fn_name, **kwargs):
    logger.debug("Entering jobRunner...")
    import sys
    nsyspath, nsysmodules = sys.path, sys.modules
    try:
        sys.path, sys.modules = ScheduledJob.sys_path, ScheduledJob.sys_modules
        pymodule, pyfunction = fn_name.split("#")
        dkwargs = dict([(k, deserialize_arg(kwargs[k])) for k in kwargs])
        result = None
        try:
            fn = FN_CACHE[pymodule + "#" + pyfunction]
            result = fn(**dkwargs)
            logger.debug("jobRunner executed successfully jyquartz function %s", fn_name)
            return result
        except Exception:
            logger.error("Error running jyquartz function %s", fn_name, exc_info=True)
            return None
    finally:
        #sys.path, sys.modules = nsyspath, nsysmodules
        pass


if QUARTZ_AVAILABLE:
    ScheduledJob.pyFunction = jobRunner
else:
    logger.warning("::Quartz Scheduler seems to be missing from your class path; jyQuartz cannot run...")
