package de.brightbyte.yates;

import java.sql.ResultSet;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;

import javax.servlet.http.HttpServletRequest;

import de.brightbyte.abstraction.Abstractor;
import de.brightbyte.abstraction.BeanPropertyAbstractor;
import de.brightbyte.abstraction.MapAbstractor;
import de.brightbyte.abstraction.ResourceBundleAbstractor;
import de.brightbyte.db.ResultSetAbstractor;

public class YatesContext implements YatesScope {

	protected Map<String, Object> data = new HashMap<String, Object>();

	//protected Map<String, YatesRenderer> macroRenderers = new HashMap<String, YatesRenderer>();
	protected Map<Class, YatesMacro> valueRenderers = new HashMap<Class, YatesMacro>();

	protected Map<String, Abstractor> axisAbstractors = new HashMap<String, Abstractor>();
	protected Map<Class, Abstractor> defaultAbstractors = new HashMap<Class, Abstractor>();
	protected MultiMacroRepository macros = new MultiMacroRepository(); 

	public YatesContext() {
		setDefaultAbstractor(Object.class, BeanPropertyAbstractor.lenientInstance);
		setDefaultAbstractor(Map.class, MapAbstractor.instance);
		setDefaultAbstractor(ResourceBundle.class, ResourceBundleAbstractor.instance);
		setDefaultAbstractor(MacroRepository.class, MacroRepository.abstractor);
		//setDefaultAbstractor(YatesParameters.class, YatesParameters.abstractor); //XXX: not really needed
		setDefaultAbstractor(ResultSet.class, ResultSetAbstractor.instance);
		setDefaultAbstractor(HttpServletRequest.class, HttpServletRequestAbstractor.instance); //XXX: nasty dependency on servlet API!

		setAxisAbstractor("property", BeanPropertyAbstractor.lenientInstance);
		setAxisAbstractor("entry", MapAbstractor.instance);
		setAxisAbstractor("resource", ResourceBundleAbstractor.instance);
		setAxisAbstractor("macro", MacroRepository.abstractor);
		setAxisAbstractor("parameter", YatesParameters.abstractor);
		setAxisAbstractor("dbfield", ResultSetAbstractor.instance);
		setAxisAbstractor("httpparam", HttpServletRequestAbstractor.instance);

		setSetting("escaper", YatesHtml.escaper);
		setSetting("locale", Locale.getDefault());
		setSetting("nullRenderer", LiteralMacro.blank);
		
		setSharedValue("macros", macros);
		setSharedValue("htmlEscaper", YatesHtml.escaper);
		setSharedValue("urlEscaper", YatesHtml.urlEscaper);
		setSharedValue("idEscaper", YatesHtml.idEscaper);
		
		setValueRenderer(Object.class, GenericValueRenderer.instance);
		setValueRenderer(ResultSet.class, ResultSetRenderer.instance);
		setMacro("foreach", ForEachMacro.instance);	
		setMacro("repeat", RepeatMacro.instance);	
		setMacro("block", BlockMacro.instance);	
		setMacro("dump", DumpRenderer.instance);	
	}
	
	public void addMacroRepository(MacroRepository repos) {
		macros.addRepository(repos);
	}
	
	public YatesMacro getMacro(String name) throws YatesException {
		Object v = resolve(name);

		if (v==null && name.indexOf('.')<0) 
			v = macros.getMacro(name);
		
		if (v!=null && !(v instanceof YatesMacro)) {
			//XXX: render and return a LiteralMacro
			throw new YatesException(name+" is not a macro: found "+v.getClass());
		}
		
		return (YatesMacro)v;
	}
	
	public void setMacro(String name, YatesMacro macro) {
		setSharedValueField("macros", name, macro);
	}
	
	protected Object getSharedValue(String name) throws YatesException {
		return resolve(name);
	}

	protected Object getSharedValueField(String base, String field) throws YatesException {
		if (!data.containsKey(base)) return null;
		return getSharedValue(base+"."+field);
	}	
	
	public StringMangler getEscaper() throws YatesException {
		return (StringMangler)getSetting("escaper");
	}

	public Locale getLocale() throws YatesException {
		return (Locale)getSetting("locale");
	}

	public YatesMacro getValueRenderer(String name) {
		return valueRenderers.get(name);
	}

	public void setValueRenderer(Class cls, YatesMacro macro) {
		valueRenderers.put(cls, macro);
	}

	public void setAxisAbstractor(String axis, Abstractor abstractor) {
		axisAbstractors.put(axis, abstractor);
	}

	public void setDefaultAbstractor(Class cls, Abstractor abstractor) {
		defaultAbstractors.put(cls, abstractor);
	}

	public void setSharedValue(String name, Object v) {
		data.put(name, v);
	}

