#!/usr/bin/env python3.8
# -*- coding: utf-8 -*-

'''
        Prácticas de Python DB-API 2.0 (PEP-249) y bases de datos biológicas
        Script de inserción de entradas en formato SWISSPROT en la base de datos
'''

import sys
import re
import sqlite3 as dbi

'''
Estas variables globales contienen los parámetros de conexión a la base de datos
'''
dbname='uniprot.db'     # El nombre de la base de datos, que tendréis que cambiarlo

def siguienteEntrada(uniF):
        '''
        Este método toma como entrada un manejador de ficheros abierto
        de manera que quien llame repetidamente a este método se deberá
        encargar tanto de abrir ficheros como de cerrarlos una vez se
        termine el trabajo de obtención de entradas.
        '''
        
        # Inicialización de variables
        acc = []
        id ''
        lastdate ''
        description ''
        sequence ''
        molw ''
        readingseq False
        try:
                for line in uniF:
                        # Lo primero, quitar el salto de línea
                        line line.rstrip('\n')
                        
                        # Detección del final del registro
                        if re.search('^//',lineis not None:
                                # Cuando se ha terminado de leer un
                                # registro hay que proceder a guardar
                                # los datos en la base de datos
                                
                                # Si no hemos conseguido capturar la descripción
                                # no quiere decir que no tenga, sino que el
                                # programa no ha sido capaz de hacerlo
                                if description == '':
                                        description None
                                
                                # Impresión de comprobación
                                print(f"ACC: {acc[0]} ; ID: {id} ; Last: {lastdate}")
                                
                                # Como ya tenemos una entrada entera
                                # paremos de leer por ahora, para poder
                                # devolver lo recopilado
                                break
                        
                        # ¿Estoy leyendo una secuencia?
                        if readingseq:
                                # Quito todos los espacios intermedios 
                                line re.compile(r"\s+").sub('',line)
                                
                                # Y concateno
                                sequence += line
                                
                        # Como no la estoy leyendo, busco los patrones apropiados
                        else:
                                matched False
                                
                                seqmatch re.search(r"^SQ.+[^0-9](\d+) MW",line)
                                if seqmatch is not None:
                                        matched True
                                        # Extracción del peso molecular
                                        # y comienzo de secuencia
                                        molw seqmatch.group(1)
                                        readingseq True
                                
                                if not matched:
                                        idmatch re.search(r"^ID   ([a-zA-Z0-9_]+)",line)
                                        if idmatch is not None:
                                                matched True
                                                # Identificador
                                                id idmatch.group(1)
                                
                                if not matched:
                                        dtmatch re.search(r"^DT   (\d{2}-[A-Z]{3}-\d{4}),",line)
                                        if dtmatch is not None:
                                                matched True
                                                # Fecha de la última actualización
                                                lastdate dtmatch.group(1)
                                
                                if not matched:
                                        acmatch re.search(r"^AC   (.+)",line)
                                        if acmatch is not None:
                                                matched True
                                                # Los accnumber, que pueden estar en varias líneas
                                                ac acmatch.group(1)
                                                # Elimino los espacios y quito el posible último punto y coma
                                                ac re.compile(r"\s+").sub('',ac).rstrip(';')
                                                
                                                # Rompo por los puntos y coma, y
                                                # añado a la lista de accnumber
                                                acc.extend(ac.split(';'))
                                
                                if not matched:
                                        dematch re.search(r"^DE   RecName: Full=(.+);",line)
                                        if dematch is not None:
                                                matched True
                                                # La descripción, que puede estar en varias líneas
                                                if description != '':
                                                        description += ', EC '
                                                description += dematch.group(1)
                                
        except EOFError:
                # Se ha acabado el fichero sin terminar de recopilar los
                # datos, así que lo indicamos poniendo por ejemplo
                # acc a None
                acc None
        else:
                # Si no ha habido fallos, pero no hemos recuperado nada,
                # poner acc a None
                if len(acc) == 0:
                        acc None

        # Devolvemos los datos de la entrada, o el indicador de que no hay datos
        return acc,id,lastdate,description,sequence,molw


# Comprobación del número de parámetros de entrada
if __name__ == '__main__':
        # sys.argv contiene tanto el nombre del script como los parámetros
        # que se le introdujeron por línea de comandos al llamar al programa
        if len(sys.argv)>1:
                infiles sys.argv[1:]
                
                # Apertura de la conexión con la base de datos
                try:
                        conn dbi.connect(dbname)
                        # Obtención de un cursor para enviar operaciones a la base de datos
                        cur conn.cursor()
                        # Esto sirve para que cada sentencia se ejecute inmediatamente
                        #conn.autocommit = True
                except dbi.Error as e:
                        print("Ha habido un problema al conectar a la base de datos: ",str(e),file=sys.stderr)
                        raise
                
                # Trabajo a nivel de transacción
                with conn:
                        # Procesamiento de cada fichero
                        for infile in infiles:
                                try:
                                        # Estamos abriendo el fichero con el encoding 'latin-1'
                                        # Para text mining lo recomendable es el encoding 'utf-8'
                                        with open(infilemode='r'encoding='latin-1'as uniF:
                                                print("Procesando fichero "infile)
                                                
                                                # Este bucle tiene su condición de salida en su interior
                                                # y por ello es un bucle potencialmente infinito
                                                while True:
                                                        # Recuperar entradas una a una
                                                        acc,id,lastdate,description,sequence,molw siguienteEntrada(uniF)
                                                        
                                                        # Como no hay entradas, me salgo
                                                        if acc is None:
                                                                break
                                                        
                                                        # Preparación de las operaciones de inserción a realizar repetidamente
                                                        # (Debería hacer un chequeo aquí, para comprobar que funcionan)
                                                        cur.execute('INSERT INTO SWISSENTRY VALUES (?,?,?,?,?,?)',(acc[0],id,lastdate,description,sequence,molw))
                                                        for accnumber in acc:
                                                                        cur.execute('INSERT INTO ACCNUMBERS(main_accnumber,accnumber) VALUES (?,?)',(acc[0],accnumber))
                                except dbi.Error as e:
                                        print("Error al insertar en la base de datos: ",str(e),file=sys.stderr)
                                        raise
                                except IOError as e:
                                        print("Error de lectura de fichero {0}: {1}".format(e.errnoe.strerror),file=sys.stderr)
                                        #raise
                                except:
                                        print("Error inesperado: "sys.exc_info()[0],file=sys.stderr)
                                        raise
                
        else:
                raise AssertionError("Debes introducir al menos un fichero con formato SW.")