package com.backfire.socket;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Looper;
import android.os.Message;

import com.backfire.utils.Log;

public final class SocketConnectImp extends SocketConnectBase {

	public interface OnSocketEvent {
		public abstract void onServerPacket(int cmd, DataPacket packet);
		
		public abstract void onConnected();
		
		public abstract void onReconnect(int num);
		
		public abstract void onLogoutTimeout();
		
		public abstract void onFailed();
	
	};
	
	public static final int NETWORK_IGNORE = -1;
	public static final int NETWORK_RECONNECT = -2;
	
	
	public static final int WAITNONE_TIMEOUT_MILLIS = 5 * 1000;
	public static final int SOCKET_TIMEOUT_MILLIS = 0;
	public static final int READ_SLEEP_MILLIS = 500;
	public static final int SOCKET_RECONNECT_MILLIS = 0;//5 * 1000;
	public static final int SOCKET_RECONNECT_MAX = 3; // 去掉重连
	public static final int SOCKET_HEART_MILLIS = 10 * 1000;
	public static final int SOCKET_IDLE_MILLIS = 2 * 1000;
	public static final int SOCKET_IDLE_MAX = 5;
	
	//set ip and port here
	private String IP; 
	private int PORT;

	public String getIP() {
		return IP;
	}

	public void setIP(String ip) {
		IP = ip;
		Log.w("setIP", "IP: "+IP+" ip:"+ip);
	}

	public int getPORT() {
		return PORT;
	}

	public void setPORT(int port) {
		PORT = port;
		Log.w("setPORT", "PORT: "+PORT+" port:"+port);
	}
	
	private OnSocketEvent event;
	private SocketHandler shandler = null;
	
	private int reconnectNum;
	private volatile int idleNum;
	private boolean firstOpen = true;
	
	private ReadThread readThread;
	private byte[] bytesHead = new byte[DataPacket.HEADER_SIZE];
	private DataPacket packetRead;
	private int lengthBody;
	private short cmd;
	
	public SocketConnectImp() {
		super();
		firstOpen = true;
		reconnectNum = 0;
		idleNum = 0;
		clearFlag();
	}
	
	public void open() {
		Log.d(0, this, "open");
		sendInit();
		sendOpen();
	}
	
	public void setLogoutTimeout() {
		addFlag(FLAG_SET_TIMEOUT);
	}
	
	public void waitNone() {
		sendInit();
		shandler.removeMessages(SocketHandler.MSG_IDLE);
		shandler.removeMessages(SocketHandler.MSG_CONNECT);
		sendClose(WAITNONE_TIMEOUT_MILLIS);
	}
	

	public void close() {
		Log.v(this, "close");
		this.setSocketEvent(null);
		sendInit();
		clearFlag();
		if(shandler!=null)shandler.removeMessages(SocketHandler.MSG_IDLE);
		if(shandler!=null)shandler.removeMessages(SocketHandler.MSG_CONNECT);
		reconnectNum = 0;
		idleNum = 0;
		sendClose(0);
	}

	public void sendCmd(DataPacket packet) {
		sendInit();
		sendWrite(packet);
	}

	public void setSocketEvent(OnSocketEvent event) {
		this.event = event;
	}

	private class SocketHandler extends Handler {
		public static final int MSG_OPEN = 1;
		public static final int MSG_CLOSE = 2;
		public static final int MSG_CONNECT = 3;
		public static final int MSG_WRITE = 4;
		public static final int MSG_HEART = 5;
		public static final int MSG_IDLE = 6;

		public SocketHandler(Looper looper) {
			super(looper);
		}

