package eu.dnetlib.enabling.utils;

import java.lang.reflect.Field;
import java.util.Map;

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

import com.google.common.collect.Maps;

import eu.dnetlib.enabling.annotations.DnetResource;
import eu.dnetlib.enabling.annotations.IndexedField;
import eu.dnetlib.enabling.annotations.IndexedFieldsContainer;
import eu.dnetlib.enabling.datastructures.AbstractXmlResource;
import eu.dnetlib.enabling.datastructures.BaseResource;

public class DnetAnnotationUtils {

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

	private static Map<String, Class<? extends BaseResource>> mapClasses = Maps.newHashMap();

	public static class IndexedFields {

		private String id;
		private String resource;
		private Object value;

		public IndexedFields() {}

		public IndexedFields(final String id, final String resource, final Object value) {
			this.id = id;
			this.resource = resource;
			this.value = value;
		}

		public String getId() {
			return id;
		}

		public void setId(final String id) {
			this.id = id;
		}

		public String getResource() {
			return resource;
		}

		public void setResource(final String resource) {
			this.resource = resource;
		}

		public Object getValue() {
			return value;
		}

		public void Object(final String value) {
			this.value = value;
		}
	}

	public static Map<String, String> getIndexedFields(final Object o) {
		final Map<String, String> map = Maps.newLinkedHashMap();
		if (o.getClass().isAnnotationPresent(DnetResource.class)) {
			switch (o.getClass().getAnnotation(DnetResource.class).format()) {
			case JSON:
				getIndexedFields(o, map);
				break;
			case XML:
				if (o instanceof AbstractXmlResource) {
					map.putAll(((AbstractXmlResource) o).getProperties());
				}
				break;
			default:
				break;
			}
		}
		return map;
	}

	private static void getIndexedFields(final Object o, final Map<String, String> map) {
		if (o instanceof Map<?, ?>) {
			for (Object o1 : ((Map<?, ?>) o).values()) {
				getIndexedFields(o1, map);
			}
		} else if (o instanceof Iterable<?>) {
			for (Object o1 : (Iterable<?>) o) {
				getIndexedFields(o1, map);
			}
		} else {
			for (Field f : o.getClass().getDeclaredFields()) {
				if (f.isAnnotationPresent(IndexedField.class)) {
					try {
						f.setAccessible(true);
						final String id = f.getAnnotation(IndexedField.class).id();
						map.put(id, f.get(o) + "");
					} catch (Exception e) {
						log.error(e);
					}
				} else if (f.isAnnotationPresent(IndexedFieldsContainer.class)) {
					try {
						f.setAccessible(true);
						getIndexedFields(f.get(o), map);
					} catch (Exception e) {
						log.error(e);
					}
				}
			}
		}
	}

	public static Map<String, String> describeIndexedFields(final Class<?> clazz) {
		final Map<String, String> map = Maps.newLinkedHashMap();
		if (clazz.isAnnotationPresent(DnetResource.class)) {
			describeIndexedFields(clazz, map);
		}
		return map;
	}

	private static void describeIndexedFields(final Class<?> clazz, final Map<String, String> map) {
		for (Field f : clazz.getFields()) {
			if (f.isAnnotationPresent(IndexedField.class)) {
				try {
					final String id = f.getAnnotation(IndexedField.class).id();
					final String label = f.getAnnotation(IndexedField.class).label();
					map.put(id, label);
				} catch (Exception e) {
					log.error(e);
				}
			} else if (f.isAnnotationPresent(IndexedFieldsContainer.class)) {
				try {
					describeIndexedFields(f.getAnnotation(IndexedFieldsContainer.class).clazz(), map);
				} catch (Exception e) {
					log.error(e);
				}
			}
		}

	}

	@SuppressWarnings("unchecked")
	public static Class<? extends BaseResource> findClass(final String type) {
		if (mapClasses.isEmpty()) {
			final Reflections reflections = new Reflections("eu.dnetlib.enabling.datastructures");
			for (Class<?> cl : reflections.getTypesAnnotatedWith(DnetResource.class)) {
				final DnetResource annotation = cl.getAnnotation(DnetResource.class);
				if (BaseResource.class.isAssignableFrom(cl)) {
					mapClasses.put(annotation.type(), (Class<? extends BaseResource>) cl);
					log.info("Annotated JSON class: " + cl.getName());
				}
			}
		}
		return mapClasses.get(type);
	}
}
