#!/usr/bin/env python3.6
# -*- coding: utf-8 -*-
"""
Application ...
"""
#    Copyright (C) 2017 by
#    Emmanuel Desmontils <emmanuel.desmontils@univ-nantes.fr>
#    Patricia Serrano-Alvarado <patricia.serrano-alvarado@univ-nantes.fr>
#    All rights reserved.
#    GPL v 2.0 license.

from queue import Empty
import multiprocessing as mp

import datetime as dt
import time

import csv
from tools.tools import now, fromISO, existFile

from tools.ssa import *


from rdflib import Variable, URIRef, Literal

from lxml import etree  # http://lxml.de/index.html#documentation
from lib.bgp import serialize2string, egal, calcPrecisionRecall, canonicalize_sparql_bgp, serializeBGP

from collections import OrderedDict

from functools import reduce

#==================================================

SWEEP_IN_ENTRY = 1
SWEEP_IN_DATA = 2
SWEEP_IN_END = 3

SWEEP_IN_QUERY = 4
SWEEP_OUT_QUERY = 5
SWEEP_IN_BGP = 6

SWEEP_ALL_BGP = False

SWEEP_START_SESSION = -1
SWEEP_END_SESSION = -2
SWEEP_PURGE = -3

SWEEP_ENTRY_TIMEOUT = 0.8  # percentage of the gap
SWEEP_PURGE_TIMEOUT = 0.1  # percentage of the gap

SWEEP_DEBUG_BGP_BUILD = False
SWEEP_DEBUB_PR = False

#==================================================


def toStr(s, p, o):
    return serialize2string(s)+' '+serialize2string(p)+' '+serialize2string(o)

def listToStr(l) :
    s = [serialize2string(s) for s in l]
    return s

def mapValues(di,i,j) :
    if (di != i) and isinstance(j, Variable):
        return i
    else:
        return None 

def hashBGP(bgp):
    rep = ''
    for (s,p,o) in bgp:
            rep += toStr(s,p,o)+' . '
    return hash(rep)

#==================================================


class TripplePattern(object):
    """docstring for TripplePattern"""

    def __init__(self, s, p, o):
        super(TripplePattern, self).__init__()
        (self.s, self.p, self.o) = (s, p, o)

    def equal(self, tp):
        return egal(self.spo(), tp.spo())

    def toStr(self):
        return serialize2string(self.s)+' '+serialize2string(self.p)+' '+serialize2string(self.o)

    def toString(self):
        return self.toStr()

    def spo(self):
        return (self.s, self.p, self.o)


class TriplePatternQuery(TripplePattern):
    """docstring for TriplePatternQuery"""

    def __init__(self, s, p, o, time, client, sm, pm, om):
        super(TriplePatternQuery, self).__init__(s, p, o)
        (self.time, self.client, self.sm, self.pm,
         self.om) = (time, client, sm, pm, om)
        self.su = set()
        self.pu = set()
        self.ou = set()

    def isDump(self):  # tp is <?s ?p ?o> ?
        return isinstance(self.s, Variable) and isinstance(self.p, Variable) and isinstance(self.o, Variable)

    def renameVars(self, i):
        if isinstance(self.s, Variable):
            self.s = Variable("s"+str(i).replace("-", "_"))
        if isinstance(self.p, Variable):
            self.p = Variable("p"+str(i).replace("-", "_"))
        if isinstance(self.o, Variable):
            self.o = Variable("o"+str(i).replace("-", "_"))

    def sign(self):
        if isinstance(self.s, Variable):
            s = Variable("s")
        else:
            s = self.s
        if isinstance(self.p, Variable):
            p = Variable("p")
        else:
            p = self.p
        if isinstance(self.o, Variable):
            o = Variable("o")
        else:
            o = self.o
        return hash(toStr(s, p, o))

    def nestedLoopOf2(self,baseTPQ):
        # 1:sp->sp 2:so->so 3:s->s 4:so->os 5:s->o 6:o->s 8:po->po 9:p->p 10:sp->po
        # 0/1/2/3 ->(0/1/2/3,0/1/2/3,0/1/2/3)
        base = (None,baseTPQ.s,baseTPQ.p,baseTPQ.o)
        if  self.s in baseTPQ.sm: 
            if self.p in baseTPQ.pm: nl = ( 2 , (1,2,0) ) # sp->sp
            elif self.o in baseTPQ.om: nl = ( 2 , (1,0,3) ) # so->so
            else: nl = ( 1 , (1,0,0) ) # s->s
        elif self.s in baseTPQ.om:
            if self.o in baseTPQ.sm: nl = ( 2 , (3,0,1) ) # so->os
            elif self.p in baseTPQ.pm: nl = ( 2 , (3,2,0) ) # sp->po
            else: nl = ( 1 , (3,0,0) ) # s->o
        elif self.o in baseTPQ.sm: nl = ( 1 , (0,0,1) ) # o->s
        elif self.p in baseTPQ.pm:
            if self.o in baseTPQ.om: nl = ( 2 , (0,2,3) ) # po->po
            else: nl = ( 1 , (0,2,0) ) # p->p
        else: nl = ( 0 , (0,0,0) )
        (couv,injection) = nl
        # print('injection:',injection)
        return ( couv , 
                { 's':doInjection(base,injection[0],self.s),  
                  'p':doInjection(base,inj
ection[1],self.p),
                  'o':d
oInjection(base,injection[2],self.o) } )

