/*
 * Copyright 2001-2004 The Apache Software Foundation.
 *
 * 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.
 */
/**
 * JOnAS : Java(TM) OpenSource Application Server
 * Copyright (C) 2004 Bull S.A.
 * Contact: jonas-team@ow2.org
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307
 * USA
 *
 * --------------------------------------------------------------------------
 * $Id$
 * --------------------------------------------------------------------------
 */
package org.ow2.jonas.generators.wsgen.generator.ews.wsdltoj2ee.writer;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;

import javax.wsdl.Binding;
import javax.wsdl.Definition;
import javax.wsdl.OperationType;
import javax.xml.namespace.QName;

import org.ow2.jonas.generators.wsgen.generator.ews.wsdltoj2ee.JOnASJ2EEWebServicesContext;
import org.ow2.jonas.generators.wsgen.generator.ews.wsdltoj2ee.emitter.JOnASWSEmitter;

import org.apache.axis.deployment.wsdd.WSDDConstants;
import org.apache.axis.constants.Use;
import org.apache.axis.utils.Messages;
import org.apache.axis.wsdl.symbolTable.FaultInfo;
import org.apache.axis.wsdl.symbolTable.Parameter;
import org.apache.axis.wsdl.symbolTable.Parameters;
import org.apache.axis.wsdl.symbolTable.SchemaUtils;
import org.apache.axis.wsdl.symbolTable.SymbolTable;
import org.apache.axis.wsdl.symbolTable.TypeEntry;
import org.apache.axis.wsdl.toJava.Emitter;
import org.apache.axis.wsdl.toJava.JavaWriter;
import org.apache.axis.wsdl.toJava.Utils;



/**
 * JOnAS Base DeployWriter. It's a placeholder for commonly used methods and fields.
 * @author Guillaume Sauthier
 * Based on J2eeDeployWriter from Ias
 * (http://cvs.apache.org/viewcvs.cgi/ws-axis/contrib/ews/src/org/apache/geronimo/ews/ws4j2ee/toWs/ws/J2eeDeployWriter.java?rev=1.13&view=markup)
 */
public abstract class JOnASDeployWriter extends JavaWriter {

    /**
     * JOnASJ2EEWebServicesContext storing JOnAS specific info.
     */
    private JOnASJ2EEWebServicesContext jonasWSContext;

    /**
     * WSDD Extension suffix
     */
    protected static final String WSDD_SUFFIX = ".wsdd";

    /**
     * count generated files
     */
    private static int count = 0;

    /**
     * Definition base used to write deploy file
     */
    private Definition definition = null;

    /**
     * Symbol Table to use
     */
    private SymbolTable symbolTable = null;

    /** Field use */
    protected Use use = Use.DEFAULT;

    /**
     * Constructor.
     * @param emitter J2EE Emitter
     * @param definition Current Definition
     * @param symbolTable SymbolTable
     */
    public JOnASDeployWriter(Emitter emitter, Definition definition, SymbolTable symbolTable) {

        super(emitter, "deploy");
        this.definition = definition;
        this.symbolTable = symbolTable;
        if (emitter instanceof JOnASWSEmitter) {
            this.jonasWSContext = ((JOnASWSEmitter) emitter).getJOnASWsContext();
        }
        if (jonasWSContext == null) {
            throw new RuntimeException("jonasWSContext can not be null");
        }
    }

    /**
     * @return Returns the fully-qualified name of the deploy.wsdd file to be generated.
     */
    protected String getFileName() {
        // put directly in "output" directory
        String dir = emitter.getNamespaces().getAsDir("");
        return dir + getPrefix() + getCount() + WSDD_SUFFIX;
    }

    /**
     * Replace the default file header with the deployment doc file header.
     *
     * @param pw PrintWriter where descriptor has to be written
     * @throws IOException not thrown
     */
    protected void writeFileHeader(PrintWriter pw) throws IOException {

        pw.println(Messages.getMessage("deploy00"));
        pw.println(Messages.getMessage("deploy02"));
        pw.println(Messages.getMessage("deploy03"));
        pw.println(Messages.getMessage("deploy05"));
        pw.println(Messages.getMessage("deploy06"));
        pw.println(Messages.getMessage("deploy07"));
        pw.println(Messages.getMessage("deploy09"));
        pw.println();
        pw.println("<deployment");
        pw.println("    xmlns=\"" + WSDDConstants.URI_WSDD + "\"");
        pw.println("    xmlns:" + WSDDConstants.NS_PREFIX_WSDD_JAVA + "=\""
                + WSDDConstants.URI_WSDD_JAVA + "\">");
    }

