/*
  * 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.test.xml;

import javax.xml.namespace.NamespaceContext;
import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import junit.framework.TestSuite;

import org.jboss.util.Strings;
import org.jboss.util.xml.DOMWriter;
import org.jboss.xb.binding.metadata.ClassMetaData;
import org.jboss.xb.binding.metadata.ValueMetaData;
import org.jboss.xb.binding.sunday.unmarshalling.CharactersHandler;
import org.jboss.xb.binding.sunday.unmarshalling.DefaultElementInterceptor;
import org.jboss.xb.binding.sunday.unmarshalling.DefaultHandlers;
import org.jboss.xb.binding.sunday.unmarshalling.ElementBinding;
import org.jboss.xb.binding.sunday.unmarshalling.ParticleBinding;
import org.jboss.xb.binding.sunday.unmarshalling.ParticleHandler;
import org.jboss.xb.binding.sunday.unmarshalling.SchemaBinding;
import org.jboss.xb.binding.sunday.unmarshalling.TypeBinding;
import org.jboss.xb.binding.sunday.unmarshalling.WildcardBinding;
import org.w3c.dom.DOMImplementation;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.xml.sax.Attributes;

/**
 * AnyComplexTypeUnitTestCase.
 * 
 * @author <a href="adrian@jboss.com">Adrian Brock</a>
 * @version $Revision: 1.1 $
 */
public class AnyComplexTypeUnitTestCase extends AbstractJBossXBTest
{  
   private static final String NS = "http://www.jboss.org/test/xml/anyComplexType";
   
   public static final TestSuite suite()
   {
      return new TestSuite(AnyComplexTypeUnitTestCase.class);
   }
   
   public AnyComplexTypeUnitTestCase(String name)
   {
      super(name);
   }

   public void testDOMFromWildcard() throws Exception
   {
      SchemaBinding schema = bind("AnyComplexType.xsd");
      schema.setIgnoreUnresolvedFieldOrClass(false);

      ClassMetaData classMetaData = new ClassMetaData();
      classMetaData.setImpl(Top.class.getName());
      ElementBinding element = schema.getElement(new QName(NS, "top"));
      assertNotNull(element);
      element.setClassMetaData(classMetaData);
      
      DOMUnresolvedHandler unresolved = new DOMUnresolvedHandler();
      TypeBinding type = schema.getType(new QName(NS, "any-complexType"));
      type.setStartElementCreatesObject(false);
      WildcardBinding wild = type.getWildcard();
      assertNotNull(wild);
      wild.setUnresolvedElementHandler(unresolved);
      wild.setUnresolvedCharactersHandler(unresolved);

      Top top = (Top) unmarshal("AnyComplexTypeFromWildCard.xml", schema, Top.class);
      Element dom = top.element;
      assertNotNull(dom);
      getLog().debug(DOMWriter.printNode(dom, true));
      assertElement("e1", dom);
      Element e1s1 = getUniqueChild(dom, "e1s1");
      assertElement("e1s1", e1s1);
      Element e1s1s1 = getUniqueChild(e1s1, "e1s1s1");
      assertElement("e1s1s1", e1s1s1);
      Element e1s1s2 = getUniqueChild(e1s1, "e1s1s2");
      assertElement("e1s1s2", e1s1s2);
      Element e1s2 = getUniqueChild(dom, "e1s2");
      assertElement("e1s2", e1s2);
   }

   public void testDOMFromWildcardAndElement() throws Exception
   {
      SchemaBinding schema = bind("AnyComplexType.xsd");
      schema.setIgnoreUnresolvedFieldOrClass(false);

      ClassMetaData classMetaData = new ClassMetaData();
      classMetaData.setImpl(Top.class.getName());
      ElementBinding element = schema.getElement(new QName(NS, "top"));
      assertNotNull(element);
      element.setClassMetaData(classMetaData);
      
      DOMUnresolvedHandler unresolved = new DOMUnresolvedHandler();
      TypeBinding type = schema.getType(new QName(NS, "any-complexType"));
      type.setStartElementCreatesObject(false);
      WildcardBinding wild = type.getWildcard();
      assertNotNull(wild);
      wild.setUnresolvedElementHandler(unresolved);
      wild.setUnresolvedCharactersHandler(unresolved);
      
      DOMInterceptor interceptor = new DOMInterceptor();
      element = schema.getElement(new QName(NS, "element"));
      element.pushInterceptor(interceptor);

      Top top = (Top) unmarshal("AnyComplexTypeFromWildCardAndElement.xml", schema, Top.class);
      Element dom = top.element;
      assertNotNull(dom);
      getLog().debug(DOMWriter.printNode(dom, true));
      assertElement("element", dom, true); // TODO FIXME
      Element e1 = getUniqueChild(dom, "e1");
      assertElement("e1", e1);
      Element e1s1 = getUniqueChild(e1, "e1s1");
      assertElement("e1s1", e1s1);
      Element e1s1s1 = getUniqueChild(e1s1, "e1s1s1");
      assertElement("e1s1s1", e1s1s1);
      Element e1s1s2 = getUniqueChild(e1s1, "e1s1s2");
      assertElement("e1s1s2", e1s1s2);
      Element e1s2 = getUniqueChild(e1, "e1s2");
      assertElement("e1s2", e1s2);
   }