def doInjection(base, injection, val) :
    if injection > 0 : return base[injection]
    else: return val

#==================================================

class BasicGraphPattern:
    def __init__(self, gap=None, tpq=None):
        self.tp_set = []
        self.input_set = set()  # ens. des hash des entrées, pour ne pas mettre 2 fois la même
        if gap is None:
            self.gap = dt.timedelta(minutes=1)
        else:
            self.gap = gap
        if tpq is None:
            self.birthTime = now()
            self.time = now()
            self.client = 'Unknown'
        else:
            self.birthTime = tpq.time
            self.time = tpq.time
            self.client = tpq.client
            self.add(tpq)

    def add(self, tpq, sgn=None):
        assert isinstance(
            tpq, TriplePatternQuery), "BasicGraphPattern.Add : Pb type TPQ"
        assert (self.client == 'Unknown') or (self.client ==
                                              tpq.client), "BasicGraphPattern.Add : client différent"
        assert tpq.time - self.time <= self.gap, "BasicGraphPattern.Add : TPQ pas dans le gap"
        if self.client == 'Unknown':
            self.client = tpq.client
            self.birthTime = tpq.time
        self.time = tpq.time
        self.tp_set.append(tpq)
        if sgn is None:
            self.input_set.add(tpq.sign())
        else:
            self.input_set.add(sgn)

    def update(self, tp, ntpq):
        if SWEEP_DEBUG_BGP_BUILD:
            print('\t Déjà présent avec ', toStr(tp.s, tp.p, tp.o))
            print('\t MàJ des mappings')
            print(  '\t\t ', listToStr(tp.sm), '+', listToStr(ntpq.sm), 
                    '\n\t\t ', listToStr(tp.pm), '+', listToStr(ntpq.pm), 
                    '\n\t\t ', listToStr(tp.om), '+', listToStr(ntpq.om) )
        tp.sm.update(ntpq.sm)
        tp.pm.update(ntpq.pm)
        tp.om.update(ntpq.om)
        self.input_set.add(ntpq.sign())  # ATTENTION : Utile ?

    def age(self):
        return now() - self.time

    def isOld(self):
        return self.age() > self.gap

    def toString(self):
        rep = ''
        for tpq in self.tp_set:
            rep += tpq.toStr() + " .\n "
        return rep

    def print(self, tab=''):
        print(tab, 'BGP:', self.client, ' at ', self.time)
        print(tab, self.toString())

    def canBeCandidate(self, tpq):
        assert isinstance(
            tpq, TriplePatternQuery), "BasicGraphPattern.canBeCandidate : Pb type TPQ"
        return (tpq.client == self.client) and (tpq.time - self.time <= self.gap) and (tpq.sign() not in self.input_set)

    def findNestedLoop(self, ntpq):
        assert isinstance(
            ntpq, TriplePatternQuery), "BasicGraphPattern.findTP : Pb type TPQ"
        ref_couv = 0
        trouve = False
        fromTP = None
        candTP = None
        mapVal = (None, None, None)
        # on regarde si une constante du sujet et ou de l'objet est une injection 
        # provenant d'un tpq existant (par son résultat)
        for (_, tpq) in enumerate(self.tp_set):
            if SWEEP_DEBUG_BGP_BUILD:
                print('_____', '\n\t\t Comparaison de :',
                      ntpq.toStr(), '\n\t\t avec le TP :', tpq.toStr())
                print('\t\tbsm:', listToStr(tpq.sm), '\n\t\t\tbsu:', listToStr(tpq.su),
                      '\n\t\tbpm:', listToStr(tpq.pm), '\n\t\t\tbpu:', listToStr(tpq.pu),
                      '\n\t\tbom:', listToStr(tpq.om), '\n\t\t\tbou:', listToStr(tpq.ou),)

            (couv, d) = ntpq.nestedLoopOf2(tpq)
            #couv : nombre 