/////////////////////////////////////////////////////////////////////////
//
// © 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:             Stuart E. Middleton
//      Created date:           2004/04/30
//      Created for project:    GEMSS
//
/////////////////////////////////////////////////////////////////////////
//
//      Dependencies: None
//
/////////////////////////////////////////////////////////////////////////
//
//      Last commit info:       $Author: $
//                              $Date: $
//                              $Revision: $
//
/////////////////////////////////////////////////////////////////////////

package uk.ac.soton.itinnovation.gemss.negotiation;

import java.lang.*;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.Properties;
import java.util.Vector;
import java.io.*;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;

import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.InputSource;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.DOMException;
import org.w3c.dom.NodeList;
import org.w3c.dom.NamedNodeMap;

import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerConfigurationException;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.OutputKeys;


/**
 * This class represents the auction details as an XML string. This class could also have been
 * constructed from an XML schema, with auto generated Java classes. It is used simply as a construct
 * to format and read auction details from an XML string.
 */
public class AuctionDetailsXML extends Object {

      // mamber variables
      private int mnMaxRounds;
      private long mnMaxDuration;
      private long mnStartTime;

      // XML tag names
      private static final String mstaticAuctionDetailsTag = "AuctionDetails";
      private static final String mstaticMaxRounds = "MaxRounds";
      private static final String mstaticMaxDuration = "MaxDuration";
      private static final String mstaticStartTime = "StartTime";
      private static final String mstaticValueAttr = "value";

      // constant
      private static final long mstaticBigNegLong = -2000000;
      private static final int mstaticBigNegInt = -32000;

      //
      // XML structure:
      //
      // <AuctionDetails>
      //    <StartTime value=?/>
      //    <MaxDuration value=?/>
      //    <MaxRounds value=?/>
      // </AuctionDetails>
      //

      // logging
      private static Logger mlogger = Logger.getLogger("uk.ac.soton.itinnovation.gemss.negotiation.auctiondetailsxml");

      /**
       * default constructor, does nothing but initialize
       */
      public AuctionDetailsXML()
      {
            super();

            // default values
            mnMaxRounds = -1;
            mnMaxDuration = -1;
            mnStartTime = -1;
      }

      /**
       * setup the values for this objects auction details
       * @param nMaxRounds max number of rounds of the auction
       * @param nStart start time millisecond GMT 2000 offset
       * @param nMaxDuration max auction duration in milliseconds from the start time
       */
      public void setAuctionDetails( int nMaxRounds, long nStart, long nMaxDuration ) throws Exception
      {
            mnMaxRounds = nMaxRounds;
            mnStartTime = nStart;
            mnMaxDuration = nMaxDuration;
      }

      /**
       * return the value for max rounds (-1 is not set)
       * @return max number of rounds of the auction
       */
      public int getMaxRounds()
      {
            return mnMaxRounds;
      }

      /**
       * return the value for start time (-1 is not set)
       * @return start time millisecond GMT 2000 offset
       */
      public long getStartTime()
      {
            return mnStartTime;
      }

      /**
       * return the value for max duration (-1 is not set)
       * @return max auction duration in milliseconds from the start time
       */
      public long getMaxDuration()
      {
            return mnMaxDuration;
      }

