/************************************************
 * Copyright (c) Shen Li. All rights reserved.  *
 * http://joyaop.sourceforge.net                *
 * -------------------------------------------- *
 * Distributable under LGPL license.            *
 * See terms of license at gnu.org.             *
 ************************************************/
package net.sf.joyaop.impl;

import net.sf.joyaop.framework.Pointcut;
import net.sf.joyaop.util.ClassUtils;

import java.lang.reflect.Method;
import java.util.regex.Pattern;

/**
 * @author Shen Li
 */
public abstract class Pointcuts {
    public static Pointcut byRegexp(String classRegexp, String methodRegexp) {
        return new Regexp(classRegexp, methodRegexp);
    }    

    public static Pointcut byInterface(Class interfaceClass) {
        return new Interface(interfaceClass);
    }

    public static Pointcut byInterface(String interfaceName) {
        return byInterface(ClassUtils.loadClass(interfaceName));
    }

    public static Pointcut and(Pointcut p1, Pointcut p2) {
        return new And(p1, p2);
    }

    public static Pointcut or(Pointcut p1, Pointcut p2) {
        return new Or(p1, p2);
    }

    public static Pointcut not(Pointcut p) {
        return new Not(p);
    }

    public static class And implements Pointcut {
        private final Pointcut p1;
        private final Pointcut p2;

        public And(Pointcut p1, Pointcut p2) {
            this.p1 = p1;
            this.p2 = p2;
        }

        public boolean matches(Class clazz) {
            return p1.matches(clazz) && p2.matches(clazz);
        }

        public boolean matches(Class clazz, Method method) {
            return p1.matches(clazz, method) && p2.matches(clazz, method);
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof And)) {
                return false;
            }
            And and = (And) object;
            if (p1.equals(and.p1) && p2.equals(and.p2) || p1.equals(and.p2) && p2.equals(and.p1)) {
                return true;
            }
            return false;
        }

        public int hashCode() {
            return p1.hashCode() * 31 + p2.hashCode();
        }
    }

    public static class Interface implements Pointcut {
        private final Class interfaceClass;

        public Interface(Class interfaceClass) {
            this.interfaceClass = interfaceClass;
        }

        public boolean matches(Class clazz) {
            return (interfaceClass.isAssignableFrom(clazz));
        }

        public boolean matches(Class clazz, Method method) {
            if (!matches(clazz)) {
                return false;
            }
            if (method.getDeclaringClass() == interfaceClass) {
                return true;
            }
            // TODO not precise
            try {
                interfaceClass.getMethod(method.getName(), method.getParameterTypes());
            } catch (NoSuchMethodException e) {
                return false;
            }
            return true;
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof Interface)) {

                return false;
            }
            Interface intf = (Interface) object;
            if (interfaceClass == intf.interfaceClass) {
                return true;
            }
            return false;
        }

        public int hashCode() {
            return interfaceClass.hashCode();
        }
    }

    public static class Not implements Pointcut {
        private final Pointcut pointcut;

        public Not(Pointcut pointcut) {
            this.pointcut = pointcut;
        }

        public boolean matches(Class clazz) {
            return !pointcut.matches(clazz);
        }

        public boolean matches(Class clazz, Method method) {
            return !pointcut.matches(clazz, method);
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof Not)) {
                return false;
            }
            Not not = (Not) object;
            if (pointcut.equals(not.pointcut)) {
                return true;
            }
            return false;
        }

        public int hashCode() {
            return pointcut.hashCode();
        }
    }

    public static class Or implements Pointcut {
        private final Pointcut p1;
        private final Pointcut p2;

        public Or(Pointcut p1, Pointcut p2) {
            this.p1 = p1;
            this.p2 = p2;
        }

        public boolean matches(Class clazz) {
            return p1.matches(clazz) || p2.matches(clazz);
        }

        public boolean matches(Class clazz, Method method) {
            return p1.matches(clazz, method) || p2.matches(clazz, method);
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof Or)) {
                return false;
            }
            Or or = (Or) object;
            if (p1.equals(or.p1) && p2.equals(or.p2) || p1.equals(or.p2) && p2.equals(or.p1)) {
                return true;
            }
            return false;
        }

        public int hashCode() {
            return p1.hashCode() * 31 + p2.hashCode();
        }
    }

    public static class Regexp implements Pointcut {
        private Pattern classPattern;
        private Pattern methodPattern;

        public Regexp(String classRegexp, String methodRegexp) {
            if (classRegexp == null) {
                classRegexp = ".*";
            }
            if (methodRegexp == null) {
                methodRegexp = ".*";
            }
            this.classPattern = Pattern.compile(classRegexp);
            this.methodPattern = Pattern.compile(methodRegexp);
        }

        public boolean matches(Class clazz, Method method) {
            return (matches(clazz) && methodPattern.matcher(method.getName()).matches());
        }

        public boolean matches(Class clazz) {
            return classPattern.matcher(clazz.getName()).matches();
        }

        public boolean equals(Object object) {
            if (this == object) {
                return true;
            }
            if (!(object instanceof Regexp)) {
                return false;
            }
            Regexp regexp = (Regexp) object;
            if (classPattern.pattern().equals(regexp.classPattern.pattern())
                    && methodPattern.pattern().equals(regexp.methodPattern.pattern())) {
                return true;
            }
            return false;
        }

        public int hashCode() {
            return classPattern.pattern().hashCode() * 31 + methodPattern.hashCode();
        }
    }
}
