
package de.ivu.elect.jee.faces;

import static org.apache.commons.lang3.StringUtils.startsWith;

import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import javax.annotation.Nullable;
import javax.faces.component.UIComponent;
import javax.faces.context.FacesContext;
import javax.faces.convert.Converter;
import javax.inject.Inject;
import javax.persistence.EntityManager;

import de.ivu.elect.jee.cdi.BeanManagerUtil;
import de.ivu.elect.jee.entity.Entity;

public abstract class AbstractEntityConverter implements Converter {

  private static final String PREFIX = "PK_HEADER";

  private static final Map<String, Class< ? >> CLASS_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();

  @Inject
  private transient EntityConverterApplicationScope entityConverterApplicationScope;

  protected AbstractEntityConverter() {
    super();
  }

  @Nullable
  @Override
  public Object getAsObject(FacesContext facesContext, UIComponent component, String value) {
    try {
      if (startsWith(value, PREFIX)) {
        return findEntity(decodeData(value));
      }
    } catch (Exception e) {
      return null; // ELECTVAPP-354 nichts loggen
    }

    return null;
  }

  EntityConverterData decodeData(String value) {
    return decodeClassnameAndPrimaryKey(value.substring(PREFIX.length()));
  }

  @Nullable
  @Override
  public String getAsString(FacesContext facesContext, UIComponent component, Object value) {
    if (value instanceof Entity< ? , ? >) {
      Entity< ? , ? > entity = (Entity< ? , ? >) value;
      return PREFIX + encodeClassnameAndPrimaryKey(entity.getEntityClass(), entity.getId());
    }

    return null;
  }

  private EntityConverterApplicationScope getEntityConverterApplicationScope() {
    if (entityConverterApplicationScope == null) {
      entityConverterApplicationScope = BeanManagerUtil.getBeanByClass(EntityConverterApplicationScope.class);
    }

    return entityConverterApplicationScope;
  }

  private EntityConverterData decodeClassnameAndPrimaryKey(String value) {
    return new EntityConverterData(new String(value.getBytes(StandardCharsets.UTF_8), StandardCharsets.UTF_8));
  }

  private String encodeClassnameAndPrimaryKey(Class< ? > entityClass, Long primaryKey) {
    EntityConverterData entityConverterData = new EntityConverterData(entityClass.getName(), primaryKey);
    CLASS_CONCURRENT_HASH_MAP.computeIfAbsent(entityConverterData.getClassName(), x -> getCleanClass(entityConverterData));
    return entityConverterData.writeToString();
  }

  private Class< ? > getCleanClass(EntityConverterData entityConverterData) {
    try {
      // Javassist vermeiden
      return Class.forName(entityConverterData.getClassName());
    } catch (ClassNotFoundException e) {
      throw new RuntimeException(e.getMessage(), e);
    }
  }

  @Nullable
  Object findEntity(EntityConverterData converterData) {
    Long primaryKey = converterData.getPrimaryKey();

    if (primaryKey != null) {
      return getEntityManager().find(findClass(converterData.getClassName()), primaryKey);
    }

    return null;
  }

  private EntityManager getEntityManager() {
    return getEntityConverterApplicationScope().getExtendedDAO().getEntityManager();
  }

  protected static Class< ? > findClass(String className) {
    return CLASS_CONCURRENT_HASH_MAP.getOrDefault(className, Object.class);
  }
}
