/////////////////////////////////////////////////////////////////////////
//
// © 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.evaluator;

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

import uk.ac.soton.itinnovation.gemss.negotiation.CallForProposalsXML;
import uk.ac.soton.itinnovation.gemss.negotiation.evaluator.QoSEvaluator;
import uk.ac.soton.itinnovation.gemss.business.PriceModel;
import uk.ac.soton.itinnovation.gemss.negotiation.wsla.WSLAQoSHandler;

/**
 * This class takes a QoS property vector and computes a score for it.
 * This score will be used by the client to judge
 * which WSLA to choose from the set of WSLA's generated by each service provider.
 * A vector dot product algorithm (i.e. cosine similarity) is used to score how well a property vector
 * matches the target vector and a simplistic multi dimensional leveling algorithm used to satisfy
 * a given threshold.
 * This is really basic stuff and the algorithm performance could be improved a lot.
 */
public class QoSEvaluatorDotProduct implements QoSEvaluator {

      // member vars
      private String mstrConstraintEqual; // equal value for constraint on ontology
      private String mstrConstraintMin; // min value for constraint on ontology
      private String mstrConstraintMax; // max value for constraint on ontology
      private String mstrEndTime; // name of end time property
      private String mstrStartTime; // name of start time property
      private String mstrPrice; // name of price property
      private double mnPropertyValueIncrement; // fractional increment for props when changing to make threshold
      private long mnMaxIncrements; // max number of increments before "timing" out (normally 1/(prop increment))
      private Vector mvectorFlexible; // list of flexible properties that can change to make a threshold
      private String mstrDoubleType; // double type (used to parse numeric values - if not double its long)
      private WSLAQoSHandler mwslaHandler; // WSLA handler for parsing etc
      private PriceModel mpriceModel; // price model
      // TODO replace this with a reference to the business module, which will hide the pricing model object behind an interface

      // config tags for config file
      private static final String mstaticConfigPropIncrement = "PropIncrement";
      private static final String mstaticConfigMaxIncrements = "MaxIncrement";
      private static final String mstaticConfigConstraintMin = "ConstraintMin";
      private static final String mstaticConfigConstraintMax = "ConstraintMax";
      private static final String mstaticConfigConstraintEqual = "ConstraintEqual";
      private static final String mstaticConfigFlexible = "FlexibleProp"; // FlexibleProp1, FlexibleProp2 .. FlexiblePropN
      private static final String mstaticConfigPropStartTime = "PropStartTime";
      private static final String mstaticConfigPropEndTime = "PropEndTime";
      private static final String mstaticConfigPropPrice = "PropPrice";
      private static final String mstaticConfigDoubleType = "DoubleType";

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

      /**
       * constructor. Passes the constraint names the appear within the CFP object.
       */
      public QoSEvaluatorDotProduct( WSLAQoSHandler wslaHandler, PriceModel priceModel, String strConfig ) throws Exception
	{
              mstrConstraintEqual = null;
              mstrConstraintMin = null;
              mstrConstraintMax = null;
              mstrStartTime = null;
              mstrEndTime = null;
              mstrPrice = null;
              mnPropertyValueIncrement = -1;
              mnMaxIncrements = -1;
              mstrDoubleType = null;
              mwslaHandler = wslaHandler;
              mpriceModel = priceModel;
              mvectorFlexible = new Vector();

              // load server config file and read values
              loadConfig( strConfig );
	}

