/*
 * Copyright 2011 NEHTA
 *
 * Licensed under the NEHTA Open Source (Apache) License; you may not use this
 * file except in compliance with the License. A copy of the License is in the
 * 'license.txt' file, which should be provided with this work.
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations
 * under the License.
 */
package au.gov.nehta.vendorlibrary.hi.ihi;

import au.gov.nehta.common.utils.ArgumentUtils;
import au.gov.nehta.vendorlibrary.hi.handler.message.HIHeaderHandler;
import au.gov.nehta.vendorlibrary.hi.handler.security.HISecurityHandler;
import au.gov.nehta.vendorlibrary.ws.TimeUtility;
import au.gov.nehta.vendorlibrary.ws.WebServiceClientUtil;
import au.gov.nehta.vendorlibrary.ws.handler.LoggingHandler;
import au.net.electronichealth.ns.hi.common.commoncoredatatypes._3_0.SexType;
import au.net.electronichealth.ns.hi.common.commoncoreelements._3_0.ProductType;
import au.net.electronichealth.ns.hi.common.commoncoreelements._3_0.SignatureContainerType;
import au.net.electronichealth.ns.hi.common.commoncoreelements._3_0.TimestampType;
import au.net.electronichealth.ns.hi.common.qualifiedidentifier._3_0.QualifiedId;
import au.net.electronichealth.ns.hi.consumercore.address._3_0.AustralianPostalAddressType;
import au.net.electronichealth.ns.hi.consumercore.address._3_0.AustralianStreetAddressType;
import au.net.electronichealth.ns.hi.consumercore.address._3_0.InternationalAddressType;
import au.net.electronichealth.ns.hi.svc.consumersearchihi._3_0.ConsumerSearchIHIPortType;
import au.net.electronichealth.ns.hi.svc.consumersearchihi._3_0.ConsumerSearchIHIService;
import au.net.electronichealth.ns.hi.svc.consumersearchihi._3_0.SearchIHI;
import au.net.electronichealth.ns.hi.svc.consumersearchihi._3_0.SearchIHIResponse;
import au.net.electronichealth.ns.hi.svc.consumersearchihi._3_0.StandardErrorMsg;

import javax.net.ssl.SSLSocketFactory;
import javax.xml.datatype.XMLGregorianCalendar;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import javax.xml.ws.handler.Handler;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * An implementation of a Healthcare Identifiers (HI) - Individual Healthcare Identifier (IHI) search client. This
 * class may be used to connect to the Medicare HI Service to do Consumer Search IHI searches.
 */
public class ConsumerSearchIHIClient {
  /**
   * Empty string.
   */
  public static final String EMPTY = "";

  private static final String SSL_SOCKET_FACTORY = "com.sun.xml.ws.transport.https.client.SSLSocketFactory";

  /**
   * CSP HPI-O header name.
   */
  private static final String HPIO_CSP_HEADER_ELEMENT_NAME = "hpio";

  /**
   * Validates search parameters are correct for the various search types.
   */
  public static class ArgumentValidator {

    /**
     * The maximum length of a postal delivery number.
     */
    public static final int POSTAL_DELIVERY_NUMBER_MAX_LENGTH = 11;

    /**
     * The length of an Australian postcode.
     */
    public static final int AUSTRALIAN_POSTCODE_LENGTH = 4;

