package de.ugoe.cs.quest.plugin.jfc;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import de.ugoe.cs.quest.plugin.jfc.eventcore.JFCEvent;

/**
 * <p>
 * This class provides the functionality to calculate the unique GUITAR replayIDs
 * for {@link JFCEvent}s. This code is mainly based on guitar source code: edu.umd.cs.guitar.
 * model.JFCDefaultIDGeneratorSimple
 *    
 * </p>
 * @author fabian.glaser
 *
 */

public class JFCReplayIDCalculator {
	
	final int prime = 31;
	
	/**
	 * Properties that are used to identify widgets
	 */
	private static List<String> ID_PROPERTIES = Arrays.asList(
			"Class","Title","Icon");
	
	private static Map<String, Integer> ID_PROPERTIES_MAP;
	static{
		Map<String, Integer> idmap = new HashMap<String, Integer>(4);
		idmap.put("Class", 1);
		idmap.put("Title", 0);
		idmap.put("Icon", 2);
		idmap.put("Index", 3);
		ID_PROPERTIES_MAP = Collections.unmodifiableMap(idmap);
	}
		
	
   /**
    * Those classes are invisible widgets but cause false-positive when
    * calculating ID (compare guitar source code: edu.umd.cs.guitar.
    * model.JFCDefaultIDGeneratorSimple)
    */
   private static List<String> IGNORED_CLASSES = Arrays.asList("javax.swing.JPanel",
         "javax.swing.JTabbedPane", "javax.swing.JScrollPane",
         "javax.swing.JSplitPane", "javax.swing.Box",
         "javax.swing.JViewport", "javax.swing.JScrollBar",
         "javax.swing.JLayeredPane",
         "javax.swing.JList$AccessibleJList$AccessibleJListChild",
         "javax.swing.JList$AccessibleJList", "javax.swing.JList",
         "javax.swing.JScrollPane$ScrollBar",
         "javax.swing.plaf.metal.MetalScrollButton");
	
	
	/**
	 * Calculates the replayID needed for compatibility with Guitar suite of a JFCEvent
	 * @param event for which the ID should be calculated
	 * @return replayID
	 */
	public String calculateReplayID(JFCEvent event){
		String replayID = "";
		long hashCode = 1;
		
		// extract target string information
		String target = event.getTarget();
		String[] targetParts = target.split("\\]\\.\\[");
		
		// we assume that first component corresponds to the notion of a 
		// window in GUITAR suite
		
		// calculate window hashcode
		String[] windowInfo = targetParts[0].split("','");
		String title = windowInfo[0];
		String fuzzyTitle = getFuzzyTitle(title);
		long windowHashCode = fuzzyTitle.hashCode();
		windowHashCode = (windowHashCode * 2) & 0xffffffffL;
		
		long propagatedHashCode = windowHashCode;
		
		for (int i = 1; i < targetParts.length; i++){
			long localHashCode = getLocalHashCode(targetParts[i]);
			hashCode = propagatedHashCode * prime + localHashCode;
	        hashCode = (hashCode * 2) & 0xffffffffL;
			
	        if (i < targetParts.length - 1){
	        	Integer index = getIndex(targetParts[i+1]);
				propagatedHashCode = prime * propagatedHashCode
				+ index.hashCode();
	        }
		}
		
		replayID = "e" + hashCode;
		
		return replayID;
	}
	
	/**
	 * Calculates the hashcode part of one component.
	 * @param targetPart A string that represents a compontent
	 * @return the local hashcode
	 */
	
	private long getLocalHashCode(String targetPart){
		long hashcode = 1;
		String[] widgetInfo = targetPart.split("','");
		widgetInfo[0] = widgetInfo[0].substring(1);
		int length = widgetInfo.length - 1;
		widgetInfo[length] = widgetInfo[length].substring(0,widgetInfo[length].length()-1);
		String wClass = widgetInfo[ID_PROPERTIES_MAP.get("Class")];
		if (IGNORED_CLASSES.contains(wClass)) {
		    hashcode = (prime * hashcode + (wClass.equals("null") ? 0 : (wClass
		               .hashCode())));
		    return hashcode;
		}
		else{
			for (String property: ID_PROPERTIES){
				String value = widgetInfo[ID_PROPERTIES_MAP.get(property)];
				if (!value.equals("null")){
					hashcode = prime * hashcode + property.hashCode();
					hashcode = prime * hashcode + value.hashCode();
				}
			}
		}
		
		hashcode = (hashcode * 2) & 0xffffffffL;
		
		return hashcode;
	}
	
	/**
	 * Guitar has a special way to deal with window titles when
	 * calculating unique widget IDs. This method mimics Guitar's
	 * behavior (compare guitar source code: edu.umd.cs.guitar.
	 * model.JFCDefaultIDGeneratorSimple).
	 * @param title
	 * @return
	 */

	private String getFuzzyTitle(String title){
		final List<String> PATTERNS = 
				Arrays.asList("Rachota .*",
						"OmegaT-.*",
						"Buddi.*",
						"Open:.*",
						"JabRef.*",
						"GanttProject.*",
						".*Pauker.*",
						".*FreeMind.*",
						".* - ArgoUML.*",
						"Save Project .*");


		for (String sPattern : PATTERNS) {
			if (matchRegex(title, sPattern)) {
				return sPattern;
			}
		}

		return title;
	}

	/**
	 * Determine if the input string matches the input regex pattern.
	 * This method mimics Guitars behavior.
	 * Attempt to match the pattern 'sPattern' with the string 'sInputString'.

	 * @param sInputString    Input string to match with pattern
	 * @param sPattern        Regex pattern to match with string
	 * @return                True if match, false otherwise
	 */
	private static boolean
	matchRegex(String sInputString,
			String sPattern)
	{
		Pattern pattern;
		Matcher matcher;

		pattern = Pattern.compile(sPattern);
		matcher = pattern.matcher(sInputString);
		if (matcher.matches()) {
			return true;
		}

		return false;
	}
	
	/**
	 * Extracts the index from a component string
	 * @param targetPart String that represents the component
	 * @return Index of the component
	 */
	
	private Integer getIndex(String targetPart){
		String[] widgetInfo = targetPart.split("','");
		String index = widgetInfo[ID_PROPERTIES_MAP.get("Index")];
		if (index.equals("-1"))
				throw new AssertionError("Index should only be -1 for components" +
						"that have no parent.");
		
		return Integer.parseInt(index);
	}
};