    /**
     * Write the body of the deploy.wsdd file.
     *
     * @param pw PrintWriter
     * @throws IOException thrown by writeDeployServices
     */
    protected void writeFileBody(PrintWriter pw) throws IOException {
        writeDeployServices(pw);
        pw.println("</deployment>");
    }

    /**
     * @return Returns the filename prefix.
     */
    protected abstract String getPrefix();

    /**
     * Writes te list of wsdd:service
     * @param pw PrintWriter
     * @throws IOException implementation may throw IOException
     */
    protected abstract void writeDeployServices(PrintWriter pw) throws IOException;

    /**
     * Raw routine that writes out the typeMapping.
     * @param pw PrintWriter
     * @param namespaceURI xml type namespace
     * @param localPart xml type localpart
     * @param javaType java classname
     * @param serializerFactory java serializer factory classname
     * @param deserializerFactory java deserializer factory classname
     * @param encodingStyle encoding style
     */
    protected void writeTypeMapping(PrintWriter pw, String namespaceURI, String localPart, String javaType,
            String serializerFactory, String deserializerFactory, String encodingStyle) {

        pw.println("      <typeMapping");
        pw.println("        xmlns:ns=\"" + namespaceURI + "\"");
        pw.println("        qname=\"ns:" + localPart + '"');
        pw.println("        type=\"java:" + javaType + '"');
        pw.println("        serializer=\"" + serializerFactory + "\"");
        pw.println("        deserializer=\"" + deserializerFactory + "\"");
        pw.println("        encodingStyle=\"" + encodingStyle + "\"");
        pw.println("      />");
    }

    /**
     * Raw routine that writes out the operation and parameters.
     *
     * @param pw PrintWriter
     * @param javaOperName java method name
     * @param elementQName wsdl operation qname
     * @param returnQName wsdl return type qname
     * @param returnType java return type classname ?
     * @param params list of params used by this operation
     * @param faults list of faults thrown by this operation
     * @param soapAction soapAction value
     */
    protected void writeOperation(PrintWriter pw, String javaOperName,
                                  QName elementQName, QName returnQName,
                                  QName returnType, Parameters params,
                                  ArrayList faults, String soapAction) {

        pw.print("      <operation name=\"" + javaOperName + "\"");

        if (elementQName != null) {
            pw.print(" qname=\""
                    + Utils.genQNameAttributeString(elementQName, "operNS")
                    + "\"");
        }

        if (returnQName != null) {
            pw.print(" returnQName=\""
                    + Utils.genQNameAttributeStringWithLastLocalPart(returnQName, "retNS")
                    + "\"");
        }

        if (returnType != null) {
            pw.print(" returnType=\""
                    + Utils.genQNameAttributeString(returnType, "rtns")
                    + "\"");
        }

        Parameter retParam = params.returnParam;
        if (retParam != null) {
            TypeEntry type = retParam.getType();
            QName returnItemQName = Utils.getItemQName(type);
            if (returnItemQName != null) {
                pw.print(" returnItemQName=\"");
                pw.print(Utils.genQNameAttributeString(returnItemQName, "tns3"));
                pw.print("\"");
            }
            QName returnItemType = Utils.getItemType(type);
            if (returnItemType != null && use == Use.ENCODED) {
                    pw.print(" returnItemType=\"");
                    pw.print(Utils.genQNameAttributeString(returnItemType, "tns2"));
                    pw.print("\"");
            }
        }

        if (soapAction != null) {
            pw.print(" soapAction=\"" + soapAction + "\"");
        }

        if (!OperationType.REQUEST_RESPONSE.equals(params.mep)) {
            String mepString = getMepString(params.mep);
            if (mepString != null) {
                pw.print(" mep=\"" + mepString + "\"");
            }
        }

        if ((params.returnParam != null) && params.returnParam.isOutHeader()) {
            pw.print(" returnHeader=\"true\"");
        }

        pw.println(" >");

        Vector paramList = params.list;

        for (int i = 0; i < paramList.size(); i++) {
            Parameter param = (Parameter) paramList.elementAt(i);

            // Get the parameter name QName and type QName
            QName paramQName = param.getQName();
            QName paramType = Utils.getXSIType(param);

            pw.print("        <parameter");

            if (paramQName == null) {
                pw.print(" name=\"" + param.getName() + "\"");
            } else {
                pw.print(" qname=\""
                        + Utils.genQNameAttributeStringWithLastLocalPart(paramQName, "pns")
                        + "\"");
            }

            pw.print(" type=\""
                    + Utils.genQNameAttributeString(paramType, "ptns") + "\"");

            // Get the parameter mode
            if (param.getMode() != Parameter.IN) {
                pw.print(" mode=\"" + getModeString(param.getMode()) + "\"");
            }

            // Is this a header?
            if (param.isInHeader()) {
                pw.print(" inHeader=\"true\"");
            }

            if (param.isOutHeader()) {
                pw.print(" outHeader=\"true\"");
            }

            QName itemQName = Utils.getItemQName(param.getType());
            if (itemQName != null) {
                pw.print(" itemQName=\"");
                pw.print(Utils.genQNameAttributeString(itemQName, "pitns"));
                pw.print("\"");
            }

            pw.println("/>");
        }

        if (faults != null) {
            for (Iterator iterator = faults.iterator(); iterator.hasNext();) {
                FaultInfo faultInfo = (FaultInfo) iterator.next();
                QName faultQName = faultInfo.getQName();

                if (faultQName != null) {
                    String className =
                            Utils.getFullExceptionName(faultInfo.getMessage(),
                                    symbolTable);

                    pw.print("        <fault");
                    pw.print(" name=\"" + faultInfo.getName() + "\"");
                    pw.print(" qname=\""
                            + Utils.genQNameAttributeString(faultQName, "fns")
                            + "\"");
                    pw.print(" class=\"" + className + "\"");
                    pw.print(
                            " type=\""
                            + Utils.genQNameAttributeString(
                                    faultInfo.getXMLType(), "ftns") + "\"");
                    pw.println("/>");
                }
            }
        }

        pw.println("      </operation>");
    }