      /**
       * format an XML string with these auction details. A fresh XML document is made using the values currently
       * setup using teh set method.
       * @return XML string or null on error
       */
      public String formatAuctionDetails() throws Exception
      {
            DocumentBuilderFactory factory;
            DocumentBuilder builder;
            Document document;
            Element element1, element2;

            try {
                  // get a doc builder
                  factory = DocumentBuilderFactory.newInstance();
                  factory.setValidating(true);
                  factory.setNamespaceAware(true);
                  builder = factory.newDocumentBuilder();
                  builder.setErrorHandler( null );

                  // make a new document
                  document = builder.newDocument();

                  // add values to this document
                  // details tag
                  element1 = document.createElement( mstaticAuctionDetailsTag );
                  document.appendChild( element1 );
                  // max rounds
                  element2 = document.createElement( mstaticMaxRounds );
                  element2.setAttribute( mstaticValueAttr, String.valueOf( mnMaxRounds ) );
                  element1.appendChild( element2 );
                  // start time
                  element2 = document.createElement( mstaticStartTime );
                  element2.setAttribute( mstaticValueAttr, String.valueOf( mnStartTime ) );
                  element1.appendChild( element2 );
                  // duration
                  element2 = document.createElement( mstaticMaxDuration );
                  element2.setAttribute( mstaticValueAttr, String.valueOf( mnMaxDuration ) );
                  element1.appendChild( element2 );

                  // convert to XML
                  return convertToXML( document );
            }
            catch( ParserConfigurationException pce ) {
                  mlogger.log( Level.INFO,"Auction details parser configuration error",pce );
                  return null;
            }
            catch( Exception ex )
            {
                  mlogger.log( Level.INFO,"Auction details error",ex );
                  return null;
            }
      }

      /**
       * parse an XML string and set the values obtained from it. These values can be queried via
       * the get methods of this class.
       * @param strXML xml string containing the auction details XML. Extraction occurs via a deep search
       * for the tag names, so other XML constructs can in principle be added and will be ignored. This is not advised
       * however.
       * @return if true the parse was a success and all values were extracted
       */
      public boolean parseAuctionDetails( String strXML )
      {
            StringReader reader;
            InputSource source;
            DocumentBuilderFactory factory;
            DocumentBuilder builder;
            Document document;

            try
            {
                  // get a doc builder
                  factory = DocumentBuilderFactory.newInstance();
                  factory.setValidating(true);
                  factory.setNamespaceAware(true);
                  builder = factory.newDocumentBuilder();
                  builder.setErrorHandler( null );

                  // parse XML string
                  reader = new StringReader( strXML );
                  source = new InputSource( reader );
                  document = builder.parse( source );

                  // ini values before extraction
                  mnMaxRounds = mstaticBigNegInt;
                  mnStartTime = mstaticBigNegLong;
                  mnMaxDuration = mstaticBigNegLong;

                  // extract values from doc and return success (or not) of the search.
                  if( !extractValues( document ) ) {
                        mlogger.log( Level.INFO,"Auction details extraction from XML string failed" );
                        return false;
                  }

                  // check for values not being set
                  if( mnMaxRounds == mstaticBigNegInt || mnStartTime == mstaticBigNegLong || mnMaxDuration == mstaticBigNegLong ) {
                        mlogger.log( Level.INFO,"Some values NOT set in auction details after value extraction" );
                        return false;
                  }

                  // all done
                  return true;
            }
            catch( SAXException se )
            {
                  mlogger.log( Level.INFO,"Auction details parse error: "+se.getMessage() );
                  return false;
            }
            catch( ParserConfigurationException pce ) {
                  mlogger.log( Level.INFO,"Auction details parser configuration error: "+pce.getMessage() );
                  return false;
            }
            catch( IOException ie )
            {
                  mlogger.log( Level.INFO,"Auction details parse IO error"+ie.getMessage() );
                  return false;
            }
            catch( Exception ex )
            {
                  mlogger.log( Level.INFO,"Auction details error",ex );
                  return false;
            }
      }

