/*
 * @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.util.List;
import java.util.Set;
import java.util.function.Predicate;
import java.util.stream.Collectors;

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

    static final IExecutables EMPTY = FElements.newExecutables(IHints.none(), CQuery.empty());

	AExecutables(IHints hints, CQuery<IExecutable> stream) {
		super(hints, stream);
	}
    
    @Override
	public List<String> names() {
        return list(IExecutable::name);
	}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

	@Override
	public AExecutables accessible() {
		forEach(IExecutable::accessible);
		return this;
	}

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

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

}
