/*******************************************************************************
 * Copyright (c) 2009 Laurent Petit.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors: 
 *    Laurent PETIT - initial API and implementation
 *******************************************************************************/

package ccw;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

import clojure.lang.Keyword;
import clojure.lang.RT;
import clojure.lang.Var;

import ccw.debug.ClojureClient;
import ccw.editors.clojure.IScanContext;
import ccw.preferences.PreferenceConstants;

public class StaticScanContext implements IScanContext {
	private Map<String,Keyword> clojureSymbolTypesCache = new HashMap<String, Keyword>();
	
	private static final Var coreSymbolType = RT.var("ccw.debug.clientrepl", "core-symbol-type");
	
	private boolean isJavaIdentifier(String s) {
		assert s != null && s.length() > 0;
		
		if (!Character.isJavaIdentifierStart(s.charAt(0))) {
			return false;
		}
		for (int i = 1; i < s.length(); i++) {
			if (!Character.isJavaIdentifierPart(s.charAt(i))) {
				return false;
			}
		}
		return true;
	}
	
	public Keyword getSymbolType(String symbol, boolean isCallableSymbol) {
		assert symbol != null && symbol.length() > 0;
		
		if (symbol.startsWith("*") && symbol.endsWith("*")) {
			// Even if it is not true that it is a global var,
			// force a global var look and feel to warn the user that
			// something is wrong (convention not respected)
			return isCallableSymbol ? PreferenceConstants.callableGLOBAL_VAR_Token : PreferenceConstants.GLOBAL_VAR_Token;
		}
		
		if (symbol.startsWith(".")  &&  symbol.length() > 1) {
			if (isJavaIdentifier(symbol.substring(1))) {
				return isCallableSymbol ? PreferenceConstants.callableJAVA_INSTANCE_METHOD_Token : PreferenceConstants.JAVA_INSTANCE_METHOD_Token;
			}
		}
		
		// This code must appear after the test for JAVA_INSTANCE_METHOD, or a bug in it would mask
		// any attempt to guess a java instance method
		int from;
		int foundIndex = -1;
		int letterAfterPointIndex = 0;
		do {
			letterAfterPointIndex = foundIndex + 1;
			if ( (letterAfterPointIndex < symbol.length()) && Character.isUpperCase(symbol.charAt(letterAfterPointIndex)) ) {
				if (symbol.substring(letterAfterPointIndex + 1).contains("/")) {
					return isCallableSymbol ? PreferenceConstants.callableJAVA_STATIC_METHOD_Token : PreferenceConstants.JAVA_STATIC_METHOD_Token;
				} else {
					return isCallableSymbol ? PreferenceConstants.callableJAVA_CLASS_Token : PreferenceConstants.JAVA_CLASS_Token;
				}
			} else {
				from = letterAfterPointIndex;
			}
		} while ((from < symbol.length()) && (foundIndex = symbol.indexOf('.', from)) > 0);
		/*
		 TODO activate this possibility via a preference to get a visible bootstrap gain
		if (isCallableSymbol) // 2.7 sec.
			return PreferenceConstants.RAW_SYMBOL_Token;
		else 
			return PreferenceConstants.callable_RAW_SYMBOL_Token;
		*/
		if (clojureSymbolTypesCache.containsKey(symbol)) {
			return getCallableOrNonCallable(clojureSymbolTypesCache.get(symbol), isCallableSymbol);
		} else {
			Keyword symbolType =  (Keyword) coreSymbolType.invoke(symbol);	
	
			if (symbolType == null) {
				clojureSymbolTypesCache.put(symbol, PreferenceConstants.RAW_SYMBOL_Token);
				return getCallableOrNonCallable(PreferenceConstants.RAW_SYMBOL_Token, isCallableSymbol);
			} else {
				try {
					clojureSymbolTypesCache.put(symbol, symbolType);
					return getCallableOrNonCallable(symbolType, isCallableSymbol);
				} catch (IllegalArgumentException e) {
					CCWPlugin.logError("The clojure code returned an invalid symbolType value:'" + symbolType + "' for enumeration SymbolType", e);
					return null;
				}
			}
		}
		
	}
	
	private static final Map<Keyword, Keyword> symbolToCallable = new HashMap<Keyword, Keyword>() {
		{
			put(PreferenceConstants.RAW_SYMBOL_Token, PreferenceConstants.callable_RAW_SYMBOL_Token);
			put(PreferenceConstants.FUNCTION_Token, PreferenceConstants.callableFUNCTION_Token);
			put(PreferenceConstants.MACRO_Token, PreferenceConstants.callableMACRO_Token);
			put(PreferenceConstants.SPECIAL_FORM_Token, PreferenceConstants.callableSPECIAL_FORM_Token);
		}
	};
	
	private Keyword getCallableOrNonCallable(Keyword symbolKeyword, boolean isCallableSymbol) {
		if (isCallableSymbol) {
			Keyword callableFlavor = symbolToCallable.get(symbolKeyword);
			if (callableFlavor != null) {
				return callableFlavor;
			} else {
				return symbolKeyword;
			}
		} else {
			return symbolKeyword;
		}
	}
			
}

@@ -575,91 +575,97 @@
 til.
-HashSet;%0Aimport java.util.Map;%0A%0Aimport clojure.lang.Keyword;%0Aimport clojure.lang.RT
+Map;%0A%0Aimport ccw.editors.clojure.IScanContext;%0Aimport ccw.preferences.PreferenceConstants
 ;%0Aim
@@ -686,13 +686,16 @@
 ang.
-Var;%0A
+Keyword;
 %0Aimp
@@ -703,115 +703,47 @@
 rt c
-cw.debug.ClojureClient;%0Aimport ccw.editors.clojure.IScanContext;%0Aimport ccw.preferences.PreferenceConstants
+lojure.lang.RT;%0Aimport clojure.lang.Var
 ;%0A%0Ap
@@ -1296,352 +1296,311 @@
 %09%0A%09p
-ublic Keyword getSymbolType(String symbol, boolean isCallableSymbol) %7B%0A%09%09assert symbol != null && symbol.length() %3E 0;%0A%09%09%0A%09%09if (symbol.startsWith(%22*%22) && symbol.endsWith(%22*%22)) %7B%0A%09%09%09// Even if it is not true that it is a global var,%0A%09%09%09// force a global var look and feel to warn the user that%0A%09%09%09// something is wrong (convention not respected)
+rivate boolean isEarmuffedVar(String symbol) %7B%0A%09%09return (symbol.startsWith(%22*%22) %7C%7C symbol.contains(%22/*%22))%0A%09%09%09   &&%0A%09%09%09   symbol.endsWith(%22*%22);%0A%09%7D%0A%09%0A%09public Keyword getSymbolType(String symbol, boolean isCallableSymbol) %7B%0A%09%09assert symbol != null && symbol.length() %3E 0;%0A%09%09%0A%09%09if (isEarmuffedVar(symbol)) %7B
 %0A%09%09%09

Fix Issue 192: *Earmuffed* vars from referred namespaces not highlighted