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

import java.io.PrintStream;
import java.util.Iterator;

import org.seasar.mayaa.cycle.ServiceCycle;
import org.seasar.mayaa.cycle.scope.RequestScope;
import org.seasar.mayaa.cycle.script.CompiledScript;
import org.seasar.mayaa.engine.Page;
import org.seasar.mayaa.engine.Template;
import org.seasar.mayaa.engine.processor.ProcessorProperty;
import org.seasar.mayaa.engine.processor.ProcessorTreeWalker;
import org.seasar.mayaa.engine.processor.TemplateProcessor;
import org.seasar.mayaa.engine.specification.NodeAttribute;
import org.seasar.mayaa.engine.specification.PrefixAwareName;
import org.seasar.mayaa.engine.specification.PrefixMapping;
import org.seasar.mayaa.engine.specification.QName;
import org.seasar.mayaa.engine.specification.SpecificationNode;
import org.seasar.mayaa.engine.specification.URI;
import org.seasar.mayaa.impl.cycle.CycleUtil;
import org.seasar.mayaa.impl.engine.processor.ElementProcessor;
import org.seasar.mayaa.impl.engine.processor.LiteralCharactersProcessor;
import org.seasar.mayaa.impl.util.StringUtil;

/**
 * @author Koji Suga (Gluegent, Inc.)
 */
public class ProcessorDump extends ElementProcessor {
    // TODO EchoProcessorを継承して使っている部分をまとめる
    // TODO ServiceProviderで差し替え可能にする

    private static final long serialVersionUID = 8044884422670533823L;

    private static final PrintStream DEFAULT_OUT = System.out;

    private PrintStream _out = DEFAULT_OUT;
    private String _headerLine = "DUMPSTART =======================================";
    private String _footerLine = "DUMPEND =========================================";
    private String _indentChar = "    ";
    private boolean _printContents = false;

    public void setOut(PrintStream out) {
        _out = out;
    }

    public void setIndentChar(String indentChar) {
        if (indentChar == null) {
            throw new IllegalStateException("indentChar is null");
        }

        _indentChar = indentChar;
    }

    public void setPrintContents(boolean printContents) {
        _printContents = printContents;
    }

    public void printSource(Page topLevelPage) {
        ServiceCycle cycle = CycleUtil.getServiceCycle();
        RequestScope request = cycle.getRequestScope();
        String requestedSuffix = request.getRequestedSuffix();
        String extension = request.getExtension();

        Template template = topLevelPage.getTemplate(requestedSuffix, extension);

        print(_headerLine);
        print(template.getSystemID());
        printTree(0, template);
        print(_footerLine);
    }

    protected void printTree(int indentCount, ProcessorTreeWalker walker) {
// TODO レイアウト機能を使う場合
        int childSize = walker.getChildProcessorSize();
        if (walker instanceof TemplateProcessor) {
            printTag(indentCount, (TemplateProcessor) walker,
                    "<", (childSize > 0 ? ">" : " />"), true);
        }
        if (childSize > 0) {
            for (int i = 0; i < childSize; i++) {
                printTree(indentCount + 1, walker.getChildProcessor(i));
            }
            if (walker instanceof TemplateProcessor) {
                printTag(indentCount, (TemplateProcessor) walker, "</", ">", false);
            }
        }
    }

    protected void printTag(
            int indentCount, TemplateProcessor processor,
            String start, String end, boolean printAttributes) {
        if (_printContents && processor instanceof LiteralCharactersProcessor) {
            print(((LiteralCharactersProcessor) processor).getText());
// TODO insert の場合
// TODO echo の場合
        } else {
            StringBuffer sb = new StringBuffer(128);

            for (int i = 0; i < indentCount; i++) {
                sb.append(_indentChar);
            }

            sb.append(start);

            SpecificationNode node = getNode(processor);
            String prefix = "";
            PrefixMapping mapping =
                node.getMappingFromURI(node.getQName().getNamespaceURI(), true);
            if (mapping != null && StringUtil.hasValue(mapping.getPrefix())) {
                prefix = mapping.getPrefix() + ":";
            }
            sb.append(prefix);
            sb.append(node.getQName().getLocalName());

            if (printAttributes) {
                writeAttributes(sb, processor);
            }

            sb.append(end);

            if (false) {// TODO original
                sb.append("<!-- mayaa/original[");
                sb.append(processor.getOriginalNode().getQName().getLocalName());
                sb.append("] -->");
            }

            print(sb.toString());
        }
    }

    protected SpecificationNode getNode(TemplateProcessor processor) {
        if (processor instanceof ElementProcessor) {
            ElementProcessor ep = (ElementProcessor) processor;
            if (ep.isDuplicated()) {
                // TODO templateElementもoriginalNodeにする
                // ep.getInjectedNode().getQName().equals(QM_TEMPLATE_ELEMENT)
                return processor.getOriginalNode();
            }
        }
        return processor.getInjectedNode();
    }

    protected void writeAttributes(StringBuffer sb, TemplateProcessor processor) {
        if (processor instanceof ElementProcessor) {
            writeElementAttributes(sb, (ElementProcessor) processor);
        } else {
            SpecificationNode node = processor.getInjectedNode();
            URI namespace = node.getQName().getNamespaceURI();
            for (Iterator it = processor.getInjectedNode().iterateAttribute();
                    it.hasNext();) {
                NodeAttribute prop = (NodeAttribute) it.next();
                QName propName = prop.getQName();
                String prefix = "";
                if (namespace.equals(propName.getNamespaceURI()) == false) {
                    PrefixMapping mapping =
                        node.getMappingFromURI(propName.getNamespaceURI(), true);
                    if (mapping != null && StringUtil.hasValue(mapping.getPrefix())) {
                        prefix = mapping.getPrefix() + ":";
                    }
                }
                writeProcessorAttributeString(
                        sb, prefix, propName.getLocalName(), prop.getValue());
            }
        }
    }

    protected void writeElementAttributes(StringBuffer sb, ElementProcessor processor) {
        for (Iterator it = processor.iterateProcesstimeProperties(); it.hasNext();) {
            ProcessorProperty prop = (ProcessorProperty) it.next();
            appendAttributeString(sb, prop.getName(), prop.getValue());
        }
        for (Iterator it = processor.iterateInformalProperties(); it.hasNext();) {
            ProcessorProperty prop = (ProcessorProperty) it.next();
            if (hasProcesstimeProperty(prop) == false
                    && prop.getValue().isLiteral() == false) {
                appendAttributeString(sb, prop.getName(), prop.getValue());
            }
        }
        for (Iterator it = processor.iterateInformalProperties(); it.hasNext();) {
            ProcessorProperty prop = (ProcessorProperty) it.next();
            if (hasProcesstimeProperty(prop) == false
                    && prop.getValue().isLiteral()) {
                appendAttributeString(sb, prop.getName(), prop.getValue());
            }
        }
    }

    protected void appendAttributeString(
            StringBuffer buffer, PrefixAwareName propName, Object value) {
        QName qName = propName.getQName();
        if (URI_MAYAA.equals(qName.getNamespaceURI())) {
            return;
        }
        // TODO 正しく描画する
//        if (getInjectedNode().getQName().equals(QM_DUPLECATED)) {
//            if (getChildProcessorSize() > 0
//                    && getChildProcessor(0) instanceof JspProcessor) {
//                JspProcessor processor = (JspProcessor)getChildProcessor(0);
//                URI injectNS = processor.getInjectedNode().getQName().getNamespaceURI();
//                if (injectNS == qName.getNamespaceURI()) {
//                    return;
//                }
//            }
//        }

        String attrPrefix = propName.getPrefix();
        if (StringUtil.hasValue(attrPrefix)) {
//            attrPrefix = getResolvedPrefix(propName);
            if (StringUtil.hasValue(attrPrefix)) {
                attrPrefix = attrPrefix + ":";
            }
        }
        StringBuffer temp = new StringBuffer();
        temp.append(" ");
        temp.append(attrPrefix);
        temp.append(qName.getLocalName());
        /* 2007.03.15 valueがnullの場合を許容する(値なしを作成可能に) */
        if (value != null) {
	        temp.append("=\"");
	        if (value instanceof CompiledScript) {
	            CompiledScript script = (CompiledScript) value;
	            if (CycleUtil.isDraftWriting()) {
	                temp.append(script.getScriptText());
	            } else {
try { // TODO 修正する [JIRA: MAYAA-5]
	                Object result = script.execute(null);
	                if (StringUtil.isEmpty(result)) {
	                    return;
	                }
	                temp.append(result);
} catch (Throwable ignore) {
	    // no-op
}
	            }
	        } else {
	            temp.append(value.toString());
	        }
	        temp.append("\"");
        }
        buffer.append(temp.toString());
    }

    protected void writeProcessorAttributeString(
            StringBuffer sb, String prefix, String localName, Object value) {
        sb.append(" ");
        sb.append(prefix);
        sb.append(localName);
        sb.append("=\"");
        if (value instanceof CompiledScript) {
            CompiledScript script = (CompiledScript) value;
            sb.append(script.getScriptText());
        } else {
            sb.append(value.toString());
        }
        sb.append("\"");
    }

    protected void print(String value) {
        _out.println(value);
    }

}
