/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements. See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership. The ASF licenses this file
 * to you 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.jboss.test.ws.jaxws.smoke.tools;

import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.PrintStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;

import jakarta.xml.bind.annotation.XmlSeeAlso;
import jakarta.xml.ws.WebServiceFeature;

import org.jboss.ws.api.tools.WSContractConsumer;
import org.jboss.wsf.test.JBossWSTest;

/**
 * @author Heiko.Braun <heiko.braun@jboss.com>
 * @author alessio.soldano@jboss.com
 */
public class WSConsumerPlugin extends JBossWSTest
{
   // Tools delegate. Recreated for every test. See setup(...)
   WSContractConsumer consumer;

   // common output dir for all tests. Tests need to be executed below 'output/tests'
   File outputDirectory;

   // default is off
   boolean toogleMessageOut = Boolean.getBoolean(WSConsumerPlugin.class.getName()+".verbose");

   private final File workDirectory;

   public WSConsumerPlugin()
   {
      // create a new consumer for every test case
      consumer = WSContractConsumer.newInstance();
      consumer.setNoCompile(true);

      if (toogleMessageOut)
      {
         consumer.setMessageStream(System.out);
      }

      // shared output directory, we go out of the test-resources directory
      outputDirectory = createResourceFile("../wsconsume/java");
      workDirectory = createResourceFile("../work");
   }

   /**
    * Specifies the JAX-WS and JAXB binding files to use on import operations.
    * See http://java.sun.com/webservices/docs/2.0/jaxws/customizations.html
    */
   public void testBindingFiles() throws Exception
   {
      List<File> files = new ArrayList<File>();
      files.add(getResourceFile("jaxws/smoke/tools/wsdl/async-binding.xml"));

      consumer.setBindingFiles(files);
      consumer.setTargetPackage("org.jboss.test.ws.tools.testBindingFiles");
      consumer.setGenerateSource(true);

      consumeWSDL();

      File sei = loadEndpointInterface("testBindingFiles");

      boolean containsAsyncOperations = false;
      BufferedReader bin = new BufferedReader( new FileReader(sei) );

      String l = bin.readLine();
      while(l!=null)
      {
         if(l.indexOf("echoAsync")!=-1)
         {
            containsAsyncOperations=true;
            break;
         }

         l = bin.readLine();
      }
      bin.close();

      assertTrue(containsAsyncOperations, "External binding file was ignored");

   }

