/*
 * (c) Copyright IBM Corp 2001, 2005 
 */

package edu.uga.cs.lsdis.meteors.wadls.xml;

import java.io.*;
import java.net.URISyntaxException;
import java.util.*;
import javax.xml.namespace.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;

import javax.wadls.*;
import javax.wadls.extensions.*;
import javax.wadls.factory.*;
import javax.wadls.xml.*;

import edu.uga.cs.lsdis.meteors.wadls.*;
import edu.uga.cs.lsdis.meteors.wadls.util.*;
import edu.uga.cs.lsdis.meteors.wadls.util.xml.*;

/**
 * This class describes a collection of methods
 * that allow a WSDL-S model to be written to a writer
 * in an XML format that follows the WSDL schema.
 *
 * @author Zixin Wu (wuzixin@uga.edu)
 * @author Matthew J. Duftler
 * @author Nirmal Mukhi
 */
public class WADLWriterImpl implements WADLWriter
{
  /**
   * Sets the specified feature to the specified value.
   * <p>
   * There are no minimum features that must be supported.
   * <p>
   * All feature names must be fully-qualified, Java package style. All
   * names starting with javax.wsdls. are reserved for features defined
   * by the JWSDL specification. It is recommended that implementation-
   * specific features be fully-qualified to match the package name
   * of that implementation. For example: com.abc.featureName
   *
   * @param name the name of the feature to be set.
   * @param value the value to set the feature to.
   * @throws IllegalArgumentException if the feature name is not recognized.
   * @see #getFeature(String)
   */
  public void setFeature(String name, boolean value)
    throws IllegalArgumentException
  {
    if (name == null)
    {
      throw new IllegalArgumentException("Feature name must not be null.");
    }
    else
    {
      throw new IllegalArgumentException("Feature name '" + name +
                                         "' not recognized.");
    }
  }

  /**
   * Gets the value of the specified feature.
   *
   * @param name the name of the feature to get the value of.
   * @throws IllegalArgumentException if the feature name is not recognized.
   * @see #setFeature(String, boolean)
   */
  public boolean getFeature(String name) throws IllegalArgumentException
  {
    if (name == null)
    {
      throw new IllegalArgumentException("Feature name must not be null.");
    }
    else
    {
      throw new IllegalArgumentException("Feature name '" + name +
                                         "' not recognized.");
    }
  }

  protected void printDefinition(Application def, PrintWriter pw)
    throws WADLSException, URISyntaxException
  {
    if (def == null)
    {
      return;
    }

    if (def.getPrefix(Constants.NS_URI_WADL) == null)
    {
      String prefix = "wsdl";
      int subscript = 0;

      while (def.getNamespace(prefix) != null)
      {
        prefix = "wsdl" + subscript++;
      }

      def.addNamespace(prefix, Constants.NS_URI_WADL);
    }

    if (def.getPrefix(Constants.NS_URI_WADLS) == null)
    {
      String prefix = Constants.PREFIX_WSDLS;
      int subscript = 0;

      while (def.getNamespace(prefix) != null)
      {
        prefix = Constants.PREFIX_WSDLS + subscript++;
      }

      def.addNamespace(prefix, Constants.NS_URI_WADLS);
    }
    
    String tagName =
      DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                 Constants.ELEM_DEFINITIONS,
                                 def);

    pw.print('<' + tagName);

    QName name = def.getQName();
    String targetNamespace = def.getTargetNamespace();
    Map namespaces = def.getNamespaces();

    if (name != null)
    {
      DOMUtils.printAttribute(Constants.ATTR_NAME, name.getLocalPart(), pw);
    }

    DOMUtils.printAttribute(Constants.ATTR_TARGET_NAMESPACE,
                            targetNamespace,
                            pw);

    printNamespaceDeclarations(namespaces, pw);

    pw.println('>');

    printDocumentation(def.getDocumentationElement(), pw);
    printIncludes(def.getIncludes(), def, pw);
    printTypes(def.getParams(), def, pw);
    printResources(def.getResources(), def, pw);



 

    pw.println("</" + tagName + '>');