   public void testDOMFromMultipleWildcardAndElement() throws Exception
   {
      SchemaBinding schema = bind("AnyComplexType.xsd");
      schema.setIgnoreUnresolvedFieldOrClass(false);

      ClassMetaData classMetaData = new ClassMetaData();
      classMetaData.setImpl(Top.class.getName());
      ElementBinding element = schema.getElement(new QName(NS, "top"));
      assertNotNull(element);
      element.setClassMetaData(classMetaData);
      
      DOMUnresolvedHandler unresolved = new DOMUnresolvedHandler();
      TypeBinding type = schema.getType(new QName(NS, "any-complexType"));
      //type.setStartElementCreatesObject(false);
      type.setHandler(new DOMUnresolvedHandler()
      {
         public void setParent(Object parent, Object o, QName qName, ParticleBinding particle,
               ParticleBinding parentParticle)
         {
            DefaultHandlers.ELEMENT_HANDLER.setParent(parent, o, qName, particle, parentParticle);
         }
      }
      );
      WildcardBinding wild = type.getWildcard();
      assertNotNull(wild);
      wild.setUnresolvedElementHandler(unresolved);
      wild.setUnresolvedCharactersHandler(unresolved);
      
      //DOMInterceptor interceptor = new DOMInterceptor();
      //element = schema.getElement(new QName(NS, "element"));
      //element.pushInterceptor(interceptor);

      Top top = (Top) unmarshal("AnyComplexTypeFromMultipleWildCardAndElement.xml", schema, Top.class);
      Element dom = top.element;
      assertNotNull(dom);
      getLog().debug(DOMWriter.printNode(dom, true));
      assertElement("element", dom, true); // TODO FIXME
      Element e1 = getUniqueChild(dom, "e1");
      assertElement("e1", e1);
      Element e1s1 = getUniqueChild(e1, "e1s1");
      assertElement("e1s1", e1s1);
      Element e1s1s1 = getUniqueChild(e1s1, "e1s1s1");
      assertElement("e1s1s1", e1s1s1);
      Element e1s1s2 = getUniqueChild(e1s1, "e1s1s2");
      assertElement("e1s1s2", e1s1s2);
      Element e1s2 = getUniqueChild(e1, "e1s2");
      assertElement("e1s2", e1s2);
      Element e2 = getUniqueChild(dom, "e2");
      assertElement("e2", e2);
   }
   
   protected void assertElement(String elementName, Element element)
   {
      assertElement(elementName, element, false);
   }
   
   protected void assertElement(String elementName, Element element, boolean ignoreChars)
   {
      assertNotNull(element);
      assertEquals(elementName, element.getLocalName());
      assertEquals(elementName + "a1", element.getAttribute(elementName + "a1"));
      assertEquals(elementName + "a2", element.getAttribute(elementName + "a2"));
      if (ignoreChars == false)
         assertEquals(elementName + "c1", getElementContent(element));
   }
   
   protected static String getElementContent(Element element)
   {
      if (element == null)
         return null;
      NodeList children = element.getChildNodes();
      StringBuffer result = new StringBuffer();
      for (int i = 0; i < children.getLength(); ++i)
      {
         Node child = children.item(i);
         if (child.getNodeType() == Node.TEXT_NODE || child.getNodeType() == Node.CDATA_SECTION_NODE)
            result.append(child.getNodeValue());
      }
      return result.toString().trim();
   }
   
   protected static Element getUniqueChild(Element element, String childName)
   {
      if (element == null)
         return null;
      NodeList children = element.getChildNodes();
      Element result = null;
      for (int i = 0; i < children.getLength(); ++i)
      {
         Node child = children.item(i);
         if (child.getNodeType() == Node.ELEMENT_NODE && childName.equals(child.getNodeName()))
         {
            if (result != null)
               fail(childName + " not unique");
            result = (Element) child;
         }
      }
      
      if (result == null)
         fail("No " + childName);
      return result;
   }
   
   public Element createTopElement(String namespace, String name)
   {
      try
      {
         DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
         factory.setNamespaceAware(true);
         DocumentBuilder builder = factory.newDocumentBuilder();
         DOMImplementation impl = builder.getDOMImplementation();
         Document document = impl.createDocument(null, null, null);
         
         Element element = document.createElementNS(namespace, name);
         document.appendChild(element);
         getLog().debug("createTopElement " + namespace + ":" + name + " result=" + toDebugString(element));
         return element;
      }
      catch (RuntimeException e)
      {
         throw e;
      }
      catch (Exception e)
      {
         throw new RuntimeException("Error creating dom", e);
      }
   }
   
   public Element appendChildElement(Element parentElement, String namespace, String name)
   {
      Document document = parentElement.getOwnerDocument();
      Element element = document.createElementNS(namespace, name);
      parentElement.appendChild(element);
      getLog().debug("appendChild parent=" + toDebugString(parentElement) + " child=" + toDebugString(element));
      return element;
   }
   