    /**
     * Checks that only the correct parameters for a basic search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void basicSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      ArgumentUtils.checkNotNullNorBlank(request.getIhiNumber(), "IHI Number");

      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      ensureNull(request.getDvaFileNumber(), "DVA File Number");
      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");
      ensureNull(request.getHistory(), "History");
      ensureNull(request.getInternationalAddress(), "International Address");
      ensureNull(request.getMedicareCardNumber(), "Medicare Card Number");
      ensureNull(request.getMedicareIRN(), "Medicare IRN");
    }

    /**
     * Checks that only the correct parameters for a basic Medicare search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void basicMedicareSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      ArgumentUtils.checkNotNullNorBlank(request.getMedicareCardNumber(), "Medicare Card Number");

      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      ensureNull(request.getDvaFileNumber(), "DVA File Number");
      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");
      ensureNull(request.getHistory(), "History");
      ensureNull(request.getInternationalAddress(), "International Address");
    }

    /**
     * Checks that only the correct parameters for a basic DVA search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void basicDvaSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      ArgumentUtils.checkNotNullNorBlank(request.getDvaFileNumber(), "DVA File Name");

      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      ensureNull(request.getMedicareCardNumber(), "Medicare Card Number");
      ensureNull(request.getMedicareIRN(), "Medicare IRN");
      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");
      ensureNull(request.getHistory(), "History");
      ensureNull(request.getInternationalAddress(), "International Address");
    }

    /**
     * Checks that only the correct parameters for a detailed search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void detailedSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      ensureNull(request.getMedicareCardNumber(), "Medicare Card Number");
      ensureNull(request.getMedicareIRN(), "Medicare IRN");
      ensureNull(request.getDvaFileNumber(), "DVA File Name");
      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");
      ensureNull(request.getHistory(), "History");
      ensureNull(request.getInternationalAddress(), "International Address");
    }

    /**
     * Checks that only the correct parameters for an Australian Postal address search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void australianPostalAddressSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      // Duplicate type 'PostalDeliveryGroupType' - remove from JAR file.
      AustralianPostalAddressType australianPostalAddress = request.getAustralianPostalAddress();
      ArgumentUtils.checkNotNull(australianPostalAddress, "Australian Postal Address");
      ArgumentUtils.checkNotNull(australianPostalAddress.getPostalDeliveryGroup(), "Postal Delivery Group");
      ArgumentUtils.checkNotNull(australianPostalAddress.getPostalDeliveryGroup().getPostalDeliveryType(), "Postal Delivery Type");
      if (australianPostalAddress.getPostalDeliveryGroup().getPostalDeliveryNumber() != null) {
        ArgumentUtils.checkMaxLength(australianPostalAddress.getPostalDeliveryGroup().getPostalDeliveryNumber(),
          POSTAL_DELIVERY_NUMBER_MAX_LENGTH,
          "Postal Delivery Number");
      }
      ArgumentUtils.checkNotNull(australianPostalAddress.getState(), "State");
      ArgumentUtils.checkNotNullNorBlank(australianPostalAddress.getPostcode(), "Post Code");
      ensureExactStringLength(australianPostalAddress.getPostcode(), AUSTRALIAN_POSTCODE_LENGTH, "Post Code");
      ArgumentUtils.checkNotNullNorBlank(australianPostalAddress.getSuburb(), "Suburb");

      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");

      australianAddressNullChecks(request);
    }

    /**
     * Checks that only the correct parameters for an Australian Street address search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void australianStreetAddressSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      AustralianStreetAddressType australianStreetAddress = request.getAustralianStreetAddress();
      ArgumentUtils.checkNotNull(australianStreetAddress, "Australian Street Address");
      ArgumentUtils.checkNotNullNorBlank(request.getAustralianStreetAddress().getStreetName(), "Street Name");
      ArgumentUtils.checkNotNullNorBlank(request.getAustralianStreetAddress().getSuburb(), "Suburb Name");
      ArgumentUtils.checkNotNull(request.getAustralianStreetAddress().getState(), "State");
      ArgumentUtils.checkNotNullNorBlank(request.getAustralianStreetAddress().getPostcode(), "Post Code");
      ensureExactStringLength(australianStreetAddress.getPostcode(), AUSTRALIAN_POSTCODE_LENGTH, "Postcode");

      if (request.getAustralianStreetAddress().getUnitGroup() != null) {
        ArgumentUtils.checkNotNull(request.getAustralianStreetAddress().getUnitGroup().getUnitType(), "Unit Type");
      }

      if (request.getAustralianStreetAddress().getLevelGroup() != null) {
        ArgumentUtils.checkNotNull(request.getAustralianStreetAddress().getLevelGroup().getLevelType(), "Level Type");
      }

      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      australianAddressNullChecks(request);
    }

    /**
     * Checks that only the correct parameters for an International address search are set.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    public final void internationalAddressSearchCheck(final SearchIHI request) {
      checkCommonMandatoryParameters(request);

      InternationalAddressType internationalAddress = request.getInternationalAddress();
      ArgumentUtils.checkNotNull(internationalAddress, "International Address");
      ArgumentUtils.checkNotNullNorBlank(internationalAddress.getInternationalAddressLine(), "International Address Line");
      ArgumentUtils.checkNotNullNorBlank(internationalAddress.getInternationalPostcode(), "International Post Code");
      ArgumentUtils.checkNotNullNorBlank(internationalAddress.getInternationalStateProvince(), "International State Province");
      ArgumentUtils.checkNotNull(internationalAddress.getCountry(), "Country");

      ensureNull(request.getMedicareCardNumber(), "Medicare Card Number");
      ensureNull(request.getMedicareIRN(), "Medicare IRN");
      ensureNull(request.getDvaFileNumber(), "DVA File Name");
      ensureNull(request.getAustralianStreetAddress(), "Australian Street Address");
      ensureNull(request.getAustralianPostalAddress(), "Australian Postal Address");
      ensureNull(request.getHistory(), "History");
    }

    /**
     * Verifies that various non-Australian Address fields are null.
     *
     * @param request the search request object containing the parameters to be checked.
     */
    private void australianAddressNullChecks(SearchIHI request) {
      ensureNull(request.getMedicareCardNumber(), "Medicare Card Number");
      ensureNull(request.getMedicareIRN(), "Medicare IRN");
      ensureNull(request.getDvaFileNumber(), "DVA File Name");
      ensureNull(request.getHistory(), "History");
      ensureNull(request.getInternationalAddress(), "International Address");
    }

