# -*- coding: latin-1 -*-
"""
Navegación por una tabla de datos mediante instrucciones tipo xBase.
"""

class Almacen :
    pass

class DBF :
    """
    Permite acceder a una consulta de SQL, con un estilo dBase, haciendo una
    lectura inicial completa de los rowids y luego moviéndose a través de los
    mismos, y leyendo el registro actual, y asignándo los valores de los campos
    a variables con el mismo nombre::

        import sqlite3 as sqlite
        import sys
        reload(sys)
        sys.setdefaultencoding( "latin-1" )

        con = sqlite.connect("v003.db")
        con.text_factory = lambda x: unicode(x, "latin-1", "ignore")

        dbf = DBF( con, "CUENTAS", "CUENTA,NOMBRE", condicion='CUENTA like "43%"', orden="cuenta asc" )
        if dbf.leer() :
            while not dbf.eof :
                prlk dbf.CUENTA, dbf.NOMBRE
                dbf.skip()
        dbf.condicion = "cuenta like '4%'"
        dbf.select = "NOMBRE, cuenta"
        if dbf.leer() :
            while not dbf.eof :
                prlk dbf.NOMBRE, dbf.cuenta
                if not dbf.skip() :
                    break
        dbf.cerrar()

        con.close()
    """
    def __init__(self, conexion, ctabla, select, condicion = "", orden = "" ):
        """
        Abre un cursor.

        @param conexion: recibe la conexion de la base de datos, crea con ella el cursor de trabajo
        @param ctabla: str tabla principal de la consulta
        @param select: str lista de campos separados por comas, como en un select sql
        @param condicion: str condición
        @param orden: str orden
        """

        self.conexion = conexion
        self.cursor = conexion.cursor()
        self.ponSelect( select )
        self.condicion = condicion
        self.orden = orden
        self.ctabla = ctabla

        self.eof = True
        self.bof = True

        self.liIDs = []

    def reccount( self ) :
        """
        Devuelve el número total de registros.
        """
        return len(self.liIDs)

    def ponSelect( self, select ) :
        self.select = select
        self.liCampos = [ campo.strip() for campo in self.select.split( "," ) ]

    def ponOrden( self, orden ) :
        """
        Cambia el orden de lectura, previo a una lectura completa.
        """
        self.orden = orden

    def ponCondicion( self, condicion ) :
        """
        Cambia la condicion, previo a una lectura completa.
        """
        self.condicion = condicion

    def leer(self):
        """
        Lanza la consulta,
        Lee todos los IDs de los registros

        @return: True/False si se han encontrado o no registros
        """
        self.bof = True
        self.recno = -1

        resto = ""
        if self.condicion :
            resto += "WHERE %s"%self.condicion
        if self.orden :
            if resto :
                resto += " "
            resto += "ORDER BY %s"%self.orden
        cSQL = "SELECT rowid FROM %s %s"%(self.ctabla,resto)
        self.cursor.execute( cSQL )
        self.liIDs = self.cursor.fetchall()
        return self.gotop()

    def _leerUno(self, numRecno ):
        """
        Lectura de un registro, y asignación a las variables = campos.
        """
        self.ID = self.liIDs[numRecno][0]
        self.cursor.execute( "SELECT %s FROM %s WHERE rowid =%d"%( self.select, self.ctabla, self.ID ) )
        liValores = self.cursor.fetchone()
        for numCampo, campo in enumerate(self.liCampos) :
            setattr( self, campo, liValores[numCampo] )

    def goto(self, numRecno):
        """
        Nos sitúa en un registro concreto con la lectura de los campos.
        """
        if numRecno < 0 or numRecno >= self.reccount() :
            self.eof = True
            self.bof = True
            self.recno = -1
            return False
        else :
            self._leerUno( numRecno )
            self.eof = False
            self.bof = False
            self.recno = numRecno
            return True

    def rowid(self, numRecno):
        """
        Devuelve el id del registro numRecno.
        @param numRecno: número de registro.
        """
        return self.liIDs[numRecno][0]

    def buscarID(self, id):
        """
        Busca el recno de un ID.

        @param id: número de id.
        """
        for r in range(self.reccount()) :
            if self.rowid( r ) == id :
                return r
        return -1

    def skip(self, num = 1):
        """
        Salta un registro.
        """
        return self.goto( num + self.recno )

    def gotop(self):
        """
        Salta al registro número 0.
        """
        return self.goto( 0 )

    def gobottom(self):
        """
        Salta al registro último.
        """
        return self.goto( self.reccount()-1 )

    def cerrar(self):
        """
        Cierra el cursor.
        """
        self.cursor.close()

    def borrarLista( self, listaRecnos, dispatch=None ) :
        for n, recno in enumerate(listaRecnos) :
            if dispatch :
                dispatch( n )
            cSQL = "DELETE FROM %s WHERE rowid = %d"%( self.ctabla, self.rowid( recno ) )
            self.cursor.execute( cSQL )
        self.conexion.commit()

    def borrarBase( self, recno ) :
        """
        Rutina interna de borrado de un registro.
        """
        if self.goto( recno ) :
            cSQL = "DELETE FROM %s WHERE rowid = %d"%( self.ctabla, self.rowid( recno ) )
            self.cursor.execute( cSQL )
            self.conexion.commit()
            return True
        else :
            return False

    def borrar(self, recno) :
        """
        Borra un registro y lo quita de la lista de IDs.
        """
        if self.borrarBase( recno ) :
            del self.liIDs[recno]
            return True
        else :
            return False

    def insertar( self, regNuevo, liCampos = None ) :
        """
        Inserta un registro.
        @param regNuevo: registro cuyas variables son los valores a insertar.
        @param liCampos: si la lista de campos a insertar es diferente se indica.
        @return : el id nuevo insertado.
        """
        if liCampos is None :
            liCampos = self.liCampos
        campos = ""
        values = ""
        liValues = []
        for campo in liCampos :
            if hasattr( regNuevo, campo ) :
                campos += campo + ","
                values += "?,"
                liValues.append( getattr( regNuevo, campo ) )
        campos = campos[:-1]
        values = values[:-1]

        cSQL = "insert into %s(%s) values(%s)"%( self.ctabla,campos, values)
        self.cursor.execute( cSQL, liValues )

        idNuevo = self.cursor.lastrowid

        self.conexion.commit()

        self.leer()

        return self.buscarID( idNuevo )

    def soloGrabar( self, dicNuevo, siCommit=False ) :
        liCampos = self.liCampos
        campos = ""
        values = ""
        liValues = []
        for campo in liCampos :
            if campo in dicNuevo :
                campos += campo + ","
                values += "?,"
                liValues.append( dicNuevo[campo] )
        campos = campos[:-1]
        values = values[:-1]

        cSQL = "insert into %s(%s) values(%s)"%( self.ctabla,campos, values)
        self.cursor.execute( cSQL, liValues )

        if siCommit :
            self.conexion.commit()

    def modificar( self, recno, regNuevo, liCampos = None ) :
        """
        Modifica un registro.
        @param recno: registro a modificar.
        @param regNuevo: almacen de datos con las variables y contenidos a modificar.
        @param liCampos: si la lista de campos a modificar es diferente se indica.
        @return: registro modificado, que puede ser diferente al indicado inicialmente.
        """
        if liCampos is None :
            liCampos = self.liCampos

        self.goto( recno )

        campos = ""
        liValues = []
        siReleer = True
        for campo in liCampos :
            if hasattr( regNuevo, campo ) :
                valorNue = getattr( regNuevo, campo )
                valorAnt = getattr( self, campo )
                if valorAnt != valorNue :
                    campos += campo + "= ?,"
                    liValues.append( valorNue )
                    if campo in self.orden :
                        siReleer = True

        if campos :
            campos = campos[:-1]

            rid = self.rowid(recno)

            cSQL = "UPDATE %s SET %s WHERE ROWID = %d"%( self.ctabla,campos, rid )
            self.cursor.execute( cSQL, liValues )

            self.conexion.commit()

            if siReleer :
                self.leer()
                return self.buscarID( rid )

        return recno

    def copiaDBF( self ) :
        """
        Se crea una copia completa del objeto, para hacer una lectura diferente, con los mismos datos básicos.
        """

        return DBF( self.conexion, self.ctabla, self.select, self.condicion, self.orden )

    def registroActual( self ) :
        """
        Devuelve un registro con los valores de las variables.
        """

        reg = Almacen()
        for campo in self.liCampos :
            setattr( reg, campo, getattr( self, campo ) )

        return reg

    def dicValores( self ) :
        dic = {}
        for campo in self.liCampos :
            dic[campo] = getattr( self, campo )
        return dic

    def commit( self ) :
        self.conexion.commit()

