package org.hdl.anima.surrogate;

import static com.google.common.base.Preconditions.checkArgument;

import java.net.InetSocketAddress;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.hdl.anima.AppConf;
import org.hdl.anima.AppConstants;
import org.hdl.anima.Application;
import org.hdl.anima.common.NamedThreadFactory;
import org.hdl.anima.protocol.AbstractMessage;
import org.hdl.anima.protocol.Broadcast;
import org.hdl.anima.protocol.CloseClientSession;
import org.hdl.anima.protocol.FreezeClientSession;
import org.hdl.anima.protocol.Kick;
import org.hdl.anima.protocol.OpenClientSession;
import org.hdl.anima.protocol.OpenLocalSession;
import org.hdl.anima.protocol.Push;
import org.hdl.anima.protocol.UnFreezeClientSession;
import org.hdl.anima.remoting.Channel;
import org.hdl.anima.remoting.ChannelHandler;
import org.hdl.anima.remoting.Client;
import org.hdl.anima.remoting.Codec;
import org.hdl.anima.remoting.Constants;
import org.hdl.anima.remoting.RemotingException;
import org.hdl.anima.remoting.Transporters;
import org.hdl.anima.remoting.support.AbstractClient;
import org.hdl.anima.remoting.support.ChannelHandlerAdapter;
import org.hdl.anima.remoting.support.HeartBeatTask;
import org.hdl.anima.session.ClientSession;
import org.hdl.anima.session.ClientSessionMgr;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * ServerSurrogate.
 * @author qiuhd
 * @since  2014年9月17日
 * @version V1.0.0
 */
public class ServerSurrogate{
	
	private static final Logger logger = LoggerFactory.getLogger(ServerSurrogate.class);
	private String serverName;			//代表的服务器名
	private final AppConf appConf;
	private final Application application;
	private Client[] clients;
	private ClientSessionMgr clientSessionMgr;
	private final AtomicInteger COUNTER = new AtomicInteger();
	private ScheduledThreadPoolExecutor scheduled ;
	// 心跳定时器
	private ScheduledFuture<?> heatbeatTimer;
	// 心跳超时,毫秒。缺省0,不会执行心跳。
	private int heartbeat = Constants.DEFAULT_HEARTBEAT * 100;
	private int heartbeatTimeout =  heartbeat * 3;
	
	public ServerSurrogate(Application application,AppConf appConf) {
		checkArgument(appConf != null, "appConf == null");
		checkArgument(application != null, "application == null");
		this.appConf = appConf;
		this.application = application;
		
		this.init();
		this.start();
	}
	
	private void init() {
		serverName = appConf.get(AppConstants.SERVER_ID_KEY);
		scheduled = new ScheduledThreadPoolExecutor(1, new NamedThreadFactory("server-surrogate-heartbeat[" + serverName + "]" , true));
		clientSessionMgr = application.getMoulde(ClientSessionMgr.class);
		int connects = this.appConf.getInt(AppConstants.CONNECTS_KEY, 1);
		clients = new ServerSideClient[connects];
	}
	
	private void start() {
		startServerSideClient();
		startHeatbeatTimer();
	}
	
	/**
	 * Start server side client
	 */
	private void startServerSideClient() {
		int connects = this.appConf.getInt(AppConstants.CONNECTS_KEY, 1);
		for (int i = 0;i < connects;i ++) {
			try {
				Codec codec = new ServerSurrogateCodec(application);
				String remoteIP = appConf.get(AppConstants.REMOTE_IP_KEY);
				int remotePort = appConf.getInt(AppConstants.REMOTE_PORT_KEY, 0);
				int connectTimeout = appConf.getInt(AppConstants.CONNECT_TIMEOUT_KEY, AppConstants.DEFAULT_CONNECT_TIMEOUT);
				AbstractClient client = Transporters.connect(appConf, new ServerSideClientHandler(i+1), codec, remoteIP, remotePort, connectTimeout);
				clients[i] = new ServerSideClient(client);
			} catch (RemotingException e) {
				//ignore
				logger.error("Start server surrogate error!",e);
			}
		}
	}
	
