package db.mapper;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import db.Table;
import db.annotation.DbField;
import db.annotation.DbLink;
import db.annotation.DbTable;

public class DbMapper {
	
	public static <T> Mapper<T> objectMapper(DbTable dbTable) {
		return new Mapper<T>() {	
			@Override
			public T map(ResultSet rs) {
				try {
					T obj = (T) dbTable.entity().getConstructor().newInstance();
					rs.next();
					for(Field field : dbTable.entity().getFields()) {
						if(!field.isAnnotationPresent(DbField.class)) {
							continue;
						}
						// Recup valeur dans le resultat
						Object value = rs.getObject(field.getAnnotation(DbField.class).value());
						// Si lien avec une autre entité
						if(value != null && field.isAnnotationPresent(DbLink.class)) {
							checkIfPersistable(field);
							Long otherId = (Long) value;
							if(otherId == 0) {
								value = null;
							} else {
								String[] split = field.getType().getName().split("\\.");
								String className = split[split.length - 1];
								// Chargement de la class Table qui gère l'entité
								Class<?> clazz = Class.forName(field.getAnnotation(DbLink.class).value() + "." + className + "Table");
								Table table = (Table) clazz.getMethod("getInstance").invoke(null);
								value = table.getById(otherId);
							}
						}
						field.set(obj, value);
					}
					return obj;
				} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | SQLException | ClassNotFoundException e) {
					throw new IllegalStateException("Unable to map value to the entity");
				}
				
			}
		};
	}
	
	public static <T> Mapper<List<T>> listMapper(DbTable dbTable) {
		return new Mapper<List<T>>() {	
			@Override
			public List<T> map(ResultSet rs) {
				try {
					List<T> list = new ArrayList<>();
					while(rs.next()) {
						T obj = (T) dbTable.entity().getConstructor().newInstance();
						for(Field field : dbTable.entity().getFields()) {
							if(!field.isAnnotationPresent(DbField.class)) {
								continue;
							}
							// Recup valeur dans le resultat
							Object val = rs.getObject(field.getAnnotation(DbField.class).value());
							// Si lien avec une autre entité
							if(val != null && field.isAnnotationPresent(DbLink.class)) {
								checkIfPersistable(field);
								Long otherId = (Long) val;
								if(otherId == 0) {
									val = null;
								} else {
									String[] split = field.getType().getName().split("\\.");
									String className = split[split.length - 1];
									// Chargement de la class Table qui gère l'entité
									Class<?> clazz = Class.forName(field.getAnnotation(DbLink.class).value() + "." + className + "Table");
									Table table = (Table) clazz.getMethod("getInstance").invoke(null);
									val = table.getById(otherId);
								}
							}
							field.set(obj, val);
						}
						list.add(obj);
					}
					return list;
				} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException | NoSuchMethodException | SecurityException | SQLException | ClassNotFoundException e) {
					throw new IllegalStateException("Unable to map value to the entity");
				}
			}
		};
	}
	
	private static void checkIfPersistable(Field f) {
		boolean ok = false;
		for(Class<?> clazz : f.getType().getInterfaces()) {
			ok |= clazz.getName().endsWith("Persistable");
		}
		if (!ok) {
			throw new IllegalStateException("Field object don't implement Persistable");
		}
	}

}