class DBFT( DBF ) :
    """
    Permite acceder a una consulta de SQL, con un estilo dBase, haciendo una
    lectura inicial completa de todos los datos y luego moviéndose a través
    de los mismos, y leyendo el registro actual, y asignándo los valores de
    los campos a variables con el mismo nombre.
    """
    def __init__(self, conexion, ctabla, select, condicion = "", orden = "" ):
        DBF.__init__( self, conexion, ctabla, select, condicion, orden )
        self.liRows = []

    def reccount( self ) :
        """
        Devuelve el número total de registros.
        """
        return len(self.liRows)

    def leer(self):
        """
        Lanza la consulta,
        Lee todos los registros

        @return: True/False si se han encontrado o no registros
        """
        self.bof = True
        self.recno = -1
        self.liCampos = [ "ROWID" ]
        self.liCampos.extend([ campo.strip() for campo in self.select.split( "," ) ])
        resto = ""
        if self.condicion :
            resto += "WHERE %s"%self.condicion
        if self.orden :
            if resto :
                resto += " "
            resto += "ORDER BY %s"%self.orden
        cSQL = "SELECT ROWID,%s FROM %s %s"%(self.select, self.ctabla, resto)
        self.cursor.execute( cSQL )
        self.liRows = self.cursor.fetchall()
        return self.gotop()

    def _leerUno(self, numRecno ):
        """
        Lectura de un registro, y asignación a las variables = campos.
        """
        liValores = self.liRows[numRecno]
        for numCampo, campo in enumerate(self.liCampos) :
            setattr( self, campo, liValores[numCampo] )

    def rowid(self, numRecno):
        """
        Devuelve el id del regitro numRecno.
        @param numRecno: número de registro.
        """
        return self.liRows[numRecno][0]

    def copiaDBF( self ) :
        """
        Se crea una copia completa del objeto, para hacer una lectura diferente, con los mismos datos básicos.
        """

        return DBFT( self.conexion, self.ctabla, self.select, self.condicion, self.orden )
