package org.ourfilesystem.com;

/*
 *  
    OurFileSystem is a peer2peer file sharing program.
    Copyright (C) 2012  Robert Gass

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License along
    with this program; if not, write to the Free Software Foundation, Inc.,
    51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.

 */

import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.ConcurrentLinkedQueue;

import org.ourfilesystem.core.CoreComInterface;
import org.ourfilesystem.core.EventInterface;
import org.ourfilesystem.db.DataBaseComInterface;
import org.ourfilesystem.db.Peer;
import org.ourfilesystem.db.Post;
import org.ourfilesystem.security.CryptoComInterface;
import org.ourfilesystem.security.KeySet;
import org.ourfilesystem.security.SecurityTools;
import org.ourfilesystem.security.SignedDigest;
import org.ourfilesystem.utilities.BBytes;
import org.ourfilesystem.utilities.FileUtils;

public class ComPeerImpl implements ComPeerInterface, ConnectionUpdateInterface {
	
	public static int FilePendingPentality = 1;
	
	private OFSConnector Connector;
	private Object PeerId;
	private OFSSocket Socket;
	private boolean AutoSign;
	private boolean Ok;
	private boolean CClosed;
	private CoreComInterface Core;
	private SignatureRequestInterface SignatureRequest;
	private DataBaseComInterface DB;
	private CryptoComInterface Crypt;
	private IncomingThread Incoming;
	private OutgoingThread Outgoing;
	private int RequestsPending;
	private File TempDir;
	private ConcurrentLinkedQueue<PostRequest> PostRequestPending;
	private ConcurrentLinkedQueue<Object> FileRequestPending;
	private LinkedList<EventInterface> Events;
	private Peer ConnectedPeer;
	private long MaxPendingWithNoData = 30L * 60L * 1000L; //30minutes.
	private InputTimer Timer;
	
	public ComPeerImpl(CoreComInterface core, DataBaseComInterface db, CryptoComInterface crypt, SignatureRequestInterface req, Peer p, File td, boolean autosign, OFSSocket sock) {
		init(core, db, crypt, req, p, td, autosign, null, sock);		
	}

	public ComPeerImpl(CoreComInterface core, DataBaseComInterface db, CryptoComInterface crypt, SignatureRequestInterface req, Peer p, File td, boolean autosign, OFSConnector con) {
		init(core, db, crypt, req, p, td, autosign, con, null);		
	}

	public ComPeerImpl(CoreComInterface core, DataBaseComInterface db, CryptoComInterface crypt, SignatureRequestInterface req, Peer p, File td, boolean autosign, OFSConnector con, OFSSocket sock) {
		init(core, db, crypt, req, p, td, autosign, con, sock);		
	}

	private void init(CoreComInterface core, DataBaseComInterface db, CryptoComInterface crypt, SignatureRequestInterface req, Peer p, File td, boolean autosign, OFSConnector con, OFSSocket sock) {
		Events = new LinkedList<EventInterface>();
		AutoSign = autosign;
		CClosed = false;
		Connector = con;
		Socket = sock;
		SignatureRequest = req;
		ConnectedPeer = p;
		if (ConnectedPeer != null) {
			PeerId = ConnectedPeer.getPeerKeysAndIdentity().getSignature().getDigest();
		}
		Core = core;
		Crypt = crypt;
		DB = db;
		Ok = true;
		TempDir = td;
		Timer = new InputTimer();
		Thread t2 = new Thread(Timer);
		t2.start();		
		PostRequestPending = new ConcurrentLinkedQueue<PostRequest>();
		FileRequestPending = new ConcurrentLinkedQueue<Object>();
		Incoming = new IncomingThread(this);
		Outgoing = new OutgoingThread(this);
		HandShakeThread hs = new HandShakeThread(this);
		Thread t = new Thread(hs);
		t.start();
	}
	
	private void startBigThreads() {
		Thread ti = new Thread(Incoming);
		ti.start();
		Thread to = new Thread(Outgoing);
		to.start();
	}
	
	@Override
	public Peer getPeer() {
		return ConnectedPeer;
	}

	@Override
	public boolean isOk() {
		return Ok;
	}

	@Override
	public int getPendingRequests() {
		return RequestsPending;
	}

