/************************************************
 * 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.config;

import net.sf.joyaop.AspectRuntimeException;
import net.sf.joyaop.framework.Pointcut;
import net.sf.joyaop.impl.Pointcuts;
import net.sf.joyaop.util.ClassUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.HashMap;
import java.util.Map;

/**
 * A primitive parser.
 *
 * @author Shen Li
 */
public final class PointcutExpressionParser {
    public static final String TARGET = "target";
    public static final String EXECUTION = "execution";
    public static final String INTERFACE = "interface";
    public static final String NOT = "!";
    public static final String AND = "AND";
    public static final String OR = "OR";
    public static final String LEFT_BRACKET = "(";
    public static final String RIGHT_BRACKET = ")";

    private static final Log log = LogFactory.getLog(PointcutExpressionParser.class);

    private final Map pointcutDefinitions = new HashMap();

    public void addPointcutDefinition(String pointcutName, String expression) {
        pointcutDefinitions.put(pointcutName, expression);
    }

    public Pointcut parse(String expression) {
        if (log.isTraceEnabled()) {
            log.trace("Parsing expression: " + expression);
        }
        Pointcut p = doParse(expression);
        if (log.isTraceEnabled()) {
            log.trace("Finished ----------------------------------");
        }
        return p;
    }

    private Pointcut doParse(String expression) {
        Pointcut pointcut;
        expression = expression.trim();
        boolean not = false;
        if (expression.startsWith(NOT)) {
            expression = expression.substring(1, expression.length()).trim();
            not = true;
        }
        if (expression.startsWith(LEFT_BRACKET) && expression.endsWith(RIGHT_BRACKET)) {
            expression = expression.substring(1, expression.length() - 1).trim();
        }
        int splitIndex;
        if ((splitIndex = findOperatorIndex(expression, OR)) >= 0) {
            pointcut = Pointcuts.or(doParse(expression.substring(0, splitIndex)),
                    doParse(expression.substring(splitIndex + OR.length(), expression.length())));
            if (log.isTraceEnabled()) {
                log.trace("OR");
            }
        } else if ((splitIndex = findOperatorIndex(expression, AND)) >= 0) {
            pointcut = Pointcuts.and(doParse(expression.substring(0, splitIndex)),
                    doParse(expression.substring(splitIndex + AND.length(), expression.length())));
            if (log.isTraceEnabled()) {
                log.trace("AND");
            }
        } else {
            int leftBracketIndex = expression.indexOf(LEFT_BRACKET);
            if (leftBracketIndex < 0 || !expression.endsWith(RIGHT_BRACKET)) { // expr may be a pointcut name
                String oldExpr = expression;
                expression = (String) pointcutDefinitions.get(expression);
                if (expression == null) {
                    throw new AspectRuntimeException("Illegal pointcut expression: " + oldExpr);
                } else {
                    pointcut = doParse(expression);
                }
            } else {
                String pointcutType = expression.substring(0, leftBracketIndex).trim();
                String pointcutExpr = expression.substring(leftBracketIndex + 1, expression.length() - 1).trim();
                if (TARGET.equals(pointcutType)) {
//                    log.trace("Pointcuts.regexp(" + pointcutExpr + ", null)");
                    if (log.isTraceEnabled()) {
                        log.trace("target(" + pointcutExpr + ")");
                    }
                    pointcut = Pointcuts.byRegexp(pointcutExpr, null);
                } else if (EXECUTION.equals(pointcutType)) {
//                    log.trace("Pointcuts.regexp(null, " + pointcutExpr + ")");
                    if (log.isTraceEnabled()) {
                        log.trace("execution(" + pointcutExpr + ")");
                    }
                    pointcut = Pointcuts.byRegexp(null, pointcutExpr);
                } else if (INTERFACE.equals(pointcutType)) {
//                    log.trace("Pointcuts.interface(" + pointcutExpr + ")");
                    if (log.isTraceEnabled()) {
                        log.trace("interface(" + pointcutExpr + ")");
                    }
                    pointcut = Pointcuts.byInterface(ClassUtils.loadClass(pointcutExpr));
                } else {
                    throw new AspectRuntimeException("Unkown pointcut type: " + pointcutType);
                }
            }
        }

        if (not) {
            if (log.isTraceEnabled()) {
                log.trace("NOT");
            }
            pointcut = Pointcuts.not(pointcut);
        }
        return pointcut;
    }

    private int findOperatorIndex(String expression, String operator) {
        return findOperatorIndex(expression, operator, 0);
    }

    private int findOperatorIndex(String expression, String operator, int beginIndex) {
        int index = expression.indexOf(operator, beginIndex);
        if (index < 0 || !isInBracket(expression, index)) {
            return index;
        }
        return findOperatorIndex(expression, operator, index + operator.length());
    }

    private boolean isInBracket(String expression, int index) {
        int leftBracketCount = 0;
        int rightBracketCount = 0;
        int i, j;
        for (i = index; i >= 0; i--) {
            if (expression.charAt(i) == '(') {
                leftBracketCount++;
            } else if (expression.charAt(i) == ')') {
                rightBracketCount++;
            }
            if (leftBracketCount > rightBracketCount) {
                break;
            }
        }
        leftBracketCount = 0;
        rightBracketCount = 0;
        for (j = index; j < expression.length(); j++) {
            if (expression.charAt(j) == '(') {
                leftBracketCount++;
            } else if (expression.charAt(j) == ')') {
                rightBracketCount++;
            }
            if (leftBracketCount < rightBracketCount) {
                break;
            }
        }
        if (i >= 0 && j < expression.length()) {
            return true;
        } else if (i < 0 && j == expression.length()) {
            return false;
        } else {
            throw new AspectRuntimeException("Illegal pointcut expression: " + expression);
        }
    }
}

