package com.baidu.beidou.navi.pbrpc.client;

import io.netty.channel.ChannelFuture;

import org.apache.commons.pool.impl.GenericObjectPool;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.baidu.beidou.navi.pbrpc.client.callback.CallFuture;
import com.baidu.beidou.navi.pbrpc.exception.client.OperationNotSupportException;
import com.baidu.beidou.navi.pbrpc.exception.client.PbrpcException;
import com.baidu.beidou.navi.pbrpc.transport.PbrpcMsg;
import com.google.protobuf.GeneratedMessage;

/**
 * ClassName: PooledPbrpcClient <br/>
 * Function: 使用连接池技术的客户端
 * 
 * @author Zhang Xu
 */
public class PooledPbrpcClient implements PbrpcClient {

    private static final Logger LOG = LoggerFactory.getLogger(PooledPbrpcClient.class);

    /**
     * socket连接池
     */
    private PbrpcClientChannelPool channelPool;

    /**
     * 连接池配置
     */
    private GenericObjectPool.Config pooledConfig;

    /**
     * 远程服务IP
     */
    private String ip;

    /**
     * 远程服务端口
     */
    private int port;

    /**
     * 客户端连接超时,单位毫秒
     */
    private int connTimeout;

    /**
     * 客户端调用超时,单位毫秒
     */
    private int readTimeout;

    public PooledPbrpcClient() {
    }

    /**
     * Creates a new instance of PooledPbrpcClient.
     * 
     * @param configuration
     *            连接池配置
     * @param clientConfig
     *            客户端配置
     * @param ip
     *            socket远程ip
     * @param port
     *            socket远程端口
     * @param connTimeout
     *            客户端连接时间,单位为毫秒
     * @param readTimeout
     *            客户端调用的超时时间,单位为毫秒,超时则会抛出{@link com.baidu.beidou.navi.pbrpc.exception.TimeoutException}
     */
    public PooledPbrpcClient(PooledConfiguration configuration,
            PbrpcClientConfiguration clientConfig, String ip, int port, int connTimeout,
            int readTimeout) {
        this.pooledConfig = configuration.getPoolConfig();
        this.ip = ip;
        this.port = port;
        this.connTimeout = connTimeout;
        this.readTimeout = readTimeout;
        channelPool = new PbrpcClientChannelPool(pooledConfig, clientConfig, ip, port, connTimeout,
                readTimeout);
    }

    /**
     * @see com.baidu.beidou.navi.pbrpc.client.PbrpcClient#asyncTransport(java.lang.Class,
     *      com.baidu.beidou.navi.pbrpc.transport.PbrpcMsg)
     */
    @Override
    public <T extends GeneratedMessage> CallFuture<T> asyncTransport(Class<T> responseClazz,
            PbrpcMsg pbrpcMsg) {
        PbrpcClientChannel channel = channelPool.getResource();
        try {
            CallFuture<T> res = channel.asyncTransport(responseClazz, pbrpcMsg, this.readTimeout);
            return res;
        } catch (Exception e) {
            LOG.error("asyncTransport failed, " + e.getMessage(), e);
            channelPool.returnBrokenResource(channel);
            throw new PbrpcException("Pbrpc invocation failed on " + getInfo() + ", "
                    + e.getMessage(), e);
        } finally {
            channelPool.returnResource(channel);
        }
    }

    /**
     * @see com.baidu.beidou.navi.pbrpc.client.PbrpcClient#syncTransport(java.lang.Class,
     *      com.baidu.beidou.navi.pbrpc.transport.PbrpcMsg)
     */
    @Override
    public <T extends GeneratedMessage> T syncTransport(Class<T> responseClazz, PbrpcMsg pbrpcMsg) {
        try {
            CallFuture<T> future = asyncTransport(responseClazz, pbrpcMsg);
            if (future != null) {
                return future.get();
            }
            return null;
        } catch (PbrpcException e) {
            throw e;
        } catch (InterruptedException e) {
            throw new PbrpcException("Pbrpc invocation failed on " + getInfo() + ", "
                    + e.getMessage(), e);
        }
    }

    /**
     * @see com.baidu.beidou.navi.pbrpc.client.PbrpcClient#connect()
     */
    @Override
    public ChannelFuture connect() {
        throw new OperationNotSupportException();
    }

    /**
     * @see com.baidu.beidou.navi.pbrpc.client.PbrpcClient#shutdown()
     */
    @Override
    public void shutdown() {
        channelPool.destroy();
    }

    /**
     * @see com.baidu.beidou.navi.pbrpc.client.PbrpcClient#getInfo()
     */
    @Override
    public String getInfo() {
        return ip + ":" + port + ", connTimeout=" + connTimeout + ", readTimeout=" + readTimeout;
    }

}