/////////////////////////////////////////////////////////////////////////
//
// © University of Southampton IT Innovation Centre, 2004
//
// Copyright in this library belongs to the IT Innovation Centre of
// 2 Venture Road, Chilworth Science Park, Southampton, SO16 7NP, UK.
//
// This software may not be used, sold, licensed, transferred, copied
// or reproduced in whole or in part in any manner or form or in or
// on any media by any person other than in accordance with the terms
// of the Licence Agreement supplied with the software, or otherwise
// without the prior written consent of the copyright owners.
//
// This software is distributed WITHOUT ANY WARRANTY, without even the
// implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
// PURPOSE, except where stated in the Licence Agreement supplied with
// the software.
//
//      Created by:             Darren Marvin
//      Created date:           2003/09/20
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.transportmessaging.payload.serialisation.basicxsd;

import eu.gemss.components.transport.payload.*;
import java.io.*;
import java.util.*;
import java.util.logging.*;
import javax.xml.namespace.*;
import org.apache.axis.*;
import org.jdom.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.payload.*;
import uk.ac.soton.itinnovation.gemss.transportmessaging.payload.typedescriptions.*;
import org.w3c.dom.*;

public class SimpleXSDDataItemDOMTranslator implements DataItemDOMTranslator {

    private static final Logger mLogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.transportmessaging.payload.serialisation.providers.basicxsd.SimpleXSDDataItemDOMTranslator");

    /**
     * Output the passed data item into a DOM object
     * @param dataItem
     * @return DOM Document
     * @throws PayloadException
     */
    public org.w3c.dom.Node outputDOM(DataItem dataItem) throws PayloadException {
        try{
            //this translator only deals with primitive types
            Object value = dataItem.getValue();
            QName typeQName = getTypeOfObjectAsQName(value);
            String strValue = getValueAsString(typeQName,value);
            if(strValue==null) {
                mLogger.log(Level.SEVERE,"Trying to translate a complex data type using a simple primitive translator. This is probably a bug please send a bug report");
                throw new PayloadException("Trying to translate a complex data type using a simple primitive translator. This is probably a bug please send a bug report");
            }
            //org.jdom.CDATA cdataSection = new org.jdom.CDATA(strValue.trim());//trimming white space from either side

            javax.xml.parsers.DocumentBuilderFactory factory = javax.xml.parsers.DocumentBuilderFactory.newInstance();
            org.w3c.dom.Document doc = factory.newDocumentBuilder().newDocument();
            org.w3c.dom.Element e = doc.createElement(dataItem.getName());
            e.appendChild(doc.createTextNode(strValue.trim()));
            e.setAttribute("xmlns:"+ Constants.NS_PREFIX_SCHEMA_XSI,Constants.URI_2001_SCHEMA_XSI);
            /*
            org.jdom.Text text = new org.jdom.Text(strValue.trim());
            org.jdom.output.DOMOutputter dOutputter = new org.jdom.output.DOMOutputter();

            org.jdom.Element element = new org.jdom.Element(dataItem.getName());
            //element.addContent(cdataSection);
            element.addContent(text);
            element.addNamespaceDeclaration(Namespace.getNamespace(Constants.NS_PREFIX_SCHEMA_XSI,Constants.URI_2001_SCHEMA_XSI));

            org.w3c.dom.Element e = dOutputter.output(element);
            e.setAttribute("xsi:type",typeQName.getLocalPart());

            //e.setNodeValue(dataItem.getName());
            /*
            //look for the text node value
            NodeList nL = e.getChildNodes();
            Node ret = null;
            for(int i=0;i<nL.getLength();i++) {
              if(nL.item(i).getNodeType()==Node.TEXT_NODE) {
                ret = nL.item(i);
              }
            }
            if(ret==null) {
              mLogger.log(Level.SEVERE,"Unable to produce XML form of data for data item '" + dataItem.getName() + "', please send a bug report with all logs");
              throw new PayloadException("Unable to produce XML form of data for data item '" + dataItem.getName() + "', please send a bug report with all logs");
            }
            return ret;
            */
            return e;
        }
        catch(javax.xml.parsers.ParserConfigurationException ex) {
            mLogger.log(Level.SEVERE,"Unable to produce XML form of data for data item '" + dataItem.getName() + "', please send a bug report with all logs",ex);
            throw new PayloadException("Unable to produce XML form of data for data item '" + dataItem.getName() + "', please send a bug report with all logs");
        }
    }

