/*
 * JBoss, Home of Professional Open Source
 * Copyright 2005, JBoss Inc., and individual contributors as indicated
 * by the @authors tag. See the copyright.txt in the distribution for a
 * full listing of individual contributors.
 *
 * This 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 (at your option) any later version.
 *
 * This software 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 software; if not, write to the Free
 * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
 * 02110-1301 USA, or see the FSF site: http://www.fsf.org.
 */
package org.jboss.ws.tools.helpers;

import java.io.File;
import java.io.IOException;
import java.io.Writer;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.jws.soap.SOAPBinding.ParameterStyle;
import javax.xml.namespace.QName;

import org.jboss.logging.Logger;
import org.jboss.ws.Constants;
import org.jboss.ws.WSException;
import org.jboss.ws.core.jaxrpc.LiteralTypeMapping;
import org.jboss.ws.core.soap.Style;
import org.jboss.ws.metadata.jaxrpcmapping.JavaWsdlMapping;
import org.jboss.ws.metadata.jaxrpcmapping.JavaXmlTypeMapping;
import org.jboss.ws.metadata.umdm.EndpointMetaData;
import org.jboss.ws.metadata.umdm.OperationMetaData;
import org.jboss.ws.metadata.umdm.ParameterMetaData;
import org.jboss.ws.metadata.umdm.ServiceMetaData;
import org.jboss.ws.metadata.umdm.UnifiedMetaData;
import org.jboss.ws.metadata.umdm.WrappedParameter;
import org.jboss.ws.metadata.wsdl.WSDLDefinitions;
import org.jboss.ws.metadata.wsdl.WSDLService;
import org.jboss.ws.tools.Configuration;
import org.jboss.ws.tools.JavaToWSDL;
import org.jboss.ws.tools.JavaWriter;
import org.jboss.ws.tools.NamespacePackageMapping;
import org.jboss.ws.tools.WSDLToJava;
import org.jboss.ws.tools.WebservicesXMLCreatorImpl;
import org.jboss.ws.tools.Configuration.GlobalConfig;
import org.jboss.ws.tools.Configuration.JavaToWSDLConfig;
import org.jboss.ws.tools.Configuration.WSDLToJavaConfig;
import org.jboss.ws.tools.XSDTypeToJava.VAR;
import org.jboss.ws.tools.client.ServiceCreator;
import org.jboss.ws.tools.interfaces.WebservicesXMLCreator;
import org.jboss.ws.tools.mapping.MappingFileGenerator;
import org.jboss.ws.tools.wsdl.WSDLWriter;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.DOMWriter;
import org.jboss.wsf.common.IOUtils;
import org.jboss.wsf.common.JavaUtils;

/**
 *  Helper class used by the cmd line tool "jbossws"
 *  and ant task "wstools"
 *  @author <mailto:Anil.Saldhana@jboss.org>Anil Saldhana
 *  @since   Aug 19, 2005
 */
public class ToolsHelper
{
   private static Logger log = Logger.getLogger(ToolsHelper.class);

