package gov.va.med.edp.vistalink;

import gov.va.med.exception.FoundationsException;
import gov.va.med.vistalink.adapter.cci.VistaLinkAppProxyConnectionSpec;
import gov.va.med.vistalink.adapter.cci.VistaLinkConnection;
import gov.va.med.vistalink.adapter.cci.VistaLinkDuzConnectionSpec;
import gov.va.med.vistalink.adapter.record.LoginsDisabledFaultException;
import gov.va.med.vistalink.adapter.record.VistaLinkFaultException;
import gov.va.med.vistalink.rpc.*;
import gov.va.med.vistalink.security.m.SecurityFaultException;
import org.springframework.dao.*;
import org.springframework.jca.cci.core.CciTemplate;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert;
import org.slf4j.LoggerFactory;
import org.slf4j.Logger;

import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
import javax.resource.cci.ConnectionSpec;
import java.sql.SQLException;
import java.text.MessageFormat;
import java.util.List;

/**
 * This is the central class in the rpc package. It simplifies the use of VistaLink RPCs and helps to avoid common errors.
 * It executes core RPC workflow, leaving application code to provide RPC names and parameters and extract results. This
 * class executes RPCs with VistaLink, initiating iteration over RPC results and catching VistaLink exceptions and
 * translating them to the generic data access exception hierarchy defined in the org.springframework.dao package.
 */
public class VistaLinkTemplate {

    static final int DEFAULT_TIMEOUT = 10000;

    private static Logger log = LoggerFactory.getLogger(VistaLinkTemplate.class);

    private int timeOut = DEFAULT_TIMEOUT;
    private ConnectionFactoryLocator connectionFactoryLocator;
    private static final String UNABLE_TO_EXECUTE_RPC = "Unable to execute RPC";

    public VistaLinkTemplate(ConnectionFactoryLocator connectionFactoryLocator) {
        this.connectionFactoryLocator = connectionFactoryLocator;
    }

    public String rpcAsUser(final String division, final String duz, final String rpcContext, final String rpcName) throws DataAccessException {
        return rpcAsUser(division, duz, rpcContext, rpcName, (List) null);
    }

//    TODO: Uncomment me on java 5
//    public String rpcAsUser(final String division, final String duz, final String rpcContext, final String rpcName, Object... params) throws DataAccessException {
//        return rpcAsUser(division, duz, rpcContext, rpcName, createParameterList(params));
//    }

    public String rpcAsUser(final String division, final String duz, final String rpcContext, final String rpcName, final List params) throws DataAccessException {
        return rpc(new VistaLinkDuzConnectionSpec(division, duz), division, duz, rpcContext, rpcName, params);
    }

    public String rpcAsApplication(final String division, final String applicationProxyName, final String rpcContext, final String rpcName) throws DataAccessException {
        return rpcAsApplication(division, applicationProxyName, rpcContext, rpcName, (List) null);
    }

//    TODO: Uncomment me on java 5
//    public String rpcAsApplication(final String division, final String applicationProxyName, final String rpcContext, final String rpcName, Object... params) throws DataAccessException {
//        return rpcAsApplication(division, applicationProxyName, rpcContext, rpcName, createParameterList(params));
//    }

    public String rpcAsApplication(final String division, final String applicationProxyName, final String rpcContext, final String rpcName, final List params) throws DataAccessException {
        return rpc(new VistaLinkAppProxyConnectionSpec(division, applicationProxyName), division, applicationProxyName, rpcContext, rpcName, params);
    }

    public String rpc(ConnectionSpec connectionSpec, final String division, final String user, final String rpcContext, final String rpcName, final List params) throws DataAccessException {
        if (log.isDebugEnabled())
            log.debug(MessageFormat.format("''{0}'' called in context ''{1}'' by ''{2}'' at facility ''{3}'' with params: {4}", new Object[]{rpcName, rpcContext, user, division, params}));

        ConnectionFactory connectionFactory = connectionFactoryLocator.getConnectionFactory(division);
        try {
            CciTemplate t = new CciTemplate(connectionFactory, connectionSpec);
            RpcResponse response = (RpcResponse) t.execute(new ConnectionCallback() {
                public Object doInConnection(Connection connection, ConnectionFactory connectionFactory) throws ResourceException, SQLException, DataAccessException {
                    try {
                        Assert.isInstanceOf(VistaLinkConnection.class, connection);
                        VistaLinkConnection conn = (VistaLinkConnection) connection;
                        conn.setTimeOut(getTimeOut());

                        RpcRequest request = RpcRequestFactory.getRpcRequest(rpcContext, rpcName);
                        request.setUseProprietaryMessageFormat(true);
                        request.setXmlResponse(false);

                        if (params != null) {
                            request.setParams(params);
                        }

                        return conn.executeRPC(request);
                    } catch (IllegalArgumentException e) {
                        throw new InvalidDataAccessApiUsageException(UNABLE_TO_EXECUTE_RPC, e);
                    } catch (NoRpcContextFaultException e) {
                        throw new VistaLinkNoRpcContextException(e);
                    } catch (RpcNotOkForProxyUseException e) {
                        throw new VistaLinkRpcNotOkForProxyUseException(e);
                    } catch (RpcNotInContextFaultException e) {
                        throw new VistaLinkRpcNotInContextException(e);
                    } catch (LoginsDisabledFaultException e) {
                        throw new VistaLinkLoginsDisabledException(e);
                    } catch (SecurityFaultException e) {
                        throw new VistaLinkPermissionDeniedException(e);
                    } catch (VistaLinkFaultException e) {
                        throw new VistaLinkDataRetrievalFailureException(e);
                    } catch (FoundationsException e) {
                        throw new DataRetrievalFailureException(UNABLE_TO_EXECUTE_RPC, e);
                    }
                }
            });
            String result = response.getResults();
            if (log.isDebugEnabled()) {
                log.debug(MessageFormat.format("''{0}'' returned: {1}", new Object[]{rpcName, result}));
            }
            return result;
        } catch (IllegalArgumentException e) {
            throw new InvalidDataAccessApiUsageException(UNABLE_TO_EXECUTE_RPC, e);
        }
    }

    public int getTimeOut() {
        return timeOut;
    }

    public void setTimeOut(int timeOut) {
        this.timeOut = timeOut;
    }

    public ConnectionFactoryLocator getConnectionFactoryLocator() {
        return connectionFactoryLocator;
    }

//  TODO: Uncomment me for java 5
//    private List createParameterList(Object... params) {
//        List paramList = new ArrayList();
//        for (Object param : params) {
//            paramList.add(param);
//        }
//        return paramList;
//    }
}
