/*
 * 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.extensions.addressing;

//$Id: EndpointReferenceImpl.java 3959 2007-07-20 14:44:19Z heiko.braun@jboss.com $

import java.io.IOException;
import java.io.StringWriter;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.namespace.QName;
import javax.xml.ws.addressing.AddressingConstants;
import javax.xml.ws.addressing.AddressingException;
import javax.xml.ws.addressing.AttributedURI;
import javax.xml.ws.addressing.EndpointReference;
import javax.xml.ws.addressing.Metadata;
import javax.xml.ws.addressing.ReferenceParameters;
import javax.xml.ws.addressing.soap.SOAPAddressingBuilder;

import org.jboss.ws.WSException;
import org.jboss.ws.extensions.addressing.soap.SOAPAddressingBuilderImpl;
import org.jboss.wsf.common.DOMUtils;
import org.jboss.wsf.common.DOMWriter;
import org.w3c.dom.Element;

/** 
 * Abstraction of EndpointReference.  
 *
 * @author Thomas.Diesler@jboss.org
 * @since 14-Nov-2005
 */
public class EndpointReferenceImpl extends AttributeElementExtensibleImpl implements EndpointReference
{
   private static AddressingConstants ADDR = new AddressingConstantsImpl();

   // The REQUIRED root element name 
   private QName rootQName = new QName(ADDR.getNamespaceURI(), "EndpointReference", ADDR.getNamespacePrefix());
   // This REQUIRED element (whose content is of type xs:anyURI) specifies the [address] property of the endpoint reference.
   private AttributedURIImpl address = new AttributedURIImpl(ADDR.getAnonymousURI());
   // This OPTIONAL element may contain elements from any namespace. Such elements form the [reference parameters] of the reference.
   private ReferenceParametersImpl refParams = new ReferenceParametersImpl();
   // This OPTIONAL element may contain elements from any namespace. 
   private MetadataImpl metadata = new MetadataImpl();

   public EndpointReferenceImpl(URI uri)
   {
      this.address = new AttributedURIImpl(uri);
   }

   public EndpointReferenceImpl(Element elRoot)
   {
      initFromElement(elRoot);
   }

   public QName getRootQName()
   {
      return rootQName;
   }

   public void setRootQName(QName rootElementName)
   {
      this.rootQName = rootElementName;
   }

   public AttributedURI getAddress()
   {
      return address;
   }

   public ReferenceParameters getReferenceParameters()
   {
      return refParams;
   }

   public Metadata getMetadata()
   {
      return metadata;
   }

   private void initFromElement(Element elRoot)
   {
      if (elRoot == null)
         throw new IllegalArgumentException("Cannot initialize from null element");

      try
      {
         Map<QName, String> attributes = DOMUtils.getAttributes(elRoot);
         for (QName attqname : attributes.keySet())
         {
            String value = attributes.get(attqname);
            addAttribute(attqname, value);
         }

         Iterator it = DOMUtils.getChildElements(elRoot);
         while (it.hasNext())
         {
            Element el = (Element)it.next();
            QName qname = DOMUtils.getElementQName(el);

            // Parse Address
            if (qname.equals(ADDR.getAddressQName()))
            {
               address = new AttributedURIImpl(DOMUtils.getTextContent(el));

               attributes = DOMUtils.getAttributes(el);
               for (QName attqname : attributes.keySet())
               {
                  String value = attributes.get(attqname);
                  address.addAttribute(attqname, value);
               }
            }
            // Parse ReferenceParameters
            else if (qname.equals(ADDR.getReferenceParametersQName()))
            {
               attributes = DOMUtils.getAttributes(el);
               for (QName attqname : attributes.keySet())
               {
                  String value = attributes.get(attqname);
                  refParams.addAttribute(attqname, value);
               }
               Iterator itel = DOMUtils.getChildElements(el);
               while (itel.hasNext())
               {
                  Element child = (Element)itel.next();
                  refParams.addElement(child);
               }
            }
            // Parse Metadata
            else if (qname.equals(ADDR.getMetadataQName()))
            {
               attributes = DOMUtils.getAttributes(el);
               for (QName attqname : attributes.keySet())
               {
                  String value = attributes.get(attqname);
                  metadata.addAttribute(attqname, value);
               }
               Iterator itel = DOMUtils.getChildElements(el);
               while (itel.hasNext())
               {
                  Element child = (Element)itel.next();
                  metadata.addElement(child);
               }
            }
            else
            {
               addElement(el);
            }
         }
      }
      catch (RuntimeException rte)
      {
         throw rte;
      }
      catch (Exception ex)
      {
         throw new AddressingException("Cannot init EPR from element", ex);
      }
   }

