/************************************************
 * 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.*;
import net.sf.joyaop.impl.InterceptorAspectImpl;
import net.sf.joyaop.impl.MixinAspectImpl;
import net.sf.joyaop.impl.InterfaceAspectImpl;
import net.sf.joyaop.util.ClassUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

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

/**
 * TODO if the interceptors or mixins are defined using annotations,
 * their xml elements could only have an "class" property.
 
 * @author Shen Li
 */
public class XmlConfiguration implements Configuration {
    private static final String DEFAULT_CONFIG_FILE_NAME = "aop.xml";

    private static final String INTERCEPTOR = "interceptor";
    private static final String MIXIN = "mixin";
    private static final String INTERFACE = "interface";
    private static final String ASPECT_NAME = "name";
    private static final String ASPECT_SCOPE = "scope";
    private static final String ASPECT_CLASS = "class";
    private static final String MIXIN_INTERFACE = "interface";

    private static final String POINTCUT = "pointcut";
    private static final String POINTCUT_NAME = "name";

    private static final String ASPECT = "aspect";
    private static final String ASPECT_POINTCUT = "pointcut";
    private static final String ASPECT_INTERCEPTOR = "interceptor";
    private static final String ASPECT_MIXIN = "mixin";
    private static final String ASPECT_INTERFACE = "interface";
    private static final String ASPECT_PARAMETER = "param";
    private static final String ASPECT_PARAMETER_NAME = "name";

    private static final String INTERCEPTOR_PRECEDENCE = "interceptor-precedence";

    private final String configFileName;

    public XmlConfiguration() {
        this(DEFAULT_CONFIG_FILE_NAME);
    }

    public XmlConfiguration(String configFileName) {
        this.configFileName = configFileName;
    }

    public void configure(AspectRuntime aspectRuntime) {
        Document doc = null;
        SAXReader reader = new SAXReader();
        try {
            doc = reader.read(getClass().getClassLoader().getResourceAsStream(configFileName));
        } catch (DocumentException e) {
            throw new AspectRuntimeException(e);
        }

        Map mixinElements = new HashMap();
        Map interceptorElements = new HashMap();
        Map interfaceElements = new HashMap();
        PointcutExpressionParser pointcutParser = new PointcutExpressionParser();
        String[] interceptorPrecedence = new String[0];
        Element rootElement = doc.getRootElement();

        for (Iterator i = rootElement.elementIterator(); i.hasNext();) {
            Element element = (Element) i.next();
            if (INTERCEPTOR.equals(element.getName())) {
                interceptorElements.put(element.attribute(ASPECT_NAME).getValue(), element);
            } else if (MIXIN.equals(element.getName())) {
                mixinElements.put(element.attribute(ASPECT_NAME).getValue(), element);
            } else if (INTERFACE.equals(element.getName())) {
                interfaceElements.put(element.attribute(ASPECT_NAME).getValue(), element);
            } else if (POINTCUT.equals(element.getName())) {
                pointcutParser.addPointcutDefinition(element.attribute(POINTCUT_NAME).getValue(), element.getTextTrim());
            } else if (INTERCEPTOR_PRECEDENCE.equals(element.getName())) {
                interceptorPrecedence = element.getTextTrim().split(",");
            }
        }

        for (Iterator i = rootElement.elementIterator(ASPECT); i.hasNext();) {
            Element aspectElement = ((Element) i.next());
            Pointcut pointcut = pointcutParser.parse(aspectElement.attribute(ASPECT_POINTCUT).getValue());

            Map params = new HashMap();
            for (Iterator it = aspectElement.elementIterator(ASPECT_PARAMETER); it.hasNext();) {
                Element paramElement = (Element) it.next();
                String paramName = paramElement.attribute(ASPECT_PARAMETER_NAME).getValue().trim();
                String paramValue = paramElement.getTextTrim();
                params.put(paramName, paramValue);
            }

            if (aspectElement.attribute(ASPECT_INTERCEPTOR) != null) {
                String[] interceptorNames = aspectElement.attribute(ASPECT_INTERCEPTOR).getValue().split(",");
                for (int index = 0; index < interceptorNames.length; index++) {
                    String name = interceptorNames[index].trim();
                    Element interceptorElement = (Element) interceptorElements.get(name);
                    if (interceptorElement == null) {
                        throw new AspectRuntimeException("No interceptor named " + name + " has been defined.");
                    }
                    InterceptorAspect interceptorAspect =
                            createInterceptorAspect(interceptorElement, pointcut, interceptorPrecedence, params);
                    aspectRuntime.addInterceptor(interceptorAspect);
                }
            }
            if (aspectElement.attribute(ASPECT_MIXIN) != null) {
                String[] mixinNames = aspectElement.attribute(ASPECT_MIXIN).getValue().split(",");
                for (int index = 0; index < mixinNames.length; index++) {
                    String name = mixinNames[index].trim();
                    Element mixinElement = (Element) mixinElements.get(name);
                    if (mixinElement == null) {
                        throw new AspectRuntimeException("No mixin named " + name + " has been defined.");
                    }
                    MixinAspect mixinAspect =
                            createMixinAspect(mixinElement, pointcut, params);
                    aspectRuntime.addMixin(mixinAspect);
                }
            }
            if (aspectElement.attribute(ASPECT_INTERFACE) != null) {
                String[] interfaceNames = aspectElement.attribute(ASPECT_INTERFACE).getValue().split(",");
                for (int index = 0; index < interfaceNames.length; index++) {
                    String name = interfaceNames[index].trim();
                    Element interfaceElement = (Element) interfaceElements.get(name);
                    if (interfaceElement == null) {
                        throw new AspectRuntimeException("No interface named " + name + " has been defined.");
                    }
                    InterfaceAspect interfaceAspect =
                            createInterfaceAspect(interfaceElement, pointcut);
                    aspectRuntime.addInterface(interfaceAspect);
                }
            }
            for (Iterator it = aspectElement.elementIterator(); it.hasNext();) {
                Element element = (Element) it.next();
                if (ASPECT_INTERCEPTOR.equals(element.getName())) {
                    InterceptorAspect interceptorAspect =
                            createInterceptorAspect(element, pointcut, interceptorPrecedence, params);
                    aspectRuntime.addInterceptor(interceptorAspect);
                } else if (ASPECT_MIXIN.equals(element.getName())) {
                    MixinAspect mixinAspect =
                            createMixinAspect(element, pointcut, params);
                    aspectRuntime.addMixin(mixinAspect);
                } else if (ASPECT_INTERFACE.equals(element.getName())) {
                    InterfaceAspect interfaceAspect =
                            createInterfaceAspect(element, pointcut);
                    aspectRuntime.addInterface(interfaceAspect);
                }
            }
        }
    }

