#    Copyright 2008 Peter Bulychev
#
#    This file is part of Clone Digger.
#
#    Clone Digger is free software: you can redistribute it and/or modify
#    it under the terms of the GNU General Public License as published by
#    the Free Software Foundation, either version 3 of the License, or
#    (at your option) any later version.
#
#    Clone Digger is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#   You should have received a copy of the GNU General Public License
#   along with Clone Digger.  If not, see <http://www.gnu.org/licenses/>.

import pdb
import types

import arguments

free_variable_cost = 0.5

class ParseError:    
    def __init__(self, descr):
	self._descr = descr
    def __str__(self):
	return self._descr

class SourceFile:        
    size_threshold = 5
    distance_threshold = 5
    def __init__(self, file_name):	
	f = open(file_name, 'r')
	def filter_func(s):
	    for i in range(len(s)-1, -2, -1):
		if i<0 or not s[i].isspace():
		    break
	    if i>=0:
		return s[:i+1]
	    else:
		return s
	self._source_lines = [filter_func(s) for s in f.readlines()]
	f.close()
	self._file_name = file_name
    def getSourceLine(self, n):
#	if n >= len(self._source_lines):
#	    return ''
#TODO
#error here
	return self._source_lines[n] 
    def _setTree(self, tree):
	self._tree = tree
    def getTree(self):
	return self._tree
    def getFileName(self):
	return self._file_name

class AbstractSyntaxTree:
    def __init__(self, name=None, line_numbers=[], source_file=None):
	self._childs = []
	self._line_numbers= line_numbers
	self._covered_line_numbers = None
	self._parent = None
	self._hash = None
	self._source_file = source_file
	self._is_statement = False
	if name != None:
	    self.setName(name)
    def getSourceFile(self):
	return self._source_file
    def setMark(self, mark):
	self._mark = mark 
    def getMark(self):
	return self._mark
    def markAsStatement(self, val=True):
	self._is_statement = val
    def isStatement(self):
	return self._is_statement
    def setName(self, name):
	self._name = name
    def getLineNumbers(self):
	return self._line_numbers   
    def getCoveredLineNumbers(self):
	return self._covered_line_numbers
    def getParent(self):
	return self._parent
    def setParent(self, parent):
	self._parent = parent
    def getAncestors(self):
	r = []
	t = self.getParent()	
	while t:
	    if t.isStatement():
		r.append(t)
	    t = t.getParent()
	return r
    def getSourceLines(self):
	source_line_numbers = set([])
	r = []
	source_line_numbers = self.getCoveredLineNumbers()
	source_line_numbers_list = list(source_line_numbers)
	source_line_numbers_list.sort()
	for source_line_number in source_line_numbers_list:
	    r.append(self.getSourceFile().getSourceLine(source_line_number) ) 
	return r
    def getName(self):
	return self._name
    def getChilds(self):
	return self._childs
    def getChildCount(self):
	return len(self._childs)
    def propagateCoveredLineNumbers(self):
	self._covered_line_numbers = set(self._line_numbers)
	for child in self.getChilds():
	    self._covered_line_numbers.update(child.propagateCoveredLineNumbers())
	return self._covered_line_numbers
    def addChild(self, child, save_parent = False):
	if not save_parent:
	    child.setParent(self)
	self._childs.append(child)
    def setChildCount(self, count):
	assert(not self._childs)
	self._childs = count * [None]
    def setNextUndefinedChild(self, c):
	for i in range(len(self.getChilds())):
	    if self.getChilds()[i] == None:
		self._childs[i] = c
	assert()
    def __str__(self):
	return ' ( ' + self.getName() + ' '.join([str(child) for child in self.getChilds()]) + ' ) '
    def getFullHash(self):
	return self.getDCupHash(-1) 
    def getDCupHash(self, level):
	if len(self._childs) == 0:
	    ret = 0 # in case of names and constants
	else:
	    ret = (level+1) * hash(self._name) * len(self._childs)
	# if level == -1, it will not stop until it reaches the leaves 
	if level != 0:
            for i in range(len(self._childs)):
		child = self._childs[i]
	        ret += (i+1)*child.getDCupHash(level-1)
	return hash(ret)
    def __hash__(self):
	#TODO check correctness
	if not self._hash:
	    self._hash =  hash(self.getDCupHash(3) + hash(self.getName()))
	return self._hash