   /**
    * Java To WSDL Generation [Serverside Generation]
    *
    * @param config
    * @param outDir
    * @throws IOException
    */
   public void handleJavaToWSDLGeneration(Configuration config, String outDir) throws IOException
   {
      JavaToWSDLConfig j2wc = config.getJavaToWSDLConfig(false);
      JavaToWSDL jwsdl = new JavaToWSDL(Constants.NS_WSDL11);
      jwsdl.setServiceName(j2wc.serviceName);
      jwsdl.setTargetNamespace(j2wc.targetNamespace);
      jwsdl.setTypeNamespace(j2wc.typeNamespace);
      jwsdl.setOperationMap(j2wc.operations);

      if ("document".equals(j2wc.wsdlStyle))
         jwsdl.setStyle(Style.DOCUMENT);
      else if ("rpc".equals(j2wc.wsdlStyle))
         jwsdl.setStyle(Style.RPC);
      else throw new WSException("Unrecognized Style:" + j2wc.wsdlStyle);

      if ("wrapped".equals(j2wc.parameterStyle))
         jwsdl.setParameterStyle(ParameterStyle.WRAPPED);
      else if ("bare".equals(j2wc.parameterStyle))
         jwsdl.setParameterStyle(ParameterStyle.BARE);
      else throw new WSException("Unrecognized Parameter Style:" + j2wc.parameterStyle);

      Class endpointClass = loadClass(j2wc.endpointName);

      if (endpointClass == null)
         throw new WSException("Endpoint " + j2wc.endpointName + " cannot be loaded");

      //Take care of passing global config details
      GlobalConfig gcfg = config.getGlobalConfig(false);
      if (gcfg != null)
      {
         if (gcfg.packageNamespaceMap != null)
            jwsdl.setPackageNamespaceMap(gcfg.packageNamespaceMap);
      }
      WSDLDefinitions wsdl = jwsdl.generate(endpointClass);
      //Create the WSDL Directory
      createDir(outDir + "/wsdl");
      String wsdlPath = outDir + "/wsdl/" + j2wc.serviceName + ".wsdl";
      //Generate the WSDL
      Writer fw = IOUtils.getCharsetFileWriter(new File(wsdlPath), Constants.DEFAULT_XML_CHARSET);
      new WSDLWriter(wsdl).write(fw, Constants.DEFAULT_XML_CHARSET);
      fw.close();

      //Generate the Mapping File
      if (j2wc.mappingFileNeeded)
      {
         UnifiedMetaData unifiedMetaData = jwsdl.getUnifiedMetaData();
         JavaWsdlMapping mapping = jwsdl.getJavaWsdlMapping();

         createWrapperTypes(j2wc, outDir, unifiedMetaData, mapping, endpointClass);
         Writer writer = IOUtils.getCharsetFileWriter(new File(outDir + "/" + j2wc.mappingFileName), Constants.DEFAULT_XML_CHARSET);
         writer.write(Constants.XML_HEADER);
         writer.write(DOMWriter.printNode(DOMUtils.parse(mapping.serialize()), true));
         writer.close();
      }

      //Generate the webservices.xml file
      if (j2wc.wsxmlFileNeeded)
      {
         WebservicesXMLCreator wscr = new WebservicesXMLCreatorImpl();
         wscr.setTargetNamespace(j2wc.targetNamespace);
         //wscr.setLocation(new File(outDir).toURL());
         wscr.setSeiName(j2wc.endpointName);
         wscr.setServiceName(j2wc.serviceName);
         //Get the portname from wsdl definitions
         WSDLService wsdlService = wsdl.getService(j2wc.serviceName);
         String portName = wsdlService.getEndpoints()[0].getName().getLocalPart();
         //wscr.setPortName(j2wc.serviceName + "Port");
         wscr.setPortName(portName);
         //wscr.setMappingFileName(j2wc.mappingFileName);
         if (j2wc.servletLink != null)
         {
            wscr.setMappingFile("WEB-INF/" + j2wc.mappingFileName);
            wscr.setWsdlFile("WEB-INF/wsdl/" + j2wc.serviceName + ".wsdl");
            wscr.setServletLink(j2wc.servletLink);
         }
         else
         {
            wscr.setMappingFile("META-INF/" + j2wc.mappingFileName);
            wscr.setWsdlFile("META-INF/wsdl/" + j2wc.serviceName + ".wsdl");
            wscr.setEjbLink(j2wc.ejbLink);
         }
         wscr.setAppend(j2wc.wsxmlFileAppend);
         wscr.generateWSXMLDescriptor(new File(outDir + "/webservices.xml"));
      }
   }

