import sys, base64, types

from Ft.Rdf import Model

SYSTEM_MODEL_NAME = 'system'
USER_MODEL_NAME = 'user'

BLOB_CHUNK_LENGTH = 3600


def EscapeQuotes(qstr):
    if not qstr:
        return ""
    else:
        return str(qstr.replace("'", "''"))

def BuildConnectString(dbname, host, port, user, passwd):
    return "fours/fours@SQUID"
    conn_str = ""
    #if host:
    #    conn_str += ""%host
    #if port != -1:
    #    conn_str += " port=%s"%port
    if user and passwd:
        conn_str += user + '/' + passwd + "@"
    conn_str += dbname
    return str(conn_str)


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 DCOracle2
    global OrableRdf

    # Load the required database interface module
    import DCOracle2

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


def Exists(properties):
    """See if this repo exists.  The return value for this is three state.
    1.  Everything is present
    0.  Some things are there
    -1  Nothing is there.
    """
    return 1
    conn_str = BuildConnectString(properties['DbName'],
                                  properties['Host'],
                                  properties['Port'],
                                  properties['User'],
                                  properties['Passwd'])
    if not OracleRdf.ExistsDb(rdf_conn_str, SYSTEM_MODEL_NAME):return -1
    if not OracleRdf.ExistsDb(rdf_conn_str, USER_MODEL_NAME):return 0
    return 1


def Initialize(properties):
    """
    Initialize a new copy of the repo.  This is a prerequiste to a full
    4ss_manager init.  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
    """
    return
    rdf_conn_str = BuildConnectString(properties['DbName'],
                                      properties['Host'],
                                      properties['Port'],
                                      properties['User'],
                                      properties['Passwd'])
    OracleRdf.CreateDb(rdf_conn_str, SYSTEM_MODEL_NAME)
    OracleRdf.CreateDb(rdf_conn_str, USER_MODEL_NAME)

    conn_str = BuildConnectString('template1',
                                  properties['Host'],
                                  properties['Port'],
                                  properties['User'],
                                  properties['Passwd'])
    db = DCOracle2.connect(conn_str)
    cur = db.cursor()
    pg_db_name = 'ft__' + str(properties['DbName'])
    cur.execute("SELECT datname FROM pg_database WHERE datname='%s'", [pg_db_name])
    res = cur.fetchone()
    db.rollback()
    if not res:
        #Must rollback to avoid "ERROR:  CREATE DATABASE: may not be called in a transaction block"
        cur.execute('ROLLBACK; CREATE DATABASE ' + pg_db_name)
    db.close()

    conn_str = BuildConnectString(pg_db_name,
                                  properties['Host'],
                                  properties['Port'],
                                  properties['User'],
                                  properties['Passwd'])
    db = DCOracle2.connect(conn_str)
    cur = db.cursor()

    cur.execute('CREATE TABLE ftss_stringdata (idx NUMBER, blobidx NUMBER, blobdata VARCHAR2)')
    cur.execute('CREATE TABLE ftss_strings (idx NUMBER PRIMARY KEY, path VARCHAR2, type NUMBER)')
    cur.execute('CREATE SEQUENCE ftss_string_id')
    db.commit()
    db.close()
    return
   

def Destroy(properties, tryOnly=0):
    """Completly dismantle the repo"""
    return
    rdf_conn_str = BuildConnectString(properties['DbName'],
                                      properties['Host'],
                                      properties['Port'],
                                      properties['User'],
                                      properties['Passwd'])
    OracleRdf.DestroyDb(rdf_conn_str, SYSTEM_MODEL_NAME)
    OracleRdf.DestroyDb(rdf_conn_str, USER_MODEL_NAME)

    conn_str = BuildConnectString('ft__' + properties['DbName'],
                                  properties['Host'],
                                  properties['Port'],
                                  properties['User'],
                                  properties['Passwd'])
    db = DCOracle2.connect(conn_str)
    cur = db.cursor()

    for tn in ['ftss_strings',
               'ftss_stringdata',
               ]:
        try:
            cur.execute('DROP TABLE %s'% str(tn))
        except:
            sys.stderr.write("Unable to drop table %s\n" % tn);
    try:
        cur.execute('DROP SEQUENCE ftss_string_id')
    except:
        sys.stderr.write("Unable to drop sequence %s\n" % 'string_id');
    return


