package com.hmsoft.remote.rmi.future;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import com.hmsoft.remote.rmi.exception.RemotingException;
import com.hmsoft.remote.rmi.exception.TimeoutException;
import com.hmsoft.remote.rmi.model.Request;
import com.hmsoft.remote.rmi.model.Response;
import com.hmsoft.remote.rmi.transport.NettyChannel;

/**
 * 
 * @author zq
 *
 */
public class TransFuture implements IFuture {

    private final long id;
    private final NettyChannel nettyChannel;
    private final Request request;
    private volatile Response response;
    private final int timeout;
    private final Lock lock = new ReentrantLock();
    private final Condition done = lock.newCondition();
    private final long start = System.currentTimeMillis();
    private volatile long sent;
	private static final Map<Long, NettyChannel> CHANNELS = new ConcurrentHashMap<Long, NettyChannel>();
	private static final Map<Long, TransFuture> FUTURES   = new ConcurrentHashMap<Long, TransFuture>();

    
    public TransFuture(NettyChannel channel, Request request, int timeout) {
        this.nettyChannel = channel;
        this.request = request;
        this.id = request.getId();
        this.timeout = timeout;
        //set into waiting
        FUTURES.put(id, this);
        CHANNELS.put(id, channel);
    }
	
	@Override
	public Object get() throws RemotingException {
		return get(timeout);
	}

	@Override
	public Object get(int timeout) throws RemotingException {
        if (timeout <= 0) {
            timeout = this.timeout;
        }
        if (!isDone()) {
            long start = System.currentTimeMillis();
            lock.lock();
            try {
                while (!isDone()) {
                    done.await(timeout, TimeUnit.MILLISECONDS);
                    if (isDone() || System.currentTimeMillis() - start > timeout) {
                        break;
                    }
                }
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            } finally {
                lock.unlock();
            }
            if (!isDone()) {
                throw new TimeoutException("超时了");
            }
        }
        return returnFromResponse();
    }

	private Object returnFromResponse() throws RemotingException {
        Response res = response;
        if (res == null) {
            throw new IllegalStateException("response cannot be null");
        }
        if (res.getStatus() == Response.OK) {
            return res.getResult();
        }
        if (res.getStatus() == Response.CLIENT_TIMEOUT || res.getStatus() == Response.SERVER_TIMEOUT) {
            throw new TimeoutException("超时了啊");
        }
        throw new RemotingException("超时了啊");
    }

	@Override
	public boolean isDone() {
		return response != null;
	}

    public void cancel() {
        Response errResponse = new Response(id);
        errResponse.setErrMsg("request future has been canceled.");
        response = errResponse ;
        FUTURES.remove(id);
        CHANNELS.remove(id);
    }
	
    public static TransFuture getFuture(long id) {
        return FUTURES.get(id);
    }

    public static boolean hasFuture(NettyChannel channel) {
        return CHANNELS.containsValue(channel);
    }
    
    public static void sent(NettyChannel channel, Request request) {
    	TransFuture future = FUTURES.get(request.getId());
        if (future != null) {
            future.doSent();
        }
    }

    private void doSent() {
        sent = System.currentTimeMillis();
    }
    
    public static void received(NettyChannel channel, Response response) {
        try {
        	TransFuture future = FUTURES.remove(response.getId());
            if (future != null) {
                future.doReceived(response);
            } else {
                System.err.println("The timeout response finally returned ..");
            }
        } finally {
            CHANNELS.remove(response.getId());
        }
    }
    
    //接收
    private void doReceived(Response res) {
        lock.lock();
        try {
            response = res;
            if (done != null) {
                done.signal();
            }
        } finally {
            lock.unlock();
        }
    }
    
    private static class RemotingInvocationTimeoutScan implements Runnable {

        public void run() {
            while (true) {
                try {
                    for (TransFuture future : FUTURES.values()) {
                        if (future == null || future.isDone()) {
                            continue;
                        }
                        if (System.currentTimeMillis() - future.getStart() > future.getTimeout()) {
                            // create exception response.
                            Response timeoutResponse = new Response(future.getId());
                            // set timeout status.
                            timeoutResponse.setStatus(future.isSent() ? Response.SERVER_TIMEOUT : Response.CLIENT_TIMEOUT);
                            timeoutResponse.setErrMsg("超时");
                            // handle response.
                            TransFuture.received(future.getNettyChannel(), timeoutResponse);
                        }
                    }
                    Thread.sleep(30);
                } catch (Throwable e) {
                    e.printStackTrace();
                }
            }
        }
    }

    static {
        Thread th = new Thread(new RemotingInvocationTimeoutScan(), "MtzjClient-ResponseTimeoutScaner");
        th.setDaemon(true);
        th.start();
    }
    
	public Response getResponse() {
		return response;
	}

	public void setResponse(Response response) {
		this.response = response;
	}

    private boolean isSent() {
        return sent > 0;
    }

	public long getId() {
		return id;
	}

	public NettyChannel getNettyChannel() {
		return nettyChannel;
	}

	public Request getRequest() {
		return request;
	}

	public int getTimeout() {
		return timeout;
	}

	public long getStart() {
		return start;
	}
}
