package tools;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.HashMap ;
import java.util.Iterator;
import java.util.Random;
import java.util.StringTokenizer;
import java.util.TreeSet;
import java.util.Vector;

import authoritymanager.client.DataSerial;
import authoritymanager.client.Path;
import authoritymanager.client.Query;
import authoritymanager.client.SearchQuery;

public class ElementData implements Comparable, Serializable {
	public static Random generator = new Random(System.currentTimeMillis()) ;
	public static Vector<String> sortingField = new Vector<String>() ;
	
	public String id ;
	public Authority authority ;
	public HashMap<String, Collection> data ;
	public String fileName ;
	
	public static long curTime ;
	public static long cost1 = 0, cost2 = 0, cost3 = 0, cost4 = 0;
	
	public ElementData(Authority authority) {
		this.id = genID() ;
		this.authority = authority ;
		this.data = new HashMap<String, Collection>() ;		
	}
	public Collection getField(String fieldName) {
		return data.get(fieldName) ;
	}
	
	public static ElementData fromDataSerial(Authority authority, DataSerial dataSerial) {
		ElementData result = new ElementData(authority) ;
		Collection<String> fields = dataSerial.getFields() ;
		for (String fieldName: fields) {
			Collection<?> collection = dataSerial.get(fieldName) ;
			for (Object value: collection) {
				if (value instanceof DataSerial) {
					result.addData(fieldName, fromDataSerial(authority, (DataSerial) value)) ;
				}
				if (value instanceof String) {
					result.addData(fieldName, (String) value) ; 
				}
			}
		}
		return result ;
	}
	
	public static ElementData fromDataSerial(DataSerial dataSerial) {
		return fromDataSerial(Authority.byType(dataSerial.getType()), dataSerial) ;
	}
	
	public void addAuthority(Authority authority) {
		authority.addElement(this) ;
		this.authority = authority ;
	}
	public String toXML(String delim) {
		String xml = "" ;
		
		Collection<String> fieldNames = data.keySet() ;
		for (String fieldName: fieldNames) {
			Field field = authority.getConfiguration().getField(fieldName) ;
			
			if (field != null && field.path.length() > 0) {
				if (field.path.charAt(0) == '@') {
					String value = (String) ((Collection) this.data.get(fieldName)).iterator().next() ;
					xml += " " + field.path.substring(1) + "=\"" + value + "\"" ; 
				}
			}
		}
		xml += ">\n" ;
		//boolean start = true ;
		for (String fieldName: fieldNames) {
			Field field = authority.getConfiguration().getField(fieldName) ;
			if (field != null) {
				String fieldPath = field.path ;
				if (fieldPath.length() > 0 && fieldPath.charAt(0) == '@') {
					continue ;
				}
				StringTokenizer st = new StringTokenizer(fieldPath, "/") ;
				if (!st.hasMoreElements()) {
					Object value = ((Collection) data.get(fieldName)).iterator().next() ;
					xml += delim + ((String) value) + "\n" ;
				}
				while (st.hasMoreTokens()) {
					String partPath = st.nextToken() ;
					String mainPath = partPath.contains("[") ? partPath.substring(0, partPath.indexOf("[")) : partPath  ;
					String condPath = partPath.contains("[") ? partPath.substring(partPath.indexOf("[") + 1, partPath.indexOf("]")) : "" ;
					String begin = mainPath.equals("") || mainPath.equals("/" ) ? "" : delim + "<" + mainPath ;
					if (condPath.length() > 0) {
						StringTokenizer tcon = new StringTokenizer(condPath, ",") ;
						while (tcon.hasMoreTokens()) {
							StringTokenizer tatt = new StringTokenizer(tcon.nextToken(), "=") ;
							String attr = tatt.nextToken() ;
							String val = tatt.nextToken() ;
							begin += " " + attr.replace("@", "") + "=" + "\"" + val + "\"" ;
						}						
					}
					
					String end = mainPath.equals("") || mainPath.equals("/") ? "" : "</" + mainPath + ">\n" ;
					Object valueCol = data.get(fieldName) ;					
					if (valueCol instanceof Collection<?>) {
						for (Object value: (Collection<?>) valueCol) {
							//if (start) {
								//xml += "\n" ;
								//start = false;
							//}
							
							if (value instanceof String) {
								xml += begin + ">" + (String) value + end ;
							}
							if (value instanceof ElementData) {
								xml += begin + ((ElementData) value).toXML(delim + "\t") + delim + end ;
							}
						}
					}					
				}
			}
		}
		return xml ;
	}
	
