from datetime import datetime

# Yech, Durus.
# from durus.client_storage import ClientStorage
# from durus.connection import Connection
# from durus.persistent_dict import PersistentDict

from persistent.dict import PersistentDict

from ZEO.ClientStorage import ClientStorage
from ZODB import DB
import transaction

from hashlib import sha512

from blog import Entry, Comment, User, Upload, Poll
from util import makeUniqueKey
from conf import ZEO_ADDR, ZEO_PORT, ZEO_CONNECTIONS

persistconn = []

class BlogStorage:
    '''
    Wrapper object for connection and root objects.

    Acts as transparent layer to the root object.

    Must keep a reference to this object to perform changes on its contents.
    '''

    def __init__(self, zaddr = ZEO_ADDR, zport = ZEO_PORT, conn = None):
        self.conn = conn
        if not conn:
            self.storage = ClientStorage((zaddr, zport))
            self.db = DB(self.storage)
            self.conn = self.db.open()
        self.root = self.conn.root()

    def __del__(self):
        transaction.commit()
        # persistconn.append(self.conn)

    def __getitem__(self, key):
        return self.root[key]

    def __setitem__(self, key, val):
        self.root[key] = val
        
    def keys(self): return self.root.keys()

    def items(self): return self.root.items()  

    def values(self): return self.root.values()

    def has_key(self, key): return self.root.has_key(key)

    def getEntry(self, entryid=''):
        return getEntry(entryid, store=self)
    
    def getUpload(self, uploadid):
        return getUpload(uploadid, store=self)

    def getUser(self, uname, nick=False):
        return getUser(uname, nick, store=self)

    def getPoll(self, pollid=None):
        return getPoll(pollid, store=self)

    def EntryFactory(self, title, data, summary, authorname, datatype='RST'):
        return EntryFactory(title, data, summary, authorname, datatype, store=self)

    def CommentFactory(self, data, author, entry, datatype="RST"):
        return CommentFactory(data, author, entry, datatype, store=self)

    def UserFactory(self, username, nickname, password, clearance, homepage=''):
        return UserFactory(username, nickname, password, clearance, homepage, store=self)

    def UploadFactory(self, id, data, datatype, owner):
        return UploadFactory(id, data, datatype, owner, store=self)

    def PollFactory(self, id, author, question, options):
        return PollFactory(id, author, question, options, store=self)

    def showAds(self, changeto=None):
        return showAds(changeto, store=self)

def StorageFactory(zaddr = ZEO_ADDR, zport = ZEO_PORT):
    ''' Return a BlogStorage object with an initiated connection to the
    specified ZEO server. '''
    conn = None
    '''
    if persistconn:
        print "YAY CONN"
        return BlogStorage(conn=persistconn.pop())
    print "NEW"
    '''
    f = open('trace.out', 'a')
    import traceback
    f.write('\nStorageFactory called!')
    for x in traceback.format_stack():
        f.write("\n\t"+str(x))
    f.close()
    
    return BlogStorage(zaddr, zport)
        
class DBWrapper(object):
    '''
    Wrapper for an object belonging to a ZODB instance.

    Allows automatic committing of object changes once it
    drops out of scope, without needing to track BlogStorage
    object.

    Implemented for transparent access to the object.
    '''

    def __init__(self, obj, storage):
        self.__setmyattr__('_obj', obj)
        self.__setmyattr__('storage', storage)
    
    def __getattribute__(self, attr):
        try:
            return super(DBWrapper,self).__getattribute__(attr)
        except AttributeError:
            return self._obj.__getattribute__(attr)

    def __setattr__(self, attr, val):
        self._obj.__setattr__(attr,val)

    def __setmyattr__(self, attr, val):
        super(DBWrapper,self).__setattr__(attr, val)
 

    def __nonzero__(self):
        return bool(self._obj)

    def __str__(self):
        return self._obj.__str__()

    @property
    def root(self):
        return self._obj.root

def storage_init():
    storage = StorageFactory()
    changed = False
    if not storage.has_key('entries'):
        storage['entries'] = PersistentDict({})
    if not storage.has_key('users'):
        storage['users'] = PersistentDict({})
    if not storage.has_key('uploads'):
        storage['uploads'] = PersistentDict({})
    if not storage.has_key('polls'):
        storage['polls'] = PersistentDict({})
    if not storage.has_key('show_ads'):
        storage['show_ads'] = False

def showAds(changeto = None, store=None):
    if not store: store = StorageFactory()
    if (changeto != None):
        store['show_ads'] = bool(changeto)
    return store['show_ads']

def getEntry(entryid='', store=None):
    '''
    Return a DBWrapper of the requested entry and its BlogStorage.

    Use most recent entry if no ID is given.
    Return None if ID is not found.
    '''

    if not store: store = StorageFactory()
    
    if not entryid:
        datesortentries = sorted(store['entries'].values(),
                                 key = lambda x: x.date)
        if datesortentries:
            return DBWrapper(datesortentries[-1], store)
        return DBWrapper(None, store)
    if entryid in store['entries']:
        return DBWrapper(store['entries'][entryid], store)
    return DBWrapper(None, store)

