import sqlite3
import os
import pathlib
import sys
import json
import itertools
import logging
import time
import zlib
import re

#from highSpeedSandwitch.hss import Sql,Fs,Log
'''
cut pasted from hss.py (now its a cookbook)
'''
import logging
import os,itertools

def Log(name=''):
    errorLog = logging.FileHandler('err.log')
    errorLog.setLevel(logging.ERROR)
    logging.getLogger(name).addHandler(errorLog)
    infoLog = logging.FileHandler('info.log')
    infoLog.setLevel(logging.INFO)
    logging.getLogger(name).addHandler(infoLog)
    return logging.getLogger(name)

def Fs(root):
    root = os.path.abspath(root)
    def Chunks(iterable, size=100):
        iterator = iter(iterable)
        for first in iterator:
            yield itertools.chain([first], itertools.islice(iterator, size - 1))

    def FirstChunk(iterable,size=100):
        for chunk in Chunks(iterable,size):
            return chunk

    def HssDir(ext=''):
        for ns, rgt, fls in os.walk(root):
            for fl in fls:
                if fl.endswith(ext) or not ext:
                    yield ('/'.join([ns,fl]),[ns]+rgt)

    def HssLoad(pth):
        if os.path.isdir(pth):
            return
        for row,line in enumerate(open(pth, encoding="cp1252")):
            yield line.replace(chr(28),'').replace('','').replace('','').replace('ï»¿','').strip(),row

    def RLoad(ext):
        for pth,_ in HssDir():
            if not os.path.isfile(pth):
                continue
            if pth.endswith('.'+ext):
                fname = os.path.basename(pth).replace('.'+ext,'')
                yield fname,HssLoad(pth)

    return HssDir,HssLoad,RLoad

class Sql(object):
    @staticmethod
    def compile(dbname,querytxt):
        conn = sqlite3.connect(dbname)
        try:
            cur = conn.cursor()
            cur.executescript(querytxt)
            conn.commit()
        except Exception as e:
            logging.error(e)
        return conn

    def __init__(self):
        self.conn = False

    def _connect(self):
        if not self.conn:
            self.conn = sqlite3.connect(':memory:',
                                        check_same_thread=False,
                                        isolation_level=None)
        return self

    def compile(self,querytxt):
        self._connect()
        try:
            cur = self.conn.cursor()
            cur.executescript(querytxt)
            self.conn.commit()
        except Exception as e:
            logging.error(e)
            logging.info(e)
        return self

    def attach(self,dbfile,dbname):
        if os.path.exists(dbfile+'-journal'):
            os.remove(dbfile+'-journal')
        self._connect()
        for db in self.query('PRAGMA database_list;'):
            if db['name'] == dbname:
                return
        self.conn.cursor().execute("ATTACH DATABASE '%(dbfile)s' AS %(dbname)s;" % {'dbfile':dbfile,'dbname':dbname})
        self.conn.commit()
        return self

    def query(self,querytxt,*args):
        self._connect()
        result=[]
        try:
            cur = self.conn.cursor()
            cur.execute(querytxt,args)
            col = [ attr[0] for attr in cur.description ] if cur.description else []
            for row in cur.fetchall():
                if cur.description:
                    yield dict(zip(col,row))
                else:
                    yield list(row)
        except Exception as e:
            logging.error(e)

    def update(self,querytxt,*args):
        self._connect()
        try:
            cur = self.conn.cursor()
            cur.execute(querytxt,args)
            self.conn.commit()
            return cur.lastrowid
        except Exception as e:
            if not 'UNIQUE' in str(e):
                logging.error(e)

    def bulk(self,querytxt,blk=[]):
        self._connect()
        try:
            cur = self.conn.cursor()
            cur.execute('BEGIN TRANSACTION;')
            cur.executemany(querytxt,blk)
            cur.execute('COMMIT;')
            self.conn.commit()
        except Exception as e:
            if not 'UNIQUE' in str(e):
                logging.error(e)


'''
project here
'''

