package fi.solita.utils.query.generation;

import static fi.solita.utils.functional.Functional.head;

import java.util.List;

import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.Bindable;
import javax.persistence.metamodel.CollectionAttribute;
import javax.persistence.metamodel.ListAttribute;
import javax.persistence.metamodel.SetAttribute;
import javax.persistence.metamodel.SingularAttribute;

import fi.solita.utils.functional.Option;
import fi.solita.utils.query.IEntity;
import fi.solita.utils.query.Identifiable;
import fi.solita.utils.query.QueryUtils;
import fi.solita.utils.query.attributes.AdditionalQueryPerformingAttribute;
import fi.solita.utils.query.attributes.OptionalAttribute;
import fi.solita.utils.query.attributes.PseudoAttribute;
import fi.solita.utils.query.backend.Type;

public class Cast {

    public static <E, T> SingularAttribute<E, Option<T>> optional(SingularAttribute<E, T> attribute) throws IllegalArgumentException, QueryUtils.RequiredAttributeMustNotHaveOptionTypeException {
        if (attribute instanceof PseudoAttribute) {
            throw new IllegalArgumentException("No reason to wrap a PseudoAttribute. Right?");
        }
        if (attribute instanceof Bindable && Option.class.isAssignableFrom(((Bindable<?>) attribute).getBindableJavaType())) {
            throw new IllegalArgumentException("No reason to wrap an Option<?> type. Right?");
        }
        if (attribute instanceof AdditionalQueryPerformingAttribute) {
            List<Attribute<?,?>> parameters = ((AdditionalQueryPerformingAttribute) attribute).getConstructor().getParameters();
            if (parameters.size() == 1 && head(parameters) != null && Option.class.isAssignableFrom(head(parameters).getJavaType())) {
                throw new IllegalArgumentException("No reason to wrap an Option<?> type. Right?");
            }
        }
        /* not checking this here allows a use case with subtyping. Any drawbacks in addition to allowing user to wrap values to Option by accident? 
          if (QueryUtils.isRequiredByMetamodel(attribute)) {
            throw new QueryUtils.RequiredAttributeMustNotHaveOptionTypeException(attribute);
        }*/
        return OptionalAttribute.Constructors.optional(attribute);
    }
    
    public static <T> Type.Optional<T> optional(Type<T> type) {
        return new Type.Optional<T>(type);
    }

    public static <E, T> SingularAttribute<E, Option<T>> optionalSubtype(SingularAttribute<? extends E, T> attribute) throws IllegalArgumentException {
        if (attribute instanceof PseudoAttribute) {
            throw new IllegalArgumentException("No reason to wrap a PseudoAttribute. Right?");
        }
        if (attribute instanceof Bindable && Option.class.isAssignableFrom(((Bindable<?>) attribute).getBindableJavaType())) {
            throw new IllegalArgumentException("No reason to wrap an Option<?> type. Right?");
        }
        if (attribute instanceof AdditionalQueryPerformingAttribute) {
            List<Attribute<?,?>> parameters = ((AdditionalQueryPerformingAttribute) attribute).getConstructor().getParameters();
            if (parameters.size() == 1 && Option.class.isAssignableFrom(head(parameters).getJavaType())) {
                throw new IllegalArgumentException("No reason to wrap an Option<?> type. Right?");
            }
        }
        return OptionalAttribute.Constructors.optional(attribute);
    }

    @SuppressWarnings("unchecked")
    public static <E extends IEntity<?> & Identifiable<?>, T> SingularAttribute<E, T> castSuper(SingularAttribute<? extends E, T> attribute) {
        return (SingularAttribute<E, T>) attribute;
    }

    @SuppressWarnings("unchecked")
    public static <E extends IEntity<?> & Identifiable<?>, T> SingularAttribute<E, T> cast(SingularAttribute<? super E, ? super T> attribute) {
        return (SingularAttribute<E, T>) attribute;
    }
    
    @SuppressWarnings("unchecked")
    public static <E extends IEntity<?> & Identifiable<?>, T> CollectionAttribute<E, T> cast(CollectionAttribute<? super E, ? super T> attribute) {
        return (CollectionAttribute<E, T>) attribute;
    }

    @SuppressWarnings("unchecked")
    public static <E extends IEntity<?> & Identifiable<?>, T> SetAttribute<E, T> cast(SetAttribute<? super E, ? super T> attribute) {
        return (SetAttribute<E, T>) attribute;
    }

    @SuppressWarnings("unchecked")
    public static <E extends IEntity<?> & Identifiable<?>, T> ListAttribute<E, T> cast(ListAttribute<? super E, ? super T> attribute) {
        return (ListAttribute<E, T>) attribute;
    }
}

@@ -3663,32 +3663,734 @@
 attribute;%0A    %7D
+%0A    %0A    @SuppressWarnings(%22unchecked%22)%0A    public static %3CE extends IEntity%3C?%3E & Identifiable%3C?%3E, T%3E CollectionAttribute%3CE, T%3E castSuper(CollectionAttribute%3C? extends E, T%3E attribute) %7B%0A        return (CollectionAttribute%3CE, T%3E) attribute;%0A    %7D%0A    %0A    @SuppressWarnings(%22unchecked%22)%0A    public static %3CE extends IEntity%3C?%3E & Identifiable%3C?%3E, T%3E SetAttribute%3CE, T%3E castSuper(SetAttribute%3C? extends E, T%3E attribute) %7B%0A        return (SetAttribute%3CE, T%3E) attribute;%0A    %7D%0A    %0A    @SuppressWarnings(%22unchecked%22)%0A    public static %3CE extends IEntity%3C?%3E & Identifiable%3C?%3E, T%3E ListAttribute%3CE, T%3E castSuper(ListAttribute%3C? extends E, T%3E attribute) %7B%0A        return (ListAttribute%3CE, T%3E) attribute;%0A    %7D
 %0A%0A    @SuppressW

add casting to plural attributes