/*
 * Copyright (c) 2009 JSCAPE
 * 1147 S. 53rd Pl., Mesa, Arizona, 85206, U.S.A.
 * All rights reserved.
 *
 * This software is the confidential and proprietary information of
 * JSCAPE. ("Confidential Information").  You shall not disclose such
 * Confidential Information and shall use it only in accordance with
 * the terms of the license agreement you entered into with JSCAPE.
 *
 */

package com.jscape.util.reflection;

import com.jscape.util.collections.TransformIterator;
import com.jscape.util.functor.UnaryFunction;
import com.jscape.util.functor.UnaryPredicate;

import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collection;

import static com.jscape.util.Parameters.assertNotNull;
import static com.jscape.util.collections.Algorithms.copy;

/**
 * @author Alex Usun
 */
@SuppressWarnings("unchecked")
public class MethodInvoker<T>
        extends UnaryFunction<Object, T> {

    private final UnaryPredicate<? super Method> predicate;
    private final Object[] args;

    public static <T> T invoke(Object target, UnaryPredicate<? super Method> predicate, Object... args)
            throws ReflectionException {
        try {
            Class aClass = target instanceof Class ? (Class) target : target.getClass();
            Method method = MethodFinder.find(aClass, predicate);
            method.setAccessible(true);
            return (T) method.invoke(target, args);
        } catch (Exception e) {
            throw new ReflectionException(e);
        }
    }

    public static <T> T invoke(Object target, String methodName, Class[] argTypes, Object... args)
            throws ReflectionException {
        return MethodInvoker.<T>invoke(target, new MethodNameEquals(methodName).and(new MethodParametersEqual(true, argTypes)), args);
    }

    public static <T> T invoke(Object target, String methodName, Object... args)
            throws ReflectionException {
        Collection<Class> actualTypes = copy(TransformIterator.iterator(args, new ObjectClass()));
        return MethodInvoker.<T>invoke(target, new MethodNameEquals(methodName).and(new MethodParametersEqual(false, actualTypes)), args);
    }

    public static void setProperty(Object target, String propertyName, Object argument)
            throws ReflectionException {
        invoke(target, "set" + propertyName, argument);
    }

    public static <T> T getProperty(Object target, String propertyName, Object argument)
            throws ReflectionException {
        return MethodInvoker.<T>invoke(target, new MethodNameEquals("get" + propertyName).or(new MethodNameEquals("is" + propertyName)), argument);
    }

    public static <T> T getProperty(Object target, String propertyName)
            throws ReflectionException {
        return MethodInvoker.<T>invoke(target, new MethodNameEquals("get" + propertyName).or(new MethodNameEquals("is" + propertyName)));
    }

    public MethodInvoker(UnaryPredicate<? super Method> predicate, Object... args) {
        assertNotNull(predicate);
        this.predicate = predicate;

        this.args = args;
    }

    public T evaluate(Object value) {
        try {
            return MethodInvoker.<T>invoke(value, this.predicate, this.args);
        } catch (Exception e) {
            throw new IllegalArgumentException(e);
        }
    }

    public String toString() {
        return "MethodInvoker{"
                + "predicate=" + this.predicate
                + ", args=" + Arrays.asList(this.args)
                + '}';
    }
}
