
package org.mule.modules.zuora.adapters;

import org.apache.commons.pool.KeyedPoolableObjectFactory;
import org.apache.commons.pool.impl.GenericKeyedObjectPool;
import org.mule.api.Capabilities;
import org.mule.api.Capability;
import org.mule.api.ConnectionManager;
import org.mule.api.MuleContext;
import org.mule.api.construct.FlowConstruct;
import org.mule.api.context.MuleContextAware;
import org.mule.api.lifecycle.Disposable;
import org.mule.api.lifecycle.Initialisable;
import org.mule.api.lifecycle.Startable;
import org.mule.api.lifecycle.Stoppable;
import org.mule.config.PoolingProfile;
import org.mule.modules.zuora.ZuoraModule;
import org.mule.modules.zuora.zuora.api.ZuoraClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


/**
 * A {@code ZuoraModuleConnectionManager} is a wrapper around {@link ZuoraModule } that adds connection management capabilities to the pojo.
 * 
 */
public class ZuoraModuleConnectionManager
    implements Capabilities, ConnectionManager<ZuoraModuleConnectionManager.ConnectionKey, ZuoraModuleLifecycleAdapter> , MuleContextAware, Initialisable
{

    /**
     * 
     */
    private String username;
    /**
     * 
     */
    private String password;
    private ZuoraClient<Exception> client;
    private String endpoint;
    private static Logger logger = LoggerFactory.getLogger(ZuoraModuleConnectionManager.class);
    /**
     * Mule Context
     * 
     */
    private MuleContext muleContext;
    /**
     * Flow construct
     * 
     */
    private FlowConstruct flowConstruct;
    /**
     * Connector Pool
     * 
     */
    private GenericKeyedObjectPool connectionPool;
    protected PoolingProfile connectionPoolingProfile;

    /**
     * Sets client
     * 
     * @param value Value to set
     */
    public void setClient(ZuoraClient<Exception> value) {
        this.client = value;
    }

    /**
     * Retrieves client
     * 
     */
    public ZuoraClient<Exception> getClient() {
        return this.client;
    }

    /**
     * Sets endpoint
     * 
     * @param value Value to set
     */
    public void setEndpoint(String value) {
        this.endpoint = value;
    }

    /**
     * Retrieves endpoint
     * 
     */
    public String getEndpoint() {
        return this.endpoint;
    }

    /**
     * Sets connectionPoolingProfile
     * 
     * @param value Value to set
     */
    public void setConnectionPoolingProfile(PoolingProfile value) {
        this.connectionPoolingProfile = value;
    }

    /**
     * Retrieves connectionPoolingProfile
     * 
     */
    public PoolingProfile getConnectionPoolingProfile() {
        return this.connectionPoolingProfile;
    }

    /**
     * Sets username
     * 
     * @param value Value to set
     */
    public void setUsername(String value) {
        this.username = value;
    }

    /**
     * Retrieves username
     * 
     */
    public String getUsername() {
        return this.username;
    }

    /**
     * Sets password
     * 
     * @param value Value to set
     */
    public void setPassword(String value) {
        this.password = value;
    }

    /**
     * Retrieves password
     * 
     */
    public String getPassword() {
        return this.password;
    }

    /**
     * Sets flow construct
     * 
     * @param flowConstruct Flow construct to set
     */
    public void setFlowConstruct(FlowConstruct flowConstruct) {
        this.flowConstruct = flowConstruct;
    }

    /**
     * Set the Mule context
     * 
     * @param context Mule context to set
     */
    public void setMuleContext(MuleContext context) {
        this.muleContext = context;
    }

    public void initialise() {
        GenericKeyedObjectPool.Config config = new GenericKeyedObjectPool.Config();
        if (connectionPoolingProfile!= null) {
            config.maxIdle = connectionPoolingProfile.getMaxIdle();
            config.maxActive = connectionPoolingProfile.getMaxActive();
            config.maxWait = connectionPoolingProfile.getMaxWait();
            config.whenExhaustedAction = ((byte) connectionPoolingProfile.getExhaustedAction());
        }
        connectionPool = new GenericKeyedObjectPool(new ZuoraModuleConnectionManager.ConnectionFactory(this), config);
    }

    public ZuoraModuleLifecycleAdapter acquireConnection(ZuoraModuleConnectionManager.ConnectionKey key)
        throws Exception
    {
        return ((ZuoraModuleLifecycleAdapter) connectionPool.borrowObject(key));
    }

    public void releaseConnection(ZuoraModuleConnectionManager.ConnectionKey key, ZuoraModuleLifecycleAdapter connection)
        throws Exception
    {
        connectionPool.returnObject(key, connection);
    }

    public void destroyConnection(ZuoraModuleConnectionManager.ConnectionKey key, ZuoraModuleLifecycleAdapter connection)
        throws Exception
    {
        connectionPool.invalidateObject(key, connection);
    }

    /**
     * Returns true if this module implements such capability
     * 
     */
    public boolean isCapableOf(Capability capability) {
        if (capability == Capability.LIFECYCLE_CAPABLE) {
            return true;
        }
        if (capability == Capability.CONNECTION_MANAGEMENT_CAPABLE) {
            return true;
        }
        return false;
    }

    private static class ConnectionFactory
        implements KeyedPoolableObjectFactory
    {

        private ZuoraModuleConnectionManager connectionManager;

        public ConnectionFactory(ZuoraModuleConnectionManager connectionManager) {
            this.connectionManager = connectionManager;
        }

        public Object makeObject(Object key)
            throws Exception
        {
            if (!(key instanceof ZuoraModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            ZuoraModuleLifecycleAdapter connector = new ZuoraModuleLifecycleAdapter();
            connector.setClient(connectionManager.getClient());
            connector.setEndpoint(connectionManager.getEndpoint());
            if (connector instanceof Initialisable) {
                connector.initialise();
            }
            if (connector instanceof Startable) {
                connector.start();
            }
            return connector;
        }

        public void destroyObject(Object key, Object obj)
            throws Exception
        {
            if (!(key instanceof ZuoraModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            if (!(obj instanceof ZuoraModuleLifecycleAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                ((ZuoraModuleLifecycleAdapter) obj).disconnect();
            } catch (Exception e) {
                throw e;
            } finally {
                if (((ZuoraModuleLifecycleAdapter) obj) instanceof Stoppable) {
                    ((ZuoraModuleLifecycleAdapter) obj).stop();
                }
                if (((ZuoraModuleLifecycleAdapter) obj) instanceof Disposable) {
                    ((ZuoraModuleLifecycleAdapter) obj).dispose();
                }
            }
        }

        public boolean validateObject(Object key, Object obj) {
            if (!(obj instanceof ZuoraModuleLifecycleAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                return ((ZuoraModuleLifecycleAdapter) obj).isConnected();
            } catch (Exception e) {
                logger.error(e.getMessage(), e);
                return false;
            }
        }

        public void activateObject(Object key, Object obj)
            throws Exception
        {
            if (!(key instanceof ZuoraModuleConnectionManager.ConnectionKey)) {
                throw new RuntimeException("Invalid key type");
            }
            if (!(obj instanceof ZuoraModuleLifecycleAdapter)) {
                throw new RuntimeException("Invalid connector type");
            }
            try {
                if (!((ZuoraModuleLifecycleAdapter) obj).isConnected()) {
                    ((ZuoraModuleLifecycleAdapter) obj).connect(((ZuoraModuleConnectionManager.ConnectionKey) key).getUsername(), ((ZuoraModuleConnectionManager.ConnectionKey) key).getPassword());
                }
            } catch (Exception e) {
                throw e;
            }
        }

        public void passivateObject(Object key, Object obj)
            throws Exception
        {
        }

    }


    /**
     * A tuple of connection parameters
     * 
     */
    public static class ConnectionKey {

        /**
         * 
         */
        private String username;
        /**
         * 
         */
        private String password;

        public ConnectionKey(String username, String password) {
            this.username = username;
            this.password = password;
        }

        /**
         * Sets username
         * 
         * @param value Value to set
         */
        public void setUsername(String value) {
            this.username = value;
        }

        /**
         * Retrieves username
         * 
         */
        public String getUsername() {
            return this.username;
        }

        /**
         * Sets password
         * 
         * @param value Value to set
         */
        public void setPassword(String value) {
            this.password = value;
        }

        /**
         * Retrieves password
         * 
         */
        public String getPassword() {
            return this.password;
        }

        public int hashCode() {
            int hash = 1;
            hash = ((hash* 31)+ this.username.hashCode());
            return hash;
        }

        public boolean equals(Object obj) {
            return ((obj instanceof ZuoraModuleConnectionManager.ConnectionKey)&&(this.username == ((ZuoraModuleConnectionManager.ConnectionKey) obj).username));
        }

    }

}
