/*
 * 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.readreferencedata;

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.svc.hiproviderreadreferencedata._3_2_0.ReadReferenceData;
import au.net.electronichealth.ns.hi.svc.hiproviderreadreferencedata._3_2_0.ProviderReadReferenceDataPortType;
import au.net.electronichealth.ns.hi.svc.hiproviderreadreferencedata._3_2_0.ProviderReadReferenceDataService;
import au.net.electronichealth.ns.hi.svc.hiproviderreadreferencedata._3_2_0.ReadReferenceDataResponse;
import au.net.electronichealth.ns.hi.svc.hiproviderreadreferencedata._3_2_0.StandardErrorMsg;

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 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) - Provider Read Reference service client. This
 * class may be used to connect to the Medicare HI Service to perform ReadReferenceData lookup.
 */
public class ReadReferenceDataClient {

  /**
   * Validates the ReadReferenceData types are correct for webservice invocation.
   */
  public static class ArgumentValidator {

    /**
     * Checks that the provided readReferenceData is valid.
     *
     * @param readReferenceData the ReadReferenceData to be validated (Mandatory)
     */
    public final void checkReadReferenceData(ReadReferenceData readReferenceData) {
      ArgumentUtils.checkNotNull(readReferenceData, "readReferenceData");
      checkElementName(readReferenceData.getElementNames());
    }

    /**
     * Checks that the provided element name list is valid.
     *
     * @param elementNamesList the list of element name to be validated (Mandatory)
     */
    public final void checkElementName(List<String> elementNamesList) {
      if (ArgumentUtils.isNullOrEmpty(elementNamesList)) {
        throw new IllegalArgumentException("The element names list must contain one or more elements");
      }
      for (String elementName : elementNamesList) {
        ArgumentUtils.checkNotNullNorBlank(elementName, "elementName");
      }
    }
  }

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

  /**
   * 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";

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

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

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

  /**
   * The Web Services port for the ProviderReadReferenceDataPortType service
   */
  private ProviderReadReferenceDataPortType providerReadReferenceDataPort;
  /**
   * 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 ReadReferenceDataClient with an endpoint and an SSL Socket Factory.
   *
   * @param readReferenceDataServiceEndpoint
   *                              the Web Service endpoint for the Medicare HI Service interface (Mandatory)
   * @param individualQualifiedId The qualified user ID for connecting to the ReadReferenceData service (Mandatory)
   * @param productHeader         The product header data for connecting to the ReadReferenceData 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 ReadReferenceDataClient(final String readReferenceDataServiceEndpoint,
                                 final QualifiedId individualQualifiedId,
                                 final Holder<ProductType> productHeader,
                                 final PrivateKey signingPrivateKey,
                                 final X509Certificate signingCertificate,
                                 final SSLSocketFactory sslSocketFactory) {
    this(
      readReferenceDataServiceEndpoint,
      individualQualifiedId,
      null,
      productHeader,
      signingPrivateKey,
      signingCertificate,
      sslSocketFactory
    );
  }

  /**
   * Constructor which creates a new ReadReferenceDataClient with an endpoint and an SSL Socket Factory, with
   * the optional contracted service providers HPI-O organisation qualified ID set.
   *
   * @param readReferenceDataServiceEndpoint
   *                                the Web Service endpoint for the Medicare HI Service interface (Mandatory)
   * @param individualQualifiedId   The qualified user ID for connecting to the ReadReferenceData service (Mandatory)
   * @param organisationQualifiedId The qualified organisation ID for connecting to the ReadReferenceData service (Optional)
   * @param productHeader           The product header data for connecting to the ReadReferenceData 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 ReadReferenceDataClient(final String readReferenceDataServiceEndpoint,
                                 final QualifiedId individualQualifiedId,
                                 final QualifiedId organisationQualifiedId,
                                 final Holder<ProductType> productHeader,
                                 final PrivateKey signingPrivateKey,
                                 final X509Certificate signingCertificate,
                                 final SSLSocketFactory sslSocketFactory) {

    ArgumentUtils.checkNotNullNorBlank(readReferenceDataServiceEndpoint, "readReferenceDataServiceEndpoint");
    ArgumentUtils.checkNotNull(individualQualifiedId, "individualQualifiedId");
    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.

    // Add argument validation.
    List<Handler> handlerChain = new ArrayList<Handler>();
    handlerChain.add(loggingHandler);
    this.providerReadReferenceDataPort = WebServiceClientUtil.getPort(ProviderReadReferenceDataPortType.class,
      ProviderReadReferenceDataService.class,
      sslSocketFactory,
      handlerChain);
    configureEndpoint(this.providerReadReferenceDataPort, readReferenceDataServiceEndpoint);
  }

  /**
   * Retrieves the  current acceptable HI reference data values for the provided readReferenceData .  <br>
   * These element include but are not limited to <br>
   * <li>providerTypeCode</li>
   * <li>providerSpeciality </li>
   * <li>providerSpecialisation</li>
   * <li>organisationTypeCode</li>
   * <li>organisationService</li>
   * <li>organisationServiceUnit</li>
   * <li>operatingSystem</li>
   *
   * @param readReferenceData containing a list of element names. (Mandatory)
   * @return Zero or more acceptable reference values for the referenceList.
   * @throws StandardErrorMsg in an event of web service failure.
   */
  public final ReadReferenceDataResponse readReferenceData(ReadReferenceData readReferenceData) throws StandardErrorMsg {

    argumentValidator.checkReadReferenceData(readReferenceData);

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

    return this.providerReadReferenceDataPort.readReferenceData(
      readReferenceData,
      this.productHeader,
      timestampHeader,
      signatureHeader,
      this.individualQualifiedId,
      this.organisationQualifiedId);
  }


  /**
   * 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;
    }
  }

  /**
   * 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 if value is not specified.
      if (this.organisationQualifiedId == null) {
        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);
    }
  }
}