    /**
     * Write out bean mappings for each type
     * @param pw PrintWriter
     * @param binding wsdl:binding
     * @param hasLiteral has a literal type ?
     * @param hasMIME has MIME type ?
     * @param use Use
     */
    protected void writeDeployTypes(PrintWriter pw, Binding binding, boolean hasLiteral, boolean hasMIME, Use use) {

        pw.println();

        if (hasMIME) {
            QName bQName = binding.getQName();

            writeTypeMapping(pw, bQName.getNamespaceURI(), "DataHandler", "javax.activation.DataHandler",
                    "org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory",
                    "org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory", use.getEncoding());
        }

        boolean useJAF = false;

        Map types = getSymbolTable().getTypeIndex();
        Collection typeCollection = types.values();
        for (Iterator i = typeCollection.iterator(); i.hasNext();) {
            TypeEntry type = (TypeEntry) i.next();

            // Note this same check is repeated in JavaStubWriter.
            boolean process = true;

            // 1) Don't register types we shouldn't
            if (!Utils.shouldEmit(type)) {
                process = false;
            }

            if (process) {
                String namespaceURI = type.getQName().getNamespaceURI();
                String localPart = type.getQName().getLocalPart();
                String javaType = type.getName();
                String serializerFactory;
                String deserializerFactory;
                String encodingStyle = "";
                QName innerType = null;

                if (!hasLiteral) {
                    encodingStyle = use.getEncoding();
                }

                if (javaType.endsWith("[]")) {
                    if (SchemaUtils.isListWithItemType(type.getNode())) {
                        serializerFactory = "org.apache.axis.encoding.ser.SimpleListSerializerFactory";
                        deserializerFactory = "org.apache.axis.encoding.ser.SimpleListDeserializerFactory";
                    } else {
                        serializerFactory = "org.apache.axis.encoding.ser.ArraySerializerFactory";
                        deserializerFactory = "org.apache.axis.encoding.ser.ArrayDeserializerFactory";
                        innerType = type.getComponentType();
                    }
                } else if ((type.getNode() != null)
                        && (Utils.getEnumerationBaseAndValues(type.getNode(), getSymbolTable()) != null)) {
                    serializerFactory = "org.apache.axis.encoding.ser.EnumSerializerFactory";
                    deserializerFactory = "org.apache.axis.encoding.ser.EnumDeserializerFactory";
                } else if (type.isSimpleType()) {
                    serializerFactory = "org.apache.axis.encoding.ser.SimpleSerializerFactory";
                    deserializerFactory = "org.apache.axis.encoding.ser.SimpleDeserializerFactory";
                } else if (type.getBaseType() != null) {
                    serializerFactory = "org.apache.axis.encoding.ser.SimpleSerializerFactory";
                    deserializerFactory = "org.apache.axis.encoding.ser.SimpleDeserializerFactory";
                } else {
                    serializerFactory = "org.apache.axis.encoding.ser.BeanSerializerFactory";
                    deserializerFactory = "org.apache.axis.encoding.ser.BeanDeserializerFactory";
                }

                List jafType = new Vector();
                jafType.add("java.awt.Image");
                jafType.add("javax.xml.transform.Source");
                jafType.add("javax.mail.internet.MimeMultipart");
                if (jafType.contains(javaType)) {
                    serializerFactory = "org.apache.axis.encoding.ser.JAFDataHandlerSerializerFactory";
                    deserializerFactory = "org.apache.axis.encoding.ser.JAFDataHandlerDeserializerFactory";
                    useJAF = true;
                }

                if (innerType == null) {
                    // no arrays
                    writeTypeMapping(pw, namespaceURI, localPart, javaType,
                        serializerFactory, deserializerFactory,
                        encodingStyle);
                } else {
                    // arrays
                    writeArrayTypeMapping(pw, namespaceURI, localPart, javaType,
                            encodingStyle, innerType);
                }
            }
        }

        if (useJAF) {
            // mime don't work with ref
            pw.println("      <parameter name=\"sendMultiRefs\" value=\"false\"/>");
        }
    }


