/*
 * Copyright 2009 Richard Zschech.
 * 
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 * 
 * http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package net.zschech.gwt.comet.server.impl;

import java.io.IOException;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import javax.servlet.http.HttpSession;

import net.zschech.gwt.comet.server.CometSession;

public class CometSessionImpl implements CometSession {
	
	private static final int INITIAL_WINDOW_SIZE = 1024 * 2;
	private static final int MIN_WINDOW_SIZE = 1024;
	private static final int MAX_WINDOW_SIZE = 1024 * 1024;
	private static final int WINDOW_SIZE_MULTIPLIER = 2;
	private static final int REFRESH_LATENCY_CUTOFF = 1000;
	
	private static final long SESSION_KEEP_ALIVE_BUFFER = 10000;
	
	private final HttpSession httpSession;
	private final Queue<Serializable> queue;
	private final AsyncServlet async;
	private final AtomicBoolean valid;
	private final AtomicReference<CometServletResponseImpl> response;
	
	private final AtomicBoolean refreshing;
	private volatile long refreshSentTime;
	private volatile int windowSize = INITIAL_WINDOW_SIZE;
	
	private volatile long lastAccessedTime;
	
	public CometSessionImpl(HttpSession httpSession, Queue<Serializable> queue, AsyncServlet async) {
		this.httpSession = httpSession;
		this.queue = queue;
		this.async = async;
		this.valid = new AtomicBoolean(true);
		this.response = new AtomicReference<CometServletResponseImpl>();
		this.refreshing = new AtomicBoolean(false);
	}
	
	private void ensureValid() {
		if (!valid.get()) {
			throw new IllegalStateException("CometSession has been invalidated");
		}
	}
	
	@Override
	public HttpSession getHttpSession() {
		ensureValid();
		return httpSession;
	}
	
	@Override
	public void enqueue(Serializable message) {
		ensureValid();
		queue.add(message);
		async.enqueued(this);
	}
	
	@Override
	public void enqueued() {
		ensureValid();
		async.enqueued(this);
	}
	
	@Override
	public Queue<? extends Serializable> getQueue() {
		return queue;
	}
	
	@Override
	public void invalidate() {
		if (valid.compareAndSet(true, false)) {
			async.invalidate(this);
			try {
				httpSession.removeAttribute(HTTP_SESSION_KEY);
			}
			catch (IllegalStateException e) {
				// HttpSession already invalidated
			}
			
			CometServletResponseImpl prevResponse = response.getAndSet(null);
			if (prevResponse != null) {
				prevResponse.tryTerminate();
			}
		}
	}
	
	@Override
	public boolean isValid() {
		return valid.get();
	}
	
	boolean isEmpty() {
		return isValid() && queue.isEmpty();
	}
	
	CometServletResponseImpl setResponse(CometServletResponseImpl response) {
		refreshing.set(false);
		
		if (refreshSentTime != 0) {
			long currentTime = System.currentTimeMillis();
			long refreshTime = currentTime - refreshSentTime;
			
			if (refreshTime > REFRESH_LATENCY_CUTOFF) {
				windowSize = Math.max(windowSize / WINDOW_SIZE_MULTIPLIER, MIN_WINDOW_SIZE);
			}
			else {
				windowSize = Math.min(windowSize * WINDOW_SIZE_MULTIPLIER, MAX_WINDOW_SIZE);
			}
			System.out.println("Refresh Time " + refreshTime + " Window Size " + windowSize);
			
		}
		
		return this.response.getAndSet(response);
	}
	
	boolean clearResponse(CometServletResponseImpl response) {
		return this.response.compareAndSet(response, null);
	}
	
	CometServletResponseImpl getResponse() {
		return response.get();
	}
	
	boolean setRefresh() {
		boolean result = refreshing.compareAndSet(false, true);
		if (result) {
			refreshSentTime = System.currentTimeMillis();
		}
		return result;
	}
	
	boolean isAndSetOverRefreshLength(int count) {
		return count > windowSize && setRefresh();
	}
	
	boolean isOverTerminateLength(int count) {
		return count > windowSize * WINDOW_SIZE_MULTIPLIER;
	}
	
	/**
	 * @param flushIfEmpty
	 *            flush if the queue is empty
	 * @throws IOException
	 */
	void writeQueue(CometServletResponseImpl response, boolean flushIfEmpty) throws IOException {
		assert Thread.holdsLock(response);
		
		int batchSize = 10;
		List<Serializable> messages = new ArrayList<Serializable>(batchSize);
		
		Serializable message = queue.remove();
		messages.add(message);
		for (int i = 0; i < batchSize - 1; i++) {
			message = queue.poll();
			if (message == null) {
				break;
			}
			messages.add(message);
		}
		
		response.write(messages, flushIfEmpty && queue.isEmpty());
	}
	
	long getKeepAliveScheduleTime() throws IllegalStateException {
		int maxInactiveInterval = httpSession.getMaxInactiveInterval();
		if (maxInactiveInterval < 0) {
			return Long.MAX_VALUE;
		}
		long lastAccessedTime = Math.max(this.lastAccessedTime, httpSession.getLastAccessedTime());
		return (maxInactiveInterval * 1000) - (System.currentTimeMillis() - lastAccessedTime) - SESSION_KEEP_ALIVE_BUFFER;
	}
	
	void setLastAccessedTime() {
		this.lastAccessedTime = System.currentTimeMillis();
	}
	
	long getLastAccessedTime() {
		return lastAccessedTime;
	}
}

@@ -3758,99 +3758,8 @@
 %09%7D%0D%0A
-%09%09%09System.out.println(%22Refresh Time %22 + refreshTime + %22 Window Size %22 + windowSize);%0D%0A%09%09%09%0D%0A
 %09%09%7D%0D

clean up