def Maintain(properties):
    """
    Perform any maintenance on the db
    """
    pass

class OracleDriver:
    """Store information in a Postgres Database using OraclePG"""

    def __init__(self, dbName, host, port, user, passwd):
        self._dbName = dbName
        self._host = host
        self._port = port
        self._user = user
        self._passwd = passwd
        self._connStr = BuildConnectString('ft__' + dbName, host, port,
                                           user, passwd)
        #FIXME: Needs refactoring to reduce connection hogging
        self._db = DCOracle2.connect(self._connStr)
        d = OracleRdf.DbAdapter('', SYSTEM_MODEL_NAME)
        d._db = self._db
        d._uriHeadCache = {}
        d._resourceCache = {}
        self._systemRdfModel = Model.Model(d)
        d = OracleRdf.DbAdapter('', USER_MODEL_NAME)
        d._db = self._db
        d._uriHeadCache = {}
        d._resourceCache = {}
        self._userRdfModel = Model.Model(d)
        return

    def maintain(self):
        """
        Perform any maintenance on the db
        """
        pass

    def createFile(self, path, typ, content):
        """When creating a resource, 
        store the content"""
        cur = self._db.cursor()
        cur.execute("SELECT ftss_string_id.nextval FROM dual")
        idx = cur.fetchone()[0]
        self._writeString(idx, content)
        cur.execute("INSERT INTO ftss_strings (idx, path, type) VALUES (%d, '%s', %d)"%(idx, EscapeQuotes(str(path)), typ))
        return

    def hasFile(self, path, typ):
        """See if we have any meta information about this resource"""
        idx = self._getId(path, typ)
        if idx: return 1
        return 0

    def fetchFile(self, path, typ):
        idx = self._getId(path, typ)
        if not idx: return None
        cur = self._db.cursor()
        cur.execute("SELECT blobdata FROM ftss_stringdata WHERE idx = %d ORDER BY blobidx"%(idx,))
        res = cur.fetchall()
        if not res:
            return None
        res = [ r[0] for r in res ]
        data = ''.join(res)
        return data

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

    def deleteFile(self, path, typ):
        """Delete an object"""
        idx = self._getId(path, typ)
        if not idx: return None
        cur = self._db.cursor()
        cur.execute("DELETE FROM ftss_stringdata where idx = %d", [idx])
        cur.execute("DELETE FROM ftss_strings where idx = %d", [idx])
        return
       
    def getSystemModel(self):
        return self._systemRdfModel

    def getUserModel(self):
        return self._userRdfModel
    
    def commit(self):
        """Make it so!"""
        self._db.commit()
        cur = self._db.cursor()
        self._db.commit()
        self._db.close()
        self._db = None
        self._systemRdfModel._db = None
        self._systemRdfModel = None
        self._userRdfModel._db = None
        self._userRdfModel = None
        return

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

    def _getId(self, path, typ):
        cur = self._db.cursor()
        cur.execute("SELECT idx FROM ftss_strings WHERE path = '%s' and type = %d"%(EscapeQuotes(str(path)), typ))
        res = cur.fetchone()
        if res:
            return int(res[0])
        return 0
        
    def _writeString(self, idx, content):
        idx = int(idx)
        cur = self._db.cursor()
        pos = 0
        if isinstance(content, types.UnicodeType):
            content = content.encode('utf-8')
        size = len(content)
        chunks = []
        while pos < size:
            chunks.append(content[pos: pos+BLOB_CHUNK_LENGTH])
            pos += BLOB_CHUNK_LENGTH

        #Delete the old blob
        cur.execute("DELETE FROM ftss_stringdata where idx = %d"%idx)

        try:
            #Add the new data
            for i in range(len(chunks)):
                #params = (idx, i+1, DCOracle2.Binary( EscapeQuotes(chunks[i])) )
                params = (idx, i+1, EscapeQuotes(chunks[i]) )
                #print params
                cur.execute("INSERT INTO ftss_stringdata (idx, blobidx, blobdata) VALUES (%d, %d, '%s')"%params)
        except:
            print content
            print "*"*100
            #print repr(q)
            raise
        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
    """
    return OracleDriver(properties['DbName'],
                       properties['Host'],
                       properties['Port'],
                       properties['User'],
                       properties['Passwd'])

NAME='Oracle'

