package com.peterphi.std.guice.hibernate.webquery.impl;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.peterphi.std.guice.restclient.jaxb.webqueryschema.WQSchemas;
import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.engine.spi.SessionFactoryImplementor;

import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Singleton
public class QEntityFactory
{
	private static final Logger log = Logger.getLogger(QEntityFactory.class);

	private final Map<Class<?>, QEntity> entities = new HashMap<>();
	private final SessionFactoryImplementor sessionFactory;
	private final Map<Class<?>, EmbeddableType<?>> embeddableTypeMap = new HashMap<>();

	@Inject
	public QEntityFactory(final SessionFactory sessionFactory)
	{
		this.sessionFactory = (SessionFactoryImplementor) sessionFactory;

		if (log.isDebugEnabled())
			log.debug("Known entities: " +
			          sessionFactory.getMetamodel().getEntities().stream().map(e -> e.getName()).collect(Collectors.toList()));

		// Pre-construct the QEntity instances for all known entities
		for (EntityType<?> entityType : sessionFactory.getMetamodel().getEntities())
		{
			final Class clazz = entityType.getJavaType();

			if (clazz != null && !Modifier.isAbstract(clazz.getModifiers()))
				get(clazz);
		}
	}


	public List<QEntity> getSubclasses(Class<?> superclass)
	{
		List<QEntity> subclasses = new ArrayList<>();

		for (EntityType<?> entityType : sessionFactory.getMetamodel().getEntities())
		{
			final Class<?> clazz = entityType.getJavaType();

			if (clazz != null && !Modifier.isAbstract(clazz.getModifiers()) && superclass.isAssignableFrom(clazz))
				subclasses.add(get(clazz));
		}

		return subclasses;
	}


	public <T> QEntity get(Class<T> clazz)
	{
		if (!entities.containsKey(clazz))
		{
			log.debug("Begin create QEntity " + clazz);
			final EntityType<T> metadata = sessionFactory.getMetamodel().entity(clazz);

			if (metadata == null)
				throw new IllegalArgumentException("Hibernate has no ClassMetadata for: " +
				                                   clazz +
				                                   " (should be following Embeddable codepath?)");

			QEntity entity = new QEntity(clazz);
			entities.put(clazz, entity);

			entity.parse(this, metadata, sessionFactory);

			log.debug("End create QEntity " + clazz);
		}

		return entities.get(clazz);
	}


	public QEntity getEmbeddable(final Class clazz, final EmbeddableType ct)
	{
		if (!entities.containsKey(clazz))
		{
			log.debug("Begin create QEntity " + clazz + " from EmbeddableType " + ct);

			QEntity entity = new QEntity(clazz);
			entities.put(clazz, entity);

			entity.parseEmbeddable(this, sessionFactory,null, ct);

			log.debug("End create QEntity " + clazz + " from EmbeddableType " + ct);
		}

		return entities.get(clazz);
	}


	public WQSchemas encode()
	{
		WQSchemas obj = new WQSchemas();

		obj.entities = getAll().stream().map(QEntity:: encode).collect(Collectors.toList());

		return obj;
	}


	public List<QEntity> getAll()
	{
		// Pre-emptively process all entities
		sessionFactory.getMetamodel().getEntities().stream().map(EntityType:: getJavaType).forEach(this :: get);

		return new ArrayList<>(entities.values());
	}
}

@@ -915,16 +915,17 @@
 p%3C%3E();%0A%0A
+%0A
 %09@Inject
@@ -2018,32 +2018,62 @@
 Key(clazz))%0A%09%09%7B%0A
+%09%09%09if (log.isDebugEnabled())%0A%09
 %09%09%09log.debug(%22Be
@@ -2103,16 +2103,17 @@
 clazz);%0A
+%0A
 %09%09%09final
@@ -2543,24 +2543,54 @@
 nFactory);%0A%0A
+%09%09%09if (log.isDebugEnabled())%0A%09
 %09%09%09log.debug
@@ -2779,16 +2779,46 @@
 z))%0A%09%09%7B%0A
+%09%09%09if (log.isDebugEnabled())%0A%09
 %09%09%09log.d
@@ -3008,16 +3008,17 @@
 Factory,
+ 
 null, ct
@@ -3021,16 +3021,46 @@
 , ct);%0A%0A
+%09%09%09if (log.isDebugEnabled())%0A%09
 %09%09%09log.d

Make all log.debug statements in QEntityFactory conditional upon debug logging being on