#	return  hash(self.getDCupHash(3) + hash(self.getName()))
 
    def __eq__(self, tree2):
	tree1 = self
	if type(tree2) == types.NoneType:
	    return False
	if tree1.getName() != tree2.getName():
	    return False
	if tree1.getChildCount() != tree2.getChildCount():
	    return False
	for i in range(tree1.getChildCount()):
	    if tree1.getChilds()[i] != tree2.getChilds()[i]:
		return False
	return True
    def getAllStatementSequences(self):
	r = []
	current = StatementSequence()
	for child in self.getChilds():
	    if child.isStatement():
		current.addStatement(child)
	    else:
		if (not current.isEmpty()) and len(current.getCoveredLineNumbers())>=arguments.size_threshold:
		    r.append(current)
		    current = StatementSequence() 
	    r.extend(child.getAllStatementSequences())
	if (not current.isEmpty()) and len(current.getCoveredLineNumbers())>=arguments.size_threshold:
	    r.append(current)
	return r
    def storeSize(self):
	observed = set()
	self._none_count = 0
	def rec_calc_size(t):
	    r = 0
	    if not t in observed:
		if t.getChildCount():
		    for c in t.getChilds():
			r += rec_calc_size(c)
		else:
		    observed.add(t)
		    if t.getName()=='None':
			self._none_count += 1
		    if t.__class__.__name__ == 'FreeVariable':
			r+= free_variable_cost
		    else:
			r+= 1
	    return r
	self._size = rec_calc_size(self)
    def getSize(self, ignore_none = True):
	ret = self._size
	if ignore_none:
	    ret -= self._none_count
	return ret    

class StatementSequence:
    def __init__(self, sequence = []):
	self._sequence = []
	self._source_file = None
	for s in sequence:
	    self.addStatement(s)
    def getCoveredLineNumbers(self):
	r = set()
	for s in self:
	    r.update(s.getCoveredLineNumbers())
	return r
    def getAncestors(self):
	return self[0].getAncestors()
    def isEmpty(self):
	return (self._sequence == [])
    def addStatement(self, statement):
	self._sequence.append(statement)
	if self._source_file == None:
	    self._source_file = statement.getSourceFile()
	else:
	    assert(self._source_file == statement.getSourceFile())
    def __getitem__(self, *args):
	return self._sequence.__getitem__(*args)
    def __len__(self):
	return self._sequence.__len__()
    def __str__(self):
	return ','.join([str(s) for s in self])
    def getWeight(self):
	return sum([s.getCluster().getUnifierSize() for s in self._sequence])
    def getSourceFile(self):
	return self._source_file
    def getSourceLines(self):
	source_line_numbers = set([])
	r = []
	for statement in self:
	    r.extend(statement.getSourceLines())
	return r
    def getLineNumbers(self):
	r = []
	for statement in self:
	    r.extend(statement.getLineNumbers())
	return r
    def getLineNumberHashables(self):	
	source_file_name = self.getSourceFile().getFileName()
	line_numbers = self.getCoveredLineNumbers()
	return set([(source_file_name, line_number) for line_number in line_numbers])
    def constructTree(self):
	tree = AbstractSyntaxTree('__SEQUENCE__')
	for statement in self:
	    tree.addChild(statement, True)
	return tree    
    def getLength(self):
	return len(self)
    def getCoveredLineNumbersCount(self):
	covered = set()
	for t in self:
	    covered.update(t.getCoveredLineNumbers())
	return len(covered)

class PairSequences:
    def __init__(self, sequences):
	self._sequences = sequences
    def __getitem__(self, *args):
	return self._sequences.__getitem__(*args)
    def __str__(self):
	return ';\t'.join([str(s) for s in self])
    def getWeight(self):
	assert(self[0].getWeight() == self[1].getWeight())
	return self[0].getWeight()
    def calcDistance(self):
	import anti_unification
	trees = [s.constructTree() for s in self]
	unifier = anti_unification.Unifier(trees[0], trees[1])
	return unifier.getSize()
    def subSequence(self, first, length):
	return PairSequences([StatementSequence(self[0][first:first+length]), StatementSequence(self[1][first:first+length])])
    def getLength(self):
	return self[0].getLength()
    def getMaxCoveredLineNumbersCount(self):
	return max([s.getCoveredLineNumbersCount() for s in self])
