#
# Tools, DB, and plagiarism...

# DO NOT USE, this is the old lib...

assert False, "PLEASE DO NOT USE THIS OLD LIB"

from sys i
mport platform
import os
import tokenize
import token
import keyword
i
mport builtins
import io
import ctypes
from itertools import permutations

from multiprocessing import Queue, Process, Lock, Value
from threading import Thread
from queue import Empty as QEmpty
import time



sDB = 'plagia.db'
SOURCE_DIR = 'codesEleves/'
# On too much procs, very strange errors using sqlite3 database ("database disk image is malformed" or "file is encrypted or is not a database" (!!!))
#  and race condition using mutiprocessing.Lock (tested ok with 8)
# Hmm, further analysis leads to weirder results: when the threads are replaced by processes (for writing the DB), less race conditions...
#  however, these leads to "disappearing processes": processes that stop without error! (hmm, they may just have finish their unbalanced queue...)
N_PROCESS = 24


# This is due to a WEIRD *something*: token.COMMENT does not exists BUT token.tok_name[55] is 'COMMENT', AND tok_name is build in token.py from the constants that are also in token.__all__ (hence exported)
# (and token.N_TOKENS is not in tok_name)
try:
    assert token.tok_name[55]=='COMMENT', 'Non-version-independant constant is not good'
    assert token.tok_name[56]=='NL', 'Non-version-independant constant is not good'
    assert token.tok_name[57]=='ENCODING', 'Non-version-independant constant is not good'
    token.COMMENT = 55
    token.NL = 56
    token.ENCODING = 57
except AssertionError:
    assert token.tok_name[54]=='COMMENT', 'Non-version-independant constant is not good'
    assert token.tok_name[55]=='NL', 'Non-version-independant constant is not good'
    assert token.tok_name[56]=='ENCODING', 'Non-version-independant constant is not good'
    token.COMMENT = 54
    token.NL = 55
    token.ENCODING = 56





#-------------------------------------------------------------------------------
# First tests with tokenized code, and pure-Python implementation for SIM
#-------------------------------------------------------------------------------
if False:
    root = ast.parse(open('test.py').read())
    root = ast.parse('dudu = 3+2')
    #PrintName().generic_visit(root)
    nl = NodeLister()
    nl.generic_visit(root)
    lNodes = nl.lNodes

    with open('test.py', 'rb') as f:
        lTok = list(tokenize.tokenize(f.readline))


    import numpy as np
    def strAlign(a,b):
        # Hypothèse que c'est pas une seule lettre qui correspond... (moins de tests)
        scMax = 0
        iM,jM = 0,0
        m = 1
        d = -1
        g = -2
        sc = lambda x,y: m if x==y else d
        D = np.zeros( (len(a), len(b)) )
        for j in range(len(b)):
            D[0,j] = max([sc(a[0],b[j]),0])
        for i in range(1,len(a)):
            D[i,0] = max([sc(a[i],b[0]),0])
            for j in range(1,len(b)):
                D[i,j] = max([D[i-1,j-1] + sc(a[i],b[j]),
                              D[i-1,j]+g,
                              D[i,j-1]+g,
                              0]) # Le zéro m'étonne beaucoup pour la deuxième ligne...
                if D[i,j] > scMax:
                    iM,jM = i,j
                    scMax = D[i,j]
        print(D)
        if iM>jM:
            print(a)
            print(' '*(iM-jM)+b)
        elif iM<jM:
            print(' '*(jM-iM)+a)
            print(b)
        else:
            print(a)
            print(b)
        print('score: {}'.format(scMax))
    def strAlignScore(a,b):
        scMax = 0
        lPrev = [0 for i in b]+[0]
        l = lPrev[:]
        g = -2
        def fSc(x,y):
            if x>DicIncr.NAME_OFFSET and y>DicIncr.NAME_OFFSET:
                return 2 if x==y else 0
            return 1 if x==y else 0 # -2 ?!
        #fSc = lambda x,y: 1 if x==y else -1
        for j,y in enumerate(b):
            lPrev[j] = max( (fSc(a[0],y),0) )
        for i,x in enumerate(a[1:]):
            for j,y in enumerate(b):
                l[j] = max([lPrev[j-1] + fSc(x,y),
                            lPrev[j] + g,
                            l[j-1] + g,
                            0])
            if max(l)>scMax:
                scMax = max(l)
            lPrev = l[:]
        return scMax
    
    def scoreBlocks(lTokin,llTokin):
        return sum(strAlignScore(lTokin,block) for block in llTokin)


    # Score is not permutative
    def score(f1,f2):
        if len(f1) > 30:
            lTokin1 = intifyTokens(tokenizeStr(f1))
        else:
            lTokin1 = intifyTokens(tokenize.tokenize(open(f1, 'rb').readline))
        if len(f2) > 30:
            lTokin2 = intifyTokens(tokenizeStr(f2))
        else:
            lTokin2 = intifyTokens(tokenize.tokenize(open(f2, 'rb').readline))
        
        sc11 = scoreBlocks(lTokin1, detectBlocks(lTokin1))
        sc22 = scoreBlocks(lTokin2, detectBlocks(lTokin2))
        sc12 = scoreBlocks(lTokin1, detectBlocks(lTokin2))
        
        return 2*sc12/(sc11+sc22)


            


