package freenet;

import java.io.IOException;
import java.util.LinkedList;

import freenet.support.Logger;

/**
 * TrailerWriter for writing a trailer to a multiplexed connection.
 * @author amphibian
 */
public class MuxTrailerWriter implements TrailerWriter {

	private static final int MAX_IDLETIME_BETWEEN_WRITES = 5*60*1000;
	MuxTrailerWriteManager writeManager;
	int written; // >2GB keys? I think not!
	final int id;
	boolean closed;
	boolean terminatedByReceiver = false;
	boolean clientTimeout = false;
	boolean writingPacket;
	boolean ready;
	boolean notifiedReady;
	final Object readinessSync = new Object();
	final boolean fromInsert;
	PeerPacketMessage notifyOnReady = null;
	long lastSentTime;
	/** Queue of messages waiting to be sent to PeerHandler.
	 * Managed by MuxTrailerWriteManager but kept here.
	 */
	final LinkedList messagePreQueue=new LinkedList();
	
	public String toString() {
	    return super.toString()+": id="+id+", written="+written+", closed="+
	    	closed+", writingPacket="+writingPacket+", ready="+ready+
	    	", notifiedReady="+notifiedReady+", preQueueSize="+
	    	messagePreQueue.size();
	}
	
	MuxTrailerWriter(MuxTrailerWriteManager wm, int id, boolean fromInsert) {
		this.writeManager = wm;
		this.id = id;
		written = 0;
		closed = false;
		writingPacket = false;
		ready = false;
		notifiedReady = false;
		this.fromInsert = fromInsert;
		lastSentTime = System.currentTimeMillis();
	}

	public synchronized void writeTrailing(byte[] block, int offset, int length, 
							  TrailerWriteCallback cb) 
		throws AlreadySendingTrailerChunkException,
			IOException {
	    long now = System.currentTimeMillis();
	    if(now - lastSentTime > MAX_IDLETIME_BETWEEN_WRITES) {
	        Core.logger.log(this, "Took more than 5 minutes to get next chunk to send ("+(now-lastSentTime)/1000+" seconds): killing "+this,
	                Logger.NORMAL);
	        close(false, true);
	    }
	    lastSentTime = now;
	    if(closed)
	        throw new IOException(terminatedByReceiver ? "Terminated by receiver": 
	            "Already closed");
		if(writingPacket) {
			throw new AlreadySendingTrailerChunkException();
		}
		writingPacket = true;
		TrailerChunkPacketMessage tcpm =
			new TrailerChunkPacketMessage(this, block, offset, length, cb, written);
		writeManager.write(tcpm);
	}

	public boolean isClosed() {
		return closed;
	}
	
	public boolean wasTerminated() {
	    return terminatedByReceiver;
	}
	
	/**
	 * @return true if we timed out waiting for a writeTrailing().
	 */
	public boolean wasClientTimeout() {
	    return clientTimeout;
	}
	
	public void close() {
	    close(false, false);
	}
	
	public synchronized void close(boolean terminated, boolean clientTimeout) {
	    if(Core.logger.shouldLog(Logger.DEBUG, this))
	        Core.logger.log(this, "Closing "+this+"(terminated="+terminated+
	                ")", new Exception("debug"), Logger.DEBUG);
		writeManager.remove(this);
		closed = true;
		if(terminated)
		    terminatedByReceiver = terminated;
		else if(clientTimeout)
		    this.clientTimeout = clientTimeout; 
	}

	public synchronized void written(int offset, int length) {
		if(Core.logger.shouldLog(Logger.DEBUG, this))
			Core.logger.log(this, "written("+offset+","+length+") on "+this,
					new Exception("debug"), Logger.DEBUG);
		writingPacket = false;
		if(written != offset) {
			Core.logger.log(this, "written("+offset+","+length+") on "+this+
					": written="+written+", offset="+offset+" !", 
					new Exception("debug"), Logger.ERROR);
			return;
		}
		written += length;
	}

	/**
	 * @param msg the message to add to the queue
	 */
	public void queueAddLast(TrailerChunkPacketMessage msg) {
		synchronized(messagePreQueue) {
			messagePreQueue.addLast(msg);
		}
	}

	/**
	 * @return
	 */
	public TrailerChunkPacketMessage removeFirstQueued() {
		synchronized(messagePreQueue) {
			if(messagePreQueue.isEmpty()) return null;
			return (TrailerChunkPacketMessage)(messagePreQueue.removeFirst());
		}
	}

	/**
	 * @param msg
	 */
	public void queueAddFirst(TrailerChunkPacketMessage msg) {
		synchronized(messagePreQueue) {
			messagePreQueue.addFirst(msg);
		}
	}

	/**
	 * @return true if the message queue is empty.
	 */
	public boolean queueEmpty() {
		return messagePreQueue.isEmpty();
	}

    public boolean isExternal() {
        return true;
    }

    /**
     * @return true if this send was generated by an insert.
     */
    public boolean fromInsert() {
        return fromInsert;
    }
}
