/*
 * @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.functional.special.AnyFunction;
import net.ranides.assira.generic.SerializationUtils;
import net.ranides.assira.reflection.*;

import java.io.Serializable;
import java.lang.annotation.Annotation;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Constructor;
import java.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

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

	@SuppressWarnings({"raw", "rawtypes"})
    private static final IConstructors EMPTY = FElements.newConstructors(IHints.none(), CQuery.empty());

	/**
	 * Internal method, used by {@link IConstructors#empty()}
	 *
	 * @param <T> T
	 * @return IConstructors
	 */
	@SuppressWarnings("unchecked")
	public static <T> IConstructors<T> $empty() {
		return EMPTY;
	}

	AConstructors(IHints hints, CQuery<IConstructor<T>> stream) {
		super(hints, stream);
	}

    @Override
	public List<String> names() {
        return list(IConstructor::name);
	}

	@Override
	public IConstructors<T> require(Predicate<? super IConstructor<T>> predicate) {
		filter(predicate);
		return this;
	}

	@Override
	public IConstructors<T> require(IAnnotations req) {
		return require(m -> {
			Set<Annotation> set = m.annotations().stream().collect(Collectors.toSet());
			return req.stream().matchAll(set::contains);
		});
	}

	@Override
	public IConstructors<T> require(Class<? extends Annotation> annotation) {
		return require(m -> !m.annotations().require(annotation).isEmpty());
	}

	@Override
	public IConstructors<T> require(IAttribute attribute) {
        hints().require(attribute);
		return attribute.equals(IAttribute.ANY) ? this : require(m -> m.attributes().contains(attribute));
	}
    
    @Override
	public IConstructors<T> require(IAttributes attributes) {
        hints().require(attributes);
		return attributes.contains(IAttribute.ANY) ? this : require(m -> m.attributes().containsAll(attributes));
	}
    
    @Override
    public IConstructors<T> require(String name) {
        return require(m -> m.name().equals(name));
    }

	@Override
	public IConstructors<T> require(IClass<?> returns) {
		return require(m -> m.returns().isSubclass(returns));
	}

	@Override
	public IConstructors<T> require(IArguments args) {
		List<IClass<?>> req = args.types().list();
		return require(m -> m.arguments().types().list().equals(req));
	}
    
    @Override
	public IConstructors<T> require(int args) {
		return require(m -> m.arguments().count()==args);
	}

	@Override
	public IConstructors<T> discard(Predicate<? super IConstructor<T>> predicate) {
		return require(predicate.negate());
	}

	@Override
	public IConstructors<T> discard(IAnnotations annotations) {
		Set<Annotation> req = annotations.stream().collect(Collectors.toSet());
		return require(m -> !m.annotations().stream().matchAny(req::contains));
	}

	@Override
	public IConstructors<T> discard(Class<? extends Annotation> annotation) {
		return require(m -> m.annotations().require(annotation).isEmpty());
	}

	@Override
	public IConstructors<T> discard(IAttribute attribute) {
        hints().discard(attribute);
		return require(m -> !m.attributes().contains(attribute));
	}
    
    @Override
	public IConstructors<T> discard(IAttributes attributes) {
        hints().discard(attributes);
		return require(m -> !m.attributes().containsAll(attributes));
	}
    
    @Override
    public IConstructors<T> discard(String name) {
        return require(m -> !m.name().equals(name));
    }

	@Override
	public IConstructors<T> discard(IClass<?> returns) {
		return require(m -> !m.returns().isSubclass(returns));
	}

	@Override
	public IConstructors<T> discard(IArguments args) {
		IClasses req = args.types();
		return require(m -> !m.arguments().types().equals(req));
	}
    
    @Override
	public IConstructors<T> discard(int args) {
		return require(m -> m.arguments().count()!=args);
	}

	@Override
	public IConstructors<T> matches(IClasses args) {
        return require(m -> m.matches(args));
	}
    
    @Override
	public IConstructors<T> accepts(Object... args) {
        return require(m -> m.accepts(args));
	}
	
	@Override
	public IClasses returns() {
		return FElements.newClasses(IHints.none(), stream().map(IConstructor::returns));
	}

	@Override
	public IClasses parent() {
		return FElements.newClasses(IHints.none(), stream().map(IConstructor::parent));
	}

    @Override
    public CQuery<Constructor<T>> reflective() {
        return stream().map(IConstructor::reflective);
    }

	@Override
	public IConstructors<T> accessible() {
		forEach(IConstructor::accessible);
		return this;
	}

	@Override
    public CQuery<MethodHandle> handle() {
        return stream().map(m -> m.handle());
    }

	@Override
	public AnyFunction<Object> dispatcher() {
		return new CDispatchFunctions.SimpleCall(this);
	}

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

}
