# Warning: This ODBC driver ships with win32all

def _connect(keywords):
    # Create an ODBC connection string (probably Windows specific)
    #   data source/username/password
    conn_str = keywords['dsn']
    if keywords.get('user'):
        conn_str += '/' + keywords['user']
        if keywords.get('password'):
            conn_str += '/' + keywords['password']
    return odbc.odbc(conn_str)

import sys


# -- RDF support ----------------------------------------------------

from Ft.Rdf import Model

SYSTEM_MODEL_NAME = 'system'
USER_MODEL_NAME = 'user'

def BuildDbiKeywords(properties):
    # Creates a DBI 2.0 keyword dictionary for connection
    #   dsn       Data source name as string
    #   user      Username as string  (opt)
    #   password  Password as string  (opt)
    #   host      Hostname            (opt)
    #   database  Database name       (opt)
    conn = {'dsn' : properties['DataSource']}

    if properties.get('User'):
        conn['user'] = properties['User']
        if properties.get('Password'):
            conn['password'] = properties['Password']

    if properties.get('Host'):
        conn['host'] = properties['Host']

    if properties.get('Database'):
        conn['database'] = properties['Database']

    return conn

# -- Database interface ---------------------------------------------

def InitializeModule():
    """
    Post-import hook to initialize module's runtime variables that are not
    required at import time, but will be needed before the module-level
    functions are called.
    """
    global odbc
    global RdfDriver

    # Load the required database interface module
    import odbc

    # Incorporate the RDF driver module
    from Ft.Rdf.Drivers import Memory as RdfDriver
    RdfDriver.InitializeModule()
    return


def Exists(properties):
    """
    See if this repo exists.  The return value for this is three states:
         1  Everything is present,
         0  Some things are there, or
        -1  Nothing is there.
    """
    conn = _connect(BuildDbiKeywords(properties))
    cursor = conn.cursor()
    exists = -1
    try:
        for table in ('ftss_strings', 'ftss_stringdata', 'ftss_sequences'):
            # This should return a list of tuples
            cursor.execute('SELECT COUNT(*) FROM %s' % table)
            if cursor.fetchall():
                exists = 0
    except:
        pass
    else:
        # No exceptions means all tables where successfully created
        exists = 1
    cursor.close()
    conn.rollback()
    conn.close()
    return exists


def Initialize(properties):
    """Initialize a new copy of the repo.  This is not the same as a 4ss_manager init.  This
    is very raw.  The adapter will take care of calling our other interfaces
    (createContainer, createUser, etc) with all of the information
    about the rest of the 'init' stuff to do
    """
    keywords = BuildDbiKeywords(properties)
    RdfDriver.CreateDb(keywords, SYSTEM_MODEL_NAME)
    RdfDriver.CreateDb(keywords, USER_MODEL_NAME)

    conn = _connect(keywords)
    cursor = conn.cursor()
    cursor.execute('CREATE TABLE ftss_strings (sid integer, path varchar, type integer)')
    cursor.execute('CREATE TABLE ftss_stringdata (sid integer, chunk integer, data varchar)')
    cursor.execute('CREATE TABLE ftss_sequences (name varchar, position integer)')
    cursor.execute("INSERT INTO ftss_sequences VALUES ('string_id', 1)")
    cursor.close()
    conn.commit()
    conn.close()
    return


def Destroy(properties,tryOnly=0):
    """Completly destroy the repo"""

    keywords = BuildDbiKeywords(properties)
    RdfDriver.DestroyDb(keywords, SYSTEM_MODEL_NAME)
    RdfDriver.DestroyDb(keywords, USER_MODEL_NAME)

    conn = _connect(keywords)
    cursor = conn.cursor()

    for table_name in ['ftss_strings',
                       'ftss_stringdata',
                       'ftss_sequences',
                       ]:
        try:
            cursor.execute('DROP TABLE %s' % table_name)
        except:
            sys.stderr.write("Unable to drop table %s\n" % table_name);
    
    cursor.close()
    conn.commit()
    conn.close()
    return


def Begin(**properties):
    """Begin a new transaction.  Every driver must support this interface.
    The properties keyword arguments are passed from the config file
    (or where ever) to the driver.  The Begin file is responsible
    for doing what ever is needed to validate these arguements
    """
    keywords = BuildDbiKeywords(properties)
    return OdbcDriver(keywords)