    /**
     * Raw routine that writes out the arrayMapping.
     * @param pw PrintWriter
     * @param namespaceURI xml type namespace
     * @param localPart xml type localpart
     * @param javaType java classname
     * @param encodingStyle encoding style
     * @param innerType array component type QName
     */
    protected void writeArrayTypeMapping(
            PrintWriter pw, String namespaceURI, String localPart, String javaType, String encodingStyle, QName innerType) {

        pw.println("      <arrayMapping");
        pw.println("        xmlns:ns=\"" + namespaceURI + "\"");
        pw.println("        qname=\"ns:" + localPart + '"');
        pw.println("        type=\"java:" + javaType + '"');
        pw.println("        innerType=\"" + Utils.genQNameAttributeString(innerType, "cmp-ns") + '"');
        pw.println("        encodingStyle=\"" + encodingStyle + "\"");
        pw.println("      />");
    }

    /**
     * Method getModeString
     *
     * @param mode Parameter mode (IN, INOUT, OUT)
     * @return Parameter Mode String representation
     */
    public String getModeString(byte mode) {

        if (mode == Parameter.IN) {
            return "IN";
        } else if (mode == Parameter.INOUT) {
            return "INOUT";
        } else {
            return "OUT";
        }
    }

    /**
     * Method getPrintWriter
     * @param filename file to open
     * @return Returns the printWriter for the file
     * @throws IOException When File cannot be open/written
     */
    protected PrintWriter getPrintWriter(String filename) throws IOException {

        File file = new File(filename);
        File parent = new File(file.getParent());

        parent.mkdirs();

        FileOutputStream out = new FileOutputStream(file);
        OutputStreamWriter writer = new OutputStreamWriter(out, "UTF-8");

        return new PrintWriter(writer);
    }

    /**
     * @return Returns the count.
     */
    public static int getCount() {
        return count++;
    }

    /**
     * @return Returns the definition.
     */
    public Definition getDefinition() {
        return definition;
    }

    /**
     * @return Returns the jonasWSContext.
     */
    public JOnASJ2EEWebServicesContext getJonasWSContext() {
        return jonasWSContext;
    }

    /**
     * @return Returns the symbolTable.
     */
    public SymbolTable getSymbolTable() {
        return symbolTable;
    }

    /**
     * Store operation type String representation
     */
    private static Map mepStrings = new HashMap();
    static {
        mepStrings.put(OperationType.REQUEST_RESPONSE, "request-response");
        mepStrings.put(OperationType.ONE_WAY, "oneway");
    }

    /**
     * @param mep OperationType
     * @return Returns the String representation of this type
     */
    private String getMepString(OperationType mep) {
        return (String) mepStrings.get(mep);
    }
}
