/*
 * 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 java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.net.ssl.SSLSocketFactory;
import javax.xml.ws.Binding;
import javax.xml.ws.BindingProvider;
import javax.xml.ws.Holder;
import javax.xml.ws.handler.Handler;

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.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.consumermessages.searchihibatchmessages._3_0.SearchIHIRequestType;
import au.net.electronichealth.ns.hi.svc.consumersearchihibatchsync._3_0.ConsumerSearchIHIBatchSyncPortType;
import au.net.electronichealth.ns.hi.svc.consumersearchihibatchsync._3_0.ConsumerSearchIHIBatchSyncService;
import au.net.electronichealth.ns.hi.svc.consumersearchihibatchsync._3_0.SearchIHIBatchResponse;
import au.net.electronichealth.ns.hi.svc.consumersearchihibatchsync._3_0.SearchIHIBatchSync;
import au.net.electronichealth.ns.hi.svc.consumersearchihibatchsync._3_0.StandardErrorMsg;
/**
 * An implementation of a Healthcare Identifiers (HI) - Individual Healthcare Identifier (IHI) batch search client.
 * This class may be used to connect to the Medicare HI Service to do Consumer Search IHI Synchronized Batch
 * searches.
 */
public class ConsumerSearchIHIBatchSyncClient {


  /**
   * Empty String variable.
   */
  public static final String EMPTY = "";

  /**
   * Helper inner class to create a batch of validated IHI searches.
   */
  public static class SearchBatch {
    /**
     * Container for validated searches
     */
    private SearchIHIBatchSync searchIhiBatchSync = new SearchIHIBatchSync();

    /**
     * Container for validated searches
     */
    private List<SearchIHIRequestType> searches = searchIhiBatchSync.getSearchIHIBatchRequest();

    /**
     * Argument Validator
     */
    private ConsumerSearchIHIClient.ArgumentValidator argumentValidator = new ConsumerSearchIHIClient.ArgumentValidator();

    /**
     * Validates a Basic search and adds it to the batch if successful.
     *
     * @param search the search object containing the following mandatory fields:
     *               IHI Number
     *               Family Name
     *               Date of Birth
     *               Sex
     *               and the following optional fields
     *               Given Name
     */
    public final void addBasicSearch(SearchIHIRequestType search) {
      this.argumentValidator.basicSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    /**
     * Validates a Basic Medicare IHI search and adds it to the batch if successful.
     *
     * @param search the search object containing the following mandatory fields:
     *               Medicare Card Number
     *               Family Name
     *               Date of Birth
     *               Sex
     *               and the following optional fields
     *               Given Name
     *               Medicare IRN
     */
    public final void addBasicMedicareSearch(SearchIHIRequestType search) {
      this.argumentValidator.basicMedicareSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }


    /**
     * Validates a Basic DVA search and adds it to the batch if successful.
     *
     * @param search the search object containing the following mandatory fields:
     *               DVA File Number
     *               Family Name
     *               Date of Birth
     *               Sex
     *               and the following optional fields
     *               Given Name
     */
    public final void addBasicDvaSearch(SearchIHIRequestType search) {
      this.argumentValidator.basicDvaSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    /**
     * Validates a Detailed search and adds it to the batch if successful.
     *
     * @param search the search object containing the following mandatory fields:
     *               Family Name
     *               Date of Birth
     *               Sex
     *               and the following optional fields
     *               Given Name
     */
    public final void addDetailedSearch(SearchIHIRequestType search) {
      this.argumentValidator.detailedSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    /**
     * Validates a Australian Postal Address search and adds it to the batch if successful.
     *
     * @param search the search 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
     */
    public final void addAustralianPostalAddressSearch(SearchIHIRequestType search) {
      this.argumentValidator.australianPostalAddressSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    /**
     * Validates a Australian Street Address search and adds it to the batch if successful.
     *
     * @param search the search 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
     */
    public final void addAustralianStreetAddressSearch(SearchIHIRequestType search) {
      this.argumentValidator.australianStreetAddressSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    /**
     * Validates a Australian Street Address search and adds it to the batch if successful.
     *
     * @param search the search 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
     */
    public final void addInternationalAddressSearch(SearchIHIRequestType search) {
      this.argumentValidator.internationalAddressSearchCheck(search.getSearchIHI());
      this.validateRequestIdentifier(search.getRequestIdentifier());
      this.searches.add(search);
    }

    // Helper methods

    private void validateRequestIdentifier(String requestIdentifier) {
      if (requestIdentifier == null) {
        throw new IllegalArgumentException("request Identifier may not be null");
      } else {
        if (requestIdentifier.length() != REQUEST_IDENTIFIER_LENGTH) {
          throw new IllegalArgumentException("request Identifier must have a length of " + REQUEST_IDENTIFIER_LENGTH);
        }
      }
    }

    public final SearchIHIBatchSync getBatch() {
      return this.searchIhiBatchSync;
    }
  }

  /**
   * Constant for Request Identifier Length.
   */
  private static final short REQUEST_IDENTIFIER_LENGTH = 36;

  /**
   * SSL socket factory class name
   */
  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";


  /**
   * 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 ConsumerSearchIHIBatchSync service
   */
  private QualifiedId individualQualifiedId;

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

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

  /**
   * The Web Services port for the ConsumerSearchIHIBatchSync service
   */
  private ConsumerSearchIHIBatchSyncPortType consumerSearchIHIBatchSyncPort;
  /**
   * 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 ConsumerSearchIHIBatchSyncClient 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 ConsumerSearchIHIBatchSync service (Mandatory)
   * @param productHeader            The product header data for connecting to the ConsumerSearchIHIBatchSync 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 ConsumerSearchIHIBatchSyncClient(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 ConsumerSearchIHIBatchSyncClient 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 ConsumerSearchIHIBatchSync service (Mandatory)
   * @param organisationQualifiedId  The qualified organisation ID for connecting to the ConsumerSearchIHIBatchSync service (Optional)
   * @param productHeader            The product header data for connecting to the ConsumerSearchIHIBatchSync 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 ConsumerSearchIHIBatchSyncClient(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.consumerSearchIHIBatchSyncPort = WebServiceClientUtil.getPort(
      ConsumerSearchIHIBatchSyncPortType.class,
      ConsumerSearchIHIBatchSyncService.class,
      sslSocketFactory,
      handlerChain);
    configureEndpoint(this.consumerSearchIHIBatchSyncPort, searchIhiServiceEndpoint);
  }

  /**
   * Executes a SearchIHIBatchSync search.
   *
   * @param request the SearchBatch request object containing the searches to be performed.
   * @return the response from the SearchIHIBatchSync service
   * @throws StandardErrorMsg if the Web Service call fails.
   */
  public final SearchIHIBatchResponse batchSearch(SearchBatch request) throws StandardErrorMsg {
    TimestampType timestampHeader = new TimestampType();
    timestampHeader.setCreated(TimeUtility.nowXMLGregorianCalendar());
    Holder<SignatureContainerType> signatureHeader = null;
    return consumerSearchIHIBatchSyncPort.searchIHIBatchSync(
      request.getBatch(),
      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 Outoging 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;
    }
  }
}