    /**
     * Checks that the following mandatory parameters are set correctly:
     * Family Name
     * Date of birth
     * Sex
     *
     * @param request the search request object containing the parameters to be checked.
     */
    private void checkCommonMandatoryParameters(SearchIHI request) {
      if (request == null) {
        throw new IllegalArgumentException("The request may not be null");
      }
      ArgumentUtils.checkNotNullNorBlank(request.getFamilyName(), "Family Name");
      XMLGregorianCalendar dateOfBirth = request.getDateOfBirth();
      if (dateOfBirth == null) {
        throw new IllegalArgumentException("Date of birth may not be null");
      } else {
        if (!dateOfBirth.isValid()) {
          throw new IllegalArgumentException("Date of birth is invalid");
        }
      }
      SexType sex = request.getSex();
      if (sex == null) {
        throw new IllegalArgumentException("Sex may not be null");
      }
    }

    /**
     * Ensures the passed object is null
     *
     * @param theObject  the object being checked (Mandatory)
     * @param objectName the name of the Object variable (for use in Exception messages) (Mandatory)
     */
    private void ensureNull(Object theObject, String objectName) {
      if (theObject != null) {
        throw new IllegalArgumentException(objectName + " may not be not-null");
      }
    }

    /**
     * Ensures the passed String has a specific length
     *
     * @param theString  the String being checked (Mandatory)
     * @param length     the required length (Mandatory)
     * @param stringName the name of the String variable (for use in Exception messages) (Mandatory)
     */
    private void ensureExactStringLength(String theString, Integer length, String stringName) {
      if (theString.length() != length) {
        throw new IllegalArgumentException(stringName + " must have a length of " + length);
      }
    }
  }

  /**
   * Validates the arguments passed for the various operations
   */
  private ArgumentValidator argumentValidator = new ArgumentValidator();

  /**
   * The SSL Socket Factory to be used for connecting to the Web Service provider
   */
  private SSLSocketFactory sslSocketFactory;

  /**
   * The Private Key to be used for Signing
   */
  private PrivateKey signingPrivateKey;

  /**
   * The Certificate to be used for Signing
   */
  private X509Certificate signingCertificate;

  /**
   * The Logging handler instance for logging SOAP request and Response messages.
   */
  private LoggingHandler loggingHandler;

  /**
   * The user Qualified ID associated with this use of the ConsumerSearchIHI service
   */
  private final QualifiedId individualQualifiedId;

  /**
   * The organisation Qualified ID associated with this use of the ConsumerSearchIHI service
   */
  private final QualifiedId organisationQualifiedId;

  /**
   * The Product details associated with this use of the ConsumerSearchIHI service
   */
  private final Holder<ProductType> productHeader;