    private InterceptorAspect createInterceptorAspect(Element interceptorElement, Pointcut pointcut,
                                                      String[] interceptorPrecedence, Map params) {
        InterceptorAspect aspect = new InterceptorAspectImpl();
        String aspectClassName = (String) interceptorElement.attribute(ASPECT_CLASS).getValue().trim();
        aspect.setAspectClass(ClassUtils.loadClass(aspectClassName));
        if (interceptorElement.attribute(ASPECT_SCOPE) != null) {
            aspect.setScope((String) interceptorElement.attribute(ASPECT_SCOPE).getValue().trim());
        }
        aspect.setPointcut(pointcut);
        int precedence = 0;
        if (interceptorElement.attribute(ASPECT_NAME) != null) {
            String name = interceptorElement.attribute(ASPECT_NAME).getValue();
            for (int i = 0; i < interceptorPrecedence.length; i++) {
                if (name.equals(interceptorPrecedence[i].trim())) {
                    precedence = interceptorPrecedence.length - i;
                    break;
                }
            }
        }
        aspect.setPrecedence(precedence);
        aspect.setParameters(params);
        return aspect;
    }

    private MixinAspect createMixinAspect(Element mixinElement, Pointcut pointcut, Map params) {
        MixinAspect aspect = new MixinAspectImpl();
        String interfaceClassName = (String) mixinElement.attribute(MIXIN_INTERFACE).getValue().trim();
        aspect.setInterfaceClass(ClassUtils.loadClass(interfaceClassName));
        String aspectClassName = (String) mixinElement.attribute(ASPECT_CLASS).getValue().trim();
        aspect.setAspectClass(ClassUtils.loadClass(aspectClassName));
        if (mixinElement.attribute(ASPECT_SCOPE) != null) {
            aspect.setScope((String) mixinElement.attribute(ASPECT_SCOPE).getValue().trim());
        }
        aspect.setPointcut(pointcut);
        aspect.setParameters(params);
        return aspect;
    }

    private InterfaceAspect createInterfaceAspect(Element interfaceElement, Pointcut pointcut) {
        InterfaceAspect aspect = new InterfaceAspectImpl();
        String aspectClassName = (String) interfaceElement.attribute(ASPECT_CLASS).getValue().trim();
        aspect.setAspectClass(ClassUtils.loadClass(aspectClassName));
        aspect.setPointcut(pointcut);
        return aspect;
    }
}