      /**
       * load a config file and setup the config variables. Will also setup price model and QoS evaluator objects.
       * This config file is also passed to the QoSEvaluator, so config tag constants are shared between the two classes.
       * @param strConfig config filename
       */
      private void loadConfig( String strConfig ) throws Exception
      {
            Properties props;
            String strValue;
            int i;

            // check param
            if( strConfig == null ) throw new IOException("Evaluator: null config filename");

            // read the configuration options from the config file
            props = new Properties();
            props.load( new FileInputStream( strConfig ) );

            // read config properties
            mstrConstraintMin = props.getProperty( mstaticConfigConstraintMin );
            mstrConstraintMax = props.getProperty( mstaticConfigConstraintMax );
            mstrConstraintEqual = props.getProperty( mstaticConfigConstraintEqual );
            strValue = props.getProperty( mstaticConfigPropIncrement );
            mnPropertyValueIncrement = Double.parseDouble( strValue );
            strValue = props.getProperty( mstaticConfigMaxIncrements );
            mnMaxIncrements = Long.parseLong( strValue );
            mstrStartTime = props.getProperty( mstaticConfigPropStartTime );
            mstrEndTime = props.getProperty( mstaticConfigPropEndTime );
            mstrPrice = props.getProperty( mstaticConfigPropPrice );
            mstrDoubleType = props.getProperty( mstaticConfigDoubleType );

            // read all the flexible props
            i = 1;
            while( i != -1 ) {
                  strValue = props.getProperty( mstaticConfigFlexible+i, "" );
                  if( !strValue.equals("") ) {
                        mlogger.log( Level.INFO,"Evaluator: Evaluator config: Property "+strValue+" is flexible" );
                        mvectorFlexible.addElement( strValue );
                        i++;
                  }
                  else i = -1;
            }
      }