	@SuppressWarnings("unchecked")
	protected void setSharedValueField(String base, String field, Object v) {
		Object b = data.get(base);
		if (b==null) {
			b = new HashMap<String, Object>();
			data.put(base, b);
		}
		
		Abstractor abstractor = guessAbstractor(b);
		if (abstractor==null) throw new IllegalArgumentException("can't set property in shared value of type "+b.getClass());
		
		abstractor.setProperty(b, field, v);
	}

	public void setSetting(String name, Object v) {
		setSharedValueField("settings", name, v);
	}

	public Object getSetting(String name) throws YatesException {
		return getSharedValueField("settings", name);
	}
	
	public YatesMacro guessRenderer(Object subject) throws YatesException {
		if (subject==null) return getNullRenderer(); 
		
		Class c = subject.getClass();
		YatesMacro r = null;
		while (c != null && r == null) {
			r = valueRenderers.get(c);
			if (r!=null) break;
			
			Class[] cc = c.getInterfaces();
			for(Class ic: cc) {
				r = valueRenderers.get(ic);
				if (r!=null) break;
			}
			
			c = c.getSuperclass();
		}

		return r;
	}

	protected Abstractor getAbstractor(String axis) {
		return axisAbstractors.get(axis);
	}

	protected <T>Abstractor<T> guessAbstractor(T subject) {
		Class c = subject.getClass();
		Abstractor a = null;
		while (c != null && a == null) {
			a = defaultAbstractors.get(c);
			if (a!=null) break;
			
			Class[] cc = c.getInterfaces();
			for(Class ic: cc) {
				a = defaultAbstractors.get(ic);
				if (a!=null) break;
			}
			
			c = c.getSuperclass();
		}

		return a;
	}

	@SuppressWarnings("unchecked")
	public Object resolve(Object base, String[] path, int ofs) throws YatesException {
		if (base==null) throw new YatesException("can't resolve members of null!");
		
		String n = path[ofs];
		String axis = null;
		
		int idx = n.indexOf("::");
		if (idx>0) {
			axis = n.substring(0, idx);
			n = n.substring(idx+2);
		}
		
		Abstractor abstractor;
		if (axis!=null) {
			abstractor = getAbstractor(axis.toLowerCase());
			if (abstractor==null) throw new IllegalArgumentException("unsupported axis: "+axis);
		}
		else {
			abstractor = guessAbstractor(base);
		}
		
		Object v = abstractor.getProperty(base, n);
		
		ofs++;
		if (ofs<path.length) {
			if (v==null) throw new YatesException("can't resolve members of null: "+n+"!");
			v = resolve(v, path, ofs);
		}
		
		return v;
	}


	public <T>boolean hasProperty(T v, String n) {
		if (v==null) return false;
		Abstractor<T> abstractor = guessAbstractor(v);
		return abstractor.hasProperty(v, n);
	}
	
	/*
	@SuppressWarnings("unchecked")
	public void setSharedValues(Map<String, Object> m) {
		for(Map.Entry<String, Object> e: m.entrySet()) {
			String base = e.getKey(); //FIXME: handle complex keys!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!1
			Object b = e.getValue();
			if (!(b instanceof Map)) {
				Object old = data.get(base);
				if (old!=null && old instanceof Map) {
					throw new IllegalArgumentException("supplied shared value "+base+" is not a map: found "+b.getClass());
				}
				data.put(base, b);
			}
			else {
				setSharedValueFields(base, (Map<String, Object>)b);
			}
		}
	}

	@SuppressWarnings("unchecked")
	public void setSharedValueFields(String base, Map<String, Object> m) {
		Object b = data.get(base); //FIXME: handle complex keys!
		if (b==null) {
			b = new HashMap<String, Object>(m);
			setSharedValue(base, b);
		}
		else {
			//TODO: use Abstractor!
			if (!(b instanceof Map)) throw new IllegalArgumentException("existing shared value "+base+" is not a map: found "+b.getClass());
			else ((Map)b).putAll(m);
		}
	}
	*/
	
	public Object resolve(String name) throws YatesException { //NOTE: code very similar to YatesParameters.resolve
		String[] path = null;
		
		if (data==null) return null;
		if (name.equals(".")) return null;
		
		int idx = name.indexOf('.');
		if (idx>=0) {
			path = name.split("\\.");
			name = path[0];
		}

		if (name.equals("")) name = ".";
		
		Object v;
		
		if (name.equals("macros")) v = macros.getMacro(name); //NOTE: don't use this.getMacro, that would recurse!
		else v = data.get(name);
		
		if (path!=null) {
			if (v==null) throw new YatesException("can't resolve members of null: "+name+"!");
			v = resolve(v, path, 1); //HACK: fugly!
		}
		
		return v;
	}

	public Object getSubject() {
		return null;
	}

	public YatesMacro getNullRenderer() throws YatesException {
		return (YatesMacro)getSetting("nullRenderer");
	}
	
}