#-------------------------------------------------------------------------------
# Used for custom-tokenization of identifiers
#-------------------------------------------------------------------------------
class DicIncr:
    NAME_OFFSET = 1024
    def __init__(self):
        self.N = 1 # Not 0, reserved but ... by what?
        self.d = {}
    def __getitem__(self,s):
        if s not in self.d:
            self.d[s] = self.N
            self.N += 1
        return self.d[s]
    def __str__(self): return 'PAB'+str(self.d)
    def __repr__(self): return 'PAB'+repr(self.d)
    def setAfterTokens(self):
        self.N = token.NT_OFFSET
    def setCustomNames(self):
        self.N = DicIncr.NAME_OFFSET
        


def initNameToToken():
    nameToToken = DicIncr()
    nameToToken.setAfterTokens()
    # Record operators (optional)
    for s in ['+', '-', '*', '**', '/', '//', '%', '<<', '>>', '&', '|', '^', '~', '<', '>', '<=', '>=', '==', '!=',
              '(', ')', '[', ']', '{', '}', ',', ':', '.', ';', '@', '=', '+=', '-=', '*=', '/=', '//=', '%=', '&=', '|=', '^=', '>>=', '<<=', '**=',]:
        nameToToken[s]
    # Record some keywords, so that it gives average identification...
    for s in keyword.kwlist:
        nameToToken[s]
    for s in dir(builtins):
        nameToToken[s]
    # Now begins custom names
    nameToToken.setCustomNames()
    return nameToToken


# Use Python tokenizer to analyse the source code
def tokenizeStr(s):
    sb = io.BytesIO()
    sb.write( s.encode() )
    sb.seek(0)
    return list(tokenize.tokenize(sb.readline))


# Converts tokens to integers (it's easier to compare, and to customize)
def intifyTokens(lTok):
    l = []
    for tok in lTok:
        # Translates operators to integers (a single token OP is used by Python to reduce the number of tokens)
        if tok.type in (token.OP, token.NAME):
            l.append(nameToToken[tok.string])
        # Ignore newlines, comments (there content should be treated separately, and their position can be changed too easily) and some other tokens
        elif tok.type in (token.COMMENT, token.NL, token.ENCODING):
        #elif tok.type in (token.NL, token.ENCODING):
            continue
        else:
            l.append(tok.type)
    return l



#-------------------------------------------------------------------------------
# SIM method
# http://lowara.googlecode.com/svn/KPI/diplom/source_code/Sim_%20A%20Utility%20For%20Detecting%20Similarity%20in%20Computer%20Programs.pdf
#-------------------------------------------------------------------------------

# Detects subblocks of code, by their level of indentation
#  Will make subblocks from functions, if-statements, for-loops, ...
#  Will not work well with functions in functions
def simDetectBlocks(lTokin):
    ll = []
    a = 0
    N = 3 # Should separate if statement and for loops in methods of classes
    for i,x in enumerate(lTokin):
        if x in (token.INDENT, token.DEDENT) and N>0:
            ll.append(lTokin[a:i])
            a = i
        if x == token.INDENT:
            N -= 1
        elif x == token.DEDENT:
            N += 1
    ll.append( lTokin[a:] )