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

package net.ranides.assira.reflection.impl.bean;

import lombok.RequiredArgsConstructor;
import net.ranides.assira.reflection.*;

import java.io.Serializable;
import java.util.Collection;

/**
 * Multidispatch implementation of BeanMethod
 *
 * Stores reference to all methods with the same name.
 * It makes call to first method mathing arguments, it uses linear search.
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class RBeanMethods implements BeanMethod, Serializable {

    private static final IArguments ANY_ARGUMENTS = IArguments.typeinfo(Object[].class);

    private final IMethod[] methods;
    
    RBeanMethods(Collection<BeanException> errors, Collection<IMethod> methods) {
        this.methods = methods.toArray(new IMethod[0]);
    }

    @Override
    public String name() {
        return methods[0].name();
    }

    @Override
    public IClass<?> parent() {
        return methods[0].parent();
    }

    @Override
    public IClass<?> returns() {
        return IClass.OBJECT;
    }

    @Override
    public IArguments arguments() {
        return ANY_ARGUMENTS;
    }

    @Override
    public BeanMethodBinding bind(Object that) {
        if(null == that) {
            throw new BeanException(parent(), "non-static method requires non-null 'this'");
        }
        return new RBinding(that);
    }

    @Override
    public Object invoke(Object that) {
        for(IMethod m : methods) {
            if(m.arguments().isEmpty()) {
                return m.invoke(that);
            }
        }
        throw new BeanException(parent(), "method " + name() + " can't be invoked with no arguments.");
    }

    @Override
    public Object invoke(Object that, Object... arguments) {
        for(IMethod m : methods) {
            if(m.accepts(arguments)) {
                return m.invoke(that, arguments);
            }
        }
        throw new BeanException(parent(), "method " + name() + " can't be invoked with passed arguments.");
    }

    @Override
    public Object call(Object... arguments) {
        for (IMethod m : methods) {
            if (m.$accepts1(arguments)) {
                return m.call(arguments);
            }
        }
        throw new BeanException(parent(), "method " + name() + " can't be invoked with passed arguments.");
    }

    @Override
    public Object apply(Object that, Object[] arguments) {
        for (IMethod m : methods) {
            if (m.accepts(arguments)) {
                return m.apply(that, arguments);
            }
        }
        throw new BeanException(parent(), "method " + name() + " can't be invoked with passed arguments.");
    }
    
    

    @Override
    public boolean matches(IClasses arguments) {
        for(IMethod m : methods) {
            if(m.matches(arguments)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public boolean accepts(Object... arguments) {
        for(IMethod m : methods) {
            if(m.accepts(arguments)) {
                return true;
            }
        }
        return false;
    }

    @RequiredArgsConstructor
    private class RBinding implements BeanMethodBinding {

        private final Object that;

        @Override
        public String name() {
            return RBeanMethods.this.name();
        }

        @Override
        public boolean matches(IClasses arguments) {
            return RBeanMethods.this.matches(arguments);
        }

        @Override
        public Object apply(Object[] arguments) {
            return RBeanMethods.this.invoke(that, arguments);
        }
    }
}
