
import uuid
import datetime
import time
from twisted.internet import reactor, threads, defer
from zope.interface import implements

from interface import *
import core
import util
import loader
import server
from smap.contrib import dtutil

class SmapDriver:
    # this is actually a shim layer which presents a ISmapInstance to
    # drivers
    implements(ISmapInstance)
    load_chunk_size = datetime.timedelta(days=1)


    @classmethod
    def get_driver(cls, inst, name, attach_point, namespace):
        """Create a managed driver which will manage a driver whose
        implementation is named by "name"
        """
        cmps = name.split('.')
        mod = __import__('.'.join(cmps[:-1]), globals(), locals(), [cmps[-1]]) 
        klass = getattr(mod, cmps[-1])
        driver = klass(inst, attach_point, namespace)
        inst.add_driver(attach_point, driver)
        return driver

    flush = lambda self: self.__inst.reports.flush()
    _flush = lambda self: self.__inst.reports._flush()

    def __init__(self, smap_instance, attach_point, namespace):
        self.__inst = smap_instance
        self.__attach_point = attach_point
        self.namespace = namespace

    def __join_id(self, id):
        if util.is_string(id) and id.startswith('/'):
            return util.norm_path(self.__attach_point + '/' + id)
        else:
            return id

    # override
    def setup(self, opts={}):
        pass

    # override
    def start(self):
        pass

    # ISmapInstance implementation

    # drivers get a pass-through version of the SmapInstance which
    # knows where they are attached in the tree.  Drivers may, for the
    # most part, assume that they have the instance to themselves.
    # However, the main exception to this is that their names are
    # still in a global namespace.  Therefore they should either
    # generate their own uuids for their streams based on the uuid in
    # their root collection (inst.get_collection('/')['uuid'])
    def lookup(self, id, **kwargs):
        return self.__inst.lookup(self.__join_id(id), **kwargs)
    def get_timeseries(self, id):
        return self.__inst.get_timeseries(self.__join_id(id))
    def get_collection(self, id):
        return self.__inst.get_collection(self.__join_id(id))
    def add_timeseries(self, path, *args, **kwargs):
        kwargs['namespace'] = self.namespace
        if len(args) <= 1:
            key = path
        elif len(args) == 2:
            key = args[0]
            args = args[1:]
        return self.__inst.add_timeseries(self.__join_id(path), key, *args, **kwargs)
    def add_actuator(self, path, unit, klass, **kwargs):
        return self.add_timeseries(path, unit, klass=klass, **kwargs)
    def add_collection(self, path, *args):
        self.__inst.add_collection(self.__join_id(path), *args)
    def set_metadata(self, id, *metadata):
        return self.__inst.set_metadata(self.__join_id(id), *metadata)
    def add(self, id, *args):
        return self.__inst.add(self.__join_id(id), *args)
    def _add(self, id, *args):
        return self.__inst._add(self.__join_id(id), *args)
    def uuid(self, key):
        return self.__inst.uuid(key, namespace=self.namespace)

    def load(self, startdt, enddt):
        """Default load method tries to call update with one-day chunks"""
        self.start_dt = startdt
        self.end_dt = enddt
        return self._load_time_chunk(self)

    def _load_time_chunk(self, *args):
        if self.start_dt >= self.end_dt:
            return None

        # pick a new window
        start = self.start_dt
        end = self.start_dt + self.load_chunk_size
        if end > self.end_dt: end = self.end_dt

        print "loading", self.start_dt, '-', self.end_dt
        self.start_dt = self.start_dt + self.load_chunk_size
        print start, end
        d = defer.maybeDeferred(self.update, start, end)
        d.addCallback(self.update)
        d.addCallback(lambda _: self._flush())
        d.addCallback(self._load_time_chunk)

        def err(e):
            print e
        d.addErrback(err)

        return d


class BaseDriver(SmapDriver):
    def setup(self, opts={}):
        self.t = self.add_timeseries('/sensor0', 'mytimeseries', 'SDH')
        self.set_metadata('/sensor0', { 
            'Instrument/ModelName' : 'ExampleInstrument',
            'Extra/ModbusAddr' : opts.get('ModbusAddr', '')
            })
        self.counter = int(opts.get('StartVal', 0))

    def start(self):
        self.counter = 0
        util.periodicSequentialCall(self.read).start(1)

    def read(self):
        # print "Add", self.counter
        self.t.add(self.counter)
        self.counter += 1
        # print self.counter


if __name__ == '__main__':
    # inst = loader.load('default.ini', autoflush=False)
    # d = SmapDriverManager.get_driver('driver.BaseDriver', '/newsensor')
    # d.setup(inst)
    import uuid
    inst = core.SmapInstance(uuid.uuid1())
    bd = SmapDriver.get_driver(inst, 'driver.BaseDriver', '/newsensor', None)
    bd.setup()

    server.run(inst)

@@ -416,17 +416,120 @@
 days=1)%0A
+    # Dict of modules that can be loaded without calling __import__, used for py2exe%0A    modules = None
 %0A
-
 %0A    @cl
@@ -765,44 +765,380 @@
     
-mod = __import__('.'.join(cmps%5B:-1%5D)
+assert len(cmps) %3E 1%0A        (mod_name, class_name) = ('.'.join(cmps%5B:-1%5D), cmps%5B-1%5D)%0A%0A        if SmapDriver.modules:%0A          if mod_name in SmapDriver.modules:%0A            mod = SmapDriver.modules%5Bmod_name%5D%0A          else:%0A            raise Exception('Module %25s not found in %25s' %25 (mod_name, SmapDriver.modules.keys()))%0A        else:%0A          mod = __import__(mod_name
 , gl
@@ -1162,19 +1162,22 @@
 , %5Bc
-mps%5B-1%5D
+lass_name
 %5D) %0A
+%0A
     
@@ -1202,23 +1202,25 @@
 r(mod, c
-mps%5B-1%5D
+lass_name
 )%0A      

Add a way to preload sMAP driver modules instead of using __import__ for py2exe compatibility