# -*- coding: UTF-8 -*-
################################################################################
#
# Copyright (c) 2019 Baidu.com, Inc. All Rights Reserved
#
################################################################################
"""
定时感知器实现,该感知器可以在达到定时时间时自动生成事件。定时设置通过定时任务列表
"""

import bisect
import threading
import time
import json

from croniter import croniter
from ark.are import log
from ark.are import exception
from ark.are.sensor import PullCallbackSensor


class _CronTimer(object):
    """
    定时器,该类的主要作用是提供自定义的排序函数
    """
    def __init__(self, timer_str, param_str, now=None):
        """
        初始化方法

        :param str timer_str: crontab风格的定时时间
        :param str param_str: 附属参数
        """
        self._timer_str = timer_str
        self._param_str = param_str
        self._current = 0
        self.next(now)

    def next(self, now=None):
        """
        获取下次触发时间

        :return: 下次的触发时间
        :rtype: float
        """
        if now is None:
            now = time.time()

        self._current = croniter(self._timer_str, now).get_next()
        return self._current

    @property
    def current(self):
        return self._current

    @property
    def param(self):
        return self._param_str

    @property
    def timer(self):
        return self._timer_str

    def __eq__(self, other):
        return ((self._timer_str, self._param_str) ==
                (other.timer, other.param))

    def __lt__(self, other):
        # 返回按照触发时间排序的比较结果
        return self. current < other.current


class _CronClock(object):
    """
    该类主要用于管理定时器队列
    """

    def __init__(self):
        """
        初始化方法

        """
        self._timer_queue = []
        self._lock = threading.Lock()

    def delete_cron(self, cron_list):
        """
        删除一组定时器

        :param list cron_list: 待删除的定时器列表
        :return: 返回删除的下标
        :rtype: list
        """

        pos = 0
        now = time.time()
        result = []
        timer_list = []

        # 获取每个定时器下次到期时间,按照到期时间排序
        # 避免self._timer_queue中timer被trigger()重新计算下次执行时间
        with self._lock:
            for cronstr in cron_list:
                timer = _CronTimer(cronstr[0], cronstr[1], now)
                timer_list.append(timer)

            timer_list.sort()


            # 按照下次到期时间的顺序,在定时器队列中寻找相同的定时器位置。将寻找到的位置记录到result中
            # 如果未找到,则忽略
            for timer in timer_list:
                pos = bisect.bisect_left(self._timer_queue, timer, pos)
                for i in range(pos, len(self._timer_queue)):
                    if self._timer_queue[i] == timer:
                        result.append(i)
                    if self._timer_queue[i] > timer:
                        break

            # 按照倒序方式删除对应元素
            for i in range(len(result) - 1, -1, -1):
                self._timer_queue.pop(result[i])

        return result

    def add_cron(self, cron_list):
        """
        增加一组定时器

        :param list cron_list: 待增加的定时器列表
        :return: 返回新增的下标
        :rtype: list
        """

        pos = 0
        now = time.time()
        result = []
        timer_list = []

        # 获取每个定时器下次到期时间,按照到期时间排序
        with self._lock:
            for cronstr in cron_list:
                timer = _CronTimer(cronstr[0], cronstr[1], now)
                timer_list.append(timer)

            timer_list.sort()

            for timer in timer_list:
                pos = bisect.bisect_right(self._timer_queue, timer, pos)
                self._timer_queue.insert(pos, timer)
                result.append(pos)
        return result

    def trigger(self):
        """
        触发一次定时器

        :return: 如果到时触发了一个定时器,则返回这个定时器的附属参数。否则返回None
        :rtype: str
        """

        with self._lock:
            if len(self._timer_queue) > 0 and self._timer_queue[0].current <= int(time.time()):
                top_timer = self._timer_queue.pop(0)
                param = top_timer.param
                top_timer.next()
                bisect.insort_right(self._timer_queue, top_timer)
                return param
            else:
                return None

    def clear(self):
        with self._lock:
            self._timer_queue = []


class CronSensor(PullCallbackSensor):
    """
    定时感知器。通过提供的定时列表,该感知器在某个定时器到时时,生成定时事件
    """

    def __init__(self, reload_interval, timer_interval=3):
        """
        初始化方法

        :param int reload_interval: 重新获取完整定时列表的间隔时间
        :param int timer_interval: 定时处理间隔
        """
        super(CronSensor, self).__init__(timer_interval)

        self._reload_interval = reload_interval
        self._timer_interval = timer_interval
        self._old_cron = set()
        self._clock = _CronClock()
        self._reload_thread = None

    def refresh(self):
        """
        重新获取定时列表。定时列表是一个list,每行均为二值元组:linux风格的定时时间(具体格式参考manpage)、附属参数(字符串)。

        .. Note:: 为了避免影响主消息泵及CronSensor的定时触发,刷新定时触发列表会在单独的子线程进行,因此访问共享数据时应注意加锁

        :return: 返回的定时列表
        :rtype: list
        """

        raise exception.ENotImplement("function is not implement")

    def _reload(self):
        """
        重新加载定时列表。加载时会对新的定时列表与上次获取的做比对。仅处理新增或者删除的定时器

        """
        while not self._stop_tag:
            try:
                current_cron = set(self.refresh())
                delete_list = self._old_cron - current_cron
                add_list = current_cron - self._old_cron
                if len(delete_list) != 0:
                    self._clock.delete_cron(delete_list)
                    log.i("refresh cron list, delete:{num}".format(num=len(delete_list)))
                if len(add_list) != 0:
                    self._clock.add_cron(add_list)
                    log.i("refresh cron list, add:{num}".format(num=len(add_list)))
                self._old_cron = current_cron
            except:
                log.f("reload failed, err")

            time.sleep(self._reload_interval)

    def active(self):
        """
        重载感知器生效函数,加入自动刷新线程的生效操作。

        :return: 无返回
        :rtype: None
        :raises ThreadError: 创建线程失败
        """
        super(CronSensor, self).active()
        try:
            self._reload_thread = threading.Thread(
                target=self._reload)
            self._reload_thread.daemon = True
            self._reload_thread.start()
        except threading.ThreadError as e:
            log.r(e, "create new thread err")

    def inactive(self):
        """
        感知器结束工作。该函数会关闭拉取外部事件的线程,并清空事件队列,避免残留事件在感知器再次启动后继续执行。

        :return: 无返回
        :rtype: None
        """
        super(CronSensor, self).inactive()
        self._stop_tag = True
        if self._reload_thread:
            self._reload_thread = None
        self._clock.clear()
        self._old_cron.clear()

    def get_event(self):
        """
        根据当前时间判断是否需要触发事件

        :return: 定时器事件,事件参数以词典KV返回
        :rtype: dict
        """

        param = self._clock.trigger()
        if param:
            return json.loads(param)
        else:
            return None