/*
 * Copyright 2018 IBM Corporation
 * 
 * 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 com.roguecloud.client.container;

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

import javax.websocket.RemoteEndpoint.Basic;
import javax.websocket.Session;

import com.roguecloud.client.LibertyClientInstance;
import com.roguecloud.utils.Logger;

public class LibertyClientBrowserSessionWrapper {
	
	private static final boolean SIMULATE_INTERMITTENT_FAILURES = false;
	
	private static final Logger log = Logger.getInstance();

	private final Object lock = new Object();
	
	/** Synchronize on me when accessing, and when writing to */
	private final Session session_synch;
	
	private final ClientSessionThread thread;
	
	private boolean disposed_synch_lock = false;
	
//	private LibertyWSClientWorldStateListener listener;
	
	public LibertyClientBrowserSessionWrapper(Session session) {
		if(session == null) { throw new IllegalArgumentException(); }
		
		LibertyClientInstance.getInstance().add(this);
		
		this.session_synch = session;
		thread = new ClientSessionThread();
		thread.start();
	}
	
	public boolean isSessionOpenUnsynchronized() {
		return session_synch.isOpen();		
	}
	
	public void sendMessage(String message) {
		thread.addMessage(message);
	}

//	public void setListener(LibertyWSClientWorldStateListener listener) {
//		this.listener = listener;
//	}
	
//	public LibertyWSClientWorldStateListener getListener() {
//		return listener;
//	}
	
	
	public void dispose() {
		thread.interrupt();
		
		synchronized(lock) {
			
			if(disposed_synch_lock) { return; }
			disposed_synch_lock = true;			
		}
		
		// Dispose of session on a separate thread.
		new Thread() {
			public void run() {
				try {
					session_synch.close();
				} catch (IOException e) {
					/* ignore */
				}
			}
		}.start();
		
		
		thread.dispose();
	}
	
	private class ClientSessionThread extends Thread {
		
		private final List<String> messagesToSend_synch = new ArrayList<>();
		
		private final AtomicBoolean isRunning_synch = new AtomicBoolean(true);
		
		public ClientSessionThread() {
			setDaemon(true);
			setName(ClientSessionThread.class.getName());
		}
		
		@Override
		public void run() {
			
			try {
				innerRun();
			} catch (IOException e) {
				log.err("Error from inner run", e, null);
				e.printStackTrace();
			} catch (InterruptedException e) {
				/* ignore, this is expected on dispose. */
			} finally {
				dispose();
				synchronized (isRunning_synch) {
					isRunning_synch.set(false);
				}
			}
			
		}
		
		@SuppressWarnings("unused")
		private void innerRun() throws IOException, InterruptedException {
			Basic b;
			synchronized(session_synch) {
				b = session_synch.getBasicRemote();
			}
			
			List<String> localMessagesToSend = new ArrayList<>();
			
			while(isSessionOpenUnsynchronized() && !isInterrupted()) {

				if(SIMULATE_INTERMITTENT_FAILURES && Math.random() < 0.1) {
					try {
						session_synch.close(); 
						System.out.println("nuking browser.");
						
					} catch(Exception e) {
						/* ignore */
					}
				}
				
				synchronized (messagesToSend_synch) {
					if(messagesToSend_synch.size() == 0) {
						messagesToSend_synch.wait(1000);
					}
					
					localMessagesToSend.addAll(messagesToSend_synch);
					messagesToSend_synch.clear();
					
				}
				
				synchronized(session_synch) {
					for(String str : localMessagesToSend) {
						b.sendText(str);
//						System.out.println("sending text: "+str);
					}
				}
				
				localMessagesToSend.clear();
			}
		}
		
		public void addMessage(String message) {
			synchronized (isRunning_synch) {
				if(!isRunning_synch.get()) { return; }
			}
			
			synchronized (messagesToSend_synch) {
				messagesToSend_synch.add(message);
				messagesToSend_synch.notify();
			}
		}
		
		
		public void dispose() {
			synchronized(messagesToSend_synch) {
				messagesToSend_synch.clear();
			}
		}
		
	}

	public boolean isDisposed() {
		synchronized (lock) {
			return disposed_synch_lock;
		}
	}
	
}