    /**
     * Output the passed DOM object into a DataItem
     * This translator only deals with primitive types.
     * It assumes that element passed has a primitive as its only child node
     * @param element
     * @param QName for type
     * @return data item
     * @throws PayloadException
     */
    public DataItem outputDataItem(org.w3c.dom.Node node,javax.xml.namespace.QName qName) throws PayloadException {
        try{
            //this translator only deals with primitive types
            //it assumes that element passed has a primitive as its only child node

            /*
            org.jdom.input.DOMBuilder dBuilder = new org.jdom.input.DOMBuilder();
            org.jdom.Element e = dBuilder.build(element);
            String typeStr = null;
            typeStr = e.getAttributeValue("type",Namespace.getNamespace(Constants.NS_PREFIX_SCHEMA_XSI,Constants.URI_2001_SCHEMA_XSI));
            if(typeStr==null) {
                //try an unqualified attribute search - dangerous but necessary
                typeStr = e.getAttributeValue("type");
            }
            QName qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, typeStr);

            List content = e.getContent();
            Iterator iterator = content.iterator();

            while(iterator.hasNext()) {
                Object obj = iterator.next();
                if(obj instanceof org.jdom.Text)
                    text = (org.jdom.Text) obj;
            }
            */
          NodeList ndList = node.getChildNodes();
          if(ndList.getLength()!=1) {
            mLogger.log(Level.SEVERE,"Unable to create value from XML object, might not be a supported type, please send a bug report with all logs");
              throw new PayloadException("Unable to create value from XML object,  might not be a supported type, please send a bug report with all logs");
        }
        //grab the text node
        Node nd = ndList.item(0);

            if(nd.getNodeType()!=Node.TEXT_NODE) {
              mLogger.log(Level.SEVERE,"Unable to create value from XML object, might not be a supported type, please send a bug report with all logs");
              throw new PayloadException("Unable to create value from XML object,  might not be a supported type, please send a bug report with all logs");
            }
            org.w3c.dom.Text textNode = (org.w3c.dom.Text) nd;

            Object obj = null;
            //now convert to object
            //if using byte array then the return element may be empty
            if(textNode == null) {
                //if using byte array then the return element may be empty
                if(qName.getLocalPart().equals(XSDTypeDescription.XSD_BYTEARRAY.getLocalPart())) {
                  obj = getValueFromString(qName,null);
                }
                //if returning string then the string could be empty, this is not the best really
                else if(qName.getLocalPart().equals(XSDTypeDescription.XSD_STRING.getLocalPart())) {
                    obj = new String("");
                }
                else {
                    mLogger.log(Level.SEVERE,"Trying to translate a complex data type using a simple primitive translator. This is probably a bug please send a bug report");
                    throw new PayloadException("Trying to translate a complex data type using a simple primitive translator. This is probably a bug please send a bug report");
                }
            }
            else
                obj = getValueFromString(qName,textNode.getData());
            if(obj==null) {
                mLogger.log(Level.SEVERE,"Unable to create value from XML object, string value is '" + textNode.getData() + "' and should be type '" + qName.getLocalPart() +"'");
                throw new PayloadException("Unable to create value from XML object, could be a bug, please send a bug report with all logs");
            }
            return new DataItemImpl(qName.getLocalPart(),new XSDTypeDescription(qName.getLocalPart(),qName),obj);
        }
        catch(Exception ex) {
            if(ex instanceof PayloadException)
                throw (PayloadException) ex;
            mLogger.log(Level.SEVERE,"Unable to create value from XML object, could be a bug, please send a bug report with all logs",ex);
            throw new PayloadException("Unable to create value from XML object, could be a bug, please send a bug report with all logs");
        }

    }

    /**
     * Test if the translator can handle the passed data item
     * @param dataItem
     * @return true if can handle, false otherwise
     * @throws PayloadException
     */
    public boolean canHandle(DataItem dataItem) throws PayloadException {
        Object value = dataItem.getValue();
        if(getTypeOfObjectAsQName(value)==null)
            return false;
        else
            return true;
    }

    /**
     * Test if the translator can handle the passed element
     * @param node
     * @return true if can handle, false otherwise
     * @throws PayloadException
     */
    public boolean canHandle(org.w3c.dom.Node node) throws PayloadException{
      /*
      if(getTypeOfNode(node,qName)==null)
            return false;
        else
            return true;
      */
      if(node.getChildNodes().item(0).getNodeType()==Node.TEXT_NODE)
        return true;
      else
        return false;
      }

    /**
     * Checks to see if the passed translator is of the same type as this one
     * @param translator DataItemDOMTranslator to check
     * @return true if match, false otherwise.
     */
    public boolean isSameTranslator(DataItemDOMTranslator translator) {
        if(translator instanceof SimpleXSDDataItemDOMTranslator)
            return true;
        else
            return false;
    }

