import time as oldtime
from unittest import TestCase
from unittest import TestSuite
from unittest import makeSuite

import Products.Transience.Transience
import Products.Transience.TransientObject
import transaction
import ZODB  # in order to get Persistence.Persistent working
from OFS.Application import Application
from Products.Transience.Transience import TransientObjectContainer
from Testing import makerequest
from ZODB.DemoStorage import DemoStorage

from . import fauxtime


WRITEGRANULARITY = 30
stuff = {}


def _getApp():
    app = stuff.get('app', None)
    if not app:
        ds = DemoStorage()
        db = ZODB.DB(ds)
        conn = db.open()
        root = conn.root()
        app = Application()
        root['Application'] = app
        transaction.commit()
        stuff['app'] = app
        stuff['conn'] = conn
        stuff['db'] = db
    return app


def _openApp():
    conn = stuff['db'].open()
    root = conn.root()
    app = root['Application']
    return conn, app


def _delApp():
    transaction.abort()
    stuff['conn'].close()
    del stuff['conn']
    del stuff['app']
    del stuff['db']


class TestBase(TestCase):

    def setUp(self):
        Products.Transience.Transience.time = fauxtime
        Products.Transience.TransientObject.time = fauxtime
        Products.Transience.Transience.setStrict(1)
        self.app = makerequest.makerequest(_getApp())
        timeout = self.timeout = 1
        sm = TransientObjectContainer(
            id='sm',
            timeout_mins=timeout,
            title='SessionThing',
            addNotification=addNotificationTarget,
            delNotification=delNotificationTarget)
        self.app._setObject('sm', sm)

    def tearDown(self):
        transaction.abort()
        _delApp()
        del self.app
        Products.Transience.Transience.time = oldtime
        Products.Transience.TransientObject.time = oldtime
        Products.Transience.Transience.setStrict(0)


class TestLastAccessed(TestBase):

    def testLastAccessed(self):
        sdo = self.app.sm.new_or_existing('TempObject')
        la1 = sdo.getLastAccessed()
        # time.time() on Windows has coarse granularity (updates at
        # 18.2 Hz -- about once each 0.055 seconds).  We have to sleep
        # long enough so that "the next" call to time.time() actually
        # delivers a larger value.  _last_accessed isn't actually updated
        # unless current time.time() is greater than the last value +
        # WRITEGRANULARITY.  The time() and sleep() are fudged by a
        # factor of 60, though.  The code here used to do
        #     fauxtime.sleep(WRITEGRANULARITY + 1)
        # and that wasn't enough on Windows.  The "+1" only added 1/60th
        # of a second sleep time in real time, much less than the Windows
        # time.time() resolution.  Rounding up 0.055 to 1 digit and
        # multiplying by 60 ensures that we'll actually sleep long enough
        # to get to the next Windows time.time() tick.
        fauxtime.sleep(WRITEGRANULARITY + 0.06 * 60)
        sdo = self.app.sm.get('TempObject')
        self.assertGreater(sdo.getLastAccessed(), la1)


class TestNotifications(TestBase):

    def testAddNotification(self):
        self.app.sm.setAddNotificationTarget(addNotificationTarget)
        sdo = self.app.sm.new_or_existing('TempObject')
        now = fauxtime.time()
        k = sdo.get('starttime')
        self.assertEqual(type(k), type(now))
        self.assertLessEqual(k, now)

    def testDelNotification(self):
        self.app.sm.setDelNotificationTarget(delNotificationTarget)
        sdo = self.app.sm.new_or_existing('TempObject')
        # sleep longer than timeout
        fauxtime.sleep(self.timeout * 100.0)
        self.app.sm.get('TempObject')
        now = fauxtime.time()
        k = sdo.get('endtime')
        self.assertEqual(type(k), type(now))
        self.assertLessEqual(k, now)

    def testMissingCallbackGetCallbackReturnsNone(self):
        # in response to http://zope.org/Collectors/Zope/1403
        self.assertEqual(None, self.app.sm._getCallback('/foo/bar/baz'))


def addNotificationTarget(item, context):
    item['starttime'] = fauxtime.time()


def delNotificationTarget(item, context):
    item['endtime'] = fauxtime.time()


def test_suite():
    last_accessed = makeSuite(TestLastAccessed, 'test')
    start_end = makeSuite(TestNotifications, 'test')
    suite = TestSuite((start_end, last_accessed))
    return suite