	private void destroyServerSideCient() {
		if (clients != null) {
			for (Client client : clients) {
				client.close();
			}
		}
	}
	/**
	 * Return server side client
	 * @return
	 */
	private Client getCient() {
		if (clients.length == 1) {
			return clients[0];
		}
	    return clients[Math.abs(COUNTER.getAndIncrement() % clients.length)];
	}
	
	/**
	 * Return all server side client channel
	 * @return
	 */
	private Collection<Channel> getChannels() {
		Collection<Channel> channels = new ArrayList<Channel>();
		for (Client client : clients) {
			channels.add(client);
		}
		return channels;
	}
	/**
	 * Send message to background server
	 * @param message
	 */
	public void send(AbstractMessage message) {
		if (message == null)
			return ;
		try {
			getCient().send(message);
		} catch (RemotingException e) {
			throw new IllegalStateException(e);
		}
	}
	/**
	 * Notify background server the client session already is created 
	 * @param sid
	 * @param remoteIP
	 * @param remotePort
	 * @param localIP
	 * @param localPort
	 * @param clientType
	 */
	public void clientSessionCreated(int sid,String remoteIP,int remotePort,String localIP,int localPort,String clientType) {
		OpenClientSession req = new OpenClientSession();
		req.setSid(sid);
		req.setClientType(clientType);
		req.setRemoteIP(remoteIP);
		req.setRemotePort(remotePort);
		req.setLocalIP(localIP);
		req.setLocalPort(localPort);
		try {
			getCient().send(req);
		} catch (RemotingException e) {
			throw new IllegalStateException(e);
		}
	}
	/**
	 *  Notify background server that the client session already is closed
	 * @param sid	
	 */
	public void clientSessionClosed(int sid) {
		try {
			CloseClientSession req = new CloseClientSession(sid);
			getCient().send(req);
		} catch (RemotingException e) {
			throw new IllegalStateException(e);
		}
	}
	/**
	 * 通知后台服务器session已被冻结
	 * @param sid
	 */
	public void clientSessionFreezed(int sid) {
		try {
			FreezeClientSession req = new FreezeClientSession(sid);
			getCient().send(req);
		} catch (RemotingException e) {
			throw new IllegalStateException(e);
		}
	}
	
	/**
	 * 通知后台服务器session冻结解除
	 * @param sid
	 */
	public void clientSessionUnFreezed(int sid) {
		try {
			UnFreezeClientSession req = new UnFreezeClientSession(sid);
			getCient().send(req);
		} catch (RemotingException e) {
			throw new IllegalStateException(e);
		}
	}
	
	/**
	 * Destroy server surrogate
	 */
	public void destroy() {
		destroyServerSideCient();
		stopHeartbeatTimer();
	}
	
	/**
	 * Start server surrogate heatbeat thread
	 */
	private void startHeatbeatTimer() {
		stopHeartbeatTimer();
		if (heartbeat > 0) {
			heatbeatTimer = scheduled.scheduleWithFixedDelay(
					new HeartBeatTask(new HeartBeatTask.ChannelProvider() {
						@Override
						public Collection<Channel> getChannels() {
							return ServerSurrogate.this.getChannels();
						}

						@Override
						public boolean isClientSide() {
							return true;
						}
					}, heartbeat, heartbeatTimeout), heartbeat, heartbeat,
					TimeUnit.MILLISECONDS);
		}
	}
	
	private void stopHeartbeatTimer() {
		try {
			ScheduledFuture<?> timer = heatbeatTimer;
			if (timer != null && !timer.isCancelled()) {
				timer.cancel(true);
			}
		} catch (Throwable t) {
			logger.warn(t.getMessage(), t);
		} finally {
			heatbeatTimer = null;
		}
	}
	
	/**
	 * Server side handler
	 * @author qiuhd
	 *
	 */
	private final class ServerSideClientHandler extends ChannelHandlerAdapter {
		/**
		 * 表示连接服务器多个连接序列号
		 */
		private int id;
		
		public ServerSideClientHandler(int id) {
			this.id = id;
		}
		