  /**
   * The Web Services port for the ConsumerSearchIHIPortType service
   */
  private ConsumerSearchIHIPortType consumerSearchIHIPort;
  /**
   * The SOAP request message corresponding to the most recent web service invocation
   */
  private String lastSoapRequest;

  /**
   * The SOAP response message corresponding to the most recent web service invocation
   */
  private String lastSoapResponse;

  /**
   * Constructor which creates a new ConsumerSearchIHIClient with an endpoint and an SSL Socket Factory.
   *
   * @param searchIhiServiceEndpoint the Web Service endpoint for the Medicare HI Service interface (Mandatory)
   * @param individualQualifiedId    The qualified user ID for connecting to the ConsumerSearchIHI service (Mandatory)
   * @param productHeader            The product header data for connecting to the ConsumerSearchIHI service (Mandatory)
   * @param signingPrivateKey        The private key to be used for signing (Mandatory)
   * @param signingCertificate       The certificate to be used for signing (Mandatory)
   * @param sslSocketFactory         the SSL Socket Factory to be used when connecting to the Web Service provider (Mandatory)
   */
  public ConsumerSearchIHIClient(final String searchIhiServiceEndpoint,
                                 final QualifiedId individualQualifiedId,
                                 final Holder<ProductType> productHeader,
                                 final PrivateKey signingPrivateKey,
                                 final X509Certificate signingCertificate,
                                 final SSLSocketFactory sslSocketFactory) {
    this(
      searchIhiServiceEndpoint,
      individualQualifiedId,
      null,
      productHeader,
      signingPrivateKey,
      signingCertificate,
      sslSocketFactory
    );
  }

  /**
   * Constructor which creates a new ConsumerSearchIHIClient with an endpoint and an SSL Socket Factory, with
   * the optional contracted service providers HPI-O organisation qualified ID set.
   *
   * @param searchIhiServiceEndpoint the Web Service endpoint for the Medicare HI Service interface (Mandatory)
   * @param individualQualifiedId    The qualified user ID for connecting to the ConsumerSearchIHI service (Mandatory)
   * @param organisationQualifiedId  The qualified organisation ID for connecting to the ConsumerSearchIHI service (Optional)
   * @param productHeader            The product header data for connecting to the ConsumerSearchIHI service (Mandatory)
   * @param signingPrivateKey        The private key to be used for signing (Mandatory)
   * @param signingCertificate       The certificate to be used for signing (Mandatory)
   * @param sslSocketFactory         the SSL Socket Factory to be used when connecting to the Web Service provider (Mandatory)
   */
  private ConsumerSearchIHIClient(final String searchIhiServiceEndpoint,
                                  final QualifiedId individualQualifiedId,
                                  final QualifiedId organisationQualifiedId,
                                  final Holder<ProductType> productHeader,
                                  final PrivateKey signingPrivateKey,
                                  final X509Certificate signingCertificate,
                                  final SSLSocketFactory sslSocketFactory) {

    ArgumentUtils.checkNotNullNorBlank(searchIhiServiceEndpoint, "searchIhiServiceEndpoint");
    ArgumentUtils.checkNotNull(individualQualifiedId, "qualifiedId");
    ArgumentUtils.checkNotNull(productHeader, "productHeader");
    ArgumentUtils.checkNotNull(signingPrivateKey, "signingPrivateKey");
    ArgumentUtils.checkNotNull(signingCertificate, "signingPrivateKey");
    ArgumentUtils.checkNotNull(sslSocketFactory, "sslSocketFactory");

    //Must be set before configuring client endpoint
    this.signingPrivateKey = signingPrivateKey;
    this.signingCertificate = signingCertificate;
    this.sslSocketFactory = sslSocketFactory;
    this.individualQualifiedId = individualQualifiedId;
    this.organisationQualifiedId = organisationQualifiedId;
    this.productHeader = productHeader;
    this.loggingHandler = new LoggingHandler(false); //Set to true to dump the SOAP message to the default logger.

    List<Handler> handlerChain = new ArrayList<Handler>();
    handlerChain.add(loggingHandler);
    
    this.consumerSearchIHIPort = WebServiceClientUtil.getPort(ConsumerSearchIHIPortType.class,
      ConsumerSearchIHIService.class,
      sslSocketFactory,
      handlerChain);
    configureEndpoint(this.consumerSearchIHIPort, searchIhiServiceEndpoint);
  }

