package br.ufrgs.inf.prosoft.tigris.monitoring.metadata;

import br.ufrgs.inf.prosoft.tigris.monitoring.util.Mnemos;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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

/**
 * Key of a callable target.
 */
public class Key {

    /**
     * MethodInfo.
     */
    private final transient Method method;
    /**
     * Object callable (or class, if static method).
     */
    private final transient Object target;
    /**
     * Arguments.
     */
    private final transient Object[] arguments;

    /**
     * The Logger.
     */
    Logger logger = LoggerFactory.getLogger(Key.class);

    /**
     * Public ctor.
     *
     * @param point Joint point
     */
    public Key(final JoinPoint point) {
        this.method = MethodSignature.class
                .cast(point.getSignature()).getMethod();
        this.target = Key.targetize(point);
        this.arguments = point.getArgs();
    }

    /**
     * Calculate its target.
     *
     * @param point Proceeding point
     * @return The target
     */
    private static Object targetize(final JoinPoint point) {
        final Object tgt;
        final Method method = MethodSignature.class
                .cast(point.getSignature()).getMethod();
        if (Modifier.isStatic(method.getModifiers())) {
            tgt = method.getDeclaringClass();
        } else {
            tgt = point.getTarget();
        }
        return tgt;
    }

    @Override
    public String toString() {
        return "Key{" +
                "method=" + method +
                ", target=" + target +
                ", arguments=" + Arrays.toString(arguments) +
                '}';
    }

    @Override
    public final int hashCode() {
        return this.method.hashCode();
    }

    @Override
    public final boolean equals(final Object obj) {

        final boolean equals;
        if (this == obj) {
            equals = true;
        } else if (obj instanceof Key) {
            final Key key = Key.class.cast(obj);
            equals = key.method.equals(this.method)
                    //&& this.target.equals(key.target)
                    && Arrays.deepEquals(key.arguments, this.arguments);
        } else {
            equals = false;
        }
        return equals;
    }

    /**
     * Send a result through, with necessary logging.
     *
     * @param result The result to send through
     * @return The same result/object
     * @checkstyle DesignForExtensionCheck (2 lines)
     */
    public Object through(final Object result) {
        final Class<?> type = this.method.getDeclaringClass();
        logger.debug(
                "%s: %s from model (hit #%d, %[ms]s old)",
                this,
                Mnemos.toText(result, true, false)
        );
        return result;
    }

    /**
     * Is it related to the same target?
     *
     * @param point Proceeding point
     * @return True if the target is the same
     */
    public final boolean sameTarget(final JoinPoint point) {
        return Key.targetize(point).equals(this.target);
    }

}