def getUpload(uploadid, store=None):
    '''
    Return a tuple of a Durus Connection object
    and the upload with the upload key specified.
    If it was not found, return None.
    '''

    if not store: store = StorageFactory()
    if uploadid in store['uploads']:
        return DBWrapper(store['uploads'][uploadid], store)
    return DBWrapper(None, store)


def getUser(uname, nick=False, store=None):
    '''
    Return a tuple of a Durus Connection object
    and the User object with the username specified.
    '''

    if not store: store = StorageFactory()
    if not nick and uname in store['users']:
        return DBWrapper(store['users'][uname], store)
    else:
        users = filter(
            lambda u: uname==u.nickname,
            store['users'].values())
        if users:
            user = users[0]
        else:
            user = None
        return DBWrapper(user, store)
                      
    return DBWrapper(None, store)

def getPoll(pollid = None, store=None):
    '''
    Return a tuple of a Durus Connection object
    and the poll with the poll id specified.
    If it was not found, return None.
    '''

    if not store: store = StorageFactory()
    
    if pollid in store['polls']:
        return DBWrapper(store['polls'][pollid], store)
    if not pollid:
        if len(store['polls'])>0:
            return DBWrapper(max(store['polls'].values(), key=lambda x: x.date), store)
        else:
            return DBWrapper(None, store)
        
    return DBWrapper(None, store)


def extractUsername(user):
    username = user
    if (isinstance(user, User) or
        (isinstance(user, DBWrapper) and isinstance(user._obj, User))):
        username = user.username
    return username
        

def EntryFactory(title, data, summary, authorname, datatype='RST', store=None):
    '''
    Factory method for blog.Entry
    =============================

    Arguments:

     - title: title of the blog post (plaintext)
     - data: raw data of the post body
     - author: plaintext name of the author or their username
     - datatype: type of data found in 'data' (default='RST')

    See also blog.Entry.
    '''

    if not store: store = StorageFactory()

    from app import reserved_locations
    keys = store['entries'].keys() + reserved_locations.keys()
    k = makeUniqueKey(keys, title)
    date = datetime.utcnow()
    author = authorname
    if author in store['users']:
        author = store['users'][author]
    entry = Entry(title, data, summary, author, date, datatype=datatype, key=k)
    store['entries'][k] = entry
    return DBWrapper(entry, store)

def CommentFactory(data, author, entry, datatype="RST", store=None):
    '''
    Factory method for blog.Comment
    ===============================
    
    Arguments:

     - data: raw data of the comment
     - author: User object or plaintext name of the author
     - entry: Entry this comment belongs to
     - dbconn: Durus database Connection this entry was obtained using
     - datatype: type of data found in 'data' (default='RST')

    See also blog.Comment.
    '''
    if not store: store = StorageFactory()
    
    newentry = store['entries'][entry.key]
    date = datetime.utcnow()

    nicks = filter(
        lambda u: author==u.nickname,
        store['users'].values())
    if nicks:
        author = nicks[0]

    comment = Comment(data, author, date, newentry, datatype)
    newentry.comments.append(comment)
    if isinstance(author, User):
        author.comments.append(comment)
    return DBWrapper(comment, store)

def UserFactory(username, nickname, password, clearance, homepage='', store=None):
    '''
    Factory method for blog.User
    ============================

    Arguments:

     - username: username of the new user
     - nickname: nickname of the new user
     - password: plaintext password
     - clearance: integer clearance value as defined in security module
     - homepage: a URL to the user's homepage

    See also blog.User.
    '''
    
    passhash = sha512(password).hexdigest()
    user = User(username, nickname, passhash, clearance, homepage=homepage)
    if not store: store = StorageFactory()
    store['users'][username] = user
    
def UploadFactory(id, data, datatype, owner, store=None):
    '''
    Factory method for blog.User
    ============================

    Arguments:

     - id: the ID which can be used to access the Upload
     - data: the Upload's raw data in string format
     - datatype: the mimetype of the data (e.g. 'image/jpeg')
     - owner: the person who uploaded this (as a User object,
              or str of the username), can be None

    See also blog.User.
    '''

    if not store: store = StorageFactory()

    if owner:
        username = extractUsername(owner)
        owner = store['users'][username]

    upload = Upload(id, data, datatype, owner)

    store['uploads'][id] = upload
        

def PollFactory(id, author, question, options, store=None):
    '''
    Factory method for blog.Poll
    ============================

    Arguments:
    
     - id: the ID by which the Poll can be accessed
     - author: the creator of the Poll, responsible for its content
     - question: a string of the question the Poll poses
     - options: a list of strings of the different options available

    See also blog.Poll
    '''

    if not store: store = StorageFactory()

    if author: 
        username = extractUsername(author)
        author = store['users'][username]
    optionsdict = {}
    for option in options:
        optionid = makeUniqueKey(optionsdict.keys(), title=option)
        optionsdict[optionid] = option
    
    date = datetime.utcnow()

    poll = Poll(id, author, date, question, optionsdict)

    store['polls'][id] = poll