	public String toXML() {
		String elementTag = authority.getConfiguration().getElementTag() ;
		String xml = "<" + elementTag + toXML("\t") + "</" + elementTag + ">\n" ;
		return xml ;		
	}
	
	
	Object getValue(String path) {
		int index = path.indexOf("/") ;		
		if (index == -1) {
			return data.get(path) ;
		}		
		else {
			Collection result = new Vector() ;
			String first = path.substring(0, index) ;
			String last = path.substring(index + 1) ;
			
			Collection tmp = (Collection) data.get(first) ;
			if (tmp != null) {
				for (Iterator it = tmp.iterator() ; it.hasNext() ; ) {
					Object value = ((ElementData) it.next()).getValue(last) ;
					if (value instanceof String) {
						result.add((String) value) ;
					}
					if (value instanceof Collection) {
						result.addAll((Collection) value) ;
					}
				}
			}		
			return result ;
		}
		
	}
	private String genID() {
		String id = "" ;
		for (int i = 0 ; i < 32 ; i ++) {
			id += (char) (generator.nextInt('Z' - 'A' + 1) + 'A') ;
		}		
		return id;
	}

	public String toString() {
		String res = "[FILE = " + fileName + "]\n" ;
		//getConfiguration() getConfiguration() = authority.getConfiguration() ;
		//Collection<Field> fields = getConfiguration().getField().values() ;
		Collection<String> keySet = data.keySet() ;
		for (String name: keySet) {
			Object value = data.get(name) ;
			res += name + ":" + toString(value) + "\n" ;
		}
		
		return res ;
	}
	private String toString(Object value) {
		String res = "" ;
		if (value instanceof String) {
			res = (String) value ;
		}
		if (value instanceof Collection<?>) {
			for (Object curValue: (Collection<?>) value) {
				res += curValue + "#" ;
			}
		}
		return res ;
	}
	
	private Object cleanData(Object value) {
		if (value instanceof String) {
			StringTokenizer st = new StringTokenizer((String) value, " \t\n,;`\"][{}+=") ;
			String res = "" ;
			boolean start = true ;
			while (st.hasMoreTokens()) {
				if (!start) res += " " ;
				else start = false ;
				res += st.nextToken() ;
			}
			return res ;			
		}
			
		return value ;
	}
	public boolean addData(String name, Object value) {	
		if (data.containsKey(name)) {
			((Collection) data.get(name)).add(cleanData(value)) ;
			/*
			if (value instanceof String) {
				((Collection<String>) data.get(name)).add(cleanData(value)) ;				
			}
			if (value instanceof ElementData) {
				((Collection<ElementData>) data.get(name)).add((ElementData) value) ;				
			}
			*/			
		}
		else {			
			if (value instanceof String) {
				Collection<String> collection = new Vector<String>(1) ;
				collection.add((String) cleanData(value)) ;
				data.put(name, collection) ;
			}
			if (value instanceof ElementData) {
				Collection<ElementData> collection = new Vector<ElementData>(1) ;
				collection.add((ElementData) cleanData(value)) ;
				data.put(name, collection) ;
			}			
		}
		return true ;
		/*
		if (authority.getConfiguration().getField().containsKey(name)) {
			if (data.containsKey(name)) {
				// ??? what if it already contains this name
				data.put(name, value) ;
				return false ;
			}
			else {
				//System.out.println("Putting (" + name + ", " + value + ")") ;
				data.put(name, value) ;
				return true ;
			}	
		}
		else {
			return false ;
		}		
		*/
	}

	public double similarity(ElementData record) {
		return similarity(record, authority.getConfiguration().getOptimizationThreshold()) ;
	}
	