      /**
       * calculate a score for a QoS property vector
       * @param hashValues hash of QoS property values obtained from a WSLA [prop name, vector(unit uri, type uri, value)]
       * @param call call for proposals with properties, weights and min/max values
       * @return score between 0 and 1. 1 is highest and 0 is lowest. -1 is returned if the bid is outside the acceptable ranges
       */
      public double calculateScore( Hashtable hashValues, CallForProposalsXML call ) throws Exception
	{
            int i;
            double nPropValue, nMax, nMin, nRange, nProposalValue, nWeight;
            double nTotalA, nTotalB, nTotalAB, nA, nB, nAB, nRoot;
            Vector vectorCFP, vectorProp;
            String strPropHash, strPropCFP, strPropValue, strWeight, strMax, strMin, strConstraint;
            Enumeration enum;

            try {
                  // init
                  nTotalA = 0;
                  nTotalB = 0;
                  nTotalAB = 0;

                  //
                  // algorithm - dot product similarity metric adjusted to measure how close a parameter vector is
                  // to the ideal param vector, determined by the min/max values of the cfp
                  // similarity = sum( A*w x B*w ) / sqrt( sum( A*w ) * sum( B*w ) )
                  // A = ideal vector (always 1 since we normalize values to a range 0..1 )
                  // B = actual value vector
                  // Both vectors have normalized values = ( value - min ) / abs( min - max )
                  // AND an enforced positive direction (either (value - min) OR (max - value) depending on constraint type)
                  // w = vector dimension weights
                  //
                  // A high similarity value means the ideal vector is close to the actual vector, so the vector is
                  // a good match. In real terms this means the proposal value's parameters match closely the cfp objectives,
                  // so a high value is a better bid
                  // 0 <= similarity value <= 1
                  //
                  // A value of -1 is returned if the bid is unacceptable (outside min/max range)
                  //

                  // loop on property vector dimensions
                  enum = hashValues.keys();
                  while( enum.hasMoreElements() ) {
                        // get prop name
                        strPropHash = (String) enum.nextElement();

                        // get prop vector (unit uri, type uri, value)
                        vectorProp = (Vector) hashValues.get( strPropHash );
                        nWeight = -1;

                        // find prop in cfp
                        for( i=0;i<call.getSize();i++ ) {
                              // (prop name, weight, min, max, constraint)
                              vectorCFP = call.getProperty(i);

                              // get property name
                              strPropCFP = (String) vectorCFP.elementAt(0);

                              // match?
                              if( strPropHash.equals( strPropCFP ) ) {
                                    // get constraint type from cfp
                                    strWeight = (String) vectorCFP.elementAt(1);
                                    strMin = (String) vectorCFP.elementAt(2);
                                    strMax = (String) vectorCFP.elementAt(3);
                                    strConstraint = (String) vectorCFP.elementAt(4);

                                    // get prop value
                                    strPropValue = (String) vectorProp.elementAt(2);

                                    // convert weight to a number (use double since all inclusive number type)
                                    nWeight = Double.parseDouble( strWeight );
                                    nA = 0;
                                    nB = 0;

                                    // switch on constraint type
                                    if( strConstraint.equals( mstrConstraintEqual ) ) {
                                          // for equal constraints we check to see if the min is matched (max is empty "")
                                          // If it matches we get 1, if not we get 0
                                          // A*w
                                          nA = nWeight;

                                          // B*w
                                          if( strPropValue.equals( strMin ) ) nB = nWeight;
                                          else nB = 0;
                                    }
                                    else if( strConstraint.equals( mstrConstraintMax ) || strConstraint.equals( mstrConstraintMin )  ) {
                                          // convert hash value to a numeric format
                                          nPropValue = Double.parseDouble( strPropValue );

                                          // get property range abs( max - min ) (if any)
                                          nMax = Double.parseDouble( strMax );
                                          nMin = Double.parseDouble( strMin );

                                          // check for a well formatted set of values
                                          if( nMin > nMax ) {
                                                mlogger.log( Level.INFO, "Evaluator: Min > Max, for "+strPropHash+" - bid value -1, min = "+nMin+", max = "+nMax);
                                                return -1;
                                          }
                                          if( nPropValue > nMax ) {
                                                mlogger.log( Level.INFO, "Evaluator: Value > Max, for "+strPropHash+" - bid value -1, value = "+nPropValue+", max = "+nMax);
                                                return -1;
                                          }
                                          if( nPropValue < nMin ) {
                                                mlogger.log( Level.INFO, "Evaluator: Value < Min, for "+strPropHash+" - bid value -1, value = "+nPropValue+", min = "+nMin);
                                                return -1;
                                          }

                                          nRange = Math.abs( nMin - nMax );

                                          // is range zero?
                                          if( nRange != 0 ) {
                                                // enforse a positive direction so higher vector values are always better
                                                // if low is good (max constraint) then use (max - value) / range
                                                // if high is good (min constraint) then use (value - min) / range

                                                // calc A*w ideal is always 1 * weight since we are normalizing to values of 0 .. 1
                                                nA = nWeight;

                                                // calc B*w
                                                if( strConstraint.equals( mstrConstraintMax ) ) nB = ((nMax - nPropValue) * nWeight) / nRange;
                                                else nB = ((nPropValue - nMin) * nWeight) / nRange;

//                                                if( strConstraint.equals( mstrConstraintMax ) ) nA = ((nMin-nMin) * nWeight) / nRange; // ideal is min for a max constraint
//                                                else nA = ((nMax-nMin) * nWeight) / nRange; // ideal is max for a min constraint
//                                                nB = ((nPropValue-nMin) * nWeight) / nRange;
                                          }
                                          else {
                                                // range zero so only value must be the correct value
                                                nA = nWeight;
                                                nB = nWeight;
                                          }
                                    } // prop type check

                                    // (A*w) * (B*w)
                                    nAB = nA * nB;

                                    // running totals
                                    nTotalA += nA;
                                    nTotalB += nB;
                                    nTotalAB += nAB;


                                    // stop looking
                                    i = call.getSize();
                              } // match check
                        } // next prop in CFP

                        // did we find the property?
                        if( nWeight == -1 ) {
                              mlogger.log( Level.WARNING,"Evaluator: Property "+strPropHash+" not found in CFP - bid value -1" );
                              return -1;
                        }
                  } // next prop in hash

                  // calc totalAB / sqrt( totalA * totalB )
                  if( nTotalAB == 0 ) return 0; // 0*x = 0, 0*sqrt(0) = sqrt(0) = 0
                  if( nTotalA * nTotalB == 0 ) return 1; // x / sqrt(0) = x / 0 = infinity ==> just return max of 1
                  nProposalValue = (nTotalAB) / Math.sqrt(nTotalA * nTotalB);

                  // return value for this proposal, high is good
                  return nProposalValue;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Failed to calculateProposalValue",ex);
                  throw new Exception("calculateProposalValue failed");
            }
	}