    /**
     * Retrieve the QName used to describe the passed value
     * @param value
     * @return qualified name
     */
    public QName getTypeOfObjectAsQName(Object value) throws PayloadException {
       //returns null if any error occurs
       QName qName = null;
       if(value instanceof String)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:string");
       else if(value instanceof Boolean)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:boolean");
       else if(value instanceof Float)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:float");
       else if(value instanceof Integer)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:int");
       else if(value instanceof java.math.BigInteger)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:integer");
       else if(value instanceof Double)
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:double");
       else if(value instanceof byte[])
           qName = new QName(Constants.URI_DEFAULT_SCHEMA_XSD, "xsd:base64Binary");
       else{
           //it must be some complex type and hence not supported
           throw new PayloadException("Unsupported type: " + value.getClass());
       }
       return qName;
    }

/*
    private QName getTypeOfNode(org.w3c.dom.Node node,javax.xml.namespace.QName qName) {

      QName qName = null;
        org.jdom.input.DOMBuilder dBuilder = new org.jdom.input.DOMBuilder();
        org.jdom.Element e = dBuilder.build(element);
        String typeStr = e.getAttributeValue("type"); //would like to qualify more but axis does not seem to.
        if(typeStr==null) {
            //try to qualify more
             mLogger.log(Level.WARNING,"Type string is null.");
             typeStr = e.getAttributeValue("type",Namespace.getNamespace(Constants.NS_PREFIX_SCHEMA_XSI,Constants.URI_DEFAULT_SCHEMA_XSI));
             if(typeStr==null) {
                 mLogger.log(Level.SEVERE,"Type string is still null.");
             }
        }
        if(typeStr!=null)
          return new QName(Constants.URI_DEFAULT_SCHEMA_XSD, typeStr);
        else
          return qName;
    }
*/


    private String getValueAsString(QName type, Object value) throws PayloadException {
        String strValue = null;

        if(value==null) {
            mLogger.log(Level.SEVERE,"Trying to get a string from an object but the object is null");
            throw new PayloadException("A problem has occured, please send a bug report along with all log files");
        }
        if(type.getLocalPart().equals("xsd:string"))
           strValue = (String) value;
        else if(type.getLocalPart().equals("xsd:boolean"))
            strValue = ((Boolean) value).toString();
        else if(type.getLocalPart().equals("xsd:float"))
            strValue = ((Float) value).toString();
        else if(type.getLocalPart().equals("xsd:int"))
            strValue = ((Integer) value).toString();
        else if(type.getLocalPart().equals("xsd:integer"))
            strValue = ((java.math.BigInteger) value).toString();
        else if(type.getLocalPart().equals("xsd:double"))
            strValue = ((Double) value).toString();
        else if(type.getLocalPart().equals("xsd:base64Binary"))
            strValue = new String(org.bouncycastle.util.encoders.Base64.encode((byte[]) value));
        if(strValue==null) {
            mLogger.log(Level.SEVERE,"Could not create serialisation value for supplied object");
            throw new PayloadException("Could not create serialisation value for supplied object");
        }
        return strValue;
    }

    private Object getValueFromString(QName type, String value) throws PayloadException {
        /*
        if(value==null) {
            mLogger.log(Level.SEVERE,"Trying to get a value from a string but the string is null");
            throw new PayloadException("A problem has occured, please send a bug report along with all log files");
        }
        */
        Object obj = null;
        if(value==null) {
            //likely it is a null string
            obj = new String("");
        }
        else {

          /**
         Note for the future SOAP 1.2 uses soapenc:string etc not xsd prefix
         */

          if(type.getLocalPart().equals("xsd:boolean") || type.getLocalPart().equals("soapenc:boolean"))
            obj = new Boolean(value);
          else if(type.getLocalPart().equals("xsd:float") || type.getLocalPart().equals("soapenc:float"))
            obj = new Float(value);
          else if(type.getLocalPart().equals("xsd:int") || type.getLocalPart().equals("soapenc:int"))
            obj = new Integer(value);
          else if(type.getLocalPart().equals("xsd:integer") || type.getLocalPart().equals("soapenc:integer"))
            obj = new java.math.BigInteger(value);
          else if(type.getLocalPart().equals("xsd:double") || type.getLocalPart().equals("soapenc:double"))
            obj = new Double(value);
          else if(type.getLocalPart().equals("xsd:base64Binary") || type.getLocalPart().equals("soapenc:base64Binary")) {
            //if no data exists then value might actually be null, in which case create empty byte array
            if(value==null)
              obj = new byte[0];
            else
              obj = org.bouncycastle.util.encoders.Base64.decode(value);
          }
          else if(type.getLocalPart().equals("xsd:string") || type.getLocalPart().equals("soapenc:string") || type.getLocalPart().equals("")) {
            obj = value;
          }
          if(obj==null) {
            //treat it as a string - this is actually an incorrect response from the server really.
            mLogger.log(Level.SEVERE,"Could not create object value for string value '" + value + "'");
            throw new PayloadException("Could not create the correct data item value");

          }
        }
        return obj;
    }

    private void writeObject(java.io.ObjectOutputStream out) throws IOException {
        //no special data members

   }

   private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException {
       //no special data members
    }
}