import enum
import logging
import time

import croniter

from threading import Thread

from platypush.procedure import Procedure

logger = logging.getLogger(__name__)


class CronjobState(enum.IntEnum):
    IDLE = 0
    WAIT = 1
    RUNNING = 2
    DONE = 3
    ERROR = 4


class Cronjob(Thread):
    def __init__(self, name, cron_expression, actions):
        super().__init__()
        self.cron_expression = cron_expression
        self.name = name
        self.state = CronjobState.IDLE
        self.actions = Procedure.build(name=name + '__Cron', _async=False,
                                       requests=actions)

    def run(self):
        self.state = CronjobState.WAIT
        self.wait()
        self.state = CronjobState.RUNNING

        try:
            logger.info('Running cronjob {}'.format(self.name))
            context = {}
            response = self.actions.execute(_async=False, **context)
            logger.info('Response from cronjob {}: {}'.format(self.name, response))
            self.state = CronjobState.DONE
        except Exception as e:
            logger.exception(e)
            self.state = CronjobState.ERROR

    def wait(self):
        now = int(time.time())
        cron = croniter.croniter(self.cron_expression, now)
        next_run = int(cron.get_next())
        time.sleep(next_run - now)

    def should_run(self):
        now = int(time.time())
        cron = croniter.croniter(self.cron_expression, now)
        next_run = int(cron.get_next())
        return now == next_run


class CronScheduler(Thread):
    def __init__(self, jobs):
        super().__init__()
        self.jobs_config = jobs
        self._jobs = {}
        logger.info('Cron scheduler initialized with {} jobs'.
                    format(len(self.jobs_config.keys())))

    def _get_job(self, name, config):
        job = self._jobs.get(name)
        if job and job.state not in [CronjobState.DONE, CronjobState.ERROR]:
            return job

        self._jobs[name] = Cronjob(name=name, cron_expression=config['cron_expression'],
                                   actions=config['actions'])

        return self._jobs[name]

    def run(self):
        logger.info('Running cron scheduler')

        while True:
            for (job_name, job_config) in self.jobs_config.items():
                job = self._get_job(name=job_name, config=job_config)
                if job.state == CronjobState.IDLE:
                    job.start()

            time.sleep(0.5)


# vim:sw=4:ts=4:et:
