/*
 * Copyright 2010 Red Hat, Inc. and/or its affiliates.
 *
 * 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.jbpm.compiler.xml.compiler;

import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

import org.drools.compiler.lang.ReflectiveVisitor;
import org.drools.drl.ast.descr.AccumulateDescr;
import org.drools.drl.ast.descr.AccumulateDescr.AccumulateFunctionCallDescr;
import org.drools.drl.ast.descr.AndDescr;
import org.drools.drl.ast.descr.AttributeDescr;
import org.drools.drl.ast.descr.BindingDescr;
import org.drools.drl.ast.descr.CollectDescr;
import org.drools.drl.ast.descr.EvalDescr;
import org.drools.drl.ast.descr.ExistsDescr;
import org.drools.drl.ast.descr.ExprConstraintDescr;
import org.drools.drl.ast.descr.ForallDescr;
import org.drools.drl.ast.descr.FromDescr;
import org.drools.drl.ast.descr.FunctionDescr;
import org.drools.drl.ast.descr.FunctionImportDescr;
import org.drools.drl.ast.descr.GlobalDescr;
import org.drools.drl.ast.descr.ImportDescr;
import org.drools.drl.ast.descr.LiteralRestrictionDescr;
import org.drools.drl.ast.descr.NotDescr;
import org.drools.drl.ast.descr.OrDescr;
import org.drools.drl.ast.descr.PackageDescr;
import org.drools.drl.ast.descr.PackageDescrDumper;
import org.drools.drl.ast.descr.PatternDescr;
import org.drools.drl.ast.descr.PredicateDescr;
import org.drools.drl.ast.descr.QualifiedIdentifierRestrictionDescr;
import org.drools.drl.ast.descr.QueryDescr;
import org.drools.drl.ast.descr.ReturnValueRestrictionDescr;
import org.drools.drl.ast.descr.RuleDescr;
import org.drools.drl.ast.descr.VariableRestrictionDescr;
import org.drools.util.StringUtils;

/**
 * This utility will take a AST of a rule package, and emit XML.
 * This can be used in porting from DRL to XML.
 */
