package com.arjuna.webservices11.wsat.client;

import com.arjuna.webservices.SoapFault;
import com.arjuna.webservices11.wsarj.InstanceIdentifier;
import com.arjuna.webservices11.wsat.AtomicTransactionConstants;
import com.arjuna.webservices11.wsat.client.WSATClient;
import com.arjuna.webservices11.ServiceRegistry;
import com.arjuna.webservices11.SoapFault11;
import com.arjuna.webservices11.wsaddr.client.SoapFaultClient;
import com.arjuna.webservices11.wsaddr.AddressingHelper;
import com.arjuna.webservices11.wsaddr.NativeEndpointReference;
import com.arjuna.webservices11.wsaddr.EndpointHelper;
import org.oasis_open.docs.ws_tx.wsat._2006._06.CoordinatorPortType;
import org.oasis_open.docs.ws_tx.wsat._2006._06.Notification;

import javax.xml.ws.addressing.AddressingBuilder;
import javax.xml.ws.addressing.AddressingProperties;
import javax.xml.ws.addressing.AttributedURI;
import javax.xml.ws.addressing.EndpointReference;
import javax.xml.ws.wsaddressing.W3CEndpointReference;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URI;

/**
 * The Client side of the Coordinator.
 * @author kevin
 */
public class CoordinatorClient
{
    /**
     * The client singleton.
     */
    private static final CoordinatorClient CLIENT = new CoordinatorClient() ;

    /**
     * The prepared action.
     */
    private AttributedURI preparedAction = null;
    /**
     * The aborted action.
     */
    private AttributedURI abortedAction = null;
    /**
     * The read only action.
     */
    private AttributedURI readOnlyAction = null;
    /**
     * The committed action.
     */
    private AttributedURI committedAction = null;
    /**
     * The fault action.
     */
    private AttributedURI faultAction = null;

    /**
     * The participant URI for replies.
     */
    private EndpointReference participant ;

    /**
     * The participant URI for secure replies.
     */
    private EndpointReference secureParticipant ;

    /**
     * Construct the coordinator client.
     */
    private CoordinatorClient()
    {
        final AddressingBuilder builder = AddressingBuilder.getAddressingBuilder();
        try {
            preparedAction = builder.newURI(AtomicTransactionConstants.WSAT_ACTION_PREPARED);
            abortedAction = builder.newURI(AtomicTransactionConstants.WSAT_ACTION_ABORTED);
            readOnlyAction = builder.newURI(AtomicTransactionConstants.WSAT_ACTION_READ_ONLY);
            committedAction = builder.newURI(AtomicTransactionConstants.WSAT_ACTION_COMMITTED);
            faultAction = builder.newURI(AtomicTransactionConstants.WSAT_ACTION_FAULT);
        } catch (URISyntaxException use) {
            // TODO - log fault and throw exception
        }
        // final HandlerRegistry handlerRegistry = new HandlerRegistry() ;

        // Add WS-Addressing
        // AddressingPolicy.register(handlerRegistry) ;
        // Add client policies
        // ClientPolicy.register(handlerRegistry) ;

        final String participantURIString =
            ServiceRegistry.getRegistry().getServiceURI(AtomicTransactionConstants.PARTICIPANT_SERVICE_NAME, false);
        final String secureParticipantURIString =
            ServiceRegistry.getRegistry().getServiceURI(AtomicTransactionConstants.PARTICIPANT_SERVICE_NAME, true);
        try {
            URI participantURI = new URI(participantURIString);
            participant = builder.newEndpointReference(participantURI);
        } catch (URISyntaxException use) {
            // TODO - log fault and throw exception
        }
        try {
            URI secureParticipantURI = new URI(secureParticipantURIString);
            secureParticipant = builder.newEndpointReference(secureParticipantURI);
        } catch (URISyntaxException use) {
            // TODO - log fault and throw exception
        }
    }

    /**
     * Send a prepared request.
     * @param addressingProperties addressing context initialised with to and message ID.
     * @param identifier The identifier of the initiator.
     * @throws com.arjuna.webservices.SoapFault For any SOAP errors.
     * @throws java.io.IOException for any transport errors.
     */
    public void sendPrepared(final W3CEndpointReference endpoint, final AddressingProperties addressingProperties, final InstanceIdentifier identifier)
        throws SoapFault, IOException
    {
        EndpointReference participant = getParticipant(endpoint, addressingProperties);
        AddressingHelper.installFromFaultTo(addressingProperties, participant, identifier);
        CoordinatorPortType port = getPort(endpoint, addressingProperties, preparedAction);
        Notification prepared = new Notification();

        port.preparedOperation(prepared);
    }