      /**
       * satisfy a threshold score value. The hash table values will be changed to meet the
       * score required. If the threshold cannot be met then the hash values will be left in an
       * undefined state (i.e. values that could score anything) and false returned.
       * @param strWSLATemplate WSLA template, needed to invoke the business module to get a price
       * @param call call for proposals with properties, weights and min/max values
       * @return WSLA string, or null if failed to satisfy threshold
       */
      public String satisfyThreshold( String strWSLATemplate, CallForProposalsXML call, double nThreshold ) throws Exception
	{
            int i,j;
            long nLong, nCount;
            double nValue, nOldValue, nPropValue, nMax, nMin, nRange, nIncrement;
            boolean bOk, bBadPrice, bMovementOccured;
            Vector vectorCFP, vectorProp;
            String strPropHash, strPropCFP, strPropValue, strConstraint, strMax, strMin, strWSLA;
            String strNewWSLA;
            Enumeration enum;
            Hashtable hashValues;

            try {
                  // check params
                  if( strWSLATemplate == null || call == null || nThreshold < 0 ) throw new Exception("invalid params");

                  // parse WSLA template and get values from it
                  hashValues = mwslaHandler.parseSLAParameters( strWSLATemplate );
                  if( hashValues == null ) throw new Exception("WSLA parse failed");

                  // get a starting set of values from the CFP limit values
                  for( i=0;i<call.getSize();i++ ) {
                        // (prop name, weight, min, max, constraint)
                        vectorCFP = call.getProperty( i );

                        // get property name
                        strPropCFP = (String) vectorCFP.elementAt(0);
                        strConstraint = (String) vectorCFP.elementAt(4);

                        // find prop in hash of values
                        if( !hashValues.containsKey( strPropCFP ) ) throw new Exception("cfp property not found in WSLA value hash");
                        vectorProp = (Vector) hashValues.get( strPropCFP );

                        // set WSLA props with worst allowed CFP values - Tight bidding ;) This is the most advantagous strategy
                        // for the service provider - start with a bad offer and work up to the least acceptable offer
                        // ie if min constraint, use the min value, max constariny use the max value, and equals constraint use the min value (only one set)
                        if( strConstraint.equals( mstrConstraintEqual ) ) vectorProp.setElementAt( vectorCFP.elementAt(2),2 );
                        else if( strConstraint.equals( mstrConstraintMin ) ) vectorProp.setElementAt( vectorCFP.elementAt(2),2 );
                        else if( strConstraint.equals( mstrConstraintMax ) ) vectorProp.setElementAt( vectorCFP.elementAt(3),2 );
                        else throw new Exception("Invalid constraint type");
                  }

                  // apply heuristics to enfore rules like start time <= end time
                  applyHeuristics( hashValues );

                  // get the price for this WSLA, which might move the price out of the min/max threshold
                  bBadPrice = calcPrice( hashValues, strWSLATemplate, call );

// test
/*
                  for( i=0;i<call.getSize();i++ ) {
                        // (prop name, weight, min, max, constraint)
                        vectorCFP = call.getProperty( i );

                        // get property name
                        strPropCFP = (String) vectorCFP.elementAt(0);
                        strConstraint = (String) vectorCFP.elementAt(4);

                        // find prop in hash of values
                        if( !hashValues.containsKey( strPropCFP ) ) throw new Exception("cfp property not found in WSLA value hash");
                        vectorProp = (Vector) hashValues.get( strPropCFP );

                        mlogger.log( Level.INFO,"Evaluator: \tInitial Bid: Property "+strPropCFP+" set to "+((String)vectorProp.elementAt(2)) );
                  }
                  mlogger.log( Level.INFO,"Evaluator: Initial Bid Value: "+calculateScore( hashValues,call ) );
*/
// end test

                  // TODO How about moving properties with the most weight, or moving properties in order of weight ???
                  // TODO lost of work here really - this is just a basic algorithm
                  // loop until we get a proposal that meets the bid threshold
                  // increase flexible props by min amount to make the bid threshold - give up if cannot and return no bid
                  nValue = -1;
                  nCount = 0;
                  bMovementOccured = true;
                  while( nValue <= nThreshold && nCount < mnMaxIncrements ) {
                        // check value of current proposal
                        nOldValue = nValue;
                        nValue = calculateScore( hashValues,call ); // could be -1 if we have a bad price

                        // we will note if we have been able to move on any of the flexible properties
                        bMovementOccured = false;
                        nCount++;

                        // if value no good then increase flexible property values
                        if( nValue <= nThreshold ) {
                              // loop on flexible properties
                              enum = hashValues.keys();
                              while( enum.hasMoreElements() ) {
                                    // get prop name
                                    strPropHash = (String) enum.nextElement();

                                    // get prop vector (unit uri, type uri, value)
                                    vectorProp = (Vector) hashValues.get( strPropHash );

                                    // check to see if this is a flexible property we can adjust
                                    // make sure that price is NOT a flexible property, else it will be trying to change price only to have
                                    // the price overridden - this might result in an infinite loop (!)
                                    if( isPropertyFlexible( strPropHash ) ) {
                                          // find prop in cfp
                                          for( j=0;j<call.getSize();j++ ) {
                                                // (prop name, weight, min, max, constraint)
                                                vectorCFP = call.getProperty(j);

                                                // get property name
                                                strPropCFP = (String) vectorCFP.elementAt(0);

                                                // match?
                                                if( strPropHash.equals( strPropCFP ) ) {
                                                      // get constraint type from cfp
                                                      strMin = (String) vectorCFP.elementAt(2);
                                                      strMax = (String) vectorCFP.elementAt(3);
                                                      strConstraint = (String) vectorCFP.elementAt(4);

                                                      // numeric property?
                                                      if( strMin.equals("") || strMax.equals("") || strConstraint.equals( mstrConstraintEqual ) ) {
                                                            // cannot incrementally adjust an equal property (e.g. boolean)
                                                      }
                                                      else {
                                                            // min/max constrained properties are numeric properties
                                                            // convert hash value to a numeric format (double) even if its a long
                                                            strPropValue = (String) vectorProp.elementAt(2);
                                                            nPropValue = Double.parseDouble( strPropValue );

                                                            // get property range abs( max - min ) (if any)
                                                            nMax = Double.parseDouble( strMax );
                                                            nMin = Double.parseDouble( strMin );
                                                            nRange = Math.abs( nMax - nMin );

                                                            // calc property increment
                                                            nIncrement = nRange * mnPropertyValueIncrement;

                                                            // apply increment to the property value
                                                            if( strConstraint.equals( mstrConstraintMin ) ) {
                                                                  // is there movement on this flexible property?
                                                                  if( nPropValue < nMax ) bMovementOccured = true;

                                                                  // raise a min constraint
                                                                  nPropValue = nPropValue + nIncrement;

                                                                  // check new value is within min/max
                                                                  if( nPropValue > nMax ) nPropValue = nMax;
                                                            }
                                                            else if( strConstraint.equals( mstrConstraintMax ) ) {
                                                                  // is there movement on this flexible property?
                                                                  if( nPropValue > nMin ) bMovementOccured = true;

                                                                  // lower a max constraint
                                                                  nPropValue = nPropValue - nIncrement;

                                                                  // check new value is within min/max
                                                                  if( nPropValue < nMin ) nPropValue = nMin;
                                                            }
                                                            else throw new Exception("invalid constraint type "+strConstraint);

                                                            // convert back to string
                                                            // if double keep full precision
                                                            if( mstrDoubleType.equals( (String) vectorProp.elementAt(2) ) ) {
                                                                  strPropValue = String.valueOf( nPropValue );
                                                            }
                                                            else { // if not double convert to a long
                                                                  nLong = Math.round( nPropValue );
                                                                  strPropValue = String.valueOf( nLong );
                                                            }

                                                            // set new value into property hash
                                                            vectorProp.setElementAt( strPropValue,2 );
                                                      } // change check for property
                                                } // prop name check
                                          } // next prop in CFP
                                    } // flexible numeric type check (ignore inflexible properties)
                              } // next prop in hash

                              // apply heuristics to enforse things like start time <= end time
                              // note: the heuristic constraints might force property values outside the min/max range specified
                              // in the call for proposals. If this is the case then this bid will have no value.
                              // The heuristics might also UNDO the movement made previously, and therefore trigger a potential
                              // infinite loop where the incremental changes are undone by the heuristics (e.g. start < end )
                              // in which case the max iteration timeout will occur to save us
                              applyHeuristics( hashValues );
                              bBadPrice = calcPrice( hashValues, strWSLATemplate, call );

                              if( !bMovementOccured ) {
// test
                                    for( i=0;i<call.getSize();i++ ) {
                                          // (prop name, weight, min, max, constraint)
                                          vectorCFP = call.getProperty( i );

                                          // get property name
                                          strPropCFP = (String) vectorCFP.elementAt(0);
                                          strConstraint = (String) vectorCFP.elementAt(4);

                                          // find prop in hash of values
                                          if( !hashValues.containsKey( strPropCFP ) ) throw new Exception("cfp property not found in WSLA value hash");
                                          vectorProp = (Vector) hashValues.get( strPropCFP );

//                                          mlogger.log( Level.INFO,"Evaluator: \tFailed Bid: Property "+strPropCFP+" set to "+((String)vectorProp.elementAt(2)) );
                                    }

//                                    mlogger.log( Level.INFO,"Evaluator: Failed Bid Value: "+calculateScore( hashValues,call ) );
// end test

                                    // we are not getting any more value out of the proposal so give up
                                    mlogger.log( Level.INFO,"Unable to attain call's bid threshold by changing flexible properties (no room for movement) - no bid");
                                    return null;
                              }

                        } // value < threshold check
                  } // loop until value high enough or give up

                  // check for timeout
                  if( nCount >= mnMaxIncrements ) {
                        // max number of tries reached
                        mlogger.log( Level.INFO,"Unable to attain call's bid threshold by changing flexible properties (max iterations reached) - no bid");
                        return null;
                  }

/*
// test
                  for( i=0;i<call.getSize();i++ ) {
                        // (prop name, weight, min, max, constraint)
                        vectorCFP = call.getProperty( i );

                        // get property name
                        strPropCFP = (String) vectorCFP.elementAt(0);
                        strConstraint = (String) vectorCFP.elementAt(4);

                        // find prop in hash of values
                        if( !hashValues.containsKey( strPropCFP ) ) throw new Exception("cfp property not found in WSLA value hash");
                        vectorProp = (Vector) hashValues.get( strPropCFP );

                        mlogger.log( Level.INFO,"Evaluator: \tFinal Bid: Property "+strPropCFP+" set to "+((String)vectorProp.elementAt(2)) );
                  }

                  mlogger.log( Level.INFO,"Evaluator: Final Bid Value: "+calculateScore( hashValues,call ) );
// end test
*/

                  // make a WSLA out of the new values
                  strNewWSLA = mwslaHandler.setSLAValues( strWSLATemplate,hashValues );
                  if( strNewWSLA == null ) throw new Exception("Failed to make new WSLA");

                  // return true (new values are now setup within the hashValues object)
                  return strNewWSLA;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Evaluator: Failed to satisfyThreshold",ex);
                  return null;
            }
	}

