/*
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
*/
package jpa.tools.swing;

import java.awt.Color;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.MapAttribute;
import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
import jakarta.persistence.metamodel.Type;

/**
 * Static utility for analyzing persistent metadata model using JPA 2.0.
 *
 * @author Pinaki Poddar
 *
 */
public class MetamodelHelper {
    public static final int ATTR_ID      = 0;
    public static final int ATTR_VERSION = 1;
    public static final int ATTR_BASIC   = 2;
    public static final int ATTR_SINGULAR_RELATION = 3;
    public static final int ATTR_PLURAL_RELATION   = 4;

    public static final Color MIDNIGHT_BLUE = new Color(25,25,112);
    public static final Color DARK_GREEN = new Color(0,100,0);
    public static final Color KHAKI = new Color(240, 230, 140);

    public static String getDisplayName(Type<?> type) {
        if (type instanceof EntityType) {
            return getDisplayName((EntityType<?>)type);
        }
        return getDisplayName(type.getJavaType());
    }

    /**
     * Gets the displayed name of a given entity type.
     */
    public static String getDisplayName(EntityType<?> type) {
        return type.getName();
    }

    public static String getDisplayName(Class<?> cls) {
      String fullName = cls.getName();
      if (fullName.startsWith("java.") || fullName.startsWith("openbook.domain.")) {
          int i = fullName.lastIndexOf('.');
          return fullName.substring(i+1);
      }
      return fullName;
    }


    public static String getDisplayName(Attribute<?,?> attr) {
        StringBuilder buffer = new StringBuilder();
        buffer.append(getDisplayName(attr.getJavaType()));
        if (attr instanceof MapAttribute) {
            buffer.append("<").append(getDisplayName(((MapAttribute)attr).getKeyType())).append(",")
                  .append(getDisplayName(((MapAttribute)attr).getElementType())).append(">");
        } else if (attr instanceof PluralAttribute) {
            buffer.append("<").append(getDisplayName(((PluralAttribute)attr).getElementType())).append(">");
        }
        buffer.append(" ").append(attr.getName());
        return buffer.toString();
    }


    public static <T> List<Attribute<? super T,?>> getAttributes(EntityType<T> type) {
        List<Attribute<? super T,?>> list = new ArrayList<>(type.getAttributes());
        Collections.sort(list, new AttributeComparator());
        return list;
    }

    public static int getAttributeType(Attribute<?, ?> a) {
        if (a instanceof SingularAttribute) {
            SingularAttribute<?, ?> sa = (SingularAttribute<?, ?>)a;
            if (sa.isId()) return ATTR_ID;
            if (sa.isVersion()) return ATTR_VERSION;
            if (sa.isAssociation()) return ATTR_SINGULAR_RELATION;
            if (sa.isCollection()) return ATTR_PLURAL_RELATION;
        } else {
            if (a.isAssociation()) return ATTR_SINGULAR_RELATION;
            if (a.isCollection()) return ATTR_PLURAL_RELATION;
        }
        return ATTR_BASIC;

    }

    public static <T> Set<SingularAttribute<? super T, ?>> getIdAttributes(EntityType<T> type) {
        Set<SingularAttribute<? super T,?>> attrs = type.getSingularAttributes();
        Set<SingularAttribute<? super T,?>> idAttrs = new HashSet<>();
        for (SingularAttribute<? super T,?> attr : attrs) {
            if (attr.isId()) {
                idAttrs.add(attr);
            }
        }
        return idAttrs;
    }

    /**
     * Finds the derived target of the given type, if any. Otherwise null.
     */
    public static <T> EntityType<?> derivedTarget(EntityType<T> type) {
        Set<SingularAttribute<? super T,?>> ids = getIdAttributes(type);
        for (SingularAttribute<? super T,?> id : ids) {
            EntityType<?> derived = getParentType(id);
            if (derived != null) {
                return derived;
            }
        }
        return null;
    }

