/*
	Copyright (c) 2009-2011 Olivier Chafik, All Rights Reserved
	
	This file is part of JNAerator (http://jnaerator.googlecode.com/).
	
	JNAerator is free software: you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation, either version 3 of the License, or
	(at your option) any later version.
	
	JNAerator is distributed in the hope that it will be useful,
	but WITHOUT ANY WARRANTY; without even the implied warranty of
	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
	GNU General Public License for more details.
	
	You should have received a copy of the GNU General Public License
	along with JNAerator.  If not, see <http://www.gnu.org/licenses/>.
*/
package com.ochafik.lang.reflect;

//import static org.junit.Assert.assertNull;
//import static org.junit.Assert.assertTrue;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;

import com.ochafik.util.string.RegexUtils;
import com.ochafik.util.string.StringUtils;

public class GettersAndSettersHelper {
	public static class GetterAndSetterInfo {
		public Method getter;
		public Method setter;
		//public Field field;
		public String fieldName;
		public boolean isFull() {
			return getter != null && setter != null;
		}
		public Class<?> elementType;
		public GetterAndSetterInfo(String fieldName, Class<?> elementType, Method getter, Method setter, Field field) {
			this.elementType = elementType;
			this.fieldName = fieldName;
			this.getter = getter;
			this.setter = setter;
			//this.field = field;
		}
		public GetterAndSetterInfo() {}
		public boolean isConsistent() {
			if (getter == null || setter == null)
				return true;
			
			Class<?>[] pts = setter.getParameterTypes();
			if (pts.length != 1)
				return false;
			Class<?> pt = pts[0];
			if (!pt.isAssignableFrom(getter.getReturnType())) {
				//System.out.println("Setter with " + pt.getName() + " not consistent with getter returning " + getter.getReturnType().getName());
				return false;
			}
			return true;
		}
	}
	
	public final Map<String, GetterAndSetterInfo> gettersAndSetters = new LinkedHashMap<String, GetterAndSetterInfo>();
	
	static Pattern getterSetterPattern = Pattern.compile("(is|get|set)([A-Z]\\w+)");
	
	public GettersAndSettersHelper(Class<?> type, FieldGetter fieldGetter) {
		this.fieldGetter = fieldGetter;
		for (Method method : type.getMethods()) {
			int nParams = method.getParameterTypes().length;
			if (nParams > 1)
				continue;
			
			String name = method.getName();
			String match[] = RegexUtils.match(name, getterSetterPattern);
			if (match != null) {
				Class<?> returnType = method.getReturnType();
				String fieldName = StringUtils.uncapitalize(match[2]);
				boolean isGetter = !match[1].equals("set");
				if (isGetter) {
					if (nParams == 0) {
						GetterAndSetterInfo getterAndSetter = getOrCreatePair(fieldName);
						if (getterAndSetter.getter != null) {
							if (getterAndSetter.getter.getReturnType().isAssignableFrom(returnType)) {
								// refinement
								getterAndSetter.getter = method;
							}
						} else {
							getterAndSetter.getter = method;
						}

						if (!getterAndSetter.isConsistent())
							getterAndSetter.setter = null;
						
					}
					//assertNull("Already found getter " + getterAndSetter.getter, getterAndSetter.getter);
					//getterAndSetter.setFirst(method);
				} else if (nParams == 1) {
					GetterAndSetterInfo getterAndSetter = getOrCreatePair(fieldName);
					//assertNull("Already found setter " + getterAndSetter.setter, getterAndSetter.setter);
					//assert getterAndSetter.setter == null;
                    if (getterAndSetter.setter == null) {
                        getterAndSetter.setter = method;
                        if (!getterAndSetter.isConsistent())
                            getterAndSetter.setter = null;
                    }
				}
			}
		}
		/*
		for (Map.Entry<String, GetterAndSetterInfo> e : gettersAndSetters.entrySet()) {
			if (e.getValue().setter == null)
				continue;
			try {
				e.getValue().field = fieldGetter == null ? type.getField(e.getKey()) : fieldGetter.getField(type, e.getKey());
			} catch (Exception ex) {
				assertTrue("Failed to find field '" + e.getKey() + "' in " + type.getName(), false);
			}
		}*/
		/*for (Field field : type.getFields()) {
			GetterAndSetterInfo info = gettersAndSetters.get(field.getName());
			if (info == null)
				continue;
			info.field = field;
		}*/
	}
	final FieldGetter fieldGetter;
	public interface FieldGetter {
		public Field getField(Class<?> c, String name) throws SecurityException, NoSuchFieldException;
	}
	public Set<String> getFieldNames() {
		return gettersAndSetters.keySet();
	}
	public Method getGetter(String fieldName) {
		GetterAndSetterInfo pair = gettersAndSetters.get(fieldName);
		return pair == null ? null : pair.getter;
	}
	public Method getSetter(String fieldName) {
		GetterAndSetterInfo pair = gettersAndSetters.get(fieldName);
		return pair == null ? null : pair.setter;
	}
	public Type getFieldType(String fieldName) {
		GetterAndSetterInfo pair = gettersAndSetters.get(fieldName);
		if (pair == null)
			return null;
		if (pair.getter != null)
			return pair.getter.getGenericReturnType();
		if (pair.setter != null)
			return pair.setter.getGenericParameterTypes()[0];
		return null;
	}
	public void assertConsistentPair(GetterAndSetterInfo p) {
		if (p.getter != null && p.setter != null) {
			Class<?> getType = p.getter.getReturnType(),
				setType = p.setter.getParameterTypes()[0];
			
			//assertTrue("Setter argument cannot be given getter result", setType.isAssignableFrom(getType));
			assert setType.isAssignableFrom(getType);
		}
	}
	protected GetterAndSetterInfo getOrCreatePair(String fieldName) { 
		GetterAndSetterInfo getterAndSetter = gettersAndSetters.get(fieldName);
		if (getterAndSetter == null)
			gettersAndSetters.put(fieldName, getterAndSetter = new GetterAndSetterInfo());
		
		return getterAndSetter;
	}
}