   public void addAttributes(Element element, Attributes attrs)
   {
      for (int i = 0; i < attrs.getLength(); ++i)
      {
         String local = attrs.getLocalName(i);
         String nsURI = attrs.getURI(i);
         String value = attrs.getValue(i);
         getLog().debug("setAttribute " + nsURI + " " + local + " element=" + toDebugString(element) + " value=" + value);
         element.setAttributeNS(nsURI, local, value);
      }
   }
   
   public void setText(Object owner, Object value, QName qName)
   {
      if (value == null)
         return;
      if (owner == null || owner instanceof Element == false)
         throw new IllegalStateException("Unexpected owner: " + owner + " for " + qName);
      if (value instanceof String == false)
         throw new IllegalStateException("Unexpected value " + value + " for " + qName);
      Element element = (Element) owner;
      Text text = element.getOwnerDocument().createTextNode((String) value);
      getLog().debug("setText " + qName + " parent=" + toDebugString(owner) + " child=" + toDebugString(value));
      element.appendChild(text);
   }

   public class DOMUnresolvedHandler extends CharactersHandler implements ParticleHandler
   {
      public Object startParticle(Object parent, QName elementName, ParticleBinding particle, Attributes attrs, NamespaceContext nsCtx)
      {
         getLog().debug("startParticle " + elementName + " parent=" + toDebugString(parent));
         Element element = null;
         if (parent == null || parent instanceof Element == false)
            element = createTopElement(elementName.getNamespaceURI(), elementName.getLocalPart());
         else
            element = appendChildElement((Element) parent, elementName.getNamespaceURI(), elementName.getLocalPart());
         addAttributes(element, attrs);
         return element;
      }

      public Object endParticle(Object o, QName elementName, ParticleBinding particle)
      {
         getLog().debug("endParticle " + elementName + " result=" + toDebugString(o));
         return o;
      }

      public void setParent(Object parent, Object o, QName elementName, ParticleBinding particle, ParticleBinding parentParticle)
      {
         getLog().debug("setParent " + elementName + " parent=" + toDebugString(parent) + " o=" + toDebugString(o));
      }

      public void setValue(QName qName, ElementBinding element, Object owner, Object value)
      {
         setText(owner, value, qName);
      }

      public Object unmarshal(QName qName, TypeBinding typeBinding, NamespaceContext nsCtx, ValueMetaData valueMetaData, String value)
      {
         return value;
      }

      public Object unmarshalEmpty(QName qName, TypeBinding typeBinding, NamespaceContext nsCtx, ValueMetaData valueMetaData)
      {
         return null;
      }
   }

   public class DOMInterceptor extends DefaultElementInterceptor
   {
      public Object startElement(Object parent, QName qName, TypeBinding type)
      {
         getLog().debug("startElement " + qName + " parent=" + toDebugString(parent));
         Element element = null;
         if (parent == null || parent instanceof Element == false)
         {
            element = createTopElement(qName.getNamespaceURI(), qName.getLocalPart());
            ((Top) parent).element = element;
         }
         else
            element = appendChildElement((Element) parent, qName.getNamespaceURI(), qName.getLocalPart());
         return element;
      }

      public void attributes(Object o, QName elementName, TypeBinding type, Attributes attrs, NamespaceContext nsCtx)
      {
         getLog().debug("setAttributes " + elementName + " parent=" + toDebugString(o));
         if (o == null || o instanceof Element == false)
            throw new IllegalStateException(Strings.defaultToString(o) + " is not an instanceof Element");
         addAttributes((Element) o, attrs);
      }

      public void characters(Object o, QName qName, TypeBinding type, NamespaceContext nsCtx, String text)
      {
         if (o == null || o instanceof Element == false)
            throw new IllegalStateException(Strings.defaultToString(o) + " is not an instanceof Element");
         setText(o, text, qName);
      }

      public void add(Object parent, Object child, QName qName)
      {
         getLog().debug("add " + qName + " parent=" + toDebugString(parent) + " child=" + toDebugString(child));
         if (parent == null || parent instanceof Element == false)
            throw new IllegalStateException(Strings.defaultToString(parent) + " is not an instanceof Element");
         if (child == null || child instanceof Element == false)
            throw new IllegalStateException(Strings.defaultToString(child) + " is not an instanceof Element");
         
         Element parentElement = (Element) parent;
         Element childElement = (Element) child;
         
         parentElement.getOwnerDocument().adoptNode(childElement);
         parentElement.appendChild(childElement);
      }
   }
   
   public static class Top
   {
      public Element element;
   }
   
   public static String toDebugString(Object object)
   {
      if (object == null)
         return "null";
      if (object instanceof String)
         return object.toString();
      if (object instanceof Element == false)
         return Strings.defaultToString(object);
      Element element = (Element) object;
      return "Element@" + System.identityHashCode(element) + "{" + element.getLocalName() + "}";
   }
}