		public void handleMessage(Message msg) {
			DataPacket packet = null;
			int closeFlag;
			switch (msg.what) {
			case MSG_OPEN:
				onMsgOpen();
				break;
			case MSG_CLOSE:
				closeFlag = (Integer) (msg.obj);
				onMsgClose(closeFlag);
				break;
			case MSG_WRITE:
				packet = (DataPacket) msg.obj;
				onMsgWrite(packet);
				break;
			case MSG_IDLE:
				onMsgIdle();
				break;
			case MSG_CONNECT:
				onMsgConnect();
				break;
			case MSG_HEART:
				onMsgHeart();
				break;
			default:
				break;
			}
			super.handleMessage(msg);
		}
	}

	private void onMsgOpen() {
		if (testFlag(FLAG_OPEN))
			return;
		closeSocket();
		stopReadThread();
		clearFlag();
		addFlag(FLAG_OPEN);
		addFlag(FLAG_NEED_CONNECT);
		reconnectNum = 0;
		idleNum = 0;
		sendConnect();
	}

	private void onMsgClose(int closeFlag) {
		// set interrupt first
		if (null != readThread) {
			readThread.interrupt();
		}
		// close
		closeSocket();
		stopReadThread();
		stopSendThread();
		
		if (1 == closeFlag) {
			if (null != event) {
				event.onLogoutTimeout();
			}
		}
	}

	private void onMsgConnect() {
		if (testFlag(FLAG_OPEN) && testFlag(FLAG_NEED_CONNECT)) {
			if (testFlag(FLAG_SET_TIMEOUT)) {
				reconnectNum = 0;
				idleNum = 0;
				removeFlag(FLAG_NEED_CONNECT);
				Log.d(0, this, "need connect but set timeout");
			} else {
				if (reconnectNum < SOCKET_RECONNECT_MAX) {
					closeSocket();
					stopReadThread();
					
					Log.d(0, this,
							"connect try=" + String.valueOf(reconnectNum));
					if (openSocket(IP,
							PORT, SOCKET_RECONNECT_MILLIS)) {
						Log.d(0, this, "OpenSocket success");
						removeFlag(FLAG_NEED_CONNECT);
						reconnectNum = 1;
						idleNum = 0;
						if (firstOpen)
						{
							firstOpen = false;
						}
						if (null != event) {
							try {
								event.onConnected();
							} catch (Exception ex) {
								Log.e(0, this, ex);
							}
						}

						sendHeart(SOCKET_HEART_MILLIS);
						startReadThread();
						Log.d(0, this, "socket connectivity " + idleNum);
					} else {
						Log.d(0, this, "OpenSocket failed");
						if ( firstOpen )
						{
							firstOpen = false;
							reconnectNum = 0;
							if (null != event) {
								Log.d(0, this, "onFailed");
								try {
									event.onFailed();
								} catch (Exception ex) {
									Log.e(0, this, ex);
								}
							}
						}
						
						reconnectNum++;
						if (null != event) {
							try {
								event.onReconnect(reconnectNum);
							} catch (Exception ex) {
								Log.e(0, this, ex);
							}
						}
						Log.d(0, this, "Sleep-->");
						try {
							Thread.sleep(SOCKET_RECONNECT_MILLIS);
						} catch (Exception e) {
							Log.e(0, this, e);
						}
						Log.d(0, this, "<--Sleep");

						sendConnect();
					}
				} else {
					if (reconnectNum == SOCKET_RECONNECT_MAX) {
						reconnectNum = 0;
						if (null != event) {
							Log.d(0, this, "onFailed");
							try {
								event.onFailed();
							} catch (Exception ex) {
								Log.e(0, this, ex);
							}
						}
					}
				}
			}
		} else {
			Log.d(0, this, "check reconnect");
		}
	}

	private void onMsgHeart() {
		if (testFlag(FLAG_OPEN) && !testFlag(FLAG_NEED_CONNECT)) {
			DataPacket packet = DataPacket.allocPacket();
			packet.writeBegin(SocketCommand.CLI_HEART_BEAT_REQ);
			packet.writeEnd();
			sendCmd(packet);
		}
	}