   /**
    * Sets the OASIS XML Catalog file to use for entity resolution.
    *
    */
   public void testCatalog() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testCatalog");
      consumer.setCatalog(getResourceFile("jaxws/smoke/tools/wsdl/jax-ws-catalog.xml"));
      consumer.setGenerateSource(true);
      consumer.setOutputDirectory(outputDirectory);
      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestServiceCatalog.wsdl").getCanonicalPath());
   }

   /**
    * Sets the main output directory. If the directory does not exist, it will be created.
    *
    */
   public void testOutputDirectory() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testOutputDirectory");
      consumer.setGenerateSource(true);
      consumer.setSourceDirectory(new File(workDirectory, "testOutputDirectory/java/"));

      consumeWSDL();

      File sei = new File(workDirectory, "testOutputDirectory/java/org/jboss/test/ws/tools/testOutputDirectory/EndpointInterface.java");
      assertTrue(sei.exists(), "Output directory switch ignored");
   }

   /**
    * Sets the source directory. This directory will contain any generated Java source.
    * If the directory does not exist, it will be created. If not specified,
    * the output directory will be used instead.
    *
    */
   public void testSourceDirectory() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testSourceDirectory");
      consumer.setGenerateSource(true);
      consumer.setSourceDirectory(new File(workDirectory, "wsconsumeSource/java/"));

      consumeWSDL();

      File sei = new File(workDirectory, "wsconsumeSource/java/org/jboss/test/ws/tools/testSourceDirectory/EndpointInterface.java");
      assertTrue(sei.exists(), "Source directory switch ignored");
   }

   public void testNoCompile() throws Exception
   {
      File sourceDir = new File(workDirectory, "wsconsumeNoCPSources/java/");
      File outputDir = new File(workDirectory, "wsconsumeNoCPOutput/java/");
      consumer.setTargetPackage("org.jboss.test.ws.tools.testSourceDirectory");
      consumer.setSourceDirectory(sourceDir);
      consumer.setOutputDirectory(outputDir);
      consumer.setGenerateSource(true);

      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestService.wsdl").getCanonicalPath());

      File sei = new File(workDirectory, "wsconsumeNoCPSources/java/org/jboss/test/ws/tools/testSourceDirectory/EndpointInterface.java");
      assertTrue(sei.exists(), "Expected sei not generated in the expected directory " + outputDir.getPath());

      File notExistSei = new File(workDirectory, "wsconsumeNoCPOutput/java/org/jboss/test/ws/tools/testSourceDirectory/EndpointInterface.java");
      assertFalse(notExistSei.exists(), "Directory " + sourceDir.getPath() + "  is expected to empty");
   }

   public void testNoCompileNoKeep() throws Exception
   {
      File sourceDir = new File(workDirectory, "wsconsumeNoCPNoKeepsource/java/");
      File outputDir = new File(workDirectory, "wsconsumeNoCPNoKeepOutput/java/");
      consumer.setTargetPackage("org.jboss.test.ws.tools.testSourceDirectory");
      consumer.setSourceDirectory(sourceDir);
      consumer.setOutputDirectory(outputDir);
      consumer.setGenerateSource(false);

      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestService.wsdl").getCanonicalPath());

      File sourceSei = new File(workDirectory, "wsconsumeNoCPNoKeepsource/java/org/jboss/test/ws/tools/testSourceDirectory/EndpointInterface.java");
      assertFalse(sourceSei.exists(), "Directory " + sourceDir.getPath() + "  is expected to be empty");

      File outputSei = new File(workDirectory, "wsconsumeNoCPNoKeepOutput/java/org/jboss/test/ws/tools/testSourceDirectory/EndpointInterface.java");
      assertFalse(outputSei.exists(), "Directory " + sourceDir.getPath() + "  is expected to be empty");
   }


   /**
    * Enables/Disables Java source generation.
    *
    */
   public void testGenerateSource() throws Exception
   {
      File sourceDir = new File(workDirectory, "wsconsumeGenerateSource/java/");
      consumer.setTargetPackage("org.jboss.test.ws.tools.testGenerateSource");
      consumer.setSourceDirectory(sourceDir);
      consumer.setGenerateSource(true);
      consumer.setNoCompile(true);

      consumeWSDL();

      File packageDir = new File(sourceDir, "org/jboss/test/ws/tools/testGenerateSource");
      assertTrue(packageDir.exists(), "Package not created");

      File seiSource = new File(sourceDir, "org/jboss/test/ws/tools/testGenerateSource/EndpointInterface.java");
      assertTrue(seiSource.exists(), "SEI not generated");

      sourceDir = new File(workDirectory, "wsconsumeGenerateSource2/java/");
      consumer.setTargetPackage("org.jboss.test.ws.tools.testGenerateSource2");
      consumer.setSourceDirectory(sourceDir);
      consumer.setGenerateSource(false);
      consumer.setNoCompile(false);

      consumeWSDL();

      packageDir = new File(sourceDir, "org/jboss/test/ws/tools/testGenerateSource2");
      assertFalse(packageDir.exists(), "Package should not have been created!");

      File interfaceClass = new File(outputDirectory, "org/jboss/test/ws/tools/testGenerateSource2/EndpointInterface.class");
      assertTrue(interfaceClass.exists(), "SEI not generated");
   }

   /**
    * Sets the target package for generated source. If not specified the default
    * is based off of the XML namespace.
    *
    */
   public void testTargetPackage() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testTargetPackage");
      consumer.setGenerateSource(true);

      consumeWSDL();

      File packageDir = new File(outputDirectory, "org/jboss/test/ws/tools/testTargetPackage");
      assertTrue(packageDir.exists(), "Package not created");

      File seiSource = new File(outputDirectory, "org/jboss/test/ws/tools/testTargetPackage/EndpointInterface.java");
      assertTrue(seiSource.exists(), "SEI not generated");

      File seiClass = loadEndpointInterface("testTargetPackage");
      assertTrue(seiClass.exists(), "Cannot load SEI class");
   }

   /**
    * Sets the @@WebService.wsdlLocation and @@WebServiceClient.wsdlLocation attributes to a custom value.
    *
    */
   public void testWsdlLocation() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testWsdlLocation");
      consumer.setWsdlLocation("http://foo.bar.com/endpoint?wsdl");
      consumer.setGenerateSource(true);

      consumeWSDL();

      File sei = loadEndpointInterface("testWsdlLocation", "TestService.java");
      BufferedReader bin = new BufferedReader( new FileReader(sei) );

      boolean match = false;
      boolean annotationFound = false;
      String l = bin.readLine();
      while(l!=null)
      {
         if (l.startsWith("@WebServiceClient"))
         {
            annotationFound = true;
         }
         if (l.indexOf("public class TestService")!=-1 && annotationFound)
         {
            match = true;
            break;
         }
         l = bin.readLine();
      }
      bin.close();

      assertTrue(match, "@WebServiceClient not generated on service interface");
   }

   /**
    * Sets the PrintStream to use for status feedback.
    * The simplest example would be to use System.out.
    */
   public void testMessageStream() throws Exception
   {
      ByteArrayOutputStream bout = new ByteArrayOutputStream();
      PrintStream pout = new PrintStream(bout);

      consumer.setTargetPackage("org.jboss.test.ws.tools.testMessageStream");
      consumer.setMessageStream(pout);

      consumeWSDL();

      String messageOut = new String(bout.toByteArray());
      System.out.println("-- Begin captured output -- ");
      System.out.println(messageOut);
      System.out.println("--- End captured output --");

      assertTrue(messageOut.indexOf("wsdl2java -exsh false -p org.jboss.test.ws.tools.testMessageStream") != -1, "Tools output not correctly redirected");
   }

   /**
    * Sets the additional classpath to use if/when invoking the Java compiler.
    * Typically an implementation will use the system <code>java.class.path</code>
    * property. So for most normal applications this method is not needed. However,
    * if this API is being used from an isolated classloader, then it needs to
    * be called in order to reference all jars that are required by the
    * implementation.
    *
    */
   public void testAdditionalCompilerClassPath()
   {
      // JBWS-1773 WSContractConsumer.setAdditionalCompilerClassPath() method is tested in wsconsume ant task
      // that is invoked on each test run. See WSConsumeTask.java for more information how this is tested.
   }

   /**
    * Tests the SOAP 1.2 binding extension
    *
    */
   public void testSOAP12Extension() throws Exception
   {
      consumer.setOutputDirectory(outputDirectory);
      consumer.setTargetPackage("org.jboss.test.ws.tools.testSOAP12Extension");
      consumer.setGenerateSource(true);
      consumer.setExtension(true);
      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestServiceSoap12.wsdl").getCanonicalPath());

      File sei = new File(outputDirectory, "org/jboss/test/ws/tools/testSOAP12Extension/EndpointInterface.java");
      assertTrue(sei.exists(), "SEI not generated");
      File service = new File(outputDirectory, "org/jboss/test/ws/tools/testSOAP12Extension/TestService.java");
      assertTrue(service.exists(), "Service not generated");
   }

   public void testAdditionalHeaders() throws Exception
   {
      consumer.setTargetPackage("org.jboss.test.ws.tools.testAdditionalHeaders1");
      consumer.setAdditionalHeaders(false);
      consumer.setNoCompile(false);

      consumeWSDL();
      ClassLoader loader = getArtefactClassLoader();
      Class<?> sei = loader.loadClass("org.jboss.test.ws.tools.testAdditionalHeaders1.EndpointInterface");
      Method m = (sei.getMethods())[0];
      assertEquals(1, m.getParameterTypes().length);
      consumer.setOutputDirectory(outputDirectory);
      consumer.setTargetPackage("org.jboss.test.ws.tools.testAdditionalHeaders2");
      consumer.setAdditionalHeaders(true);
      consumer.setNoCompile(false);
      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestServiceImplicitHeader.wsdl").getCanonicalPath());
      loader = getArtefactClassLoader();
      sei = loader.loadClass("org.jboss.test.ws.tools.testAdditionalHeaders2.EndpointInterface");
      m = (sei.getMethods())[0];
      assertEquals(2, m.getParameterTypes().length);
   }

   private void consumeWSDL() throws Exception
   {
      consumer.setOutputDirectory(outputDirectory);
      consumer.consume(getResourceFile("jaxws/smoke/tools/wsdl/TestService.wsdl").getCanonicalPath());
   }

   private File loadEndpointInterface(String testName, String... fileName) throws MalformedURLException, ClassNotFoundException
   {
      String name = fileName.length> 0 ? fileName[0] : "EndpointInterface.java";
      String interfaceFile = "org/jboss/test/ws/tools/" + testName + "/"+name;
      File sei = new File(outputDirectory, interfaceFile);
      if(!sei.exists()) throw new IllegalStateException(sei.getAbsolutePath() + " doesn't exist!");
      return sei;
   }

   private ClassLoader getArtefactClassLoader() throws Exception {
      URLClassLoader loader = new URLClassLoader(
        new URL[] { outputDirectory.toURI().toURL() },
        Thread.currentThread().getContextClassLoader()
      );

      return loader;
   }
}