      /**
       * Ask the business module to calculate a price. The new price will be set in the hashValues. The validity of the
       * price is returned as a boolean, true if the price is within the min/max criteria
       * @param hashValues property hash [prop name,(unit, type, value)] of WSLA properties and new values
       * @param strWSLATemplate a template WSLA since the business module will need a WSLA to calc with
       * @param call call for proposals with properties, weights and min/max values
       * @return false if the new price lies OUTSIDE the min/max range for the price property
       */
      private boolean calcPrice( Hashtable hashValues, String strWSLATemplate, CallForProposalsXML call ) throws Exception
      {
            Vector vectorProp, vectorCall;
            double nPrice, nMin, nMax;
            PriceModel priceModel;
            String strUnit, strWSLA, strProp, strValue;
            int i;

            try {
                  // make new WSLA
                  strWSLA = mwslaHandler.setSLAValues( strWSLATemplate, hashValues );
                  if( strWSLA == null ) throw new Exception("Failed to make a WSLA from hash of values");

                  // call the buisness module to get a price
                  nPrice = mpriceModel.calculatePrice( strWSLA );
                  if( nPrice == -1 ) throw new Exception("Price model failed - check logs for reason why");

                  // set the price into the hashValues
                  vectorProp = (Vector) hashValues.get( mstrPrice );
                  vectorProp.setElementAt( String.valueOf( nPrice ),2 );

                  // find the property witin the call to check if it's within the min/max range
                  for( i=0;i<call.getSize();i++ ) {
                        // (prop name, weight, min, max, constraint)
                        vectorCall = call.getProperty( i );

                        // get property name
                        strProp = (String) vectorCall.elementAt(0);

                        // find prop in hash of values
                        if( mstrPrice.equals( strProp ) ) {
                              // get min and max values
                              strValue = (String) vectorCall.elementAt(2);
                              nMin = Double.parseDouble( strValue );
                              strValue = (String) vectorCall.elementAt(3);
                              nMax = Double.parseDouble( strValue );

                              // check price - return false if it's invalid
                              if( nPrice < nMin || nPrice > nMax ) return false;

                              // good price :)
                              return true;
                        }
                  } // next call

                  // could not find price within call
                  mlogger.log( Level.INFO, "Price property not found within call for proposals" );

                  // guess its an ok price then!
                  return true;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Evaluator: Failed to calcPrice",ex);
                  throw new Exception("calcPrice failed");
            }
      }

