/////////////////////////////////////////////////////////////////////////
//
// © 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 a call for proposal 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 CallForProposalsXML extends Object {

      // mamber variables
      private double mnBidThreshold;
      private long mnDeadlineTimeoffset; // offset from 2000 GMT
      private Vector mvectorProperties; // vector of (property URI, weight, direction, limit, range)

      // XML tag names
      private static final String mstaticCFPTag = "CallForProposals";
      private static final String mstaticBidThreshold = "BidThreshold";
      private static final String mstaticDeadline = "Deadline";
      private static final String mstaticValueAttr = "value";
      private static final String mstaticProperty = "Property";
      private static final String mstaticPropNameAttr = "name";
      private static final String mstaticPropWeightAttr = "weight";
      private static final String mstaticPropMinAttr = "min";
      private static final String mstaticPropMaxAttr = "max";
      private static final String mstaticPropConstraintAttr = "constraint";

      //
      // XML structure:
      //
      // <CallForProposals>
      //    <BidThreshold value=.../>
      //    <Deadline value=.../>
      //    <Property name=... weight=... min=... max=... constraint=max/>
      //    <Property name=... weight=... min=... max=... constraint=min/>
      //    <Property name=... weight=... min=... max=... constraint=equal/>
      // </CallForProposals>
      //

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

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

            // default values
            mnBidThreshold = -1;
            mnDeadlineTimeoffset = -1;
            mvectorProperties = new Vector();
      }

      /**
       * setup the bidding threshold and deadline for cfp
       * @param nBidThreshold bid threshold value
       * @param nDeadlineTimeoffset deadline for bid generation as an offset from GMT 2000
       */
      public void setDetails( double nBidThreshold, long nDeadlineTimeoffset ) throws Exception
      {
            mnBidThreshold = nBidThreshold;
            mnDeadlineTimeoffset = nDeadlineTimeoffset;
      }

      /**
       * add a proprty to the prop vector
       * @param vectorProperties vector of property value strings (prop name, weight, min, max, constraint)
       */
      public void addProperty( String strName, String strWeight, String strMin, String strMax, String strConstraint ) throws Exception
      {
            Vector vector;

            vector = new Vector();
            vector.addElement( strName );
            vector.addElement( strWeight );
            vector.addElement( strMin );
            vector.addElement( strMax );
            vector.addElement( strConstraint );
            mvectorProperties.addElement( vector );
      }

      /**
       * return the bidding threshold value
       * @return bid threshold
       */
      public double getBidThreshold()
      {
            return mnBidThreshold;
      }

      /**
       * return the deadline time offset
       * @return deadline time offset from GMT 2000
       */
      public double getDeadline()
      {
            return mnDeadlineTimeoffset;
      }


      /**
       * return a property given the index
       * @return vector of strings (prop name, weight, min, max, constraint)
       */
      public Vector getProperty( int nIndex ) throws Exception
      {
            return (Vector) mvectorProperties.elementAt( nIndex );
      }

      /**
       * return the size of the property vector
       * @return size of property vector
       */
      public int getSize() throws Exception
      {
            return mvectorProperties.size();
      }

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

            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
                  // CFP tag
                  element1 = document.createElement( mstaticCFPTag );
                  document.appendChild( element1 );

                  // bid threshold
                  element2 = document.createElement( mstaticBidThreshold );
                  element2.setAttribute( mstaticValueAttr, String.valueOf( mnBidThreshold ) );
                  element1.appendChild( element2 );

                  // deadline
                  element2 = document.createElement( mstaticDeadline );
                  element2.setAttribute( mstaticValueAttr, String.valueOf( mnDeadlineTimeoffset ) );
                  element1.appendChild( element2 );

                  // loop on properties
                  for( i=0;i<mvectorProperties.size();i++ ) {
                        // get property
                        vectorProp = (Vector) mvectorProperties.elementAt(i);
                        if( vectorProp == null ) throw new Exception("null property vector");
                        if( vectorProp.size() != 5 ) throw new Exception("Vector size "+vectorProp.size()+" != 5");

                        // (prop name, weight, min, max, constraint)
                        element2 = document.createElement( mstaticProperty );
                        element2.setAttribute( mstaticPropNameAttr, (String) vectorProp.elementAt(0) );
                        element2.setAttribute( mstaticPropWeightAttr, (String) vectorProp.elementAt(1) );
                        element2.setAttribute( mstaticPropMinAttr, (String) vectorProp.elementAt(2) );
                        element2.setAttribute( mstaticPropMaxAttr, (String) vectorProp.elementAt(3) );
                        element2.setAttribute( mstaticPropConstraintAttr, (String) vectorProp.elementAt(4) );
                        element1.appendChild( element2 );
                  } // next prop

                  // 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 parseCFP( 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
                  mnBidThreshold = -1;
                  mnDeadlineTimeoffset = -1;
                  mvectorProperties = new Vector();

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

                  // check for values not being set
                  if( mnBidThreshold == -1 || mnDeadlineTimeoffset == -1 || mvectorProperties == null ) {
                        mlogger.log( Level.INFO,"Some values NOT set in CFP after value extraction" );
                        return false;
                  }

                  // all done
                  return true;
            }
            catch( SAXException se )
            {
                  mlogger.log( Level.INFO,"CFP parse error: "+se.getMessage() );
                  return false;
            }
            catch( ParserConfigurationException pce ) {
                  mlogger.log( Level.INFO,"CFP parser configuration error: "+pce.getMessage() );
                  return false;
            }
            catch( IOException ie )
            {
                  mlogger.log( Level.INFO,"CFP parse IO error"+ie.getMessage() );
                  return false;
            }
            catch( Exception ex )
            {
                  mlogger.log( Level.INFO,"CFP 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;
            Vector vectorProp;

            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 ) {
                        // bid threshold?
                        if( mstaticBidThreshold.equals( node.getLocalName() ) ) {
                              // init value
                              strValue = null;

                              // get value 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

                              // parse double
                              if( strValue == null ) throw new Exception("No value attribute found for bid threshold");
                              mnBidThreshold = Double.parseDouble( strValue );

                              // all done - do not recurse further
                              return true;
                        } // tag check
                        // deadline?
                        if( mstaticDeadline.equals( node.getLocalName() ) ) {
                              // init value
                              strValue = null;

                              // get value 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

                              // parse double
                              if( strValue == null ) throw new Exception("No value attribute found for deadline");
                              mnDeadlineTimeoffset = Long.parseLong( strValue );

                              // all done - do not recurse further
                              return true;
                        } // tag check
                        else if( mstaticProperty.equals( node.getLocalName() ) ) { // property?
                              // init vector
                              vectorProp = new Vector(5);
                              vectorProp.addElement( null ); // add nulls so they can be filled in when traverse the
                              vectorProp.addElement( null ); // right node
                              vectorProp.addElement( null );
                              vectorProp.addElement( null );
                              vectorProp.addElement( null );
                              mvectorProperties.addElement( vectorProp );

                              // loop on attributes
                              nodeMap = node.getAttributes();
                              if( nodeMap != null ) {
                                    for( i=0;i<nodeMap.getLength();i++ ) {
                                          // get node and its value
                                          node2 = nodeMap.item(i);

                                          // check atributes we know about
                                          if( mstaticPropNameAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                                vectorProp.setElementAt( strValue,0 );
                                          }
                                          if( mstaticPropWeightAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                                vectorProp.setElementAt( strValue,1 );
                                          }
                                          if( mstaticPropMinAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                                vectorProp.setElementAt( strValue,2 );
                                          }
                                          if( mstaticPropMaxAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                                vectorProp.setElementAt( strValue,3 );
                                          }
                                          if( mstaticPropConstraintAttr.equals( node2.getLocalName() ) ) {
                                                // get the value of the attribute
                                                strValue = node2.getNodeValue();
                                                if( strValue == null ) throw new Exception("null value for known node");
                                                vectorProp.setElementAt( strValue,4 );
                                          }
                                    } // next node
                              } // null check

                              // check vector is ok
                              if( vectorProp.size() != 5 ) throw new Exception("vector size "+vectorProp.size()+" != 5 for prop vector");
                              for( i=0;i<vectorProp.size();i++ )
                                    if( vectorProp.elementAt(i) == null ) throw new Exception("Prop vector index "+i+" is null");

                              // 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 CFP XML string [transformer-config]",tce );
                  throw tce;
            }
            catch (TransformerException te) {
                  mlogger.log( Level.WARNING,"Failed to parse CFP XML string [transformer]",te );
                  throw te;
            }
            catch ( Exception ex )
            {
                  mlogger.log( Level.WARNING,"Failed to parse CFP XML string",ex );
                  throw ex;	// re-throw exception
            }
      }

} // end of CallForProposasXML