def LoadSql(root,sql):
    '''
    load sql script
    into output database
    '''
    HssDir,HssLoad,RLoad = Fs(root)
    for pth,lines in RLoad('sql'):
        began = time.time()
        logging.info(' : SQL %s',pth)
        script = '\n'.join([ line for line,_ in lines ])
        logging.info('try %s...', (script))
        sql.compile(script)
        logging.info('| took %f %s',time.time() - began,'second(s) to import ')

def LoadTsv(root,sql):
    HssDir,HssLoad ,RLoad = Fs(root)
    for pth,lines in RLoad('tsv'):
        began = time.time()
        logging.info(' : TSV %s',pth)
        tbl = pth #os.path.basename(pth).replace('.tsv','')
        hdr = None
        ntr = []
        for line,_ in lines:
            if not hdr:
                logging.info('create table %s from %s',tbl,pth)
                hdr = line.split('\t')
                sql.compile('\n'.join(
                    ["CREATE TABLE IF NOT EXISTS HL7.'%s' (" % (tbl) ] +
                    [
                        ', '.join(
                        [
                            "'%s' TEXT NULL" % (colname)
                            for colname in hdr
                        ]+
                        [
                            "'%sCRC' INTEGER NOT NULL" % (colname)
                            for colname in hdr
                        ]
                        )
                    ]+
                    [
                        ", UNIQUE (",
                        ', '.join([
                            "'%sCRC'" % (colname)
                            for colname in hdr
                        ]),
                        ")"
                    ]+
                    [
                        ");"
                    ]))
            else:
                row = line.split('\t')
                #row.append(zlib.crc32(row[-1].encode()))
                ntr.append(row + [zlib.crc32(col.encode()) for col in row])
        ins = ''.join(
            ["INSERT OR IGNORE INTO HL7.'%s' (" % tbl]+
            [
                ', '.join(["'%s'"%col for col in hdr]+
                            ["'%sCRC'"%col for col in hdr])
            ]+
            [') VALUES (']+
            [
                ', '.join(['?' for col in hdr]+
                          ['?' for col in hdr])
            ]+
            [")"])
        logging.info('try : %s',ins)
        sql.bulk(ins,ntr)
        logging.info('| took %f %s',time.time() - began,'second(s) to import ')


def SqlHl7(sql):
    sql.attach('hl7.db','HL7')
    sql.compile('''
        CREATE TABLE IF NOT EXISTS HL7.HL7 (
            SRC INTEGER NOT NULL,
            SEQ INTEGER NOT NULL,
            ROW INTEGER NOT NULL,
            COL INTEGER NOT NULL,
            COM INTEGER NOT NULL,
            SUB INTEGER NOT NULL,
            REP INTEGER NOT NULL,
            CRC INTEGER NOT NULL,
            VAL TEXT NOT NULL,
            TS TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
            UNIQUE (SRC,SEQ,ROW,COL,COM,SUB,REP,CRC)
        );
    ''')

    def LoadHl7():
        for result in sql.query('select VAL,SEQ,ROW,COL,COM,SUB,REP,SRC from HL7.HL7 ORDER BY SRC,SEQ,ROW,COL,REP'):
            yield result['VAL'],result['SEQ'],result['ROW'],result['COL'],result['COM'],result['SUB'],result['REP'],result['SRC']

    def SaveHl7(hl7Iter=[]):
        sql.bulk("insert or ignore into HL7.HL7 (VAL,SEQ,ROW,COL,COM,SUB,REP,SRC,CRC) values (?, ?, ?, ?, ?, ?, ?, ?, ?)",hl7Iter)

    return SaveHl7, LoadHl7

def Splt(line='',sep=','):
    i = 0
    while line:
        fld, final, line = line.partition(sep)
        if fld:
            val = fld.rstrip()
            if val:
                yield i,val
        elif final != sep:
            val = final.rstrip()
            if val:
                yield i,val
        i+=1
        