      /**
       * apply known heuristics for certain properties, setting new values that meet the requirements of the heuristics.
       * There is currently one heuristic
       * (1) start time <= end time
       * @param hashValues property hash [prop name,(unit, type, value)] of WSLA properties and new values
       */
      private void applyHeuristics( Hashtable hashValues ) throws Exception
      {
            String strStartTime, strEndTime;
            Vector vectorProp;
            long nStart, nEnd;

            try {
                  // Loop on properties are react to known properties
                  // supported heuristics:
                  // Start time <= End time

                  // init
                  nStart = -1;
                  nEnd = -1;

                  // Start time < end time
                  // get start time and end time (if we can)
                  if( hashValues.containsKey( mstrStartTime ) && hashValues.containsKey( mstrEndTime ) ) {
                        // get start time
                        vectorProp = (Vector) hashValues.get( mstrStartTime );
                        strStartTime = (String) vectorProp.elementAt(2);

                        // get end time
                        vectorProp = (Vector) hashValues.get( mstrEndTime );
                        strEndTime = (String) vectorProp.elementAt(2);

                        // get millisecond offset values
                        nStart = Long.parseLong( strStartTime );
                        nEnd = Long.parseLong( strEndTime );
                        // if start > end?
                        if( nStart > nEnd ) {
                              // enforce start = end so a good (early) end time dominated a late start time
                              // this will mean a 0 millisecond exec time is requested, but the QoSManagement module will sort
                              // out a realistic exec time based on a performance model
                              nStart = nEnd;
                              vectorProp = (Vector) hashValues.get( mstrStartTime );
                              vectorProp.setElementAt( String.valueOf( nStart ),2 );
                        }
                  }

                  // all done
                  return;
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Evaluator: Failed to applyHeuristics",ex);
                  throw new Exception("applyHeuristics failed");
            }
      }

      /**
       * check to see if a property is flexible. This is defined within the neg config file, and can be different for each
       * service provider.
       * @param strPropName name of the propeorty
       * @return true if the property is flexible, false if not
       */
      private boolean isPropertyFlexible( String strPropName ) throws Exception
      {
            try {
                  // check to see of prop name is in the list of flexible properties
                  return mvectorFlexible.contains( strPropName );
            }
            catch( Exception ex ) {
                  mlogger.log( Level.SEVERE,"Evaluator: Failed to check property for flexibility - assuming it's not flexible",ex );
                  return false;
            }
      }

} // end of QoSEvaluatorDotProduct