	private void onMsgIdle() {
		if (testFlag(FLAG_OPEN) && !testFlag(FLAG_NEED_CONNECT)) {
			idleNum++;
			Log.d(0, this, "socket connectivity " + idleNum);

			if (idleNum >= SOCKET_IDLE_MAX) {
				idleNum = 0;
				addFlag(FLAG_NEED_CONNECT);
				sendConnect();
			} else {
				if (canUse()) {
					sendIdle();
				} else {
					Log.d(0, this, "local socket failed");
					addFlag(FLAG_NEED_CONNECT);
					sendConnect();
				}
			}
		}
	}

	private void onMsgWrite(DataPacket packet) {
		
		if (packet.getCmd() == SocketCommand.CLI_LOGIN_OUT_REQ) {
			this.waitNone();
		}
		
		if (testFlag(FLAG_OPEN)) {
			if (testFlag(FLAG_NEED_CONNECT)) {
				sendConnect();
				sendCmd(packet);
			} else {
				if (canUse()) {
					try {
						Log.i(this, SocketCommand.toString(packet.getCmd()));
						write(packet.getBuffer(), packet.getLength());
						packet.recycle();

						if (packet.getCmd() == SocketCommand.CLI_HEART_BEAT_REQ) {
							sendIdle();
						}

					} catch (Exception e) {
						e.printStackTrace();
						Log.e(0, this, e);
						addFlag(FLAG_NEED_CONNECT);
						sendConnect();
					} finally {

					}
				} else {
					Log.i(0, this, "can not use");
					addFlag(FLAG_NEED_CONNECT);
					sendConnect();
				}
			}
		}
	}

	private void sendInit() {
		if (null == shandler) {
			HandlerThread sthread = new HandlerThread("SocketHandlerThread");
			sthread.setDaemon(true);
			sthread.start();	

			shandler = new SocketHandler(sthread.getLooper());
		}
	}

	private void sendOpen() {
		shandler.sendMessage(shandler.obtainMessage(SocketHandler.MSG_OPEN));
	}

	private void sendClose(long delayMillis) {
		if (0 == delayMillis) {
			if(shandler!=null)shandler.sendMessage(shandler.obtainMessage(
					SocketHandler.MSG_CLOSE, 0));
		} else {
			if(shandler!=null)shandler.sendMessageDelayed(
					shandler.obtainMessage(SocketHandler.MSG_CLOSE, 1),
					delayMillis);
		}
	}

	private void sendConnect() {
		if(shandler!=null)shandler.sendMessage(shandler.obtainMessage(SocketHandler.MSG_CONNECT));
	}

	private void sendWrite(DataPacket packet) {
		if(shandler!=null)shandler.sendMessage(shandler
				.obtainMessage(SocketHandler.MSG_WRITE, packet));
	}

	private void sendIdle() {
		if(shandler!=null)shandler.sendMessageDelayed(
				shandler.obtainMessage(SocketHandler.MSG_IDLE),
				SOCKET_IDLE_MILLIS);
	}

	private void sendIdleClear() {
		if(shandler!=null)shandler.removeMessages(SocketHandler.MSG_IDLE);
	}

	private void sendHeart(long delayMillis) {
		if (0 == delayMillis) {
			
		} else {
			if(shandler!=null)shandler.sendMessageDelayed(
					shandler.obtainMessage(SocketHandler.MSG_HEART),
					delayMillis);
		}
	}

	class ReadThread extends Thread {
		private volatile boolean alive = true;

		public void kill() {
			alive = false;
		}

		public void run() {
			while (alive) {
				try {
					boolean flag = read();
					if(!flag){
						if (null != event) {
							event.onFailed();
						}
					}
				} catch(Exception e){
					e.printStackTrace();
				}finally {

				}
			}
		}
	};