public class XmlDumper extends ReflectiveVisitor
        implements
        PackageDescrDumper {

    private StringBuilder xmlDump;
    private boolean patternContext;
    private static final String eol = System.getProperty("line.separator");
    private String template;

    public synchronized String dump(final PackageDescr packageDescr) {
        this.xmlDump = new StringBuilder();
        visitPackageDescr(packageDescr);
        return this.xmlDump.toString();
    }

    public void visitAndDescr(final AndDescr descr) {
        this.template = "";
        if (descr.getDescrs() != Collections.EMPTY_LIST) {
            if (!this.patternContext)
                this.template = "<and-conditional-element>" + processDescrList(descr.getDescrs()) + "</and-conditional-element>";
            else
                this.template = "<and-constraint-connective>" + processDescrList(descr.getDescrs()) + "</and-constraint-connective>";
        }
    }

    public void visitAttributeDescr(final AttributeDescr attributeDescr) {
        this.template = "";
        this.template = "<rule-attribute name=\"" + attributeDescr.getName() + "\" value=\"" + attributeDescr.getValue() + "\" />" + XmlDumper.eol;
    }

    public void visitVariableRestrictionDescr(final VariableRestrictionDescr descr) {
        this.template = "";
        this.template = "<variable-restriction evaluator=\"" + replaceIllegalChars(descr.getEvaluator()) + "\" identifier=\"" + descr.getIdentifier() + "\" />" + XmlDumper.eol;
    }

    public void visitPatternDescr(final PatternDescr descr) {
        this.patternContext = true;
        this.template = "";
        StringBuilder localString = new StringBuilder();

        if (descr.getDescrs() != Collections.EMPTY_LIST) {
            if (descr.getIdentifier() != null) {
                localString.append(
                        "<pattern identifier=\"" + descr.getIdentifier() + "\" object-type=\"" + descr.getObjectType() + "\" >" + XmlDumper.eol + processDescrList(descr.getDescrs()) + XmlDumper.eol);
            } else {
                localString.append("<pattern object-type=\"" + descr.getObjectType() + "\" >" + XmlDumper.eol + processDescrList(descr.getDescrs()) + XmlDumper.eol);
            }
        } else {
            if (descr.getIdentifier() != null) {
                localString.append("<pattern identifier=\"" + descr.getIdentifier() + "\" object-type=\"" + descr.getObjectType() + "\" >" + XmlDumper.eol);
                ;
            } else {
                localString.append("<pattern object-type=\"" + descr.getObjectType() + "\" >" + XmlDumper.eol);
            }
        }

        if (descr.getSource() != null) {
            visit(descr.getSource());
            localString.append(this.template);
        }
        localString.append("</pattern>" + XmlDumper.eol);

        this.template = localString.toString();

        this.patternContext = false;
    }

    public void visitExprConstraintDescr(final ExprConstraintDescr descr) {
        this.template = "<expr>" + XmlDumper.eol + StringUtils.escapeXmlString(descr.getExpression()) + XmlDumper.eol + "</expr>";
    }

    public void visitCollectDescr(final CollectDescr descr) {
        StringBuilder tmpstr = new StringBuilder();
        tmpstr.append("<from> <collect>");
        visit(descr.getInputPattern());
        tmpstr.append(this.template);
        tmpstr.append(" </collect> </from> ");
        this.template = tmpstr.toString();
    }

    public void visitAccumulateDescr(final AccumulateDescr descr) {
        String tmpstr = "";
        tmpstr += this.template + " <from> <accumulate> ";

        if (descr.isSinglePattern()) {
            visitPatternDescr(descr.getInputPattern());
        } else {
            this.patternContext = false;
            visit(descr.getInput());
        }
        tmpstr += this.template;

        if (descr.isExternalFunction()) {
            AccumulateFunctionCallDescr func = descr.getFunctions().get(0);
            tmpstr += "<external-function evaluator=\"" + func.getFunction() + "\" expression=\"" + func.getParams()[0] + "\"/>";
        } else
            tmpstr += "<init>" + descr.getInitCode() + "</init><action>" + descr.getActionCode() + "</action><result>" + descr.getResultCode() + "</result>";

        this.template = tmpstr + " </accumulate> </from> ";
    }

    public void visitFromDescr(final FromDescr descr) {
        String tmpstr = "";
        tmpstr += this.template + " <from> <expression> ";
        tmpstr += descr.getDataSource();
        this.template = tmpstr + " </expression> </from> ";
    }

    public void visitForallDescr(final ForallDescr descr) {
        this.template = "<forall>" + processDescrList(descr.getDescrs()) + "</forall>";
    }

    public void visitEvalDescr(final EvalDescr descr) {
        this.template = "";
        this.template = "<eval>" + replaceIllegalChars((String) descr.getContent()) + "</eval>" + XmlDumper.eol;
    }

    public void visitExistsDescr(final ExistsDescr descr) {
        this.template = new String();
        if (descr.getDescrs() != Collections.EMPTY_LIST) {
            this.template = "<exists>" + processDescrList(descr.getDescrs()) + "</exists>";
        } else {
            this.template = "<exists> </exists>";
        }
    }

    public void visitBindingDescr(final BindingDescr descr) {
        this.template = new String();
        this.template = "<field-binding field-name=\"" + descr.getExpression() + "\" identifier=\"" + descr.getVariable() + "\" />" + XmlDumper.eol;
    }

    public void visitFunctionDescr(final FunctionDescr functionDescr) {
        this.template = new String();
        final String parameterTemplate = processParameters(functionDescr.getParameterNames(),
                functionDescr.getParameterTypes());

        this.template = "<function return-type=\"" + functionDescr.getReturnType() + "\" name=\"" + functionDescr.getName() + "\">" + XmlDumper.eol + parameterTemplate + "<body>" + XmlDumper.eol
                + replaceIllegalChars(functionDescr.getText())
                + XmlDumper.eol + "</body>" + XmlDumper.eol + "</function>" + XmlDumper.eol;
    }

    public void visitLiteralRestrictionDescr(final LiteralRestrictionDescr descr) {
        this.template = new String();
        this.template = "<literal-restriction evaluator=\"" + replaceIllegalChars(descr.getEvaluator()) + "\" value=\"" + replaceIllegalChars(descr.getText()) + "\" />" + XmlDumper.eol;
    }

    public void visitQualifiedIdentifierRestrictionDescr(final QualifiedIdentifierRestrictionDescr descr) {
        this.template = new String();
        this.template = "<qualified-identifier-restriction evaluator=\"" + replaceIllegalChars(descr.getEvaluator()) + "\">" + replaceIllegalChars(descr.getText())
                + " </qualified-identifier-restriction>" + XmlDumper.eol;
    }

    public void visitNotDescr(final NotDescr descr) {
        this.template = new String();
        if (descr.getDescrs() != Collections.EMPTY_LIST) {
            this.template = "<not>" + processDescrList(descr.getDescrs()) + "</not>";
        } else {
            this.template = "<not> </not>";
        }

    }

    public void visitOrDescr(final OrDescr descr) {
        this.template = new String();
        if (descr.getDescrs() != Collections.EMPTY_LIST) {
            if (!this.patternContext)
                this.template = "<or-conditional-element>" + processDescrList(descr.getDescrs()) + "</or-conditional-element>";
            else
                this.template = "<or-constraint-connective>" + processDescrList(descr.getDescrs()) + "</or-constraint-connective>";
        }
    }

    public void visitPackageDescr(final PackageDescr packageDescr) {
        final String packageName = packageDescr.getName();
        final String xmlString = "<?xml version=\"1.0\" encoding=\"UTF-8\"?> " + XmlDumper.eol + " <package name=\"" + packageName + "\"  " + XmlDumper.eol
                + "\txmlns=\"http://drools.org/drools-5.2\" " + XmlDumper.eol
                + "\txmlns:xs=\"http://www.w3.org/2001/XMLSchema-instance\" " + XmlDumper.eol + "\txs:schemaLocation=\"http://drools.org/drools-5.2 drools.org/drools-5.2.xsd\"> " + XmlDumper.eol;
        appendXmlDump(xmlString);
        appendXmlDump(processImportsList(packageDescr.getImports()));
        appendXmlDump(processGlobalsList(packageDescr.getGlobals()));
        appendXmlDump(processFunctionImportsList(packageDescr.getFunctionImports()));
        appendXmlDump(processFunctionsList(packageDescr.getFunctions()));
        appendXmlDump(processRules(packageDescr.getRules()));
        appendXmlDump("</package>");
    }

    public void visitPredicateDescr(final PredicateDescr descr) {
        this.template = new String();
        this.template = "<predicate>" + replaceIllegalChars((String) descr.getContent()) + "</predicate>" + XmlDumper.eol;
    }

    public void visitReturnValueRestrictionDescr(final ReturnValueRestrictionDescr descr) {
        this.template = new String();
        this.template = "<return-value-restriction evaluator=\"" + replaceIllegalChars(descr.getEvaluator()) + "\" >" + replaceIllegalChars((String) descr.getContent()) + "</return-value-restriction>"
                + XmlDumper.eol;
    }

    public void visitQueryDescr(final QueryDescr descr) {
        this.template = new String();
        this.template = "<query name=\"" + descr.getName() + "\">" + "<lhs>" + processDescrList(descr.getLhs().getDescrs()) + "</lhs>" + "</query>";
    }

    private String processRules(final List rules) {
        StringBuilder ruleList = new StringBuilder();
        for (final Iterator iterator = rules.iterator(); iterator.hasNext();) {
            final RuleDescr ruleDescr = (RuleDescr) iterator.next();
            String rule = "<rule name=\"" + ruleDescr.getName() + "\">" + XmlDumper.eol;
            final String attribute = processAttribute(ruleDescr.getAttributes().values());
            String lhs = "";
            if (ruleDescr.getLhs().getDescrs() != Collections.EMPTY_LIST) {
                lhs = "<lhs>" + processDescrList(ruleDescr.getLhs().getDescrs()) + "</lhs>";
            } else {

                lhs = "<lhs> </lhs>";
            }

            final String rhs = "<rhs>" + replaceIllegalChars((String) ruleDescr.getConsequence()) + "</rhs>" + XmlDumper.eol;
            rule += attribute;
            rule += lhs;
            rule += rhs;
            rule += "</rule>";
            ruleList.append(rule);
        }

        return ruleList + XmlDumper.eol;
    }

    private String processDescrList(final List descr) {
        StringBuilder descrString = new StringBuilder();
        for (final Iterator iterator = descr.iterator(); iterator.hasNext();) {
            visit(iterator.next());
            descrString.append(this.template);
            descrString.append(XmlDumper.eol);
        }
        return descrString + XmlDumper.eol;
    }

    private String processFunctionsList(final List functions) {
        StringBuilder functionList = new StringBuilder();

        for (final Iterator iterator = functions.iterator(); iterator.hasNext();) {
            visit(iterator.next());
            functionList.append(this.template);
        }

        return functionList + XmlDumper.eol;
    }

    private String processAttribute(final Collection<AttributeDescr> attributes) {

        StringBuilder attributeList = new StringBuilder();
        for (final AttributeDescr attributeDescr : attributes) {
            visit(attributeDescr);
            attributeList.append(this.template);
        }
        return attributeList + XmlDumper.eol;
    }

    private String processParameters(final List parameterNames,
            final List parameterTypes) {
        StringBuilder paramList = new StringBuilder();
        int i = 0;
        for (final Iterator iterator = parameterNames.iterator(); iterator.hasNext(); i++) {
            final String paramName = (String) iterator.next();
            final String paramType = (String) parameterTypes.get(i);
            final String paramTemplate = "<parameter identifier=\"" + paramName + "\" type=\"" + paramType + "\" />" + XmlDumper.eol;
            paramList.append(paramTemplate);
        }

        return paramList + XmlDumper.eol;
    }

    private String processFunctionImportsList(final List imports) {
        StringBuilder importList = new StringBuilder();

        for (final Iterator it = imports.iterator(); it.hasNext();) {
            final String importString = ((FunctionImportDescr) it.next()).getTarget();
            final String importTemplate = "<importfunction name=\"" + importString + "\"/>" + XmlDumper.eol;
            importList.append(importTemplate);
        }
        return importList + XmlDumper.eol;
    }

    private String processGlobalsList(final List globals) {
        StringBuilder globalList = new StringBuilder();
        for (final Iterator iterator = globals.iterator(); iterator.hasNext();) {
            final GlobalDescr global = (GlobalDescr) iterator.next();
            final String identifier = global.getIdentifier();
            final String type = global.getType();
            final String globalTemplate = "<global identifier=\"" + identifier + "\" type=\"" + type + "\" />" + XmlDumper.eol;
            globalList.append(globalTemplate);
        }

        return globalList + XmlDumper.eol;
    }

    private String processImportsList(final List imports) {
        StringBuilder importList = new StringBuilder();

        for (final Iterator iterator = imports.iterator(); iterator.hasNext();) {
            final String importString = ((ImportDescr) iterator.next()).getTarget();
            final String importTemplate = "<import name=\"" + importString + "\" /> " + XmlDumper.eol;
            importList.append(importTemplate);
        }
        return importList + XmlDumper.eol;
    }

    private void appendXmlDump(final String temp) {
        this.xmlDump.append(temp);
    }

    /**
     * Replace illegal xml characters with their escaped equivalent
     * <P>
     * The escaped characters are :
     * <ul>
     * <li><
     * <li>>
     * <li>&
     * </ul>
     * </p>
     */
    public static String replaceIllegalChars(final String code) {
        final StringBuilder sb = new StringBuilder();
        if (code != null) {
            final int n = code.length();
            for (int i = 0; i < n; i++) {
                final char c = code.charAt(i);
                switch (c) {
                    case '<':
                        sb.append("&lt;");
                        break;
                    case '>':
                        sb.append("&gt;");
                        break;
                    case '&':
                        sb.append("&amp;");
                        break;
                    default:
                        sb.append(c);
                        break;
                }
            }
        } else {
            sb.append("null");
        }
        return sb.toString();
    }
}
