/*
 *
 * The Seasar Software License, Version 1.1
 *
 * Copyright (c) 2003-2004 The Seasar Project. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 *
 * 1. Redistributions of source code must retain the above
 *    copyrightnotice, this list of conditions and the following
 *    disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials provided
 *    with the distribution.
 *
 * 3. The end-user documentation included with the redistribution,
 *    if any, must include the following acknowledgement:
 *    "This product includes software developed by the
 *    Seasar Project (http://www.seasar.org/)."
 *    Alternately, this acknowledgement may appear in the software
 *    itself, if and wherever such third-party acknowledgements
 *    normally appear.
 *
 * 4. Neither the names "The Seasar Project" nor　the name of its
 *    contributors ay be used to endour or promote products derived
 *    from this software without specific prior written permission of
 *    the Seasar Project.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIESOF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE SEASER　PROJECT
 * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL,SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
 * WHETHER IN CONTRACT, STRICT LIABILITY,OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

package org.seasar.groovy;

import groovy.lang.Closure;

import groovy.util.BuilderSupport;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.seasar.framework.aop.Pointcut;
import org.seasar.framework.aop.impl.PointcutImpl;
import org.seasar.framework.container.ArgDef;
import org.seasar.framework.container.AspectDef;
import org.seasar.framework.container.ComponentDef;
import org.seasar.framework.container.DestroyMethodDef;
import org.seasar.framework.container.InitMethodDef;
import org.seasar.framework.container.MethodDef;
import org.seasar.framework.container.PropertyDef;
import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.S2ContainerFactory;
import org.seasar.framework.container.impl.ArgDefImpl;
import org.seasar.framework.container.impl.AspectDefImpl;
import org.seasar.framework.container.impl.ComponentDefImpl;
import org.seasar.framework.container.impl.DestroyMethodDefImpl;
import org.seasar.framework.container.impl.InitMethodDefImpl;
import org.seasar.framework.container.impl.PropertyDefImpl;
import org.seasar.framework.container.impl.S2ContainerImpl;
import org.seasar.framework.container.impl.SimpleComponentDef;
import org.seasar.framework.util.StringUtil;

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


/**
 * GroovyMarkupによってS2コンテナーを作成するためのビルダー。
 *
 * @author takai
 *
 * @see groovy.util.BuilderSupport
 */
