package com.cim.idm.utils;

import com.rabbitmq.client.*;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.io.IOException;
import java.util.LinkedList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RabbitRequester implements IRequester {

    private Lock lock = new ReentrantLock();
    private static RabbitRequester rabbitMqRPCClient;
    private Log log = LogFactory.getLog(this.getClass());
    private String host;
    private String port;

    private int channelNum =0;
    private String username;
    private String password;
    private String virtualHost;

    private String exchange;
    private String routingKey;
    private long timeOut	= 300000;

    private int connectPoolSize = 10;
    private int currentConnNum = 0;
    private int connectSize =0;
    private List<Connection> connectionList = new LinkedList<>();

    public String getExchange() {
        return exchange;
    }

    public void setExchange(String exchange) {
        this.exchange = exchange;
    }

    public String getRoutingKey() {
        return routingKey;
    }

    public void setRoutingKey(String routingKey) {
        this.routingKey = routingKey;
    }

    public int getConnectSize() {
        return connectSize;
    }

    public void setConnectPoolSize(int connectPoolSize) {
        this.connectPoolSize = connectPoolSize;
    }

    public void afterPropertiesSet() throws Exception {
        for(int i=0; i<connectPoolSize; i++) {
            try {
                //建立一个连接和一个通道，并为回调声明一个唯一的'回调'队列
                ConnectionFactory factory = new ConnectionFactory();
                factory.setHost(this.getHost());
                factory.setPort(Integer.parseInt(this.getPort()));
                factory.setUsername(this.getUsername());
                factory.setPassword(this.getPassword());
                factory.setVirtualHost(this.getVirtualHost());
                try {
                    Connection connection = factory.newConnection();
                    connectionList.add(connection);
                    connectSize++;
                }catch(Exception e1){
                    log.error("connection["+i+"] error ",e1);
                    continue;
                }
            } catch (Exception e) {
                log.error(e,e);
            }
        }
    }
    public String getHost() {
        return host;
    }

    public void setHost(String host) {
        this.host = host;
    }

    public String getPort() {
        return port;
    }

    public void setPort(String port) {
        this.port = port;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getVirtualHost() {
        return virtualHost;
    }

    public void setVirtualHost(String virtualHost) {
        this.virtualHost = virtualHost;
    }

    public long getTimeOut() {
        return timeOut;
    }

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

    @Override
    public void send(Object message) {

    }

    @Override
    public void send(String subjectName, Object message) {

    }

    @Override
    public void reply(String subjectName, Object message) {

    }

    @Override
    public Object sendRequest(Object message) {
        try {
            return this.call(getCurrentConnect(),
                    this.getExchange(),this.getRoutingKey(),
                    message.toString(),this.getTimeOut()
                    );
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object sendRequest(Object message, int replyTimeout) {
        try {
            return this.call(getCurrentConnect(),
                    this.getExchange(),this.getRoutingKey(),
                    message.toString(),replyTimeout
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object sendRequest(String subjectName, Object message) {
        try {
            return this.call(getCurrentConnect(),
                    this.getExchange(),subjectName,
                    message.toString(),this.getTimeOut()
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Object sendRequest(String subjectName, Object message, long replyTimeout) {
        try {
            return this.call(getCurrentConnect(),
                    this.getExchange(),subjectName,
                    message.toString(),replyTimeout
            );
        } catch (IOException e) {
            throw new RuntimeException(e);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
    public synchronized Connection getCurrentConnect(){

        if(currentConnNum>=connectPoolSize) {
            currentConnNum =0;
        }
        if (connectionList.size() > currentConnNum) {
            return connectionList.get(currentConnNum++);
        } else {
            return connectionList.get(currentConnNum++%connectionList.size());
        }
    }
    public  String  call(Connection connection ,String exchange, String routingKey, String message, long timeout) throws IOException, InterruptedException {
        lock.lock();
        Channel channel = connection.createChannel();
        lock.unlock();
        try{
            //定义一个临时变量的接受队列名
            String replyQueueName = channel.queueDeclare().getQueue();

            //生成一个唯一的字符串作为回调队列的编号
            final String corrId =  UUID.randomUUID().toString();
            //String basicConsume(String queue, boolean autoAck, Consumer callback)
            //发送请求消息，消息使用了两个属性：replyto和correlationId
            //服务端根据replyto返回结果，客户端根据correlationId判断响应是不是给自己的
            AMQP.BasicProperties props = new AMQP.BasicProperties.Builder().correlationId(corrId).replyTo(replyQueueName).build();

            //发布一个消息，requestQueueName路由规则
            channel.basicPublish(exchange, routingKey, props, message.getBytes("UTF-8"));

            //由于我们的消费者交易处理是在单独的线程中进行的，因此我们需要在响应到达之前暂停主线程。
            //这里我们创建的 容量为1的阻塞队列ArrayBlockingQueue，因为我们只需要等待一个响应。
            final BlockingQueue<String> response = new ArrayBlockingQueue<String>(1);

            channel.basicConsume(replyQueueName, true, new DefaultConsumer(channel) {
                @Override
                public void handleDelivery(String consumerTag, Envelope envelope, AMQP.BasicProperties properties, byte[] body) throws IOException {
                    //检查它的correlationId是否是我们所要找的那个
                    System.out.println("Connection:" +currentConnNum+",DeliveryTag:"+envelope.getDeliveryTag());
                    if (properties.getCorrelationId().equals(corrId)) {
                        //如果是，则响应BlockingQueue
                        response.offer(new String(body, "UTF-8"));
                    }
                }
            });
            String result = response.poll(timeout, TimeUnit.MILLISECONDS);
            return result;
        }catch (Exception e){
            System.out.println(e.getMessage());
        }finally {
            try {
                channel.close();
            } catch (TimeoutException e) {
                e.printStackTrace();
            }
        }
        return null;
    }

    public void close(Connection connection) throws IOException {
        connectionList.remove(connection);
        connection.close();
    }
}