  /**
   * Executes a basic ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                IHI Number
   *                Family Name
   *                Date of Birth
   *                Sex
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse basicSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.basicSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId);
  }

  /**
   * Executes a basic Medicare ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                Medicare Card Number
   *                Family Name
   *                Date of Birth
   *                Sex
   *                and the following optional fields
   *                Given Name
   *                Medicare IRN
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse basicMedicareSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.basicMedicareSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  /**
   * Executes a basic DVA ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                DVA File Number
   *                Family Name
   *                Date of Birth
   *                Sex
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse basicDvaSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.basicDvaSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  /**
   * Executes a Detailed ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                Family Name
   *                Date of Birth
   *                Sex
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse detailedSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.detailedSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  /**
   * Executes a Australian Postal Address ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                Family Name
   *                Date of Birth
   *                Sex
   *                Australian Street Address: Postal Delivery Group
   *                Australian Street Address: Postal Delivery Type
   *                Australian Street Address: State
   *                Australian Street Address: Post Code
   *                Australian Street Address: Suburb
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse australianPostalAddressSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.australianPostalAddressSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  /**
   * Executes a Australian Street Address ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                Family Name
   *                Date of Birth
   *                Sex
   *                Australian Street Address: Street Name
   *                Australian Street Address: Suburb
   *                Australian Street Address: State
   *                Australian Street Address: Post Code
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse australianStreetAddressSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.australianStreetAddressSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;

    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  /**
   * Executes an International Address ConsumerSearchIHI search.
   *
   * @param request the SearchIHI request object containing the following mandatory fields:
   *                Family Name
   *                Date of Birth
   *                Sex
   *                International Address: Line
   *                International Address: Post Code
   *                International Address: State/Province
   *                International Address: Country
   *                and the following optional fields
   *                Given Name
   * @return the response from the ConsumerSearchIHI service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIResponse internationalAddressSearch(SearchIHI request) throws StandardErrorMsg {
    argumentValidator.internationalAddressSearchCheck(request);

    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;
    return consumerSearchIHIPort.searchIHI(
      request,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId
    );
  }

  // Helper Methods

  /**
   * Configure the endpoint using the provided configuration information.
   *
   * @param servicePort the service definition. (Mandatory)
   * @param endpoint    the URL for the Web service endpoint. (Mandatory)
   */
  private void configureEndpoint(final Object servicePort, final String endpoint) {
    final BindingProvider bindingProvider = (BindingProvider) servicePort;

    // Set details on request context
    final Map<String, Object> requestContext = bindingProvider.getRequestContext();
    requestContext.put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, endpoint.trim());
    requestContext.put(SSL_SOCKET_FACTORY, this.sslSocketFactory);

    if (bindingProvider != null && bindingProvider.getBinding() != null) {
      Binding binding = bindingProvider.getBinding();
      // Get handler chain
      List<Handler> handlerChain = binding.getHandlerChain();

      // Remove hpioHeader as it is not included in this message.
      List<String> headerNames = new ArrayList<String>();
      headerNames.add(HPIO_CSP_HEADER_ELEMENT_NAME);
      handlerChain.add(new HIHeaderHandler(headerNames));

      //Add HISecurityHandler to sign the Outgoing SOAP message and verify the incoming SOAP message.
      handlerChain.add(new HISecurityHandler(this.signingCertificate, this.signingPrivateKey));

      //Add handler to capture inbound and outbound SOAP messages
      handlerChain.add(this.loggingHandler);
      binding.setHandlerChain(handlerChain);
    }
  }

  /**
   * Getter for lastSoapResponse.
   *
   * @return lastSoapResponse the lastSoapResponse instance variable
   */
  public final String getLastSoapResponse() {
    if (loggingHandler != null) {
      return loggingHandler.getLastSoapResponse();
    } else {
      return EMPTY;
    }
  }

  /**
   * Getter for lastSoapRequest.
   *
   * @return lastSoapRequest the lastSoapRequest instance variable (Mandatory)
   */
  public final String getLastSoapRequest() {
    if (loggingHandler != null) {
      return loggingHandler.getLastSoapRequest();
    } else {
      return EMPTY;
    }
  }
}
