package gov.va.med.edp.rpc;

import gov.va.med.edp.vo.BigBoardDebugInfoVO;
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.VistaLinkConnectionSpec;
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.adapter.spi.VistaLinkServerInfo;
import gov.va.med.vistalink.rpc.*;
import gov.va.med.vistalink.security.m.SecurityFaultException;
import gov.va.med.vistalink.security.m.SecurityDivisionDeterminationFaultException;
import org.apache.log4j.Logger;
import org.springframework.dao.*;
import org.springframework.jca.cci.core.CciTemplate;
import org.springframework.jca.cci.core.ConnectionCallback;
import org.springframework.util.Assert;

import javax.resource.ResourceException;
import javax.resource.cci.Connection;
import javax.resource.cci.ConnectionFactory;
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 VistaLinkRpcTemplate {

    private static final int USER_TYPE_DUZ = 1;
    private static final int USER_TYPE_APPLICATION_PROXY = 2;

    static final int DEFAULT_TIMEOUT = 10000;

    private static Logger log = Logger.getLogger(VistaLinkRpcTemplate.class);

    private int timeOut = DEFAULT_TIMEOUT;
    private String rpcContext;
    private ConnectionFactoryLocator connectionFactoryLocator;

    private static final String UNABLE_TO_EXECUTE_RPC = "Unable to execute RPC";

    public VistaLinkRpcTemplate(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, null);
    }

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

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

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

    public BigBoardDebugInfoVO getVistaLinkConnectionInfo(final String division, final String user) throws DataAccessException {
        return getConnectionInfo(division, user, USER_TYPE_APPLICATION_PROXY);
    }    
    
    public String rpc(final String division, final String user, final String rpcContext, final String rpcName, final List params, final int userType) 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, createConnectionSpec(division, user, userType));
            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 VistaLinkRpcInvalidApiUsageException(e);
                    } catch (RpcNotOkForProxyUseException e) {
                        throw new VistaLinkRpcInvalidApiUsageException(e);
                    } catch (RpcNotInContextFaultException e) {
                        throw new VistaLinkRpcPermissionDeniedException(e);
                    } catch (LoginsDisabledFaultException e) {
                        throw new DataAccessResourceFailureException(UNABLE_TO_EXECUTE_RPC, e);
                    } catch (SecurityDivisionDeterminationFaultException e) {
                        throw new VistaLinkRpcInvalidApiUsageException(e);
                    } catch (SecurityFaultException e) {
                        throw new VistaLinkRpcPermissionDeniedException(e);
                    } catch (VistaLinkFaultException e) {
                        throw new DataRetrievalFailureException(UNABLE_TO_EXECUTE_RPC, 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);
        }
    }

    private BigBoardDebugInfoVO getConnectionInfo(final String division, final String user, final int userType) throws DataAccessException {
		VistaLinkConnection conn = null;
    	ConnectionFactory connectionFactory = connectionFactoryLocator.getConnectionFactory(division);
		try {
			conn = (VistaLinkConnection)connectionFactory.getConnection(createConnectionSpec(division, user, userType));
		} catch (ResourceException e) {
			throw new DataAccessResourceFailureException("error getting connection info", e);
		}
	    VistaLinkServerInfo vistaLinkServerInfo = conn.getConnectionInfo();
	    BigBoardDebugInfoVO bbd = new BigBoardDebugInfoVO(vistaLinkServerInfo.getAddress().toString(), new Integer(vistaLinkServerInfo.getPort()).toString());
	    return bbd;
    } 	   
    	
    private VistaLinkConnectionSpec createConnectionSpec(String division, String user, int userType) {
        switch (userType) {
            case USER_TYPE_APPLICATION_PROXY:
                return new VistaLinkAppProxyConnectionSpec(division, user);
            case USER_TYPE_DUZ:
            default:
                return new VistaLinkDuzConnectionSpec(division, user);
        }
    }

    public int getTimeOut() {
        return timeOut;
    }

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

    public String getRpcContext() {
        return rpcContext;
    }

    public void setRpcContext(String rpcContext) {
        this.rpcContext = rpcContext;
    }

    public ConnectionFactoryLocator getConnectionFactoryLocator() {
        return connectionFactoryLocator;
    }
}