		@Override
		public void connected(Channel channel) throws RemotingException {
			OpenLocalSession req = new OpenLocalSession(application.getServerId(),application.getServerType(),id);
			channel.send(req);
		}

		@Override
		public void received(Channel channel, Object message) throws RemotingException {
			if (isKick(message)) {
				Kick kick = (Kick) message;
				int identity = kick.getIdentity();
				ClientSession session  = clientSessionMgr.get(identity);
				if (session != null && !session.isClosed()) {
					session.getChannel().send(kick);
				}
				return ;
			}
			
			if (isMessage(message)) {
				if (isBroadcase(message)) {
					Broadcast broadcast = (Broadcast) message;
					List<ClientSession> clientSessions = clientSessionMgr.getAll();
					for (ClientSession session : clientSessions) {
						if (session != null && !session.isClosed()) {
							session.send(broadcast);
							if (logger.isTraceEnabled()) {
								logger.trace("Broadcasting message to client identity {},message info {}",session.getId(),message);
							}
						}
					}
				}else if (isPush(message)) {
					Push push = (Push) message;
					List<Integer> receivers = push.getReceivers();
					if (receivers != null && receivers.size() > 0) {
						for (int sid : receivers) {
							ClientSession session  = clientSessionMgr.get(sid);
							if (session != null) {
								session.send(push);
								if (logger.isTraceEnabled()) {
									logger.trace("Transmit message to client id {},message info {}",session.getId(),message);
								}
							}else {
								logger.error("Failed to transmit message,mid :"+ push.getId() +",cause :client session already is cloesd!");
							}
						}
					}
				}else {
					AbstractMessage msg = (AbstractMessage)message;
					ClientSession session  = clientSessionMgr.get(msg.getSid());
					if (session != null) {
						session.send(msg);
						logger.trace("Transmit message to client identity {},Message info {}",session.getId(),message);
					}else {
						logger.error("Failed to transmit message,mid :"+ msg.getId() +",cause :client session already is cloesd!");
					}
				}
				return ;
			}
			
			throw new RemotingException(channel, "Failed to handle message,cause :unsupport message type: " + message.getClass().getName());
		}
		
		private boolean isKick(Object message) {
			return message instanceof Kick ? true : false;
		}
		
		private boolean isBroadcase(Object message) {
			return message instanceof Broadcast ? true : false;
		}
		
		private boolean isMessage(Object message) {
			return message instanceof AbstractMessage ? true : false;
		}
		
		private boolean isPush(Object message) {
			return message instanceof Push ? true : false;
		}
	}
	
	/**
	 * Server side client
	 * @author qiuhd
	 *
	 */
	private final class ServerSideClient implements Client {
		
		private final Client client ;
		private Channel channel ;
		
		public ServerSideClient(Client client) {
			checkArgument(client != null,"client == null");
			this.client = client;
			channel = client;
		}
		
		@Override
		public AppConf getConf() {
			return channel.getConf();
		}

		@Override
		public InetSocketAddress getLocalAddress() {
			return channel.getLocalAddress();
		}

		@Override
		public ChannelHandler getChannelHandler() {
			return channel.getChannelHandler();
		}

		@Override
		public void send(Object message) throws RemotingException {
			channel.send(message);
		}

		@Override
		public void close() {
			channel.close();
		}

		@Override
		public void close(int timeout) {
			channel.close(timeout);
		}

		@Override
		public boolean isClosed() {
			return channel.isClosed();
		}

		@Override
		public boolean isConnected() {
			return channel.isConnected();
		}

		@Override
		public InetSocketAddress getRemoteAddress() {
			return channel.getRemoteAddress();
		}

		@Override
		public Object getAttribute(String key) {
			return channel.getAttribute(key);
		}

		@Override
		public void setAttribute(String key, Object object) {
			this.setAttribute(key, object);
		}

		@Override
		public boolean contains(String key) {
			return channel.contains(key);
		}

		@Override
		public void removeAttribute(String key) {
			channel.removeAttribute(key);
		}

		@Override
		public void reconnect() throws RemotingException {
			client.reconnect();
		}

		@Override
		public int getConnectTimeout() {
			return client.getConnectTimeout();
		}
	}
}