	private boolean read() {
		try {
			//Log.d(0, this, "read");
			if (testFlag(FLAG_NEED_CONNECT))
				return false;
			if (false == canUse()) {
				Log.d(0, this, "canUse false");
				addFlag(FLAG_NEED_CONNECT);
				sendConnect();
				return false;
			}
			//Log.d(0, this, "canUse true");

			int ret = readBytes(bytesHead, 0, DataPacket.HEADER_SIZE);
			if (0 == ret) {
				Log.d(0, this, "read 0");
				return false;
			}
			if (-1 == ret) {
				Log.d(0, this, "read sendConnection");
				addFlag(FLAG_NEED_CONNECT);
				sendConnect();
				return false;
			}

			packetRead = DataPacket.allocPacket();

			// cmd
			cmd = packetRead.tc.byte2short(bytesHead, 4);

			int checksum = packetRead.tc.byte2int(bytesHead, 6);
			
			// body length
			lengthBody = packetRead.tc.byte2short(bytesHead, 2);
			packetRead.setReadData(cmd, checksum, lengthBody);


			if (lengthBody >= DataPacket.HEADER_SIZE && lengthBody < DataPacket.MAX_BODY_LENGTH) {
				
				
				System.arraycopy(bytesHead, 0, packetRead.buffer, 0,
						DataPacket.HEADER_SIZE);
				ret = readBytes(packetRead.getBuffer(), DataPacket.HEADER_SIZE,lengthBody-DataPacket.HEADER_SIZE);
				if (0 == ret) {
					// timeout, 以后的数据都可能出错
					Log.d(0, this, "read body timeout");
					packetRead.recycle();
					addFlag(FLAG_NEED_CONNECT);
					sendConnect();
					return false;
				}
				if (-1 == ret) {
					// error, 以后的数据都可能出错
					Log.d(0, this, "read body error");
					packetRead.recycle();
					addFlag(FLAG_NEED_CONNECT);
					sendConnect();
					return false;
				}
				//packetRead.logRead();
				if (null != event) {
					// 对外界很不信任?所以catch所有Exception.
					try {
						event.onServerPacket(cmd, packetRead);
					} catch (Exception ex) {
						Log.e(0, this, ex);
					}
				}

				// 心跳
				if (SocketCommand.SVR_HEART_BEAT_RPY == cmd) {
					idleNum = 0;
					//sendHeart(0);
					sendHeart(SOCKET_HEART_MILLIS);
					sendIdleClear();
					Log.d(0, this, "socket connectivity " + idleNum);
				}else if(cmd>0){
					idleNum = 0;
					sendIdleClear();
				}

				return true;
			} else {
				// error packet, 以后的数据都可能出错
				Log.d(0, this,
						"read body length error " + Integer.toString(lengthBody));
				packetRead.recycle();
				addFlag(FLAG_NEED_CONNECT);
				sendConnect();
				return false;
			}
		} catch (Exception e) {
			// TODO Auto-generated catch block
			Log.e(this, e);
			return false;
		}
	}
	
	private void stopSendThread() {
		Log.d(0, this, "stopSendThread");
		
		if(shandler == null)
		{
			return;
		}
		
		Looper loop = shandler.getLooper();
		if(loop!=null)
		{
			loop.quit();
		}

		shandler = null;
	}

	private void startReadThread() {
		Log.d(0, this, "startReadThread");
		if (null == readThread) {
			Log.d(0, this, "new ReadThread");
			readThread = new ReadThread();
			readThread.setName("SocketReadThread");
			readThread.setDaemon(true);
			readThread.start();
		}
	}

	private boolean stopReadThread() {
		Log.d(0, this, "stopReadThread");
		if (null == readThread)
			return false;

		readThread.kill();
		// readThread.interrupt();

		long time = System.currentTimeMillis();
		try {
			readThread.join(2000);
			// readThread.join();
		} catch (Exception e) {
			Log.e(0, this, e);
		} finally {
			readThread = null;
		}
		time = System.currentTimeMillis() - time;
		return true;
	}
}