    public static EntityType<?> getParentType(SingularAttribute<?,?> id) {
        if (id.getType() instanceof EntityType) {
            return (EntityType<?>)id.getType();
        }
        return null;
    }


    public static boolean isId(Attribute<?,?> a) {
        if (a instanceof SingularAttribute)
            return ((SingularAttribute<?,?>)a).isId();
        return false;
    }
    public static boolean isVersion(Attribute<?,?> a) {
        if (a instanceof SingularAttribute)
            return ((SingularAttribute<?,?>)a).isVersion();
        return false;
    }

    public static Color getColor(Attribute<?,?> attr) {
        if (isId(attr))
            return Color.RED;
        if (isVersion(attr))
            return Color.DARK_GRAY;
        return getColor(attr.getPersistentAttributeType());
    }

    public static Color getColor(Attribute.PersistentAttributeType type) {
        switch (type) {
        case BASIC :             return Color.BLACK;
        case EMBEDDED:           return Color.GRAY;
        case ONE_TO_ONE:         return Color.BLUE;
        case MANY_TO_ONE:        return MIDNIGHT_BLUE;
        case ONE_TO_MANY:        return DARK_GREEN;
        case MANY_TO_MANY:       return Color.PINK;
        case ELEMENT_COLLECTION: return KHAKI;
        default:                 return Color.BLACK;
        }
    }

    public static Integer getAttributeTypeCode(Attribute<?,?> attr) {
        if (isId(attr))
            return 0;
        if (isVersion(attr))
            return 1;

      switch (attr.getPersistentAttributeType()) {
      case BASIC :
      case EMBEDDED:
          return 2;
      case ONE_TO_ONE:
      case MANY_TO_ONE:
          return 3;
      case ONE_TO_MANY:
      case MANY_TO_MANY:
      case ELEMENT_COLLECTION: return 4;
      default: return 5;
      }
    }
    private static Map<Attribute<?,?>, Method> members = new HashMap<>();
    private static Object[] EMPTY_ARGS = null;
    private static Class<?>[] EMPTY_CLASSES = null;
    /**
     * Gets the value of the given persistent attribute for the given instance.
     */
    public static Object getValue(Attribute<?,?> attr, Object instance) {
        Member member = attr.getJavaMember();
        Method getter = null;
        if (member instanceof Method) {
            getter = (Method)member;
        } else if (members.containsKey(attr)) {
            getter = members.get(attr);
        } else {
            getter = getMethod(attr.getDeclaringType().getJavaType(), attr.getName());
            members.put(attr, getter);
        }
        if (getter == null)
            return null;
        try {
            return getter.invoke(instance, EMPTY_ARGS);
        } catch (Exception e) {
            return null;
        }
    }

    private static Method getMethod(Class<?> type, String p) {
        try {
            String getter = "get" + Character.toUpperCase(p.charAt(0))+p.substring(1);
            return type.getMethod(getter, EMPTY_CLASSES);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return null;

    }


    /**
     * Compares EntityType by their dependency of derived targets.
     *
     * @author Pinaki Poddar
     *
     */
    public static class EntityComparator implements Comparator<EntityType<?>> {
        @Override
        public int compare(EntityType<?> o1, EntityType<?> o2) {
            if (derivedTarget(o1) == o2)
               return 1;
            if (derivedTarget(o2) == o1)
                return -1;
            return o1.getName().compareTo(o2.getName());
        }

    }


    /**
     * Compares attribute by their qualification.
     * Identity
     * Version
     * Basic
     * Singular association
     * Plural association
     *
     * @author Pinaki Poddar
     *
     */
    public static class AttributeComparator implements Comparator<Attribute<?,?>> {
        @Override
        public int compare(Attribute<?, ?> a1, Attribute<?, ?> a2) {
            Integer t1 = getAttributeTypeCode(a1);
            Integer t2 = getAttributeTypeCode(a2);
            if (t1.equals(t2)) {
                return a1.getName().compareTo(a2.getName());
            } else {
                return t1.compareTo(t2);
            }
        }
    }
}
