import java.net.InetSocketAddress;

import java.net.Proxy;

import java.net.Proxy.Type;

import java.net.URI;

import java.net.URISyntaxException;

import java.util.concurrent.Callable;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;


import org.jboss.netty.bootstrap.ClientBootstrap;

import org.jboss.netty.channel.Channel;

import org.jboss.netty.channel.ChannelFuture;

import org.jboss.netty.channel.ChannelFutureListener;

import org.jboss.netty.channel.socket.oio.OioClientSocketChannelFactory;

import org.jboss.netty.handler.timeout.IdleStateHandler;

import org.jboss.netty.util.AlarmManagerTimer;

import org.jboss.netty.util.AlarmManagerTimerFactory;

import org.jboss.netty.util.Timer;


import android.content.Context;

import android.content.Intent;

import android.content.SharedPreferences;

import android.content.SharedPreferences.Editor;


import com.lenovo.lsf.push.log.PushLog;

import com.lenovo.lsf.push.log.PushLog.LEVEL;

import com.lenovo.lsf.push.net.pipeline.PollPipelineFactory;

import com.lenovo.lsf.push.pid.TicketManager;

import com.lenovo.lsf.push.util.PushWakeLock;


public class PushMessagePollImpl extends PushDAONetAware implements IPushMessage {


public final static int PUSH_SWITCH_ON = 1;

public final static int PUSH_SWITCH_OFF = 0;

public final static String PUSH_SWITCH_KEY = "lsf_device_push_switch";



public static final int MAX_POLL_FAIL_COUNT = 5;

public static final int INIT_POLL_FAIL_COUNT = 0;

public static final String POLL_WAKE_LOCK = "POLL_WAKE_LOCK";


private static final String SP_NAME = "lsf_sp";

private static final String KEY_ACK = "ack";





private Context context;

private String pollURI;

private String pollHost;

private int pollPort;




private ClientBootstrap bootstrap;

private ChannelFuture channelFuture;

private String lastNetType="";

private String lastIPAddress="";


private boolean isPollRunning=false;

private boolean udpAvaliable=false;



private int pollFailCount = INIT_POLL_FAIL_COUNT;



private static final String DAY_POLL_COUNT = "day_poll_count";

private static final String DAY_POLL_COUNT_SHARE_PREFERENCES = "day_poll_count_share_preferences";

private PushMessagePollDelayRetryProxy delayProxy;

private PushPollIntervalTunningManager tunningManager;

private AlarmManagerTimerFactory timerFactory;



public PushMessagePollImpl(Context context) {

super(context);

// TODO Auto-generated constructor stub

this.context = context;

delayProxy = new PushMessagePollDelayRetryProxy();

tunningManager = new PushPollIntervalTunningManager();

timerFactory = new AlarmManagerTimerFactory(PushService.ACTION_INTERNAL_ALARM_TIMER);



tunningManager.initPollKeepAlive(context);

}


public String getPollURI() {

return pollURI;

}




public void setPollURI(String pollURI) {

this.pollURI = pollURI;

}




public String getPollHost() {

return pollHost;

}




public void setPollHost(String pollHost) {

this.pollHost = pollHost;

}


public boolean isUdpAvaliable() {

return udpAvaliable;

}


public void setUdpAvaliable(boolean udpAvaliable) {

this.udpAvaliable = udpAvaliable;

}





public boolean isPollRunning() {

return isPollRunning;

}


public void setPollRunning(boolean isPollRunning) {

this.isPollRunning = isPollRunning;

}



public int getPollFailCount() {

return pollFailCount;

}


public void setPollFailCount(int pollFailCount) {

this.pollFailCount = pollFailCount;

}





public synchronized long getDayPollCount() {

SharedPreferences sp = context.getSharedPreferences(DAY_POLL_COUNT_SHARE_PREFERENCES,Context.MODE_PRIVATE);

return sp.getLong(DAY_POLL_COUNT, 0);

}



public synchronized void setDayPollCount(long dayPollCount) {


SharedPreferences sp = context.getSharedPreferences(

DAY_POLL_COUNT_SHARE_PREFERENCES, Context.MODE_PRIVATE);

Editor editor = sp.edit();

editor.putLong(DAY_POLL_COUNT, dayPollCount);

editor.commit();


}


public PushMessagePollDelayRetryProxy getDelayProxy() {

return delayProxy;

}


public void setDelayProxy(PushMessagePollDelayRetryProxy delayProxy) {

this.delayProxy = delayProxy;

}


public PushPollIntervalTunningManager getTunningManager() {

return tunningManager;

}


public void setTunningManager(PushPollIntervalTunningManager tunningManager) {

this.tunningManager = tunningManager;

}




public AlarmManagerTimerFactory getTimerFactory() {

return timerFactory;

}


public void setTimerFactory(AlarmManagerTimerFactory timerFactory) {

this.timerFactory = timerFactory;

}






@Override

public void resetDayPollCount() {

setDayPollCount(0);

}


@Override

public void resetFailCount() {

// TODO Auto-generated method stub

pollFailCount = INIT_POLL_FAIL_COUNT;

delayProxy.cancelPollRetryAlarm(context);

}



@Override

public void start() {


if(isSwitchOn() && isNetAvailable() && ((!isEmpty()) || (getDayPollCount()<= 0))){





String currentNetType = getNetType();

String currentIPAddress = getIpAddress();



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.start", "before start status: "+ (isPollRunning() ? "running" : "stop"));

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.start", "lastNetType:"+lastNetType+", currentNetType:"+currentNetType);

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.start", "lastIPAddress:"+lastIPAddress+", currentIPAddress:"+currentIPAddress);




// TODO Auto-generated method stub

if(!isPollRunning()) {

poll();

}







/*

else{


if("wifi".equals(currentNetType) || !lastNetType.equals(currentNetType) || !lastIPAddress.equals(currentIPAddress)){

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.start()", "net type changed, begin to reconnect !!!");



tunningManager.setPollKeepAlive(PushPollIntervalTunningManager.INIT_POLL_KEEP_ALIVE);

tunningManager.savePollKeepAlive(context, PushPollIntervalTunningManager.INIT_POLL_KEEP_ALIVE);



timerFactory.destroyAlarmManagerTimer(context);



isPollRunning = false;



if(future != null){

Channel channel = future.getChannel();

if(channel != null){

channel.close();

// Wait for the server to close the connection.

channel.getCloseFuture().awaitUninterruptibly();

}

}





Intent i = PushService.newIntent(context,PushService.ACTION_INTERNAL_START_ALL);

//context.sendBroadcast(i);

i = PushIntentAware.awareIntent(context, i);

context.startService(i);

}

}

*/

lastNetType = currentNetType;

lastIPAddress = currentIPAddress;



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.start", "after start status: "+ (isPollRunning() ? "running" : "stop"));


}else{

Intent i = PushService.newIntent(context,PushService.ACTION_INTERNAL_STOP_ALL);

//context.sendBroadcast(i);

i = PushIntentAware.awareIntent(context, i);

context.startService(i);

}





}


@Override

public void stop() {

// TODO Auto-generated method stub


delayProxy.cancelPollRetryAlarm(context);



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.stop", "before stop poll status: "+ (isPollRunning() ? "running" : "stop"));





if(isPollRunning()) {




timerFactory.destroyAlarmManagerTimer(context);


setPollRunning(false);



if(channelFuture != null){

Channel channel = channelFuture.getChannel();

if(channel != null){

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.stop", "channel has been closed by future !!!");

channel.close();

// Wait for the server to close the connection.

channel.getCloseFuture().awaitUninterruptibly();

}

}






}



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.stop", "after stop poll status: "+ (isPollRunning() ? "running" : "stop"));






}


@Override

public void online() {

// TODO Auto-generated method stub

start();

}


@Override

public void offline() {

// TODO Auto-generated method stub

stop();

}

public void poll() {



setPollRunning(true);



PushWakeLock.acquire(context, POLL_WAKE_LOCK, 30);



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.sendPollRequest", "init netty model !!!");





// Configure the client.

bootstrap = new ClientBootstrap(



// new NioClientSocketChannelFactory(

// Executors.newCachedThreadPool(),

// Executors.newCachedThreadPool())



new OioClientSocketChannelFactory(

Executors.newCachedThreadPool())

);

bootstrap.setOption("tcpNoDelay", true);

bootstrap.setOption("keepAlive", true);

bootstrap.setOption("reuseAddress", true);

bootstrap.setOption("connectTimeoutMillis", 1000*20);



// Set up the event pipeline factory.

bootstrap.setPipelineFactory(new PollPipelineFactory(context, timerFactory.getNewAlarmManagerTimer(context), this));





this.pollURI = getPollRequestUrl();



URI uri = null;

try {

uri = new URI(this.pollURI);

} catch (URISyntaxException e) {

// TODO Auto-generated catch block

e.printStackTrace();

}

String scheme = uri.getScheme() == null? "http" : uri.getScheme();

pollHost = uri.getHost() == null? "localhost" : uri.getHost();

pollPort = uri.getPort();

if (pollPort == -1) {

if (scheme.equalsIgnoreCase("http")) {

pollPort = 80;

} else if (scheme.equalsIgnoreCase("https")) {

pollPort = 443;

}

}



connect();

}












void connect() {





ExecutorService executor = Executors.newSingleThreadExecutor();

Future<ChannelFuture> future = (Future<ChannelFuture>) executor

.submit(new Callable<ChannelFuture>() {


@Override

public ChannelFuture call() throws Exception {

// Start the connection attempt.



String proxyHost = android.net.Proxy.getDefaultHost();

int proxyPort = android.net.Proxy.getDefaultPort();



if(proxyHost == null || "".equals(proxyHost) || proxyPort == 0){

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.connect", "connect to server directly !!!");

channelFuture = bootstrap.connect(new InetSocketAddress(pollHost, pollPort));

}else{

InetSocketAddress proxyAddress = new InetSocketAddress(proxyHost, proxyPort);

Proxy proxy = new Proxy(Type.HTTP, proxyAddress);

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.connect", "connect to server through proxy, proxyHost: "+proxyAddress.getHostName()+",proxyPort:"+proxyAddress.getPort()+" !!!");

channelFuture = bootstrap.connect(proxy.address());

}



channelFuture.addListener(new ChannelFutureListener(){


@Override

public void operationComplete(ChannelFuture future)

throws Exception {

// TODO Auto-generated method stub

if (!future.isSuccess()) {



timerFactory.destroyAlarmManagerTimer(context);



setPollRunning(false);



if(future != null){

Channel channel = future.getChannel();

if(channel != null){

channel.close();

// Wait for the server to close the connection.

channel.getCloseFuture().awaitUninterruptibly();

}

}





PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.sendPollRequest", "connect fail begin to release wake lock!!!");

PushWakeLock.release(context, POLL_WAKE_LOCK);



/*

if (pollFailCount <= MAX_POLL_FAIL_COUNT) {


PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.sendPollRequest", "connect fail and begin to retry !!!");




// Intent i = new Intent(PushInternalReceiver.ACTION_INTERNAL_START_ALL);

// context.sendBroadcast(i);


delayProxy.setPollRetryAlarm(context,pollFailCount);


}

*/

}

}



});



return channelFuture;

}


});



try {

future.get();

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}




}



@Override

public void udpAvaliable() {

// TODO Auto-generated method stub

setUdpAvaliable(true);

}


@Override

public void udpUnAvaliable() {

// TODO Auto-generated method stub

setUdpAvaliable(false);

}

private String getPollRequestUrl() {


ExecutorService executor = Executors.newSingleThreadExecutor();

Future<String> future = (Future<String>) executor

.submit(new Callable<String>() {


@Override

public String call() throws Exception {

String addr = getServerAddr("psb");

if(addr == null) {

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.getPollRequestUrl", "get server address failed");

}else{

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.getPollRequestUrl", "url:"+addr);

}




String st = getSt();

if(st == null) {

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.getPollRequestUrl", "get st failed");

}else{

PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.getPollRequestUrl", "st:"+st);

}




StringBuilder sb = new StringBuilder(addr==null ? "":addr);

sb.append("pushservice/2.1/poll?lpsst=");

sb.append(String.valueOf(st));


PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.getPollRequestUrl", "command line is:" + sb.toString());



return sb.toString();

}


});


try {

String pollURI = future.get();

return pollURI;

} catch (Exception e) {

// TODO Auto-generated catch block

e.printStackTrace();

}


return null;

}




@Override

public void expire(int requestCode) {



AlarmManagerTimer timer = timerFactory.getAlarmManagerTimer(context);



// TODO Auto-generated method stub

if(timer != null){

timer.expire(requestCode);

}else{



PushLog.log(context, LEVEL.INFO, "PushMessagePollImpl.expire()", "push service instance has been recreated, restart polling !!!");


setPollRunning(false);



if(channelFuture != null){

Channel channel = channelFuture.getChannel();

if(channel != null){

channel.close();

// Wait for the server to close the connection.

channel.getCloseFuture().awaitUninterruptibly();

}

}





Intent i = PushService.newIntent(context,PushService.ACTION_INTERNAL_START_ALL);

//context.sendBroadcast(i);

i = PushIntentAware.awareIntent(context, i);

context.startService(i);

}

}


@Override

public void switchOn() {

// TODO Auto-generated method stub

setSwitch(context, true);

}


@Override

public void switchOff() {

// TODO Auto-generated method stub

setSwitch(context, false);

}



@Override

public void resetUdpAvaliable() {

// TODO Auto-generated method stub

setUdpAvaliable(false);

}






}