package de.hsadmin.core.model;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.regex.Pattern;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

public class GenericModuleImpl implements ModuleInterface {

	private static final Log log = LogFactory.getLog(GenericModuleImpl.class);

	private Transaction tx;
	
	public GenericModuleImpl(Transaction transaction) {
		tx = transaction;
	}
	
	public Transaction getTransaction() {
		return tx;
	}

	@Override
	public AbstractEntity initialize(AbstractEntity newEntity) throws HSAdminException {
		log.trace("initialize(" + newEntity + ")");
		AbstractModuleImpl wrapper = 
			EntitySessionHelper.createEntitySessionWrapper(newEntity.getClass(), tx);
		return wrapper.initialize(newEntity);
	}

	@Override
	public AbstractEntity add(AbstractEntity newEntity) throws HSAdminException {
		validateFields(newEntity);
		if (!newEntity.isNew())
			throw new HSAdminException("cannot add an already persistent entity");
		log.trace("add(" + newEntity + ")");
		AbstractModuleImpl wrapper = 
			EntitySessionHelper.createEntitySessionWrapper(newEntity.getClass(), tx);
		return wrapper.add(newEntity);
	}

	@Override
	public AbstractEntity find(Class<? extends AbstractEntity> entityClass, Object key) throws HSAdminException {
		log.trace("find(" + entityClass + ", " + key + ")");
		AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass, tx);
		return wrapper.find(entityClass, key);
	}

	@Override
	public AbstractEntity findByString(Class<? extends AbstractEntity> entityClass, String key) throws HSAdminException {
		log.trace("find(" + entityClass + ", " + key + ")");
		AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass, tx);
		return wrapper.findByString(entityClass, key);
	}

	@Override
	public List<AbstractEntity> search(Class<? extends AbstractEntity> entityClass, String condition, String orderBy) throws HSAdminException {
		log.trace("search(" + entityClass + ", " + condition + ")");
		AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(entityClass, tx);
		return wrapper.search(entityClass, condition, orderBy);
	}

	@Override
	public AbstractEntity update(AbstractEntity existingEntity) throws HSAdminException {
		validateFields(existingEntity);
		if (existingEntity.isNew())
			return add(existingEntity);
		log.debug("update(" + existingEntity + ")");
		AbstractModuleImpl wrapper = EntitySessionHelper.createEntitySessionWrapper(existingEntity.getClass(), tx);
		return wrapper.update(existingEntity);
	}

	@Override
	public void delete(AbstractEntity existingEntity) throws HSAdminException {
		log.trace("delete(" + existingEntity + ")");
		AbstractModuleImpl wrapper = 
			EntitySessionHelper.createEntitySessionWrapper(existingEntity.getClass(), tx);
		wrapper.delete(existingEntity);
	}

	private void validateFields(AbstractEntity anEntity) throws HSAdminException {
		Class<? extends AbstractEntity> clasz = anEntity.getClass();
		Field[] fields = clasz.getDeclaredFields();
		for (Field f : fields) {
			FieldValidation fieldValidation = f.getAnnotation(FieldValidation.class);
			if (fieldValidation != null) {
				try {
					Method method = clasz.getMethod(getterName(f));
					Object valueObject = method.invoke(anEntity);
					if (valueObject instanceof String) {
						if (!Pattern.matches(fieldValidation.value(), (String) valueObject)) {
							throw new HSAdminException("validation of field " + f.getName() + " failed");
						}
					}
				} catch (SecurityException e) {
					throw new HSAdminException(e);
				} catch (NoSuchMethodException e) {
					throw new HSAdminException(e);
				} catch (IllegalArgumentException e) {
					throw new HSAdminException(e);
				} catch (IllegalAccessException e) {
					throw new HSAdminException(e);
				} catch (InvocationTargetException e) {
					throw new HSAdminException(e);
				}
			}
		}
		// Bei Datenbanken und DB-Usern stehen die Attribute in der Oberklasse
		Class<?> superclass = clasz.getSuperclass();
		if (superclass != AbstractEntity.class) {
			fields = superclass.getDeclaredFields();
			for (Field f : fields) {
				FieldValidation fieldValidation = f.getAnnotation(FieldValidation.class);
				if (fieldValidation != null) {
					try {
						Method method = superclass.getMethod(getterName(f));
						Object valueObject = method.invoke(anEntity);
						if (valueObject instanceof String) {
							if (!Pattern.matches(fieldValidation.value(), (String) valueObject)) {
								throw new HSAdminException("validation of field " + f.getName() + " failed");
							}
						}
					} catch (SecurityException e) {
						throw new HSAdminException(e);
					} catch (NoSuchMethodException e) {
						throw new HSAdminException(e);
					} catch (IllegalArgumentException e) {
						throw new HSAdminException(e);
					} catch (IllegalAccessException e) {
						throw new HSAdminException(e);
					} catch (InvocationTargetException e) {
						throw new HSAdminException(e);
					}
				}
			}
		}
	}

	private String getterName(Field f) {
		String name = f.getName();
		char firstChar = Character.toUpperCase(name.charAt(0));
		return "get" + firstChar + name.substring(1);
	}

}
