package tools.argumentTainter;

import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import neo4j.readWriteDB.Neo4JDBInterface;
import neo4j.traversals.readWriteDB.Traversals;

import org.neo4j.graphdb.DynamicRelationshipType;
import org.neo4j.graphdb.Node;
import org.neo4j.graphdb.RelationshipType;

import udg.useDefAnalysis.ASTDefUseAnalyzer;
import udg.useDefGraph.ReadWriteDbASTProvider;
import udg.useDefGraph.UseOrDef;
import databaseNodes.EdgeTypes;
import ddg.DefUseCFG.DefUseCFG;

public class DefUseCFGPatcher
{

	List<DefUseLink> newlyAddedLinks = new LinkedList<DefUseLink>();
	DefUseCFG defUseCFG;
	ASTDefUseAnalyzer astDefUseAnalyzer = new ASTDefUseAnalyzer();

	public class DefUseLink
	{
		public DefUseLink(String aSymbol, Long aStatementId, boolean aIsDef)
		{
			symbol = aSymbol;
			statement = aStatementId;
			isDef = aIsDef;
		}

		public String symbol;
		public long statement;
		public boolean isDef;
	}

	static final Map<String, Object> EMPTY_PROPERTIES = new HashMap<String, Object>();

	public void setSourceToPatch(String sourceToPatch, int argToPatch)
	{
		astDefUseAnalyzer.addTaintSource(sourceToPatch, argToPatch);
	}

	public Collection<DefUseLink> getDefUseLinksToAdd()
	{
		return newlyAddedLinks;
	}

	public void patchDefUseCFG(DefUseCFG defUseCFG,
			Collection<Node> statementsToPatch)
	{
		
		this.defUseCFG = defUseCFG;
		newlyAddedLinks.clear();

		for (Node statement : statementsToPatch)
		{
			
			if(statement == null) continue;
			
			long statementId = statement.getId();

			Node node = Traversals.getASTForStatement(statement);

			ReadWriteDbASTProvider astProvider = new ReadWriteDbASTProvider();
			astProvider.setNodeId(node.getId());

			Collection<UseOrDef> newDefs = astDefUseAnalyzer
					.analyzeAST(astProvider);

			Collection<Object> oldDefs = defUseCFG
					.getSymbolsDefinedBy(statementId);
			updateDefsToAdd(oldDefs, newDefs, statementId);

		}

	}

	private void updateDefsToAdd(Collection<Object> oldDefs,
			Collection<UseOrDef> newDefs, Long statementId)
	{
		for (UseOrDef newDef : newDefs)
		{
			if (oldDefs.contains(newDef.symbol))
				continue;
			if (!newDef.isDef)
				continue;
			DefUseLink e = new DefUseLink(newDef.symbol, statementId,
					newDef.isDef);
			// add to newlyAddedLinks
			newlyAddedLinks.add(e);
			defUseCFG.addSymbolDefined(statementId, newDef.symbol);

			// Add def-links from AST nodes to symbols
			long nodeId = ((ReadWriteDbASTProvider) newDef.astProvider)
					.getNodeId();
			if (statementId != nodeId)
			{
				DefUseLink e2 = new DefUseLink(newDef.symbol, nodeId,
						newDef.isDef);
				newlyAddedLinks.add(e2);
				defUseCFG.addSymbolDefined(nodeId, newDef.symbol);
			}

		}
	}

	public void writeChangesToDatabase()
	{
		if (defUseCFG == null)
		{
			return;
		}

		for (DefUseLink link : newlyAddedLinks)
		{
			Long fromId = link.statement;
			Long toId = (Long) defUseCFG.getIdForSymbol(link.symbol);

			if (toId == null)
			{
				Map<String, Object> properties = new HashMap<String, Object>();
				Node statementNode = Neo4JDBInterface.getNodeById(link.statement);
				
				properties.put("functionId", statementNode.getProperty("functionId"));
				properties.put("type", "Symbol");
				properties.put("code", link.symbol);
				
				Node symbolNode = Neo4JDBInterface.addNode(properties);
				toId = (Long) symbolNode.getId();
			}

			RelationshipType relType = DynamicRelationshipType
					.withName(EdgeTypes.DEF);
			Neo4JDBInterface.addRelationship(fromId, toId, relType, null, true); //JANNIK: added flag
		}
		
		
	}

}