/*
 * Copyright 2004-2012 the Seasar Foundation and the Others.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
 * either express or implied. See the License for the specific language
 * governing permissions and limitations under the License.
 */
package org.seasar.mayaa.impl.builder.library;

import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Iterator;

import javax.servlet.jsp.tagext.TagData;
import javax.servlet.jsp.tagext.TagExtraInfo;
import javax.servlet.jsp.tagext.VariableInfo;

import org.seasar.mayaa.builder.library.PropertyDefinition;
import org.seasar.mayaa.builder.library.PropertySet;
import org.seasar.mayaa.cycle.script.CompiledScript;
import org.seasar.mayaa.engine.processor.ProcessorProperty;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.impl.engine.processor.JspProcessor;
import org.seasar.mayaa.impl.util.ObjectUtil;

/**
 * @author Masataka Kurihara (Gluegent, Inc.)
 * @author Hisayoshi Sasaki (Gluegent, Inc.)
 */
public class TLDProcessorDefinition extends ProcessorDefinitionImpl {

    private static final long serialVersionUID = -646585734601397162L;
    public static final String BODY_CONTENT_EMPTY = "empty";
    public static final String BODY_CONTENT_JSP = "JSP";
    public static final String BODY_CONTENT_SCRIPTLESS = "scriptless";
    public static final String BODY_CONTENT_TAGDEPENDENT = "tagdependent";

    private Class _tagClass;
    private Class _teiClass;
    private boolean _dynamicAttribute;
    private String _bodyContent = BODY_CONTENT_JSP;

    public void setProcessorClass(Class processorClass) {
        if (JspProcessor.isSupportClass(processorClass) == false) {
            throw new IllegalArgumentException();
        }
        _tagClass = processorClass;
    }

    public Class getProcessorClass() {
        if (_tagClass == null) {
            throw new IllegalStateException();
        }
        return _tagClass;
    }

    public void setExtraInfoClass(Class teiClass) {
        if (teiClass == null
                || TagExtraInfo.class.isAssignableFrom(teiClass) == false) {
            throw new IllegalArgumentException();
        }
        _teiClass = teiClass;
    }

    public Class getExtraInfoClass() {
        return _teiClass;
    }

    public boolean isDynamicAttribute() {
        return _dynamicAttribute;
    }

    public void setDynamicAttribute(boolean dynamicAttribute) {
        this._dynamicAttribute = dynamicAttribute;
    }

    public String getBodyContent() {
        if (_bodyContent == null) {
            throw new IllegalStateException();
        }
        return _bodyContent;
    }

    public void setBodyContent(String bodyContent) {
        if ((BODY_CONTENT_EMPTY.equals(bodyContent) ||
                BODY_CONTENT_JSP.equals(bodyContent) ||// for JSP 1.2
                BODY_CONTENT_SCRIPTLESS.equals(bodyContent) ||
                BODY_CONTENT_TAGDEPENDENT.equals(bodyContent)) == false) {
            throw new IllegalArgumentException();
        }
        _bodyContent = bodyContent;
    }

    protected TemplateProcessor newInstance() {
        JspProcessor processor = new JspProcessor();
        processor.setTagClass(getProcessorClass());
        return processor;
    }

    protected void settingPropertySet(
            SpecificationNode original, SpecificationNode injected,
            TemplateProcessor processor, PropertySet propertySet) {
        Hashtable tagDataSeed = new Hashtable();

        JspProcessor jspProcessor = (JspProcessor) processor;
        for (Iterator it = propertySet.iteratePropertyDefinition();
                it.hasNext();) {
            PropertyDefinition property = (PropertyDefinition) it.next();
            Object prop =
                property.createProcessorProperty(this, jspProcessor, original, injected);
            if (prop != null) {
                ProcessorProperty currentProp = (ProcessorProperty) prop;
                jspProcessor.addProcessorProperty(currentProp);

                CompiledScript value = currentProp.getValue();
                tagDataSeed.put(
                        currentProp.getName().getQName().getLocalName(),
                        prepareScript(value));
            }
        }

        if (getExtraInfoClass() != null) {
            settingExtraInfo(jspProcessor, tagDataSeed);
        }

        if (getBodyContent().equals(BODY_CONTENT_EMPTY)) {
            jspProcessor.setForceBodySkip(true);
        }
        // TODO 他のbody-content対応
    }

    private Object prepareScript(CompiledScript script) {
        if (script.isLiteral()) {
            return script.execute(null);
        }
        return script;
    }

    protected void settingExtraInfo(
            JspProcessor processor, Hashtable seed) {
        TagExtraInfo tei =
                (TagExtraInfo) ObjectUtil.newInstance(getExtraInfoClass());

        boolean hasNestedVariable = existsNestedVariable(tei, seed);
        boolean hasDynamicName = existsDynamicName(seed);

        TLDScriptingVariableInfo variableInfo = new TLDScriptingVariableInfo();

        variableInfo.setTagExtraInfo(tei);
        variableInfo.setNestedVariable(hasNestedVariable);
        variableInfo.setDynamicName(hasDynamicName);
        if (hasNestedVariable) {
            ScriptableTagData tagData = new ScriptableTagData(seed);
            if (hasDynamicName) {
                variableInfo.setTagData(tagData);
            } else {
                variableInfo.setVariableInfos(tei.getVariableInfo(tagData));
            }
        }

        processor.setTLDScriptingVariableInfo(variableInfo);
    }

    protected boolean existsNestedVariable(
            TagExtraInfo tei, Hashtable seed) {
        VariableInfo[] dummy = tei.getVariableInfo(new DummyTagData(seed));
        if (dummy != null) {
            for (int i = 0; i < dummy.length; i++) {
                if (dummy[i].getScope() == VariableInfo.NESTED) {
                    return true;
                }
            }
        }
        return false;
    }

    protected boolean existsDynamicName(Hashtable seed) {
        Enumeration keys = seed.keys();
        while (keys.hasMoreElements()) {
            if (seed.get(keys.nextElement()) instanceof CompiledScript) {
                return true;
            }
        }
        return false;
    }

    protected static class DummyTagData extends TagData {
        public DummyTagData(Hashtable seed) {
            super(seed);
        }

        public Object getAttribute(String attName) {
            return attName;
        }

        public String getAttributeString(String attName) {
            return attName;
        }
    }

    protected static class ScriptableTagData extends TagData {
        public ScriptableTagData(Hashtable seed) {
            super(seed);
        }

        public boolean isDynamicAttribute(String attName) {
            return (super.getAttribute(attName) instanceof CompiledScript);
        }

        public Object getAttribute(String attName) {
            Object value = super.getAttribute(attName);
            if (value instanceof CompiledScript) {
                return ((CompiledScript) value).execute(null);
            }
            return value;
        }

        public String getAttributeString(String attName) {
            Object value = getAttribute(attName);
            if (value != null) {
                return String.valueOf(value);
            }
            return (String) value;
        }
    }

}