    /**
     * Send an aborted request.
     * @param addressingProperties addressing context initialised with to and message ID.
     * @param identifier The identifier of the initiator.
     * @throws com.arjuna.webservices.SoapFault For any SOAP errors.
     * @throws java.io.IOException for any transport errors.
     */
    public void sendAborted(final W3CEndpointReference endpoint, final AddressingProperties addressingProperties, final InstanceIdentifier identifier)
        throws SoapFault, IOException
    {
        EndpointReference participant = getParticipant(endpoint, addressingProperties);
        AddressingHelper.installFaultTo(addressingProperties, participant, identifier);
        CoordinatorPortType port = getPort(endpoint, addressingProperties, abortedAction);
        Notification aborted = new Notification();

        port.abortedOperation(aborted);
    }

    /**
     * Send a read only request.
     * @param addressingProperties addressing context initialised with to and message ID.
     * @param identifier The identifier of the initiator.
     * @throws com.arjuna.webservices.SoapFault For any SOAP errors.
     * @throws java.io.IOException for any transport errors.
     */
    public void sendReadOnly(final W3CEndpointReference endpoint, final AddressingProperties addressingProperties, final InstanceIdentifier identifier)
        throws SoapFault, IOException
    {
        EndpointReference participant = getParticipant(endpoint, addressingProperties);
        AddressingHelper.installFaultTo(addressingProperties, participant, identifier);
        CoordinatorPortType port = getPort(endpoint, addressingProperties, readOnlyAction);
        Notification readOnly = new Notification();

        port.readOnlyOperation(readOnly);
    }

    /**
     * Send a committed request.
     * @param addressingProperties addressing context initialised with to and message ID.
     * @param identifier The identifier of the initiator.
     * @throws com.arjuna.webservices.SoapFault For any SOAP errors.
     * @throws java.io.IOException for any transport errors.
     */
    public void sendCommitted(final W3CEndpointReference endpoint, final AddressingProperties addressingProperties, final InstanceIdentifier identifier)
        throws SoapFault, IOException
    {
        EndpointReference participant = getParticipant(endpoint, addressingProperties);
        AddressingHelper.installFaultTo(addressingProperties, participant, identifier);
        CoordinatorPortType port = getPort(endpoint, addressingProperties, committedAction);
        Notification committed = new Notification();

        port.committedOperation(committed);
    }

    /**
     * Send a fault.
     * @param addressingProperties The addressing context.
     * @param soapFault The SOAP fault.
     * @param identifier The arjuna instance identifier.
     * @throws com.arjuna.webservices.SoapFault For any SOAP errors.
     * @throws java.io.IOException for any transport errors.
     */
    public void sendSoapFault(final W3CEndpointReference endpoint, final AddressingProperties addressingProperties, final SoapFault soapFault, final InstanceIdentifier identifier)
        throws SoapFault, IOException
    {
        AddressingHelper.installNoneReplyTo(addressingProperties);
        // use the SoapFaultService to format a soap fault and send it back to the faultto address
        SoapFaultClient.sendSoapFault((SoapFault11)soapFault, endpoint, addressingProperties, faultAction);
    }

    /**
     * return a participant endpoint appropriate to the type of coordinator
     * @param endpoint
     * @return either the secure participant endpoint or the non-secure endpoint
     */
    EndpointReference getParticipant(W3CEndpointReference endpoint, AddressingProperties addressingProperties)
    {
        String address;
        if (endpoint != null) {
            NativeEndpointReference nativeRef = EndpointHelper.transform(NativeEndpointReference.class, endpoint);
            address = nativeRef.getAddress();
        } else {
            address = addressingProperties.getTo().getURI().toString();
        }

        if (address.startsWith("https")) {
            return secureParticipant;
        } else {
            return participant;
        }
    }

    /**
     * Get the Completion Coordinator client singleton.
     * @return The Completion Coordinator client singleton.
     */
    public static CoordinatorClient getClient()
    {
        return CLIENT;
    }

    /**
     * obtain a port from the coordinator endpoint configured with the instance identifier handler and the supplied
     * addressing properties supplemented with the given action
     * @param endpoint
     * @param addressingProperties
     * @param action
     * @return
     */
    private CoordinatorPortType getPort(final W3CEndpointReference endpoint,
                                                final AddressingProperties addressingProperties,
                                                final AttributedURI action)
    {
        AddressingHelper.installNoneReplyTo(addressingProperties);
        if (endpoint != null) {
            return WSATClient.getCoordinatorPort(endpoint, action, addressingProperties);
        } else {
            return WSATClient.getCoordinatorPort(action, addressingProperties);
        }
    }
}
