package aQute.lib.json;

import java.io.*;
import java.lang.reflect.*;
import java.util.*;

public class MapHandler extends Handler {
	final Class< ? >	rawClass;
	final Type			keyType;
	final Type			valueType;

	MapHandler(Class< ? > rawClass, Type keyType, Type valueType) {

		if ( rawClass != Map.class) {
			ParameterizedType type = findAncestor(rawClass,Map.class);
			this.keyType = keyType == Object.class ? resolve(type.getActualTypeArguments()[0]) : keyType;
			this.valueType = valueType == Object.class ? resolve(type.getActualTypeArguments()[1]) : valueType;
		} else {
			this.keyType = keyType;
			this.valueType = valueType;			
		}
		
		if (rawClass.isInterface()) {
			if (rawClass.isAssignableFrom(HashMap.class))
				rawClass = HashMap.class;
			else if (rawClass.isAssignableFrom(TreeMap.class))
				rawClass = TreeMap.class;
			else if (rawClass.isAssignableFrom(Hashtable.class))
				rawClass = Hashtable.class;
			else if (rawClass.isAssignableFrom(LinkedHashMap.class))
				rawClass = LinkedHashMap.class;
			else if (rawClass.isAssignableFrom(Dictionary.class))
				rawClass = Hashtable.class;
			else
				throw new IllegalArgumentException("Unknown map interface: " + rawClass);
		}
		this.rawClass = rawClass;
	}

	private Type resolve(Type type) {
		if( type instanceof TypeVariable<?>) {
			TypeVariable<?> tv = (TypeVariable<?>) type;
			Type[] bounds = tv.getBounds();
			return resolve( bounds[ bounds.length-1]);
		}
		return type;
	}

	private ParameterizedType findAncestor(Class< ? > start, Class<?> target) {
		if ( start == Object.class)
			return null;
		
		for ( Type t : start.getGenericInterfaces()) {
			if ( t instanceof ParameterizedType ) {
				if ( ((ParameterizedType) t).getRawType() == target)
					return (ParameterizedType) t;
			}
		}
		for ( Class<?> impls : start.getInterfaces()) {
			ParameterizedType ancestor = findAncestor(impls, target);
			if (ancestor != null)
				return ancestor;
		}
		
		return findAncestor(start.getSuperclass(), target);
	}

	@Override
	void encode(Encoder app, Object object, Map<Object,Type> visited) throws IOException, Exception {
		Map< ? , ? > map = (Map< ? , ? >) object;

		app.append("{");
		String del = "";
		for (Map.Entry< ? , ? > e : map.entrySet()) try {
			app.append(del);
			String key;
			if (e.getKey() != null && (keyType == String.class || keyType == Object.class))
				key = e.getKey().toString();
			else {
				key = app.codec.enc().put(e.getKey()).toString();
			}
			StringHandler.string(app, key);
			app.append(":");
			app.encode(e.getValue(), valueType, visited);
			del = ",";
		} catch( Exception ee) {
			throw new IllegalArgumentException("[\"" + e.getKey() +"\"]", ee);
		}
		app.append("}");
	}

	@Override
	Object decodeObject(Decoder r) throws Exception {
		assert r.current() == '{';

		@SuppressWarnings("unchecked")
		Map<Object,Object> map = (Map<Object,Object>) rawClass.newInstance();

		int c = r.next();
		while (JSONCodec.START_CHARACTERS.indexOf(c) >= 0) {
			Object key = r.codec.parseString(r);
			if (!(keyType == null || keyType == Object.class)) {
				Handler h = r.codec.getHandler(keyType, null);
				key = h.decode(r,(String) key);
			}

			c = r.skipWs();
			if (c != ':')
				throw new IllegalArgumentException("Expected ':' but got " + (char) c);

			c = r.next();
			Object value = r.codec.decode(valueType, r);
			if (value != null || !r.codec.ignorenull)
				map.put(key, value);

			c = r.skipWs();

			if (c == '}')
				break;

			if (c == ',') {
				c = r.next();
				continue;
			}

			throw new IllegalArgumentException("Invalid character in parsing list, expected } or , but found "
					+ (char) c);
		}
		assert r.current() == '}';
		r.read(); // skip closing
		return map;
	}

}