	public Collection<Field> getFields() {
		Collection<String> keys = data.keySet() ;
		Collection<Field> result = new Vector<Field>(1) ;
		for (String key: keys) {
			result.add(authority.getConfiguration().getField(key)) ;
		}
		return result ;
	}
	public double similarity(ElementData record, Double optimization_threshold) {		
		Configuration config = authority.getConfiguration() ;
		curTime = System.currentTimeMillis() ;
		Collection<Field> fieldsCol = getFields() ;
		Field [] fields = new Field [fieldsCol.size()] ;
		fieldsCol.toArray(fields) ;
		cost1 += System.currentTimeMillis() - curTime ;
		
		curTime = System.currentTimeMillis() ;
		Arrays.sort(fields) ;
		cost2 += System.currentTimeMillis() - curTime ;
		
		curTime = System.currentTimeMillis() ;
		double totSim = 0.0, curSim, curImp = 0.0 ;		
		double totImp = 0.0 ;
		for (int iFields =0 ; iFields < fields.length ; iFields ++) {
			totImp += fields [iFields].importance ;
		}
		for (int iFields = 0 ; iFields < fields.length ; iFields ++) {
			Field curField = fields [iFields] ;
			if (curField.getImportance() < 1e-6) {
				continue ;
			}
			Object aValue = this.data.get(curField.getName()) ;
			Object bValue = record.data.get(curField.getName()) ;
			if (aValue != null && bValue != null) {
				if (aValue instanceof Collection<?>) {
					if (((Collection<?>) aValue).iterator().next() instanceof String) {
						Iterator<String> itA = ((Collection<String>) aValue).iterator() ;
						
						double sumSim = 0 ;
						int cnt = 0 ;
						while (itA.hasNext()) {
							String aString = (String) itA.next() ;
							Iterator<String> itB = ((Collection<String>) bValue).iterator() ;
							double bestSim = 0.0 ;
							while (itB.hasNext()) {
								String bString = (String) itB.next() ;
								if (aString.length() > 0 && bString.length() > 0) {
									curSim = CompareUtilities.distance(curField.algorithm, aString, bString) ;
									if (curSim > bestSim) {
										bestSim = curSim ;
									}									
								}
							}
							if (bestSim > 1e-6) {
								sumSim += bestSim ;
								cnt ++ ;
							}
						}
						curSim = cnt == 0 ? 0.0 : sumSim / cnt ;
						/*if (curSim < 1e-6) {
							continue ;
						}
						*/
						curImp += curField.importance ;
						totSim += curField.importance * curSim ;
							 
						if (curImp > 0 && totSim / curImp < optimization_threshold) {					
							ElementData.cost4 ++ ;
							break ;
						}
					}
					if (((Collection<?>) aValue).iterator().next() instanceof ElementData) {
						Iterator<ElementData> itA = ((Collection<ElementData>) aValue).iterator() ;
						double sumSim = 0 ;
						int cnt = 0 ;
						while (itA.hasNext()) {
							ElementData aElementData = (ElementData) itA.next() ;
							Iterator<ElementData> itB = ((Collection<ElementData>) bValue).iterator() ;
							double bestSim = 0.0 ;
							while (itB.hasNext()) {
								ElementData bElementData = (ElementData) itB.next() ;
								curSim = aElementData.similarity(bElementData, curField.getOptimizationThreshold());
								if (curSim > bestSim) {
									bestSim = curSim ;																		
								}
							}
							if (bestSim > 1e-6) {
								sumSim += bestSim ;
								cnt ++ ;
							}
						}
						curSim = cnt == 0 ? 0.0 : sumSim / cnt ;
						/*if (curSim < 1e-6) {
							continue ;
						}*/
						
						curImp += curField.importance ;
						totSim += curField.importance * curSim ;
							 
						if (curImp > 0 && totSim / curImp < optimization_threshold) {					
							ElementData.cost4 ++ ;
							break ;
						}
					}					
				}
				
			}
		}
		cost3 += System.currentTimeMillis() - curTime ;
		//authority.writer.println("distance = " + dis) ;
		if (curImp > 1e-5) {
			totSim = totSim / totImp ;
			return totSim ;			
		}	
		else
			return 0.0 ;
	}

