/*
 * Cobertura - http://cobertura.sourceforge.net/
 *
 * Copyright (C) 2010 Piotr Tabor
 *
 * Note: This file is dual licensed under the GPL and the Apache
 * Source License (so that it can be used from both the main
 * Cobertura classes and the ant tasks).
 *
 * Cobertura 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 2 of the License,
 * or (at your option) any later version.
 *
 * Cobertura 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 Cobertura; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
 * USA
 */

package net.sourceforge.cobertura.coveragedata;

import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;

import net.sourceforge.cobertura.coveragedata.countermaps.AtomicCounterMap;
import net.sourceforge.cobertura.coveragedata.countermaps.CounterMap;

public class TouchCollector implements HasBeenInstrumented{
	
	private static final CounterMap<LineTouchData> touchedLines=new AtomicCounterMap<LineTouchData>();	
	private static final CounterMap<SwitchTouchData> switchTouchData=new AtomicCounterMap<SwitchTouchData>();	
	private static final CounterMap<JumpTouchData> jumpTouchData=new AtomicCounterMap<JumpTouchData>();
	
	private static AtomicInteger lastClassId=new AtomicInteger(1);
	private static final Map<String,Integer> class2classId=new ConcurrentHashMap<String, Integer>();
	private static final Map<Integer,String> classId2class=new ConcurrentHashMap<Integer,String>();

	static{
		ProjectData.initialize();
	}
	
	private static final int registerClassData(String name){		
		Integer res=class2classId.get(name);
		if (res==null){
			int new_id=lastClassId.incrementAndGet();
			class2classId.put(name, new_id);
			classId2class.put(new_id, name);
			return new_id;
		}
		return res;
	}
	
	/**
	 * This method is only called by code that has been instrumented.  It
	 * is not called by any of the Cobertura code or ant tasks.
	 */
	public static final void touchSwitch(String classId,int lineNumber, int switchNumber, int branch) {
		switchTouchData.incrementValue(new SwitchTouchData(registerClassData(classId),lineNumber, switchNumber, branch));
	}
	
	/**
	 * This method is only called by code that has been instrumented.  It
	 * is not called by any of the Cobertura code or ant tasks.
	 */
	public static final void touch(String classId,int lineNumber) {
		touchedLines.incrementValue(new LineTouchData(registerClassData(classId), lineNumber));
	}
	
	/**
	 * This method is only called by code that has been instrumented.  It
	 * is not called by any of the Cobertura code or ant tasks.
	 */
	public static final void touchJump(String classId,int lineNumber, int branchNumber, boolean branch) {
		jumpTouchData.incrementValue(new JumpTouchData(registerClassData(classId),lineNumber, branchNumber, branch));
	}	

	private static class LineTouchData implements HasBeenInstrumented{
		int classId,lineNumber;
		public LineTouchData(int classId,int lineNumber) {
			this.classId=classId;
			this.lineNumber=lineNumber;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = 1;
			result = prime * result + classId;
			result = prime * result + lineNumber;
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (obj == null)
				return false;
			if (getClass() != obj.getClass())
				return false;
			LineTouchData other = (LineTouchData) obj;
			if (classId != other.classId)
				return false;
			if (lineNumber != other.lineNumber)
				return false;
			return true;
		}		
	}
	
	private static class SwitchTouchData extends LineTouchData implements HasBeenInstrumented{
		int switchNumber, branch;
		
		public SwitchTouchData(int classId,int lineNumber, int switchNumber, int branch) {
			super(classId,lineNumber);
			this.switchNumber=switchNumber;
			this.branch=branch;
		}

		@Override
		public int hashCode() {
			final int prime = 31;
			int result = super.hashCode();
			result = prime * result + branch;
			result = prime * result + switchNumber;
			return result;
		}

		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (!super.equals(obj))
				return false;
			if (getClass() != obj.getClass())
				return false;
			SwitchTouchData other = (SwitchTouchData) obj;
			if (branch != other.branch)
				return false;
			if (switchNumber != other.switchNumber)
				return false;
			return true;
		}	
	}
	
	private static class JumpTouchData extends LineTouchData implements HasBeenInstrumented{
		int branchNumber;
		boolean branch;
		public JumpTouchData(int classId,int lineNumber, int branchNumber, boolean branch) {
			super(classId, lineNumber);
			this.branchNumber=branchNumber;
			this.branch=branch;
		}
		@Override
		public int hashCode() {
			final int prime = 31;
			int result = super.hashCode();
			result = prime * result + (branch ? 1231 : 1237);
			result = prime * result + branchNumber;
			return result;
		}
		@Override
		public boolean equals(Object obj) {
			if (this == obj)
				return true;
			if (!super.equals(obj))
				return false;
			if (getClass() != obj.getClass())
				return false;
			JumpTouchData other = (JumpTouchData) obj;
			if (branch != other.branch)
				return false;
			if (branchNumber != other.branchNumber)
				return false;
			return true;
		}		
	}	
	
	
	public static synchronized void applyTouchesOnProjectData(ProjectData projectData){
		System.out.println("Flushing results...");
		Map<LineTouchData,Integer> touches=touchedLines.getFinalStateAndCleanIt();
		for(Entry<LineTouchData, Integer> touch:touches.entrySet()){
			if(touch.getValue()>0){				
				getClassFor(touch.getKey(),projectData).touch(touch.getKey().lineNumber,touch.getValue());
			}
		}
		
		Map<SwitchTouchData,Integer> switchTouches=switchTouchData.getFinalStateAndCleanIt();
		for(Entry<SwitchTouchData, Integer> touch:switchTouches.entrySet()){
			if(touch.getValue()>0){
				getClassFor(touch.getKey(),projectData).touchSwitch(
						touch.getKey().lineNumber,
						touch.getKey().switchNumber,
						touch.getKey().branch,touch.getValue());
			}
		}
		
		Map<JumpTouchData,Integer> jumpTouches=jumpTouchData.getFinalStateAndCleanIt();
		for(Entry<JumpTouchData, Integer> touch:jumpTouches.entrySet()){
			if(touch.getValue()>0){
				getClassFor(touch.getKey(),projectData).touchJump(
						touch.getKey().lineNumber,
						touch.getKey().branchNumber,
						touch.getKey().branch,touch.getValue());
			}
		}
		System.out.println("Flushing results done");
	}

	private static ClassData getClassFor(LineTouchData key,ProjectData projectData) {
//		System.out.println("\nLooking for:"+key.classId+"\n");
		return projectData.getOrCreateClassData(classId2class.get(key.classId));
	}
		
}