    pw.flush();
  }








  protected void printResources(Map portTypes,
		  Application def,
                                PrintWriter pw)
                                  throws WADLSException, URISyntaxException
  {
    if (portTypes != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_RESOURCE,
                                   def);
      Iterator portTypeIterator = portTypes.values().iterator();

      while (portTypeIterator.hasNext())
      {
        Resource portType = (Resource)portTypeIterator.next();

        if (!portType.isUndefined())
        {
          pw.print("  <" + tagName);

          QName name = portType.getQName();

          if (name != null)
          {
            DOMUtils.printAttribute(Constants.ATTR_NAME,
                                    name.getLocalPart(),
                                    pw);
          }



          pw.println('>');

          printDocumentation(portType.getDocumentationElement(), pw);
          printOperations(portType.getMethods(), def, pw);
          pw.println("  </" + tagName + '>');
        }
      }
    }
  }
  
  protected void printOperations(List operations,
		  Application def,
                                 PrintWriter pw)
                                   throws WADLSException, URISyntaxException
  {
    if (operations != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_OPERATION,
                                   def);
      Iterator operationIterator = operations.iterator();

      while (operationIterator.hasNext())
      {
        Method operation = (Method)operationIterator.next();

        if (!operation.isUndefined())
        {
          pw.print("    <" + tagName);

          DOMUtils.printAttribute(Constants.ATTR_NAME,
                                  operation.getName(),
                                  pw);
          //write modelReference
          ModelReference mr = operation.getModelReference();
          if (mr != null){
          	if (mr.getNamespace() != null)		//has namespaceURI
          		mr.setPrefix(def.getPrefix(mr.getNamespace().toString()));
          	else								//no namespaceURI, try prefix
          		mr.setNamespace(def.getNamespace(mr.getPrefix()));
          	if (mr.getNamespace() == null)		//error
                  throw new WADLSException(WADLSException.UNBOUND_PREFIX,
                          "Cannot find the namespace for the prefix " +
                          mr.getPrefix());
          	DOMUtils.printQualifiedAttribute(Constants.Q_ATTR_MODELREF,
  						            mr.value(),
  						            def,
  						            pw);
          }
          
          DOMUtils.printAttribute(Constants.ATTR_PARAMETER_ORDER,
                   StringUtils.getNMTokens(operation.getParameterOrdering()),
                   pw);

          pw.println('>');

          printDocumentation(operation.getDocumentationElement(), pw);
            // Must be OperationType.REQUEST_RESPONSE.
            printInput(operation.getRequest(), def, pw);
            printOutput(operation.getResponse(), def, pw);
          
          printPreCondition(operation.getPreCondition(), def, pw);
          printEffect(operation.getEffect(), def, pw);

       
          
       
          
       

          pw.println("    </" + tagName + '>');
        }
      }
    }
  }
  
  protected void printPreCondition(PreCondition preCondition,
		  Application def,
							        PrintWriter pw)
							          throws WADLSException, URISyntaxException
{
	if (preCondition != null)
	{
		String tagName =
		DOMUtils.getQualifiedValue(Constants.NS_URI_WADLS,
		               Constants.ELEM_PRECON,
		               def);

		pw.print("      <" + tagName);
		
		DOMUtils.printAttribute(Constants.ATTR_NAME, preCondition.getName(), pw);
		
        //write modelReference
        ModelReference mr = preCondition.getModelReference();
        if (mr != null){
        	if (mr.getNamespace() != null)		//has namespaceURI
        		mr.setPrefix(def.getPrefix(mr.getNamespace().toString()));
        	else								//no namespaceURI, try prefix
        		mr.setNamespace(def.getNamespace(mr.getPrefix()));
        	if (mr.getNamespace() == null)		//error
                throw new WADLSException(WADLSException.UNBOUND_PREFIX,
                        "Cannot find the namespace for the prefix " +
                        mr.getPrefix());
        	DOMUtils.printQualifiedAttribute(Constants.Q_ATTR_MODELREF,
						            mr.value(),
						            def,
						            pw);
        }

		DOMUtils.printAttribute(Constants.ATTR_EXPRESSION, preCondition.getExpression(), pw);

//		printExtensibilityAttributes(PreCondition.class, preCondition, def, pw);
		
		Element docEl = preCondition.getDocumentationElement();
		
		if (docEl == null)
		{
			pw.println("/>");
		}
		else
		{
			pw.print('>');
			
	        printDocumentation(docEl, pw);
			
			pw.println("</" + tagName + '>');
		}
	}
}
  protected void printEffect(Effect effect,
		  Application def,
					        PrintWriter pw)
					          throws WADLSException, URISyntaxException
{
	if (effect != null)
	{
		String tagName =
		DOMUtils.getQualifiedValue(Constants.NS_URI_WADLS,
									Constants.ELEM_EFFECT,
									def);

		pw.print("      <" + tagName);
		
		DOMUtils.printAttribute(Constants.ATTR_NAME, effect.getName(), pw);
		
        //write modelReference
        ModelReference mr = effect.getModelReference();
        if (mr != null){
        	if (mr.getNamespace() != null)		//has namespaceURI
        		mr.setPrefix(def.getPrefix(mr.getNamespace().toString()));
        	else								//no namespaceURI, try prefix
        		mr.setNamespace(def.getNamespace(mr.getPrefix()));
        	if (mr.getNamespace() == null)		//error
                throw new WADLSException(WADLSException.UNBOUND_PREFIX,
                        "Cannot find the namespace for the prefix " +
                        mr.getPrefix());
        	DOMUtils.printQualifiedAttribute(Constants.Q_ATTR_MODELREF,
						            mr.value(),
						            def,
						            pw);
        }
        
		DOMUtils.printAttribute(Constants.ATTR_EXPRESSION, effect.getExpression(), pw);

//        printExtensibilityAttributes(Effect.class, effect, def, pw);
		
		Element docEl = effect.getDocumentationElement();
		
		if (docEl == null)
		{
			pw.println("/>");
		}
		else
		{
			pw.print('>');
			
	        printDocumentation(docEl, pw);
			
			pw.println("</" + tagName + '>');
		}
	}
}
  
  protected void printInput(Request input,
		  Application def,
                            PrintWriter pw)
                              throws WADLSException
  {
    if (input != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_INPUT,
                                   def);

      pw.print("      <" + tagName);

      DOMUtils.printAttribute(Constants.ATTR_NAME, input.getName(), pw);

      Element docEl = input.getDocumentationElement();

      if (docEl == null)
      {
        pw.println("/>");
      }
      else
      {
        pw.println('>');

        printDocumentation(docEl, pw);

        pw.println("      </" + tagName + '>');
      }
    }
  }

  protected void printOutput(Response output,
		  Application def,
                             PrintWriter pw)
                               throws WADLSException
  {
    if (output != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_OUTPUT,
                                   def);

      pw.print("      <" + tagName);

      DOMUtils.printAttribute(Constants.ATTR_NAME, output.getName(), pw);

      Element docEl = output.getDocumentationElement();

      if (docEl == null)
      {
        pw.println("/>");
      }
      else
      {
        pw.println('>');

        printDocumentation(docEl, pw);

        pw.println("      </" + tagName + '>');
      }
    }
  }





  protected void printDocumentation(Element docElement,
                                    PrintWriter pw)
                                      throws WADLSException
  {
    if (docElement != null)
    {
      DOM2Writer.serializeAsXML(docElement, pw);

      pw.println();
    }
  }

  protected void printTypes(Params types, Application def, PrintWriter pw)
    throws WADLSException
  {
    if (types != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_TYPES,
                                   def);
      pw.print("  <" + tagName);

      pw.println('>');

      printDocumentation(types.getDocumentationElement(), pw);

    



      pw.println("  </" + tagName + '>');
    }
  }

  protected void printIncludes(Map imports, Application def, PrintWriter pw)
    throws WADLSException
  {
    if (imports != null)
    {
      String tagName =
        DOMUtils.getQualifiedValue(Constants.NS_URI_WADL,
                                   Constants.ELEM_INCLUDE,
                                   def);
      Iterator importListIterator = imports.values().iterator();

      while (importListIterator.hasNext())
      {
        List importList = (List)importListIterator.next();
        Iterator importIterator = importList.iterator();

        while (importIterator.hasNext())
        {
          Include importDef = (Include)importIterator.next();

          pw.print("  <" + tagName);

          DOMUtils.printAttribute(Constants.ATTR_NAMESPACE,
                                  importDef.getNamespaceURI(),
                                  pw);
          DOMUtils.printAttribute(Constants.ATTR_LOCATION,
                                  importDef.getLocationURI(),
                                  pw);



          Element docEl = importDef.getDocumentationElement();

          if (docEl == null)
          {
            pw.println("/>");
          }
          else
          {
            pw.println('>');

            printDocumentation(docEl, pw);

            pw.println("      </" + tagName + '>');
          }
        }
      }
    }
  }

  protected void printNamespaceDeclarations(Map namespaces,
                                            PrintWriter pw)
                                              throws WADLSException
  {
    if (namespaces != null)
    {
      Set keys = namespaces.keySet();
      Iterator keyIterator = keys.iterator();

      while (keyIterator.hasNext())
      {
        String prefix = (String)keyIterator.next();

        if (prefix == null)
        {
          prefix = "";
        }

        DOMUtils.printAttribute(Constants.ATTR_XMLNS +
                                (!prefix.equals("") ? ":" + prefix : ""),
                                (String)namespaces.get(prefix),
                                pw);
      }
    }
  }


  private static Document getDocument(InputSource inputSource,
                                      String desc) throws WADLSException
  {
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();

    factory.setNamespaceAware(true);
    factory.setValidating(false);

    try
    {
      DocumentBuilder builder = factory.newDocumentBuilder();
      Document doc = builder.parse(inputSource);

      return doc;
    }
    catch (Throwable t)
    {
      throw new WADLSException(WADLSException.XSDPARSER_ERROR,
                              "Problem parsing '" + desc + "'.",
                              t);
    }
  }

  /**
   * Return a document generated from the specified WSDL model.
 * @throws URISyntaxException 
   */
  public Document getDocument(Application wsdlDef) throws WADLSException, URISyntaxException
  {
    StringWriter sw = new StringWriter();
    PrintWriter pw = new PrintWriter(sw);

    writeWADL(wsdlDef, pw);

    StringReader sr = new StringReader(sw.toString());
    InputSource is = new InputSource(sr);

    return getDocument(is, "- WSDL Document -");
  }

  /**
   * Write the specified WSDL definition to the specified Writer.
   *
   * @param wsdlDef the WSDL definition to be written.
   * @param sink the Writer to write the xml to.
 * @throws URISyntaxException 
   */
  public void writeWADL(Application wsdlDef, Writer sink)
    throws WADLSException, URISyntaxException
  {
    PrintWriter pw = new PrintWriter(sink);
    String javaEncoding = (sink instanceof OutputStreamWriter)
                          ? ((OutputStreamWriter)sink).getEncoding()
                          : null;

    String xmlEncoding = DOM2Writer.java2XMLEncoding(javaEncoding);                      

    if (xmlEncoding == null)
    {
      throw new WADLSException(WADLSException.CONFIGURATION_ERROR,
                              "Unsupported Java encoding for writing " +
                              "wsdl file: '" + javaEncoding + "'.");
    }

    pw.println(Constants.XML_DECL_START +
               xmlEncoding +
               Constants.XML_DECL_END);

    printDefinition(wsdlDef, pw);
  }

  /**
   * Write the specified WSDL definition to the specified OutputStream.
   *
   * @param wsdlDef the WSDL definition to be written.
   * @param sink the OutputStream to write the xml to.
 * @throws URISyntaxException 
   */
  public void writeWADL(Application wsdlDef, OutputStream sink)
    throws WADLSException, URISyntaxException
  {
    Writer writer = null;

    try
    {
      writer = new OutputStreamWriter(sink, "UTF8");
    }
    catch (UnsupportedEncodingException e)
    {
      e.printStackTrace();

      writer = new OutputStreamWriter(sink);
    }

    writeWADL(wsdlDef, writer);
  }

  /**
   * A test driver.
   *<code>
   *<pre>Usage:</pre>
   *<p>
   *<pre>  java edu.uga.cs.lsdis.meteors.wsdls.xml.WSDLWriterImpl filename|URL</pre>
   *<p>
   *<pre>    This test driver simply reads a WSDL document into a model
   *    (using a WSDLReader), and then serializes it back to
   *    standard out. In effect, it performs a round-trip test on
   *    the specified WSDL document.</pre>
 * @throws URISyntaxException 
   */
  public static void main(String[] argv) throws WADLSException, URISyntaxException
  {
    if (argv.length == 1)
    {
      WADLFactory wsdlFactory = WADLFactory.newInstance();
      WADLReader  wsdlReader  = wsdlFactory.newWADLReader();
      WADLWriter  wsdlWriter  = wsdlFactory.newWADLWriter();

      wsdlWriter.writeWADL(wsdlReader.readWADL(null, argv[0]), System.out);
    }
    else
    {
      System.err.println("Usage:");
      System.err.println();
      System.err.println("  java " + WADLWriterImpl.class.getName() +
                         " filename|URL");
      System.err.println();
      System.err.println("This test driver simply reads a WSDL document " +
                         "into a model (using a WSDLReader), and then " +
                         "serializes it back to standard out. In effect, " +
                         "it performs a round-trip test on the specified " +
                         "WSDL document.");
    }
  }
}
