package eu.osci.helper;

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.Vector;
import java.util.logging.Logger;

import javax.xml.namespace.QName;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;

import com.sun.tools.ws.wscompile.ErrorReceiverFilter;
import com.sun.tools.ws.wscompile.WsimportOptions;
import com.sun.tools.ws.wsdl.document.WSDLDocument;
import com.sun.tools.ws.wsdl.parser.MetadataFinder;
import com.sun.tools.ws.wsdl.parser.WSDLParser;

import eu.osci.OSCIConstants;


/**
 * This class helps to find the right service and port for the communication. It offers all the
 * typeOfBusinessScenarios of a WSDL and helps to find the associated port and service.<br>
 * Copyright © 2010 Freie Hansestadt Bremen. <br>
 * Created by bremen online services Entwicklungs- und Betriebsgesellschaft mbH & Co. KG. <br>
 * This library (Starter Kit) can be used by anybody according to the Bremer Lizenz für freie
 * Softwarebibliotheken. The license terms can be viewed at www.osci.de or can be ordered at the
 * OSCI-Leitstelle (Die Senatorin für Finanzen, Freie Hansestadt Bremen, Postfach 10 15 40, 28015 Bremen).
 * 
 * @author R. Lindemann
 */
public class EndpointLocator
{

  private static Logger LOG = Logger.getLogger(EndpointLocator.class.getName());

  private WSDLParser parser;

  private URL wsdlFile;

  private String useTypeOfBusinessScenario;

  private String usePort;

  static final String WSDL_NAMESPACE = "http://schemas.xmlsoap.org/wsdl/";

  private WSDLDocument document;

  private MetadataFinder finder;

  /**
   * Constructor with given InputStream to parse a WSDL.
   * 
   * @param wsdlStream The WSDL InputStream.
   * @throws SAXException In case of parsing errors.
   * @throws IOException In case of IO errors.
   */
  public EndpointLocator(InputStream wsdlStream) throws SAXException, IOException
  {
    LOG.fine("Constructor with InputStream.");
    WsimportOptions options = new WsimportOptions();
    options.addWSDL(new InputSource(wsdlStream));
    parser = new WSDLParser(options, new ErrorReceiverFilter());
    document = parser.parse();
    finder = parser.getDOMForest();
  }

  /**
   * Constructor with given URL to parse a WSDL.
   * 
   * @param wsdl The URL of a WSDL .
   * @throws SAXException In case of parsing errors.
   * @throws IOException In case of IO errors.
   */
  public EndpointLocator(URL wsdl) throws SAXException, IOException
  {
    LOG.fine("Constructor with URL: " + wsdl);
    wsdlFile = wsdl;
    WsimportOptions options = new WsimportOptions();
    InputSource source = new InputSource(wsdl.openConnection().getInputStream());
    source.setSystemId(wsdl.toExternalForm());
    options.addWSDL(source);
    parser = new WSDLParser(options, new ErrorReceiverFilter());
    document = parser.parse();
    finder = parser.getDOMForest();
  }

  /**
   * Constructor with a given File to parse a WSDL.
   * 
   * @param wsdl The File object of a WSDL.
   * @throws SAXException In case of parsing errors.
   * @throws IOException In case of IO errors.
   */
  public EndpointLocator(File wsdl) throws SAXException, IOException
  {
    LOG.fine("Constructor with file:" + wsdl);
    wsdlFile = wsdl.toURI().toURL();
    WsimportOptions options = new WsimportOptions();
    options.addWSDL(wsdl);
    parser = new WSDLParser(options, new ErrorReceiverFilter());
    document = parser.parse();
    finder = parser.getDOMForest();
  }

  /**
   * Finds the QName of the service that includes the associated TypeOfBusinessScenario.
   * 
   * @param typeOfBusinessScenario The TypeOfBusinessScenario to search for.
   * @return The found QName of the service or <code>null</code> if there is no endpoint with the given
   *         TypeOfBusinessScenario.
   */
  public QName getServiceForTypeOfBusinessScenario(String typeOfBusinessScenario)
  {
    LOG.fine("Entry getServiceForTypeOfBusinessScenario: " + typeOfBusinessScenario);
    Document doc = finder.get(finder.getFirstRootDocument());
    QName[] serviceListOri = getServiceList();
    NodeList serviceList = doc.getElementsByTagNameNS(WSDL_NAMESPACE, "service");
    for ( int k = 0 ; k < serviceList.getLength() ; k++ )
    {
      Element service = (Element)serviceList.item(k);
      NodeList typeOf = service.getElementsByTagNameNS(OSCIConstants.NAMESPACE_OSCI20,
                                                       "TypeOfBusinessScenario");
      for ( int i = 0 ; i < typeOf.getLength() ; i++ )
      {
        String strTypeOf = typeOf.item(i).getFirstChild().getNodeValue();
        if (strTypeOf.equals(typeOfBusinessScenario))
        {
          for ( QName name : serviceListOri )
          {
            if (name.getLocalPart().equals(service.getAttribute("name")))
            {
              LOG.fine("Service found name: " + service.getAttribute("name"));
              return name;
            }
          }
        }
      }
    }
    LOG.warning("No related service found in the WSDL for the given TypeOfBusinessScenario.");
    return null;
  }