   private void createWrapperTypes(JavaToWSDLConfig j2wc, String outDir, UnifiedMetaData wsMetaData, JavaWsdlMapping mapping, Class endpointClass) throws IOException
   {
      Map<QName, JavaXmlTypeMapping> index = indexMappingTypes(mapping);

      EndpointMetaData epMetaData = null;
      for (ServiceMetaData service : wsMetaData.getServices())
      {
         epMetaData = service.getEndpointByServiceEndpointInterface(j2wc.endpointName);
         if (epMetaData != null)
            break;
      }

      if (epMetaData == null)
         throw new WSException("Could not find endpoint in metadata: " + j2wc.endpointName);

      String packageName = endpointClass.getPackage().getName();
      ClassLoader classLoader = wsMetaData.getClassLoader();

      for (OperationMetaData opMetaData : epMetaData.getOperations())
      {
         if (opMetaData.isDocumentWrapped())
         {
            for (ParameterMetaData parameter : opMetaData.getParameters())
            {
               String name = endpointClass.getSimpleName() + "_" + opMetaData.getQName().getLocalPart() + "_RequestStruct";
               createWrapperType(parameter, name, packageName, index, classLoader, outDir);
            }

            ParameterMetaData returnParameter = opMetaData.getReturnParameter();
            if (returnParameter != null)
            {
               String name = endpointClass.getSimpleName() + "_" + opMetaData.getQName().getLocalPart() + "_ResponseStruct";
               createWrapperType(returnParameter, name, packageName, index, classLoader, outDir);
            }
         }
      }
   }

   private void createWrapperType(ParameterMetaData parameter, String name, String packageName, Map<QName, JavaXmlTypeMapping> mappingIndex, ClassLoader classLoader,
                                  String outDir) throws IOException
   {
      List<WrappedParameter> wrappedParameters = parameter.getWrappedParameters();

      if (wrappedParameters == null)
         return;

      List<VAR> vars = new ArrayList<VAR>();
      for (WrappedParameter wrapped : wrappedParameters)
      {
         String typeName = JavaUtils.convertJVMNameToSourceName(wrapped.getType(), classLoader);
         vars.add(new VAR(wrapped.getVariable(), typeName, false));
      }

      JavaWriter writer = new JavaWriter();
      writer.createJavaFile(new File(outDir), name + ".java", packageName, vars, null, null, false, false, null);

      JavaXmlTypeMapping type = mappingIndex.get(parameter.getXmlType());
      if (type == null)
         throw new WSException("JAX-RPC mapping metadata is missing a wrapper type: " + parameter.getXmlType());

      type.setJavaType(packageName + "." + name);
   }

   private Map<QName, JavaXmlTypeMapping> indexMappingTypes(JavaWsdlMapping mapping)
   {
      Map<QName, JavaXmlTypeMapping> index = new HashMap<QName, JavaXmlTypeMapping>();
      for (JavaXmlTypeMapping type : mapping.getJavaXmlTypeMappings())
      {
         QName qname = type.getRootTypeQName();
         if (qname == null)
            continue;

         index.put(qname, type);
      }

      return index;
   }

