package org.codehaus.xfire.xmlbeans;

import java.util.HashSet;
import java.util.Set;

import javax.xml.namespace.QName;
import javax.xml.stream.XMLStreamException;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.xmlbeans.SchemaProperty;
import org.apache.xmlbeans.SchemaType;
import org.apache.xmlbeans.XmlBeans;
import org.apache.xmlbeans.XmlCursor;
import org.apache.xmlbeans.XmlException;
import org.apache.xmlbeans.XmlObject;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.XFireRuntimeException;
import org.codehaus.xfire.aegis.MessageReader;
import org.codehaus.xfire.aegis.MessageWriter;
import org.codehaus.xfire.aegis.stax.ElementReader;
import org.codehaus.xfire.aegis.stax.ElementWriter;
import org.codehaus.xfire.aegis.type.Type;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.util.STAXUtils;
import org.codehaus.yom.Element;

/**
 * @author <a href="mailto:dan@envoisolutions.com">Dan Diephouse</a>
 * @since Nov 13, 2004
 */
public class XmlBeansType 
    extends Type
{
    private SchemaType schemaType;

    private final static Log logger = LogFactory.getLog(XmlBeansType.class); 
    
    public XmlBeansType()
    {
    }
    
    public XmlBeansType(SchemaType schemaType)
    {
        this.schemaType = schemaType;
        setTypeClass(schemaType.getJavaClass());
    }

    public XmlBeansType(Class clazz)
    {
        this.schemaType = XmlBeans.typeForClass(clazz);
        setTypeClass(clazz);
    }

    public void writeSchema(Element root)
    {
    }
    
    public boolean isComplex()
    {
        return !schemaType.isPrimitiveType();
    }

    public boolean isAbstract()
    {
        return schemaType.isAbstract();
    }

    public Set getDependencies()
    {
        SchemaProperty[] properties = schemaType.getProperties();
        HashSet deps = new HashSet();
        for (int i = 0; i < properties.length; i++)
        {
            SchemaType etype = properties[i].getType();
            SchemaProperty[] iprops = etype.getElementProperties();
            for (int j = 0; j < iprops.length; j++)
            {
                SchemaType itype = iprops[j].getType();
                
                if (!itype.isPrimitiveType() && itype.getSourceName() != null)
                {
                    deps.add(new XmlBeansType(itype));
                }
            }
        }
        return deps;
    }

    public QName getSchemaType()
    {
        if (schemaType.isDocumentType())
            return schemaType.getDocumentElementName();
        else if (schemaType.getName() != null)
            return schemaType.getName();
        else
        {
            // No name for this type, use outer type (and recur up if same)
            SchemaType outer = schemaType.getOuterType();
            while (outer != null)
            {
                if (outer.isDocumentType())
                    return outer.getDocumentElementName();
                else if (outer.getName() != null)
                    return outer.getName();
                else
                    outer = outer.getOuterType();
            }
            
            // No outer type, no type on this, should not be possible, so explode
            throw new XFireRuntimeException("No type name is defined for <" + schemaType + "> " +
                                            "and no outer type containing the inline type -- this " +
                                            "should not be possible to be a legally defined schema");
        }
    }

    public Object readObject(MessageReader reader, MessageContext context)
        throws XFireFault
    {
        try
        {
            return XmlObject.Factory.parse(((ElementReader)reader).getXMLStreamReader());
        }
        catch( XmlException e )
        {
            throw new XFireFault("Could not read request.", e, XFireFault.SENDER);
        }
    }

    public void writeObject(Object value, MessageWriter writer, MessageContext context)
        throws XFireFault
    {
        try
        {
            XmlObject obj = (XmlObject) value; 
       
            XmlCursor cursor = obj.newCursor();
            if (cursor.toFirstChild() && cursor.toFirstChild())
            {
                do
                {
                    STAXUtils.copy(cursor.newXMLStreamReader(), 
                                   ((ElementWriter) writer).getXMLStreamWriter());
                }
                while(cursor.toNextSibling());
            }
        } 
        catch (XMLStreamException e)
        {
            throw new XFireFault("Could not write response.", e, XFireFault.SENDER);
        }
    }
}