public class SeasarBuilder
    extends BuilderSupport
{
    protected static final Log logger = LogFactory.getLog(SeasarBuilder.class);

    public SeasarBuilder() {
    }

    protected void setParent(Object parent, Object child) {
    }

    /**
     * @see groovy.util.BuilderSupport#createNode(java.lang.Object)
     */
    protected Object createNode(Object name) {
        if (name.equals("container") || name.equals("components")) {
            logger.debug("Create S2Container.");

            return new S2ContainerImpl();
        }

        if (name.equals("arg")) {
            logger.debug("Create ArgDef.");

            return new ArgDefImpl();
        }

        throw new SeasarBuilderException("Unknown markup; " + name);
    }

    /**
     * @see groovy.util.BuilderSupport#createNode(java.lang.Object,
     *      java.util.Map)
     */
    protected Object createNode(Object name, Map attributes) {
        if (name.equals("component")) {
            logger.debug("Create ComponentDef with attributes; " + attributes);

            return setupComponentDef(attributes);
        }

        if (name.equals("prop")) {
            logger.debug("Create PropertyDef with attributes; " + attributes);

            return setupPropertyDef(attributes);
        }

        if (name.equals("arg")) {
            logger.debug("Create ArgDef with attributes; " + attributes);

            return setupArgDef(attributes);
        }

        if (name.equals("aspect")) {
            logger.debug("Create AspectDef with attributes; " + attributes);

            return setupAspectDef(attributes);
        }

        throw new SeasarBuilderException("Unknown markup; " + name
                                         + " with attributes; " + attributes);
    }

    /**
     * @see groovy.util.BuilderSupport#createNode(java.lang.Object,
     *      java.lang.Object)
     */
    protected Object createNode(Object name, Object value) {
        if (name.equals("container") || name.equals("components")) {
            logger.debug("Create S2Container with namespace `" + value + "'.");

            S2Container s2 = new S2ContainerImpl();
            s2.setNamespace((String) value);

            return s2;
        }

        if (name.equals("component")) {
            Map attributes = new HashMap();

            if (value instanceof Class) {
                attributes.put("class", value);
            } else {
                attributes.put("obj", value);
            }

            return createNode(name, attributes);
        }

        if (name.equals("arg")) {
            logger.debug("Create ArgDef with value; " + value);

            return new ArgDefImpl(value);
        }

        if (name.equals("init")) {
            logger.debug("Create InitMethodDef with value; " + value);

            return new InitMethodDefImpl((String) value);
        }

        if (name.equals("destroy")) {
            logger.debug("Create DestroyDef with value; " + value);

            return new DestroyMethodDefImpl((String) value);
        }

        if (name.equals("include")) {
            if (getCurrent() == null) {
                throw new SeasarBuilderException("S2Container#include() is not defined.");
            }

            logger.debug("Include " + value);

            return S2ContainerFactory.create((String) value);
        }

        throw new SeasarBuilderException("Unknown markup; " + name
                                         + " with value; " + value);
    }

    /**
     * @see groovy.util.BuilderSupport#nodeCompleted(java.lang.Object,
     *      java.lang.Object)
     */
    protected void nodeCompleted(Object parent, Object node) {
        if ((parent == null) && node instanceof S2Container) {
            return;
        }

        if (parent instanceof S2Container) {
            setChildToS2Container((S2Container) parent, node);

            return;
        }

        if (parent instanceof ComponentDef) {
            setChildToComponent((ComponentDef) parent, node);

            return;
        }

        if (parent instanceof ArgDef) {
            setChildToArg((ArgDef) parent, node);

            return;
        }

        if (parent instanceof MethodDef) {
            setChildToMethod((MethodDef) parent, node);

            return;
        }

        throw new SeasarBuilderException(parent + " can't be parent of " + node);
    }

    private void setChildToArg(ArgDef def, Object child) {
        if (child instanceof ComponentDef) {
            logger.debug("Setting " + child + " as child component for " + def);

            def.setChildComponentDef((ComponentDef) child);
        } else {
            throw new SeasarBuilderException("ArgDef can't be parent of "
                                             + child);
        }
    }

    private void setChildToComponent(ComponentDef def, Object child) {
        if (child instanceof PropertyDef) {
            logger.debug("Adding " + child + " as PropertyDef to " + def);
            def.addPropertyDef((PropertyDef) child);
        } else if (child instanceof AspectDef) {
            logger.debug("Adding " + child + " as AspectDef to " + def);
            def.addAspectDef((AspectDef) child);
        } else if (child instanceof ArgDef) {
            logger.debug("Adding " + child + " as ArgDef to " + def);
            def.addArgDef((ArgDef) child);
        } else if (child instanceof InitMethodDef) {
            logger.debug("Adding " + child + " as InitMethodDef to " + def);
            def.addInitMethodDef((InitMethodDef) child);
        } else if (child instanceof DestroyMethodDef) {
            logger.debug("Adding " + child + " as DestroyMethod to " + def);
            def.addDestroyMethodDef((DestroyMethodDef) child);
        } else {
            throw new SeasarBuilderException("ComponentDef can't be parent of "
                                             + child);
        }
    }

    private void setChildToMethod(MethodDef def, Object child) {
        if (child instanceof ArgDef) {
            logger.debug("Adding " + child + " as ArgDef to " + def);

            def.addArgDef((ArgDef) child);
        } else {
            throw new SeasarBuilderException("MethodDef can't be parent of "
                                             + child);
        }
    }

    private void setChildToS2Container(S2Container container, Object child) {
        if (child instanceof ComponentDef) {
            logger.debug("Registering " + child + " as ComponentDef to "
                         + container);
            container.register((ComponentDef) child);
        } else if (child instanceof S2Container) {
            logger.debug("Including " + child + " to " + container);
            container.include((S2Container) child);
        } else {
            throw new SeasarBuilderException("S2Container can't be parent of "
                                             + child);
        }
    }

    private ArgDef setupArgDef(Map attributes) {
        String referenceExpression = (String) attributes.get("ref");

        ArgDef def = new ArgDefImpl();
        def.setExpression(referenceExpression);

        return def;
    }

    private AspectDef setupAspectDef(Map attributes) {
        AspectDef def = null;
        String method = (String) attributes.get("pointcut");

        if (attributes.get("advice") instanceof String) {
            if (method != null) {
                Pointcut pointcut =
                    new PointcutImpl(StringUtil.split(method, ","));
                def = new AspectDefImpl(pointcut);
            } else {
                def = new AspectDefImpl();
            }

            def.setExpression((String) attributes.get("advice"));
        } else if (attributes.get("advice") instanceof Closure) {
            ClosureAroundAdvice advice =
                new ClosureAroundAdvice((Closure) attributes.get("advice"));

            if (method != null) {
                Pointcut pointcut =
                    new PointcutImpl(StringUtil.split(method, ","));
                def = new AspectDefImpl(advice, pointcut);
            } else {
                def = new AspectDefImpl(advice);
            }
        } else {
            throw new SeasarBuilderException("'advice' must be an expression string or a closure.");
        }

        return def;
    }

    private ComponentDef setupComponentDef(Map attributes) {
        ComponentDef def = null;

        Class cls = (Class) attributes.get("class");
        Object obj = (Object) attributes.get("obj");
        String name = (String) attributes.get("name");
        String destroy = (String) attributes.get("destroy");
        String instance = (String) attributes.get("instance");
        String binding = (String) attributes.get("binding");

        if ((cls == null) && (obj == null)) {
            throw new SeasarBuilderException("Neither class nor object is pass, or null?");
        }

        if (name != null) {
            if (cls != null) {
                def = new ComponentDefImpl(cls, name);
            } else {
                def = new SimpleComponentDef(obj, name);
            }
        } else {
            if (cls != null) {
                def = new ComponentDefImpl(cls);
            } else {
                def = new SimpleComponentDef(obj);
            }
        }

        if (instance != null) {
            def.setInstanceMode(instance);
        }

        if (binding != null) {
            def.setAutoBindingMode(binding);
        }

        return def;
    }

    private PropertyDef setupPropertyDef(Map attributes) {
        String propertyName = (String) attributes.get("name");
        Object propertyValue = attributes.get("value");
        String referenceExpression = (String) attributes.get("ref");

        boolean valIsNotNull = propertyValue != null;
        boolean refIsNotNull = referenceExpression != null;

        if (valIsNotNull && refIsNotNull) {
            throw new SeasarBuilderException("'value' and 'ref' on 'prop' is mutually exclusive parameter.");
        }

        if (valIsNotNull) {
            return new PropertyDefImpl(propertyName, propertyValue);
        } else if (refIsNotNull) {
            PropertyDef def = new PropertyDefImpl(propertyName);
            def.setExpression(referenceExpression);

            return def;
        } else {
            throw new SeasarBuilderException("'value' or 'ref' must be set.");
        }
    }

	protected Object createNode(Object name, Map attributes, Object value) {
		return null;
	}
}