   public Element toElement()
   {
      String xmlString = toXMLString(false);
      try
      {
         return DOMUtils.parse(xmlString);
      }
      catch (IOException ex)
      {
         throw new WSException("Cannot parse: " + xmlString, ex);
      }
   }

   public String toXMLString(boolean pretty)
   {
      if (pretty)
      {
         Element epRef = toElement();
         return DOMWriter.printNode(epRef, true);
      }

      SOAPAddressingBuilder builder = new SOAPAddressingBuilderImpl();
      AddressingConstants ADDR = builder.newAddressingConstants();

      String rootname = getPrefixedName(rootQName);
      StringBuilder xmlBuffer = new StringBuilder("<" + rootname);
      appendAttributes(xmlBuffer, getAttributes());
      xmlBuffer.append(">");

      // insert xmlns:wsa
      String wsaURI = ADDR.getNamespaceURI();
      String wsaPrefix = ADDR.getNamespacePrefix();
      String wsaDeclaration = " xmlns:" + wsaPrefix + "='" + wsaURI + "'";
      if (xmlBuffer.indexOf(wsaDeclaration) < 0)
      {
         xmlBuffer.insert(rootname.length() + 1, wsaDeclaration);
      }

      // append address
      xmlBuffer.append("<" + getPrefixedName(ADDR.getAddressQName()));
      appendAttributes(xmlBuffer, address.getAttributes());
      xmlBuffer.append(">");
      xmlBuffer.append(address.getURI() + "</" + getPrefixedName(ADDR.getAddressQName()) + ">");

      // append parameters
      if (refParams.getElements().size() > 0 || refParams.getAttributes().size() > 0)
      {
         xmlBuffer.append("<" + getPrefixedName(ADDR.getReferenceParametersQName()));
         appendAttributes(xmlBuffer, refParams.getAttributes());
         xmlBuffer.append(">");
         appendElements(xmlBuffer, refParams.getElements());
         xmlBuffer.append("</" + getPrefixedName(ADDR.getReferenceParametersQName()) + ">");
      }

      // append metadata
      if (metadata.getElements().size() > 0 || metadata.getAttributes().size() > 0)
      {
         xmlBuffer.append("<" + getPrefixedName(ADDR.getMetadataQName()));
         appendAttributes(xmlBuffer, metadata.getAttributes());
         xmlBuffer.append(">");
         appendElements(xmlBuffer, metadata.getElements());
         xmlBuffer.append("</" + getPrefixedName(ADDR.getMetadataQName()) + ">");
      }

      // append custom elements
      appendElements(xmlBuffer, getElements());

      xmlBuffer.append("</" + rootname + ">");

      String xmlString = xmlBuffer.toString();
      return xmlString;
   }

   private void appendAttributes(StringBuilder xmlBuffer, Map<QName, String> attributes)
   {
      for (QName qname : attributes.keySet())
      {
         String qualname = getPrefixedName(qname);
         String value = attributes.get(qname);
         xmlBuffer.append(" " + qualname + "='" + value + "'");
      }
   }

   private void appendElements(StringBuilder xmlBuffer, List<Object> elements)
   {
      for (Object obj : elements)
      {
         if (obj instanceof Element)
         {
            StringWriter strwr = new StringWriter();
            DOMWriter domWriter = new DOMWriter(strwr).setCompleteNamespaces(false);
            domWriter.print((Element)obj);
            String xmlFragment = strwr.toString();
            xmlBuffer.append(xmlFragment);
         }
         else if (obj instanceof String)
         {
            xmlBuffer.append(obj);
         }
         else
         {
            throw new AddressingException("Unsupported element: " + obj.getClass().getName());
         }
      }
   }

   private String getPrefixedName(QName qname)
   {
      String prefix = qname.getPrefix();
      String localPart = qname.getLocalPart();
      String qualname = (prefix != null && prefix.length() > 0 ? prefix + ":" + localPart : localPart);
      return qualname;
   }

   public String toString()
   {
      return toXMLString(true);
   }
}