def Hl7Splt(lines,pth,fldsp='|',comp='^',sub='&',rep='~',esc='\\'):
    tot,maxncol,maxscol = 0,0,0
    seq = 0
    lastrowmsh = 0
    rownn = 0
    pthcrc = zlib.crc32(pth.encode())
    for line,rown in lines:
        if line.startswith('MSH'):
            seq += 1
            lastromsh = rown
            rownn = 0
            fldsp,comp,rep,esc,sub = line[3:8]
            logging.info(' : new hl7 msh %s %s %s %s %s %s',line[0:20],fldsp,comp,sub,rep,esc)
            pthseq = pth + ':' + str(seq)
            pthcrc = zlib.crc32(pthseq.encode())
            logging.info(' : %s => %s %s',pthseq,pthcrc,seq)
        for coln,fld in Splt(line,fldsp):
            maxncol = max(maxncol,coln)
            for repn,repeat in Splt(fld,rep):
                for comn,component in Splt(repeat,comp):
                    for subn,subcomponent in Splt(component,sub):
                        yield subcomponent,seq,rownn,coln,comn,subn,repn,pthcrc,zlib.crc32(subcomponent.encode())
                        tot += 1
                        maxscol = max(maxscol,len(fld))
        rownn = rownn + 1
    logging.info('| dumped SRC %d %d %s %d %s %d %s',pthcrc,tot,'entries ',maxncol,'max per row',maxscol,'max len')

def FsHl7(root):
    def LoadHl7():
        HssDir,HssLoad ,RLoad = Fs(root)
        for pth,lines in RLoad('txt'):
            began = time.time()
            logging.info(' : hl7 %s')
            #pthcrc = zlib.crc32(pth.encode())
            for entry in Hl7Splt(lines,pth):
                yield entry
            logging.info('| took %f %s',time.time() - began,'second(s) to import ')

    return LoadHl7

def Listen(host,port,sqlSaver):
    import socket
    import threading
    import socketserver

    class ThreadedTCPRequestHandler(socketserver.BaseRequestHandler):
        def handle(self):
            #data = str(self.request.recv(1024), 'ascii')
            cur_thread = threading.current_thread()
            #response = bytes("{}: {}".format(cur_thread.name, data), 'ascii')
            #print(response)
            #self.request.sendall(response)
            chnk = []
            r = 0
            while r < MSGLEN:
                blk = self.sock.recv(min(MSGLEN-r,2048))
                chnk.append(blk)
                r = r + len(blk)
            print(chnk)
            self.request.sendall('ok')

    class ThreadedTCPServer(socketserver.ThreadingMixIn, socketserver.TCPServer):
        pass

    server = ThreadedTCPServer((host, port), ThreadedTCPRequestHandler)
    ip, portt = server.server_address
    server_thread = threading.Thread(target=server.serve_forever)
    server_thread.daemon = True
    server_thread.start()
    print("Server loop running in thread:", server_thread.name, ip, portt)
    return server

if __name__ == '__main__':
    '''
    TODO
    ditch SEQ, now merged with SRC, all messages are unique
        was just useless to inner join on SRC and SEQ
        now you can inner join on SRC simply
    something cleaner with TSV
    '''
    logging.basicConfig(level=logging.INFO)
    Log()
    sql = Sql()
    sqlsave,sqlload = SqlHl7(sql)
    
    if len(sys.argv) > 2:
        LoadTsv(sys.argv[2],sql)
        LoadSql(sys.argv[2],sql)
    if len(sys.argv) > 1:
        began = time.time()
        logging.info('from command line %s',sys.argv[1])
        fsload = FsHl7(sys.argv[1])
        sqlsave(fsload())
        #congratulation 8 thousand files in 25 seconds
        #200 message per seconds
        logging.info('whole importation took %s seconds', time.time()-began)
    else:
        print("usage: %s import/dir working/dir"% sys.argv)
   
    server = Listen('localhost',8889,sqlsave)

    input('''anykey to exit
    echo 'something' | netcat 'localhost' 8889
    netcat 'localhost' 8889 <<< file
    ''')

    server.shutdown()


