from . import fauxtime
from OFS.Application import Application
from Products.Transience.Transience import TransientObjectContainer
from Testing import makerequest
from unittest import makeSuite
from unittest import TestCase
from unittest import TestSuite
from ZODB.DemoStorage import DemoStorage

import Products.Transience.Transience
import Products.Transience.TransientObject
import time as oldtime
import transaction
import ZODB  # in order to get Persistence.Persistent working


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