  /**
   * Finds the QName of the port that includes the associated TypeOfBusinessScenario.
   * 
   * @param typeOfBusinessScenario The TypeOfBusinessScenario to search for.
   * @return The found QName of the port or <code>null</code> if there is no endpoint with the given
   *         TypeOfBusinessScenario.
   */
  public QName getPortForTypeOfBusinessScenario(String typeOfBusinessScenario)
  {
    LOG.fine("Entry getPortForTypeOfBusinessScenario(" + typeOfBusinessScenario + ")");
    Document doc = finder.get(finder.getFirstRootDocument());
    QName[] portListOri = getPortList();
    NodeList portList = doc.getElementsByTagNameNS(WSDL_NAMESPACE, "port");
    for ( int k = 0 ; k < portList.getLength() ; k++ )
    {
      Element port = (Element)portList.item(k);
      NodeList typeOf = port.getElementsByTagNameNS(OSCIConstants.NAMESPACE_OSCI20, "TypeOfBusinessScenario");
      String strTypeOf = typeOf.item(0).getFirstChild().getNodeValue();
      if (strTypeOf.equals(typeOfBusinessScenario))
      {
        for ( QName name : portListOri )
        {
          if (name.getLocalPart().equals(port.getAttribute("name")))
          {
            LOG.fine("Found port with name: " + name);
            return name;
          }
        }
      }
    }
    LOG.warning("No related port found in the WSDL for the given TypeOfBusinessScenario.");
    return null;
  }

  /**
   * Returns all service names of the registered WSDL file.
   * 
   * @return A list or all service QNames.
   */
  public QName[] getServiceList()
  {
    return document.getAllServiceQNames();
  }

  /**
   * Returns all port names of the registered WSDL file.
   * 
   * @return A list or all port QNames.
   */
  public QName[] getPortList()
  {
    return document.getAllPortQNames();
  }

  /**
   * Returns all TypeOfBusinessScenario names of the registered WSDL file.
   * 
   * @return A list or all TypeOfBusinessScenario names.
   */
  public String[] getTypeOfBusinessScenarioList()
  {
    Document doc = finder.get(finder.getFirstRootDocument());
    Vector<String> typeList = new Vector<String>();
    NodeList types = doc.getElementsByTagNameNS(OSCIConstants.NAMESPACE_OSCI20, "TypeOfBusinessScenario");
    for ( int k = 0 ; k < types.getLength() ; k++ )
    {
      Element type = (Element)types.item(k);
      typeList.add(type.getFirstChild().getNodeValue());
    }
    return typeList.toArray(new String[typeList.size()]);
  }

  /**
   * Sets the given TypeOfBusinessScenario name as selected.
   * 
   * @param _typeOfBusinessScenario The TypeOfBusinessScenario name to set.
   */
  public void useTypeOfBusinessScenario(String _typeOfBusinessScenario)
  {
    useTypeOfBusinessScenario = _typeOfBusinessScenario;
  }

  /**
   * Sets the given port name as selected.
   * 
   * @param portName The port name that will be used.
   */
  public void usePort(String portName)
  {
    Document doc = finder.get(finder.getFirstRootDocument());
    NodeList portList = doc.getElementsByTagNameNS(WSDL_NAMESPACE, "port");
    for ( int k = 0 ; k < portList.getLength() ; k++ )
    {
      Element port = (Element)portList.item(k);
      String name = port.getAttribute("name");
      LOG.fine("The port name: " + name);
      if (portName.equals(name))
      {
        NodeList typeOf = port.getElementsByTagNameNS(OSCIConstants.NAMESPACE_OSCI20,
                                                      "TypeOfBusinessScenario");
        useTypeOfBusinessScenario = typeOf.item(0).getFirstChild().getNodeValue();
        LOG.fine("Use TypeOfBusinessScenario: " + useTypeOfBusinessScenario);
        return;
      }
    }
    LOG.warning("No related port found!");
  }

  /**
   * Returns the used port name.
   */
  public String getPortName()
  {
    return usePort;
  }

  /**
   * Returns the URL of the registered WSDL file.
   */
  public URL getWsdlFile()
  {
    return wsdlFile;
  }

  /**
   * Returns the registered TypeOfBusinessScenario name.
   */
  public String getTypeOfBusinessScenario()
  {
    return useTypeOfBusinessScenario;
  }

}