	@Override
	public void Close() {
		if (!CClosed && PeerId != null) { Core.connectionClosed(PeerId, true); }
		Ok = false;
		CClosed = true;
		try {
			if (Socket != null) {
				Socket.close();
			}
		}
		catch (Exception e) {
		}
		//Poke the timer so it will close
		//and free up the thread space.
		Timer.DataReceived();
		if (Incoming != null) Incoming.Close();
		if (Outgoing != null) Outgoing.Close();
		failPending();
	}
	
	private void failPending() {
		//NOTE: Let's say we cannot connect to any other peers.  All pending transfers will
		//keep getting pushed back to the core where it attempts the connections again and
		//this will loop until we give up.
		try {
			Thread.sleep(500);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		while (PostRequestPending.size() > 0) {
			PostRequest pr = PostRequestPending.poll();
			if (pr != null) {
				Core.requestPostsFailed(PeerId, pr.Id, pr.Start, pr.End);
			}
		}
		while (FileRequestPending.size() > 0) {
			Object dig = FileRequestPending.poll();
			if (dig != null) {
				Core.requestFileFailed(PeerId, dig);
			}
		}
	}

	@Override
	public void requtestPosts(Object peerid, long fromnumber, long tonumber) {
		if (!CClosed) {
			//Poke the timer if nothing is pending, so that if we were close to timing out before
			//we receive the request the timer doesn't think we've been waiting
			//this long for our request and close the conneciton.
			if (getPendingRequests() == 0) {
				Timer.DataReceived();
			}
			addRequest();
			Outgoing.requestPosts(peerid, fromnumber, tonumber);
		}
		else {
			Core.requestPostsFailed(PeerId, peerid, fromnumber, tonumber);
		}
	}

	@Override
	public void requestPeers() {
		if (!CClosed) {
			//Poke the timer if nothing is pending, so that if we were close to timing out before
			//we receive the request the timer doesn't think we've been waiting
			//this long for our request and close the conneciton.
			if (getPendingRequests() == 0) {
				Timer.DataReceived();
			}
			addRequest();
			Outgoing.requestPeers();
		}
	}

	@Override
	public void requestsFile(Object digest) {
		if (!CClosed) {
			//Poke the timer if nothing is pending, so that if we were close to timing out before
			//we receive the request the timer doesn't think we've been waiting
			//this long for our request and close the conneciton.
			if (getPendingRequests() == 0) {
				Timer.DataReceived();
			}
			addFileRequest();
			Outgoing.requestFile(digest);
		}
		else {
			Core.requestFileFailed(PeerId, digest);
		}
	}

	@Override
	public void sendPost(Post post) {
		Outgoing.sendPost(post);
	}
	
	public void doRequtestPosts(Object peerid, long fromnumber, long tonumber) {
		List<Post> list = DB.requestPosts(peerid, fromnumber, tonumber);
		if (list.size() > 0) {
			Outgoing.addPosts(list);
		}
		else {
			Outgoing.addNoPosts(peerid);
		}
	}

	public void doRequestPeers() {
		List<Peer> list = DB.requestPeers();
		Outgoing.addPeers(list);
	}

	public void doRequestsFile(Object digest) {
		File f = DB.requestsFiles(digest);
		if (f != null) {
			Outgoing.addFile(f);
		}
		else {
			Outgoing.addNotFound(digest);
		}
	}
	
	private synchronized void addFileRequest() {
		RequestsPending += FilePendingPentality;
		this.fireUpdate();
	}
	
	private synchronized void popFileRequest() {
		RequestsPending -= FilePendingPentality;
		if (RequestsPending < 0) RequestsPending = 0;
		this.fireUpdate();
	}

	private synchronized void addRequest() {
		RequestsPending++;
		this.fireUpdate();
	}
	
	private synchronized void popRequest() {
		RequestsPending--;
		if (RequestsPending < 0) RequestsPending = 0;
		this.fireUpdate();
	}
	
	private void addPostRequestPending(PostRequest pr) {
		if (pr != null) {
			PostRequestPending.add(pr);
		}
	}
	
	private void addFileRequestPending(Object dig) {
		if (dig != null) {
			FileRequestPending.add(dig);			
		}
	}
	
	private void removePostRequestPending(Object dig) {
		if (dig != null) {
			synchronized (PostRequestPending) {
				Iterator<PostRequest> i = PostRequestPending.iterator();
				while (i.hasNext()) {
					PostRequest pr = i.next();
					if (pr.Id.equals(dig)) {
						i.remove();
					}
				}
			}
		}
	}
	
	private void removeFileRequestPending(Object dig) {
		while (FileRequestPending.remove(dig));
	}
	
	/**
	 * If we wait for input for this long and we've got requests pending then the
	 * connection is probably broken.
	 *
	 */
	class InputTimer implements Runnable {
		
		public synchronized void waitInputProgress() {
			long timestart = (new Date()).getTime();
			try {
				wait(MaxPendingWithNoData);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			long endtime = (new Date()).getTime();
			if ((endtime - timestart) > (MaxPendingWithNoData-10)) {
				if (getPendingRequests() > 0) {
					Close();
				}
			}
		}
		
		public synchronized void DataReceived() {
			notifyAll();
		}

		@Override
		public void run() {
			while (!CClosed) {
				waitInputProgress();
			}
		}
		
	}
	
	class HandShakeThread implements Runnable {
		private ComPeerImpl Com;
		
		public HandShakeThread(ComPeerImpl i) {
			Com = i;
		}
		
		public void run() {
			try {
				/**
				 * If it's an inbound connection the socket will already be created, so we can 
				 * just use it.  Otherwise, we try to make an outbound connection using a proxy
				 * as configured.  If at the end we still don't have a socket then we close
				 * the connection failing any pending requests the connection may have.
				 */
				if (Socket == null) {
					Socket = Connector.connect((String)ConnectedPeer.getLocation());
				}
				if (Socket == null) {
					Close();
				}
				else {
					InputStream is = Socket.getInputStream();
					OutputStream os = Socket.getOutputStream();

					BBytes outchallenge = (BBytes)Com.Crypt.getChallenge();
					FileUtils.writeBytes(os, outchallenge.getBytes());
					os.flush();

					KeySet ks = Com.DB.getMyKeySet();
					BBytes inchallenge = new BBytes(FileUtils.readBytes(is));

					SignedDigest signedchallenge = Com.Crypt.signChallenge(inchallenge, ks);

					Peer mypeer = Com.DB.getMyPeer();
					FileUtils.writePeer(os, mypeer);

					SecurityTools.writeSignedDigest(signedchallenge, os);
					os.flush();

					ConnectedPeer = FileUtils.readPeer(is);
					SignedDigest inchallenge_check = SecurityTools.readSignedDigest(is);

					//-----------------------------------------------------------------
					// Check if this is who we really wanted to connect to if we 
					// initiated this connection
					boolean rightconnection = true;
					if (Com.PeerId != null) {
						if (!Com.PeerId.equals(ConnectedPeer.getPeerKeysAndIdentity().getSignature().getDigest())) {
							rightconnection = false;
							System.out.println("ERROR: We're not connected to who we thought we were!");
							Com.Close();
						}
					}
					else {
						// We did not initiate this connection, so set the id now.
						Com.PeerId = ConnectedPeer.getPeerKeysAndIdentity().getSignature().getDigest();
					}
					//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
					// First process the new peer data.  Note, that it will only be saved
					// if it is a valid signature signed by someone we already trust.
					List<Peer> pl = new LinkedList<Peer>();
					pl.add(ConnectedPeer);
					Com.DB.processPeers(pl);

					//!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
					// First make sure we know this peer.  Get the peer data from the
					// database based on the sent peer data.  Only verify the signature
					// using the key we already have for the peer.
					Peer check_peer = Com.DB.getPeer(ConnectedPeer.getPeerKeysAndIdentity().getSignature().getDigest());
					if (check_peer == null) {
						if (!AutoSign) {
							if (!DB.isBadPeer(ConnectedPeer.getPeerKeysAndIdentity().getSignature().getDigest())) {
								SignatureRequest.SignatureRequest(ConnectedPeer);
							}
							Com.Close();
						}
						else {
							Core.signPeer(ConnectedPeer);
							check_peer = ConnectedPeer;
						}
					}
					if (check_peer != null && rightconnection) {
						//Check if the signature is correct.
						if (Com.Crypt.verifyChallenge(outchallenge, inchallenge_check, check_peer)) {
							//Now we still have to properly process the sent peer data, but it should be correct.
							List<Peer> uplist = new LinkedList<Peer>();
							uplist.add(ConnectedPeer);
							Com.DB.processPeers(uplist);
							//Now start the business threads.
							Com.startBigThreads();
							Core.newConnection(Com);
						}
						else {
							System.out.println("ERROR: Invalid authentication signature!");
							Com.Close();
						}					
					}
				}
			} catch (IOException e) {
				e.printStackTrace();
				Com.Close();
			}
		}
	}
	
	class IncomingThread implements Runnable {
		private ComPeerImpl Com;
		private boolean Ok;
		private boolean Closed;
		
		public IncomingThread(ComPeerImpl i) {
			Com = i;
			Ok = true;
			Closed = false;
		}
		
		public void Close() {
			if (!Closed) {
				Closed = true;
				Com.Close();
			}
		}
		
		@Override
		public void run() {
			try {
				InputStream is = Socket.getInputStream();
				while (Ok && Socket.isConnected() && (!Socket.isClosed()) && (!Closed)) {
					int v = is.read();
					if (v == REQUEST_PEERS) {
						addRequest();
						Com.doRequestPeers();
					}
					else if (v == REQUEST_POSTS) {
						addRequest();
						BBytes id = new BBytes(FileUtils.readBytes(is));
						long start = FileUtils.readLong(is);
						long end = FileUtils.readLong(is);
						Com.doRequtestPosts(id, start, end);
					}
					else if (v == REQUEST_FILES) {
						addFileRequest();
						BBytes id = new BBytes(FileUtils.readBytes(is));
						Com.doRequestsFile(id);
					}
					else if (v == SEND_POSTS) {
						long numposts = FileUtils.readLong(is);
						List<Post> list = new LinkedList<Post>();
						Post p = null;
						for (int cnt = 0; cnt < numposts; cnt++) {
							p = FileUtils.readPost(is, TempDir);
							list.add(p);
						}
						Com.DB.processPeerPosts(list);
						if (p != null) {
							removePostRequestPending(p.getSignedDigest().getPeerIdentifier());
							Core.requestPostSucceed(Com, p.getSignedDigest().getPeerIdentifier());
						}
						Com.popRequest();
					}
					else if (v == SEND_NO_POSTS) {
						BBytes db = new BBytes(FileUtils.readBytes(is));
						removePostRequestPending(db);
						Core.requestPostSucceed(Com, db);
						Com.popRequest();
					}
					else if (v == SEND_PEERS) {
						long numpeers = FileUtils.readLong(is);
						List<Peer> list = new LinkedList<Peer>();
						for (int cnt = 0; cnt < numpeers; cnt++) {
							Peer p = FileUtils.readPeer(is);
							list.add(p);
						}
						Com.DB.processPeers(list);
						Com.popRequest();
					}
					else if (v == SEND_FILES) {
						File f = File.createTempFile("datafile", ".dat", TempDir);
						FileUtils.readFile(is, f);
						Object dig = Com.DB.processPeerFile(f);
						removeFileRequestPending(dig);
						Core.requestFileSucceed(Com, dig);
						Com.popFileRequest();
					}
					else if (v == PUSH_POST) {
						Post p = FileUtils.readPost(is, TempDir);
						List<Post> l = new LinkedList<Post>();
						l.add(p);
						Com.DB.processPeerPosts(l);
					}
					else if (v == SEND_FILE_NOT_FOUND) {
						BBytes db = new BBytes(FileUtils.readBytes(is));
						Core.requestFileFailed(PeerId, db);
						Com.popFileRequest();
					}
					else {
						System.out.println("Unexpected command: " + v);
						Close();
					}
					Timer.DataReceived();
				}
			}
			catch (Exception e) {
				e.printStackTrace();
				Close();
			}
		}
	}
	
	class PostRequest {
		public Object Id;
		public long Start;
		public long End;
		public boolean equals(Object obj) {
			if (obj == null) return false;
			if (!(obj instanceof PostRequest)) {
				return false;
			}
			PostRequest pr = (PostRequest)obj;
			if (Id == null) return false;
			if (pr.Id == null) return false;
			return (Id.equals(pr.Id));
		}
		public int hashCode() {
			return Id.hashCode();
		}
	}
	
	private static int REQUEST_POSTS = 0x01;
	private static int REQUEST_PEERS = 0x02;
	private static int REQUEST_FILES = 0x03;
	private static int SEND_POSTS = 0x04;
	private static int SEND_PEERS = 0x05;
	private static int SEND_FILES = 0x06;
	private static int PUSH_POST = 0x07;
	private static int SEND_FILE_NOT_FOUND = 0x08;
	private static int SEND_NO_POSTS = 0x09;
	
	class OutgoingThread implements Runnable {
		private ComPeerImpl Com;
		private boolean Ok;
		private boolean Closed;
		private boolean RequestPeers;
		private ConcurrentLinkedQueue<PostRequest> PostRequestQueue;
		private ConcurrentLinkedQueue<Object> FileRequestQueue;
		private ConcurrentLinkedQueue<List<Peer>> PeerQueue;
		private ConcurrentLinkedQueue<List<Post>> PostQueue;
		private ConcurrentLinkedQueue<File> FileQueue;
		private ConcurrentLinkedQueue<Post> OutPostQueue;
		private ConcurrentLinkedQueue<Object> NotFoundQueue;
		private ConcurrentLinkedQueue<Object> NoPostsQueue;
		
		public OutgoingThread(ComPeerImpl i) {
			Com = i;
			Ok = true;
			Closed = false;
			RequestPeers = false;
			PeerQueue = new ConcurrentLinkedQueue<List<Peer>>();
			PostQueue = new ConcurrentLinkedQueue<List<Post>>();
			FileQueue = new ConcurrentLinkedQueue<File>();
			PostRequestQueue = new ConcurrentLinkedQueue<PostRequest>();
			FileRequestQueue = new ConcurrentLinkedQueue<Object>();
			OutPostQueue = new ConcurrentLinkedQueue<Post>();
			NotFoundQueue = new ConcurrentLinkedQueue<Object>();
			NoPostsQueue = new ConcurrentLinkedQueue<Object>();
		}
		
		public synchronized void requestFile(Object id ) {
			FileRequestQueue.add(id);
			notifyAll();
		}
		
		public synchronized void requestPosts(Object id, long start, long end) {
			PostRequest pr = new PostRequest();
			pr.Id = id;
			pr.Start = start;
			pr.End = end;
			PostRequestQueue.add(pr);
			notifyAll();
		}
		
		public synchronized void addNoPosts(Object peerid) {
			NoPostsQueue.add(peerid);
			notifyAll();
		}
		
		public synchronized void addNotFound(Object dig) {
			NotFoundQueue.add(dig);
			notifyAll();
		}
		
		public synchronized void sendPost(Post p) {
			OutPostQueue.add(p);
			notifyAll();
		}
		
		public synchronized void requestPeers() {
			RequestPeers = true;
			notifyAll();
		}
		
		public synchronized void addPeers(List<Peer> p) {
			PeerQueue.add(p);
			notifyAll();
		}
		
		public synchronized void addPosts(List<Post> p) {
			PostQueue.add(p);
			notifyAll();
		}
		
		public synchronized void addFile(File f) {
			FileQueue.add(f);
			notifyAll();
		}
		
		public void Close() {
			if (!Closed) {
				Closed = true;
				Com.Close();
			}
			while (PostRequestQueue.size() > 0) {
				PostRequest pr = PostRequestQueue.poll();
				if (pr != null) {
					Com.Core.requestPostsFailed(Com.PeerId, pr.Id, pr.Start, pr.End);
				}
			}
			while (FileRequestQueue.size() > 0) {
				Object dig = FileRequestQueue.poll();
				if (dig != null) {
					Com.Core.requestFileFailed(Com.PeerId, dig);
				}
			}
		}
		
		private synchronized void WaitForData() {
			if (RequestPeers) return;
			if (PeerQueue.size() > 0) return;
			if (PostQueue.size() > 0) return;
			if (FileQueue.size() > 0) return;
			if (PostRequestQueue.size() > 0) return;
			if (FileRequestQueue.size() > 0) return;
			if (OutPostQueue.size() > 0) return;
			if (NotFoundQueue.size() > 0) return;
			if (NoPostsQueue.size() > 0) return;
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		
		@Override
		public void run() {
			try {		
				OutputStream os = Socket.getOutputStream();
				while (Ok && Socket.isConnected() && (!Socket.isClosed()) && (!Closed)) {
					WaitForData();
					if (RequestPeers) {
						RequestPeers = false;
						os.write(REQUEST_PEERS);
					}
					PostRequest pr = PostRequestQueue.poll();
					addPostRequestPending(pr);
					if (pr != null) {
						os.write(REQUEST_POSTS);
						BBytes dig = (BBytes)pr.Id;
						FileUtils.writeBytes(os, dig.getBytes());
						FileUtils.writeLong(pr.Start, os);
						FileUtils.writeLong(pr.End, os);
					}
					Object dig = FileRequestQueue.poll();
					addFileRequestPending(dig);
					if (dig != null) {
						os.write(REQUEST_FILES);
						BBytes b = (BBytes)dig;
						FileUtils.writeBytes(os, b.getBytes());
					}
					Object nfd = NotFoundQueue.poll();
					if (nfd != null) {
						BBytes nfb = (BBytes)nfd;
						os.write(SEND_FILE_NOT_FOUND);
						FileUtils.writeBytes(os, nfb.getBytes());
						popFileRequest();
					}
					Object np = NoPostsQueue.poll();
					if (np != null) {
						BBytes nfb = (BBytes)np;
						os.write(SEND_NO_POSTS);
						FileUtils.writeBytes(os, nfb.getBytes());
						popRequest();
					}
					Post pp = OutPostQueue.poll();
					if (pp != null) {
						os.write(PUSH_POST);
						FileUtils.writePost(os, pp);						
					}
					List<Peer> peerlist = PeerQueue.poll();
					if (peerlist != null) {
						os.write(SEND_PEERS);
						int sendsize = peerlist.size();
						int sentsize = 0;
						FileUtils.writeLong(sendsize, os);
						Iterator<Peer> i = peerlist.iterator();
						while (i.hasNext() && sentsize < sendsize) {
							Peer peer = i.next();
							FileUtils.writePeer(os, peer);
							sentsize++;
						}
						if (sentsize != sendsize) {
							Close();
						}
						popRequest();
					}
					List<Post> postlist = PostQueue.poll();
					if (postlist != null) {
						os.write(SEND_POSTS);
						int sendsize = postlist.size();
						int sentsize = 0;
						FileUtils.writeLong(sendsize, os);
						Iterator<Post> i = postlist.iterator();
						while (i.hasNext() && sentsize < sendsize) {
							Post post = i.next();
							FileUtils.writePost(os, post);
							sentsize++;
						}
						if (sentsize != sendsize) {
							Close();
						}
						popRequest();
					}
					File sf = FileQueue.poll();
					if (sf != null) {
						os.write(SEND_FILES);
						FileUtils.sendFile(os, sf);
						popFileRequest();
					}
				}
			}
			catch (Exception e) {
				e.printStackTrace();
				Close();
			}
		}
	}

	public long getMaxPendingWithNoData() {
		return MaxPendingWithNoData;
	}

	/**
	 * This sets how long we're willing to wait for a connection to receive data.
	 * There could be a case where the connection does not close, but no data is being
	 * sent in response to requests.  This will ensure that we'll fail this connection
	 * properly if we've been waiting too long, so that another connection can be tried.
	 * @param maxPendingWithNoData
	 */
	public void setMaxPendingWithNoData(long maxPendingWithNoData) {
		MaxPendingWithNoData = maxPendingWithNoData;
	}
	
	private void fireUpdate() {
		LinkedList<EventInterface> rl = new LinkedList<EventInterface>();
		synchronized (Events) {
			rl.addAll(Events);
		}
		Iterator<EventInterface> i = rl.iterator();
		while (i.hasNext()) {
			i.next().connectionEvent(this);
		}
	}

	@Override
	public void addEventInterface(EventInterface ev) {
		synchronized (Events) {
			Events.add(ev);
		}
	}
	
	public boolean isAutoSign() {
		return AutoSign;
	}

	public void setAutoSign(boolean autoSign) {
		AutoSign = autoSign;
	}

	
}
