package com.rebelmonkey.monkeywrench.mweb;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentLinkedQueue;

import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonGenerator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.rebelmonkey.monkeywrench.mweb.api.MWEBDataFilter;
import com.rebelmonkey.userinfo.MWUserInfo;
import com.sun.enterprise.web.connector.grizzly.comet.CometEvent;
import com.sun.enterprise.web.connector.grizzly.comet.CometHandler;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
/**
 * 
 * @author jeffpk
 */
class CometClientHandler implements CometHandler {
	private static final boolean DEBUGMODE = true;
	private static final Logger logger = LoggerFactory
			.getLogger(CometClientHandler.class);

	private HttpServletResponse httpServletResponse;
	private String userName;
	private boolean waiting = false;
	private ConcurrentLinkedQueue<Properties> packetQueue = new ConcurrentLinkedQueue<Properties>();

	private List<String> channelList = new ArrayList<String>();
	private List<MWEBDataFilter> filterList = new ArrayList<MWEBDataFilter>();

	public CometClientHandler(String username) {
		userName = username;
		if (DEBUGMODE) {
			channelList.add("test");
		}
	}

	public void attach(Object arg0) {
		httpServletResponse = (HttpServletResponse) arg0;
		
	}
	
	
	
	private void queueForDelivery(Properties props) throws IOException {
		logger.info("queueForDelivery >>> ");
		packetQueue.add(props);
		checkToSend();
		logger.info("queueForDelivery <<<");
	}
	
	private void checkToSend() throws IOException{
		logger.info("Check to send >>> ");
		synchronized(packetQueue){
			if ((!packetQueue.isEmpty()) && waiting){
				send(packetQueue.remove());
				waiting=false;
			}
		}
		logger.info("Check to send <<< ");
	}

	private void send(Properties props) throws IOException {
		logger.info("Send>>> ");
		logger.info("packet=" + props);
		if (props.get("packetType").equals("private")) {
			if (props.get("userName").equals(userName)) {
				deliver(props);
			}
		} else if (props.get("packetType").equals("channel")) {
			if (channelList.contains(props.get("channelName"))) {
				deliver(props);
			}
		} else if (props.get("packetType").equals("control")){
			if (props.get("controlType").equals("connected")){
				deliver(props);
			}
		}
		logger.info("Send<<<");
	}

	private void deliver(Properties props) throws IOException {
		logger.info("Deliver>>> ");
		PrintWriter writer = httpServletResponse.getWriter();
		JsonGenerator gen = new JsonFactory().createJsonGenerator(writer);
		gen.writeStartObject();
		for (Entry entry : props.entrySet()) {
			logger.info("Adding pair to packet: "+(String) entry.getKey()+","+ (String) entry
					.getValue());
			gen.writeStringField((String) entry.getKey(), (String) entry
					.getValue());
		}
		gen.writeEndObject();
		gen.flush();
		writer.flush();
		gen.close();
		logger.info("Deliver<<<");
	}

	public synchronized void onEvent(CometEvent event) throws IOException {
		logger.info("OnEvent>>>");
		Properties props = (Properties) event.attachment();
		if (event.getType() == CometEvent.READ) { // packet to send
			logger.info(userName+" received data packet: "+props);
			queueForDelivery(props);
		} else if (event.getType() == CometEvent.NOTIFY) { // control packet
			logger.info(userName+" received notification packet: "+props);
			String notificationType = props.getProperty("notificationType");
			if (notificationType.equals("connected")) {
				if (props.getProperty("userName").equals(userName)) {
					Properties pkt = new Properties();
					pkt.put("packetType","control");
					pkt.put("controlType", "connected");
					pkt.put("userName", userName);
					queueForDelivery(pkt);
				}
			} else if (notificationType.equals("addUserToChannel")) {
				if (props.getProperty("userName").equals(userName)) {
					addChannel(props.getProperty("channelName"));
				}
			} else if (notificationType.equals("removeUserFromChannel")) {
				if (props.getProperty("userName").equals(userName)) {
					removeChannel(props.getProperty("channelName"));
				}
			} else if (notificationType.equals("addDataFilter")) {
				if (props.getProperty("userName").equals(userName)) {
					try {
						Class filterClass = Class.forName(props.getProperty("filterClass"));
						MWEBDataFilter filter = (MWEBDataFilter) filterClass.newInstance();
						filterList.add(filter);
					} catch (ClassNotFoundException e) {
						logger.error("Failed to load filter class",e);
					} catch (InstantiationException e) {
						logger.error("Failed to instantiate data filter",e);
					} catch (IllegalAccessException e) {
						logger.error("Failed to instantiate data filter",e);
					}
				}
			} else if (notificationType.equals("removeDataFilter")) {
			    String className = props.getProperty("filterClass");
				if (props.getProperty("userName").equals(userName)) {
					boolean found = false;
					for(MWEBDataFilter filter : filterList){
						if (filter.getClass().getName().equals(className)){
							filterList.remove(filter);
						}
					}
					if (!found){
						logger.warn("Found no data filter on user "+userName+"of class "+className);
					}
				}
				
			}
		}
		event.getCometContext().resumeCometHandler(this);
		logger.info("OnEvent<<<");
	}

	private void removeChannel(String chanName) {
		channelList.remove(chanName);
		logger.info("User "+userName+" left channel "+chanName);
	}

	private void addChannel(String chanName) {
		for (String s : channelList) {
			if (s.equals(chanName)) { // already joined
				logger.warn("User " + userName
						+ " attempted join to already joined channel "
						+ chanName);
				return;
			}
		}
		channelList.add(chanName);
		logger.info("User "+userName+" joined channel "+chanName);
	}

	public void onInitialize(CometEvent arg0) throws IOException {
		logger.info("++initialize");
		synchronized(packetQueue){
			waiting = true;
			checkToSend();
		}
	}

	public void onTerminate(CometEvent arg0) throws IOException {
		logger.info("++terminate");
		waiting = false;
	}

	/**
	 * Occurs when client drops connection
	 */
	public void onInterrupt(CometEvent arg0) throws IOException {
		logger.info("++interrupt");
		waiting = false;
	}

	
}