	@Override
	public int compareTo(Object a) {
		if (a instanceof ElementData) {
			ElementData elementA = (ElementData) a ;
			String value1 = this.getPath(this.authority.getSortingPath()) ;
			if (value1 != null) {
				String value2 = elementA.getPath(this.authority.getSortingPath()) ;
				if (value2 != null) {
					return value1.compareTo(value2) ;
				}
				else {
					return -1 ;
				}
			}
			else {
				return 1 ;
			}
		}
		return 0 ;
	}

	private String getPath(int iPath, Path sortingPath) {
		if (iPath >= sortingPath.getPath().size()) {
			return null ;
		}
		Collection values = this.getField(sortingPath.getPath().get(iPath)) ;
		if (values != null) {
			Object value = values.iterator().next() ;
			if (value instanceof String) {
				return (String) value ;
			}
			if (value instanceof ElementData) {
				for (ElementData next: (Collection<ElementData>) values) {
					String result = next.getPath(iPath + 1, sortingPath) ;
					if (result != null) {
						return result ;
					}
				}
			}
		}
		return null ;
	}
	
	private String getPath(Path sortingPath) {
		return this.getPath(0, sortingPath) ;
	}
	
	public DataSerial toDataSerial() {
		DataSerial result = new DataSerial() ;
		Collection<String> keys = data.keySet() ;
		for (String key: keys) {
			Collection colValue = data.get(key) ;
			if (colValue != null) {
				for (Object value: colValue) {
					if (value instanceof ElementData) {
						result.put(key, ((ElementData) value).toDataSerial()) ;
					}
					if (value instanceof String) {
						result.put(key, (String) value) ;
					}
				}
			}
			
		}
		return result;
	}

	public DataSerial toDataSerial(String [] fields) {
		if (fields == null) {
			return this.toDataSerial() ;
		}
		DataSerial result = new DataSerial() ;
		for (String key: fields) {
			Collection colValue = data.get(key) ;
			if (colValue != null) {
				for (Object value: colValue) {
					if (value instanceof ElementData) {
						result.put(key, ((ElementData) value).toDataSerial()) ;
					}
					if (value instanceof String) {
						result.put(key, (String) value) ;
					}
				}
			}
			
		}
		return result;
	}
	public boolean satisfy(SearchQuery searchQuery) {
		Collection<Query> queries = searchQuery.getQueries() ;
		for (Query query: queries) {
			if (!satisfy(query)) {
				return false ;
			}
		}
		return true;
	}

	private boolean satisfy(Query query) {
		Vector<Path> paths = query.getPaths() ;
		for (Path path: paths) {
			if (satisfy(path, query.getFieldRelation(), query.getFieldValue())) {
				return true ;
			}
		}
		return false;
	}

	private boolean satisfy(Path path, String fieldRelation, String fieldValue) {
		if (fieldValue.length() == 0 || fieldValue.equals(Query.ANY.toUpperCase())) {
			return true ;
		}
		return satisfy(0, path.getPath(), fieldRelation, fieldValue) ;
	}

	private boolean satisfy(int iPath, Vector<String> path, String relation, String value) {
		if (iPath > path.size()) {
			return false ;
		}
		Collection result = data.get(path.get(iPath)) ;
		if (result != null) {
			for (Object current: result) {
				if (current instanceof ElementData) {
					return ((ElementData) current).satisfy(iPath + 1, path, relation, value) ;
				}
				if (current instanceof String) {
					return satisfy(((String) current).toUpperCase(), relation, value) ;
				}
			}
		}
		return false;
	}

	private boolean satisfy(String current, String relation, String value) {
		if (current == null) {
			return false ;
		}
		if (value.length() == 0 || value.equals(Query.ANY)) {
			return true ;
		}
		if (relation.equals(Query.EQUALS)) {
			return current.equals(value) ;
		}
		if (relation.equals(Query.BEGINS_WITH)) {
			return current.startsWith(value) ;
		}
		if (relation.equals(Query.CONTAINS)) {
			return current.contains(value) ;
		}
		
		return false;
	}	
}
