/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.reflection.impl;

import net.ranides.assira.collection.query.CQuery;
import net.ranides.assira.generic.Ref;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.reflection.*;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.annotation.Repeatable;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * This is default implementation of IAnnotations.
 * Storage behaviour is defined by base class {@link AElements}.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class AAnnotations extends AElements<Annotation> implements IAnnotations, Serializable {

    private static final long serialVersionUID = 3L;

    private static final IAnnotations EMPTY = FElements.newAnnotations(IHints.none(), CQuery.empty());

    /**
     * Internal method, used by {@link IAnnotations#empty()}
     *
     * @return IAnnotations
     */
    public static IAnnotations $empty() {
        return EMPTY;
    }

    AAnnotations(IHints hints, CQuery<Annotation> stream) {
        super(hints, stream);
    }
    
    @Override
    public List<String> names() {
        return list(a -> a.annotationType().getName());
    }

    @Override
    public <A extends Annotation> Ref<A> first(Class<A> type) {
        return stream(type).first();
    }

    @Override
    public <A extends Annotation> CQuery<A> stream(Class<A> type) {
        return stream0(type);
    }

    @Override
    public <A extends Annotation> List<A> list(Class<A> type) {
        return stream(type).list();
    }

    @Override
    public IAnnotations require(Predicate<? super Annotation> predicate) {
        filter(predicate);
        return this;
    }

    @Override
    public IAnnotations require(Class<? extends Annotation> type) {
        rebind(stream0(type));
        return this;
    }

    @Override
    public IAnnotations discard(Predicate<? super Annotation> predicate) {
        return require(predicate.negate());
    }

    @Override
    public IAnnotations discard(Class<? extends Annotation> type) {
        Repeatable repeat = type.getAnnotation(Repeatable.class);
        if(repeat == null) {
            return require(a -> !type.isInstance(a));
        } else {
            Class<? extends Annotation> container = repeat.value();
            return require(a -> !type.isInstance(a) && !container.isInstance(a));
        }
    }

    @SuppressWarnings({"rawtypes", "unchecked"})
    protected <T> CQuery<T> stream0(Class<?> type) {
        Repeatable repeat = type.getAnnotation(Repeatable.class);
        if(repeat == null) {
            return (CQuery)stream().filter(a -> type.isInstance(a));
        }

        Class<? extends Annotation> container = repeat.value();
        IMethod method = IClass.typeinfo(container).method("value").get();

        return (CQuery)stream()
            .filter(a -> type.isInstance(a) || container.isInstance(a))
            .flatIterableIf(a -> container.isInstance(a), a -> Arrays.asList(method.$call(a)));
    }

    @Override
    public IElements<? extends IElement> parent() {
        throw new IReflectiveException("Annotations don't have parents");
    }

    protected Object writeReplace() {
        final IHints cHints = this.hints();
        final List<Annotation> cList = this.list();
        return SerializationUtils.proxy(() -> FElements.newAnnotations(cHints, CQuery.from(cList)));
    }

}
