# -*- coding: utf-8 -*-
import cherrypy
import datetime
import time

from girder.models.notification import Notification, ProgressState
from girder.exceptions import ValidationException, RestException


class ProgressContext(object):
    """
    This class is a context manager that can be used to update progress in a way
    that rate-limits writes to the database and guarantees a flush when the
    context is exited. This is a no-op if "on" is set to False, which is
    meant as a convenience for callers. Any additional kwargs passed to this
    constructor are passed through to the ``initProgress`` method of the
    notification model.

    :param on: Whether to record progress.
    :type on: bool
    :param interval: Minimum time interval at which to write updates to the
        database, in seconds.
    :type interval: int or float
    :param user: The user creating this progress.
    :type user: dict
    :param title: The title for the task being tracked.
    :type title: str
    """

    def __init__(self, on, interval=0.5, **kwargs):
        self.on = on
        self.interval = interval

        if on:
            self._lastSave = time.time()
            self.progress = Notification().initProgress(**kwargs)

    def __enter__(self):
        return self

    def __exit__(self, excType, excValue, traceback):
        """
        Once the context is exited, the progress is marked for deletion 30
        seconds in the future, which should give all listeners time to poll and
        receive the final state of the progress record before it is deleted.
        """
        if not self.on:
            return

        if excType is None and excValue is None:
            state = ProgressState.SUCCESS
            message = 'Done'
        else:
            state = ProgressState.ERROR
            message = 'Error'
            if isinstance(excValue, (ValidationException, RestException)):
                message = 'Error: '+excValue.message

        Notification().updateProgress(
            self.progress, state=state, message=message,
            expires=datetime.datetime.utcnow() + datetime.timedelta(seconds=30)
        )

    def update(self, force=False, **kwargs):
        """
        Update the underlying progress record. This will only actually save
        to the database if at least self.interval seconds have passed since
        the last time the record was written to the database. Accepts the
        same kwargs as Notification.updateProgress.

        :param force: Whether we should force the write to the database. Use
            only in cases where progress may be indeterminate for a long time.
        :type force: bool
        """
        # Extend the response timeout, even if we aren't reporting the progress
        setResponseTimeLimit()
        if not self.on:
            return
        save = (time.time() - self._lastSave > self.interval) or force
        self.progress = Notification().updateProgress(self.progress, save, **kwargs)

        if save:
            self._lastSave = time.time()


noProgress = ProgressContext(False)


def setResponseTimeLimit(duration=600, onlyExtend=True):
    """
    If we are currently within a cherrypy response, extend the time limit.  By
    default, cherrypy (version < 12.0) responses will timeout after 300
    seconds, so any activity which can take longer should call this function.

    Note that for cherrypy responses that include streaming generator
    functions, such as downloads, the timeout is only relevant until the first
    ``yield`` is reached.  As such, long running generator responses do not
    generally need to call this function.

    @deprecated - remove this function once we pin to CherryPy >= 12.0

    :param duration: additional duration in seconds to allow for the response.
    :param onlyExtend: if True, only ever increase the timeout.  If False, the
                       new duration always replaces the old one.
    """
    # CherryPy 12.0 no longer has a timeout propery on a response.  Since we
    # had only been using this to extend the time, if the timeout property is
    # not present, do nothing.
    if (cherrypy.response and getattr(cherrypy.response, 'time', None) and
            getattr(cherrypy.response, 'timeout', None)):
        newTimeout = time.time() - cherrypy.response.time + duration
        if not onlyExtend or newTimeout > cherrypy.response.timeout:
            cherrypy.response.timeout = newTimeout
