#
# Copyright 2021 Splunk Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#

"""
A timer queue implementation
"""

import queue
import threading
import traceback
import warnings
from time import time

from splunktalib.common import log
from splunktalib.timer import Timer


class TimerQueue:
    """
    A timer queue implementation, runs a separate thread to handle timers
    """

    import sortedcontainers as sc

    def __init__(self):
        warnings.warn(
            "This class is deprecated. "
            "Please see https://github.com/splunk/addonfactory-ta-library-python/issues/38",
            DeprecationWarning,
            stacklevel=2,
        )
        self._timers = TimerQueue.sc.SortedSet()
        self._cancelling_timers = {}
        self._lock = threading.Lock()
        self._wakeup_queue = queue.Queue()
        self._thr = threading.Thread(target=self._check_and_execute)
        self._started = False

    def start(self):
        """
        Start the timer queue to make it start function
        """

        if self._started:
            return
        self._started = True

        self._thr.start()
        log.logger.info("TimerQueue started.")

    def tear_down(self):
        if not self._started:
            return
        self._started = True
        self._wakeup(None)
        self._thr.join()

    def add_timer(self, callback, when, interval):
        """
        Add timer to the queue
        """

        timer = Timer(callback, when, interval)
        with self._lock:
            self._timers.add(timer)
        self._wakeup()
        return timer

    def remove_timer(self, timer):
        """
        Remove timer from the queue.
        """

        with self._lock:
            try:
                self._timers.remove(timer)
            except ValueError:
                log.logger.info(
                    "Timer=%s is not in queue, move it to cancelling " "list",
                    timer.ident(),
                )
            else:
                self._cancelling_timers[timer.ident()] = timer

    def _check_and_execute(self):
        wakeup_queue = self._wakeup_queue
        while 1:
            (next_expired_time, expired_timers) = self._get_expired_timers()
            for timer in expired_timers:
                try:
                    timer()
                except Exception:
                    log.logger.error(traceback.format_exc())

            self._reset_timers(expired_timers)

            # Calc sleep time
            if next_expired_time:
                now = time()
                if now < next_expired_time:
                    sleep_time = next_expired_time - now
                else:
                    sleep_time = 0.1
            else:
                sleep_time = 1

            try:
                wakeup = wakeup_queue.get(timeout=sleep_time)
                if wakeup is None:
                    break
            except queue.Empty:
                pass
        log.logger.info("TimerQueue stopped.")

    def _get_expired_timers(self):
        next_expired_time = 0
        now = time()
        expired_timers = []
        with self._lock:
            for timer in self._timers:
                if timer.get_expiration() <= now:
                    expired_timers.append(timer)

            if expired_timers:
                del self._timers[: len(expired_timers)]

            if self._timers:
                next_expired_time = self._timers[0].get_expiration()
        return (next_expired_time, expired_timers)

    def _reset_timers(self, expired_timers):
        has_new_timer = False
        with self._lock:
            cancelling_timers = self._cancelling_timers
            for timer in expired_timers:
                if timer.ident() in cancelling_timers:
                    log.logger.INFO("Timer=%s has been cancelled", timer.ident())
                    continue
                elif timer.get_interval():
                    # Repeated timer
                    timer.update_expiration()
                    self._timers.add(timer)
                    has_new_timer = True
            cancelling_timers.clear()

        if has_new_timer:
            self._wakeup()

    def _wakeup(self, something="not_None"):
        self._wakeup_queue.put(something)