      /**
       * extract the auction values from a DOM document. A deep search is performed, using ths function recursively.
       * @param node DOM node to search from
       * @return true if successfully completed search of this node (might not have found all the values but at least the
       * XML was ok to search)
       */
      private boolean extractValues( org.w3c.dom.Node node ) throws Exception
      {
            NodeList nodeList;
            NamedNodeMap nodeMap;
            org.w3c.dom.Node node2;	// clash with Jena Node class
            int i;
            String strValue;

            try
            {
                  // check node is ok
                  if( node == null ) throw new Exception("null node");
                  if( node.getNodeType() != org.w3c.dom.Node.ELEMENT_NODE &&
                     node.getNodeType() != org.w3c.dom.Node.DOCUMENT_NODE ) return true; // ignore non-element/doc nodes - do not recurse

                  // check name (can be null for document node)
                  if( node.getLocalName() != null ) {
                        // get value if one of our required tags
                        if( mstaticMaxRounds.equals( node.getLocalName() ) ||
                            mstaticMaxDuration.equals( node.getLocalName() ) ||
                            mstaticStartTime.equals( node.getLocalName() ) ) {
                              // init value
                              strValue = null;

                              // get name attribute
                              nodeMap = node.getAttributes();
                              if( nodeMap != null ) {
                                    for( i=0;i<nodeMap.getLength();i++ ) {
                                          node2 = nodeMap.item(i);
                                          if( mstaticValueAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the value attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                          } // value attribute name check
                                    } // next node
                              } // null check

                              if( strValue == null ) throw new Exception("No value attribute found");

                              // parse value
                              if( mstaticMaxRounds.equals( node.getLocalName() ) ) {
                                    // parse the int and set the value
                                    mnMaxRounds = Integer.parseInt( strValue );
                              }
                              else if( mstaticMaxDuration.equals( node.getLocalName() ) ) {
                                    // parse the int and set the value
                                    mnMaxDuration = Long.parseLong( strValue );
                              }
                              else if( mstaticStartTime.equals( node.getLocalName() ) ) {
                                    // parse the int and set the value
                                    mnStartTime = Long.parseLong( strValue );
                              }

                              // all done - do not recurse further
                              return true;
                        } // tag check
                  } // null local name check

                  // loop on children
                  nodeList = node.getChildNodes();
                  if( nodeList != null ) {
                        // loop on all nodes in list, looking for other ELEMENT nodes
                        for( i=0;i<nodeList.getLength();i++ ) {
                              node2 = nodeList.item( i );
                              if( node2 == null ) throw new Exception("null node");
                              if( node2.getNodeType() == org.w3c.dom.Node.ELEMENT_NODE ) {
                                    // recurse on these nodes
                                    if( !extractValues( node2 ) ) return false;
                              } // type check
                        } // next node
                  } // null check

                  // all done for last node in branch
                  return true;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.INFO,"Extraction error : "+ex.getMessage(),ex );
                  return false;
            }
      }

      /**
       * convert a DOM document to an XML string
       * @return XML string or null on error
       */
      private String convertToXML( Document document ) throws Exception
      {
            TransformerFactory tFactory;
            Transformer transformer;
            String strNewWSLA, strValue;
            DOMSource domSource;
            StreamResult result;
            StringWriter writerString;

            try {
                  // serialize the WSLA document with it's new values
                  // Use a Transformer for output
                  tFactory = TransformerFactory.newInstance();
                  transformer = tFactory.newTransformer();
                  transformer.setOutputProperty( OutputKeys.INDENT, "yes" );

                  // preserve DOCTYPE = XML (NOT NEEDED?)
//                  if (docWSLA.getDoctype() != null) {
//                        strValue = ( new File( docWSLA.getDoctype().getSystemId() ) ).getName();
//                        transformer.setOutputProperty( OutputKeys.DOCTYPE_SYSTEM, strValue );
//                  }

                  // transform DOM -> string writer
                  domSource = new DOMSource( document );
                  writerString = new StringWriter();
                  result = new StreamResult( writerString );
                  transformer.transform( domSource, result );

                  // make string from stream
                  return writerString.toString();
            }
            catch (TransformerConfigurationException tce) {
                  mlogger.log( Level.WARNING,"Failed to parse auction details string [transformer-config]",tce );
                  throw tce;
            }
            catch (TransformerException te) {
                  mlogger.log( Level.WARNING,"Failed to parse auction details string [transformer]",te );
                  throw te;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to parse auction details string",ex );
                  throw ex;	// re-throw exception
            }
      }

} // end of AuctionDetailsXML