   /**
    * Client Side Generation [WSDL To Java]
    *
    * @param config
    * @param outDir
    */
   public void handleWSDLToJavaGeneration(Configuration config, String outDir)
   {
      WSDLToJavaConfig w2jc = config.getWSDLToJavaConfig(false);
      GlobalConfig glc = config.getGlobalConfig(false);

      WSDLToJava wsdlToJava = new WSDLToJava();
      wsdlToJava.setTypeMapping(new LiteralTypeMapping());
      wsdlToJava.setGenerateSerializableTypes(w2jc.serializableTypes);

      WSDLDefinitions wsdl = null;
      try
      {
         URL wsdlURL = null;
         try
         {
            wsdlURL = new URL(w2jc.wsdlLocation);
         }
         catch (MalformedURLException e)
         {
            // ignore
         }

         if (wsdlURL == null)
         {
            File wsdlFile = new File(w2jc.wsdlLocation);
            if (wsdlFile.exists())
            {
               wsdlURL = wsdlFile.toURL();
            }
         }

         if (wsdlURL == null)
         {
            ClassLoader ctxLoader = Thread.currentThread().getContextClassLoader();
            wsdlURL = ctxLoader.getResource(w2jc.wsdlLocation);
         }

         if (wsdlURL == null)
            throw new IllegalArgumentException("Cannot load wsdl: " + w2jc.wsdlLocation);

         wsdl = wsdlToJava.convertWSDL2Java(wsdlURL);
         if (glc != null)
            wsdlToJava.setNamespacePackageMap(glc.packageNamespaceMap);

         wsdlToJava.setParameterStyle(w2jc.parameterStyle);
         wsdlToJava.generateSEI(wsdl, new File(outDir));

         //Generate the Service File
         this.generateServiceFile(getPackageName(wsdl, glc), wsdl, outDir);

         //Generate the Mapping File
         if (w2jc.mappingFileNeeded)
         {
            MappingFileGenerator mgf = new MappingFileGenerator(wsdl, new LiteralTypeMapping());
            if (glc != null && glc.packageNamespaceMap != null)
               mgf.setNamespacePackageMap(glc.packageNamespaceMap);        
            mgf.setServiceName(wsdl.getServices()[0].getName().getLocalPart());
            mgf.setParameterStyle(w2jc.parameterStyle);

            JavaWsdlMapping jwm = mgf.generate();
            Writer writer = IOUtils.getCharsetFileWriter(new File(outDir + "/" + w2jc.mappingFileName), Constants.DEFAULT_XML_CHARSET);
            writer.write(Constants.XML_HEADER);
            writer.write(DOMWriter.printNode(DOMUtils.parse(jwm.serialize()), true));
            writer.close();
         }

         //Generate the webservices.xml file
         if (w2jc.wsxmlFileNeeded)
         {
            String seiName = "mypackage.MyServiceEndpointInterface";
            String serviceName = "MyServiceName";

            if (wsdl.getServices().length == 1)
               serviceName = wsdl.getServices()[0].getName().getLocalPart();

            if (wsdl.getInterfaces().length == 1)
            {
               String seiPackage = getPackageName(wsdl, glc);
               seiName = seiPackage + "." + wsdlToJava.getServiceEndpointInterfaceName(wsdl.getInterfaces()[0]);
            }

            WebservicesXMLCreator wscr = new WebservicesXMLCreatorImpl();
            wscr.setTargetNamespace(wsdl.getTargetNamespace());
            wscr.setSeiName(seiName);
            wscr.setServiceName(serviceName);
            WSDLService wsdlService = wsdl.getService(serviceName);
            String portName = wsdlService.getEndpoints()[0].getName().getLocalPart();
            wscr.setPortName(portName);

            String wsdlShortName = wsdlURL.getPath();
            wsdlShortName = wsdlShortName.substring(wsdlShortName.lastIndexOf("/"));

            if (w2jc.servletLink != null)
            {
               wscr.setMappingFile("WEB-INF/" + w2jc.mappingFileName);
               wscr.setWsdlFile("WEB-INF/wsdl" + wsdlShortName);
               wscr.setServletLink(w2jc.servletLink);
            }
            else
            {
               wscr.setMappingFile("META-INF/" + w2jc.mappingFileName);
               wscr.setWsdlFile("META-INF/wsdl" + wsdlShortName);
               wscr.setEjbLink(w2jc.ejbLink);
            }
            wscr.generateWSXMLDescriptor(new File(outDir + "/webservices.xml"));
         }
      }
      catch (RuntimeException rte)
      {
         throw rte;
      }
      catch (Exception ex)
      {
         throw new WSException(ex);
      }
   }

   //PRIVATE METHODS
   private Class loadClass(String cls)
   {
      Class clazz = null;
      try
      {
         clazz = Thread.currentThread().getContextClassLoader().loadClass(cls);
      }
      catch (ClassNotFoundException e)
      {
         log.error("Cannot load endpoint:" + e.getLocalizedMessage());
      }
      return clazz;
   }

   private void generateServiceFile(String packageName, WSDLDefinitions wsdl, String location) throws IOException
   {
      ServiceCreator sc = new ServiceCreator();
      sc.setPackageName(packageName);
      sc.setDirLocation(new File(location));
      sc.setWsdl(wsdl);
      sc.createServiceDescriptor();
   }

   private String getPackageName(WSDLDefinitions wsdl, GlobalConfig glc)
   {
      String targetNamespace = wsdl.getTargetNamespace();
      //Get it from global config if it is overriden
      if (glc != null && glc.packageNamespaceMap != null)
      {
         String pkg = glc.packageNamespaceMap.get(targetNamespace);
         if (pkg != null)
         {
            return pkg;
         }
      }
      return NamespacePackageMapping.getJavaPackageName(targetNamespace);
   }

   private void createDir(String path)
   {
      File file = new File(path);
      if (file.exists() == false)
         file.mkdirs();
   }
}