class OdbcDriver:
    """Store information via an ODBC connection to a database"""

    def __init__(self, keywords):
        self._keywords = keywords
        self._conn = _connect(keywords)
        self._cursor = self._conn.cursor()

        # Create the system model
        rdf_db = RdfDriver.DbAdapter({}, SYSTEM_MODEL_NAME)
        rdf_db._db = self._cursor
        self._systemRdfModel = Model.Model(rdf_db)

        # Create the user model
        rdf_db = RdfDriver.DbAdapter({}, USER_MODEL_NAME)
        rdf_db._db = self._cursor
        self._userRdfModel = Model.Model(rdf_db)
        return

    def _getId(self, path, type):
        self._cursor.execute("SELECT sid FROM ftss_strings "
                             "WHERE path='%s' and type=%d" % (path, type))
        result = self._cursor.fetchall()
        if result:
            return int(result[0][0])
        return 0

    def _writeString(self, sid, content):
        """
        Convert a large string into manageable chunks fro the database.
        """
        chunks = Split(content)

        # Remove the old data (a no-op if it doesn't exist)
        self._cursor.execute("DELETE FROM ftss_stringdata where sid=%d" % sid)

        # Add the new data
        inputs = map(lambda pos, chunk, sid=sid: (sid, pos, chunk),
                     range(len(chunks)), chunks)
        self._cursor.execute("INSERT INTO ftss_stringdata VALUES(?,?,?)", inputs)
        return

    def createFile(self, path, type, content):
        """
        When creating a resource, store the content
        """
        self._cursor.execute("SELECT position FROM ftss_sequences "
                             "WHERE name='string_id'")
        sid = self._cursor.fetchall()[0][0]
        self._cursor.execute("UPDATE ftss_sequences SET position=%d "
                             "WHERE name='string_id'" % (sid + 1))
        self._writeString(sid, content)
        self._cursor.execute("INSERT INTO ftss_strings VALUES (%d, '%s', %d)" %
                             (sid, path, type))
        return

    def hasFile(self, path, type):
        """See if we have any meta information about this resource"""
        sid = self._getId(path, type)
        # Force a boolean value
        return not not sid

    def fetchFile(self, path, type):
        sid = self._getId(path, type)
        if not sid:
            return None

        self._cursor.execute("SELECT data FROM ftss_stringdata WHERE sid=%d "
                             "ORDER BY chunk" % sid)
        chunks = self._cursor.fetchall()
        if chunks:
            print chunks
            data = Join(chunks)
            print 'path',path
            print repr(data)
        else:
            data = None
        return data

    def updateFile(self, path, type, content):
        """Update only the content about this resource"""
        sid = self._getId(path, type)
        if not sid:
            return None
        self._writeString(sid, content)
        return

    def deleteFile(self, path, type):
        """Delete an object"""
        sid = self._getId(path, type)
        if not sid:
            return None
        self._cursor.execute("DELETE FROM ftss_stringdata WHERE sid=%d" % sid)
        self._cursor.execute("DELETE FROM ftss_strings WHERE sid=%d" % sid)
        return
       
    def getSystemModel(self):
        return self._systemRdfModel

    def getUserModel(self):
        return self._userRdfModel

    def commit(self):
        """Make it so!"""
        self._cursor.close()
        self._conn.commit()
        self._conn.close()
        self._systemRdfModel = None
        self._userRdfModel = None
        return

    def rollback(self):
        """Undo it"""
        self._cursor.close()
        self._conn.rollback()
        self._conn.close()
        self._systemRdfModel = None
        self._userRdfModel = None
        return

# -- Large file support -----------------------------------

import base64
BLOB_CHUNK_LENGTH = 7900

def Split(content):
    data = base64.encodestring(content.encode('utf-8'))

    chunks = []
    index = 0
    while (len(data) - index) > BLOB_CHUNK_LENGTH:
        chunks.append(data[index:index+BLOB_CHUNK_LENGTH])
        index = index + BLOB_CHUNK_LENGTH
        
    chunks.append(data[index:])
    print chunks
    return chunks

def Join(chunks):
    return unicode(base64.decodestring(''.join(map(lambda x:x[0],chunks))),'utf-8')

#try:
#    # use the C-optimized versions, if available
#    from Postgres import EscapeAndSplitc
#    Split = EscapeAndSplitc.split
#    Join = EscapeAndSplitc.join
#except ImportError:
#    pass


NAME='Odbc'
