package freenet.clients.fcp;

import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;

import freenet.node.Node;
import freenet.support.LogThresholdCallback;
import freenet.support.Logger;
import freenet.support.SimpleFieldSet;
import freenet.support.Logger.LogLevel;
import freenet.support.api.BucketFactory;
import freenet.support.io.PersistentTempBucketFactory;

public abstract class FCPMessage {
	/*
	 * Fields used by FCP messages. These are in TitleCaps by convention.
	 */
	public static final String BUILD = "Build";
	public static final String CODE = "Code";
	public static final String HTL = "HopsToLive";
	public static final String IDENTIFIER = "Identifier";
	public static final String LINK_LENGTHS = "LinkLengths";
	public static final String LOCAL = "Local";
	public static final String LOCATION = "Location";
	public static final String OUTPUT_BANDWIDTH = "OutputBandwidth";
	public static final String PROBE_IDENTIFIER = "ProbeIdentifier";
	public static final String STORE_SIZE = "StoreSize";
	public static final String TYPE = "Type";
	public static final String UPTIME_PERCENT = "UptimePercent";
	public static final String BULK_CHK_REQUEST_REJECTS = "Rejects.Bulk.Request.CHK";
	public static final String BULK_SSK_REQUEST_REJECTS = "Rejects.Bulk.Request.SSK";
	public static final String BULK_CHK_INSERT_REJECTS = "Rejects.Bulk.Insert.CHK";
	public static final String BULK_SSK_INSERT_REJECTS = "Rejects.Bulk.Insert.SSK";
	public static final String OUTPUT_BANDWIDTH_CLASS = "OutputBandwidthClass";
	public static final String OVERALL_BULK_OUTPUT_CAPACITY_USAGE = "OverallBulkOutputCapacityUsage";
	

        private static volatile boolean logDEBUG;
	static {
		Logger.registerLogThresholdCallback(new LogThresholdCallback(){
			@Override
			public void shouldUpdate(){
				logDEBUG = Logger.shouldLog(LogLevel.DEBUG, this);
			}
		});
	}

	public void send(OutputStream os) throws IOException {
		SimpleFieldSet sfs = getFieldSet();
		if(sfs == null) {
			Logger.warning(this, "Not sending message "+this);
			return;
		}
		sfs.setEndMarker(getEndString());
		String msg = sfs.toString();
		os.write((getName()+ '\n').getBytes(StandardCharsets.UTF_8));
		os.write(msg.getBytes(StandardCharsets.UTF_8));
		if(logDEBUG) {
			Logger.debug(this, "Outgoing FCP message:\n"+getName()+'\n'+sfs.toString());
			Logger.debug(this, "Being handled by "+this);
		}
	}

	String getEndString() {
		return "EndMessage";
	}
	
	public abstract SimpleFieldSet getFieldSet();

	public abstract String getName();
	
	/**
	 * Create a message from a SimpleFieldSet, and the message's name, if possible. 
	 */
	public static FCPMessage create(String name, SimpleFieldSet fs, BucketFactory bfTemp, PersistentTempBucketFactory bfPersistent) throws MessageInvalidException {
		if(name.equals(AddPeer.NAME))
			return new AddPeer(fs);
		if(name.equals(ClientGetMessage.NAME))
			return new ClientGetMessage(fs);
		if(name.equals(ClientHelloMessage.NAME))
			return new ClientHelloMessage(fs);
		if(name.equals(ClientPutComplexDirMessage.NAME))
			return new ClientPutComplexDirMessage(fs, bfTemp, bfPersistent);
		if(name.equals(ClientPutDiskDirMessage.NAME))
			return new ClientPutDiskDirMessage(fs);
		if(name.equals(ClientPutMessage.NAME))
			return new ClientPutMessage(fs);
		if(name.equals(SendBookmarkMessage.NAME))
			return new SendBookmarkMessage(fs);
		if(name.equals(SendURIMessage.NAME))
			return new SendURIMessage(fs);
		if(name.equals(SendTextMessage.NAME))
			return new SendTextMessage(fs);
		if(name.equals(DisconnectMessage.NAME))
			return new DisconnectMessage(fs);
		if(name.equals(FCPPluginClientMessage.NAME))
			return new FCPPluginClientMessage(fs);
		if(name.equals(GenerateSSKMessage.NAME))
			return new GenerateSSKMessage(fs);
		if(name.equals(GetConfig.NAME))
			return new GetConfig(fs);
		if(name.equals(GetNode.NAME))
			return new GetNode(fs);
		if(name.equals(GetPluginInfo.NAME))
			return new GetPluginInfo(fs);
		if(name.equals(GetRequestStatusMessage.NAME))
			return new GetRequestStatusMessage(fs);
		if(name.equals(ListPeerMessage.NAME))
			return new ListPeerMessage(fs);
		if(name.equals(ListPeersMessage.NAME))
			return new ListPeersMessage(fs);
		if(name.equals(ListPeerNotesMessage.NAME))
			return new ListPeerNotesMessage(fs);
		if(name.equals(ListPersistentRequestsMessage.NAME))
			return new ListPersistentRequestsMessage(fs);
		if(name.equals(LoadPlugin.NAME))
			return new LoadPlugin(fs);
		if(name.equals(ModifyConfig.NAME))
			return new ModifyConfig(fs);
		if(name.equals(ModifyPeer.NAME))
			return new ModifyPeer(fs);
		if(name.equals(ModifyPeerNote.NAME))
			return new ModifyPeerNote(fs);
		if(name.equals(ModifyPersistentRequest.NAME))
			return new ModifyPersistentRequest(fs);
		if(name.equals(ReloadPlugin.NAME))
			return new ReloadPlugin(fs);
		if(name.equals(RemovePeer.NAME))
			return new RemovePeer(fs);
		if(name.equals(RemovePersistentRequest.NAME)
				|| name.equals(RemovePersistentRequest.ALT_NAME))
			return new RemovePersistentRequest(fs);
		if(name.equals(RemovePlugin.NAME))
			return new RemovePlugin(fs);
		if(name.equals(ShutdownMessage.NAME))
			return new ShutdownMessage();
		if(name.equals(WatchFeedsMessage.NAME))
			return new WatchFeedsMessage(fs);
		if(name.equals(SubscribeUSKMessage.NAME))
			return new SubscribeUSKMessage(fs);
		if(name.equals(WatchFeedsMessage.NAME))
			return new WatchFeedsMessage(fs);
		if(name.equals(UnsubscribeUSKMessage.NAME))
			return new UnsubscribeUSKMessage(fs);
		if(name.equals(TestDDARequestMessage.NAME))
			return new TestDDARequestMessage(fs);
		if(name.equals(TestDDAResponseMessage.NAME))
			return new TestDDAResponseMessage(fs);
		if(name.equals(WatchGlobal.NAME))
			return new WatchGlobal(fs);
		if(name.equals(ProbeRequest.NAME)) return new ProbeRequest(fs);
		if(name.equals(FilterMessage.NAME))
			return new FilterMessage(fs, bfTemp);
		if(name.equals("Void"))
			return null;

		throw new MessageInvalidException(ProtocolErrorMessage.INVALID_MESSAGE, "Unknown message name "+name, null, false);
	}
	
	/**
	 * Create a message from a SimpleFieldSet, and the message's name, if possible. 
	 * Useful for FCPClients
	 */
	public static FCPMessage create(String name, SimpleFieldSet fs) throws MessageInvalidException {
		return FCPMessage.create(name, fs, null, null);
	}

	/**
	 * Returns an FCP message that delegates the methods {@link #getFieldSet()}, {@link #getName()},
	 * and {@link #run(FCPConnectionHandler, Node)} to the given FCP message, adding a
	 * “ListRequestIdentifier” field to the {@link SimpleFieldSet} returned by {@link
	 * #getFieldSet()}.
	 *
	 * @param fcpMessage
	 *         The FCP message to wrap
	 * @param listRequestIdentifier
	 *         The list request identifier to add (may be {@code null} in which case nothing is
	 *         added)
	 * @return The new FCP message
	 */
	public static FCPMessage withListRequestIdentifier(final FCPMessage fcpMessage, final String listRequestIdentifier) {
		if ((listRequestIdentifier == null) || (fcpMessage == null)) {
			return fcpMessage;
		}
		return new FCPMessage() {
			@Override
			public void send(OutputStream os) throws IOException {
				fcpMessage.send(os);
			}

			@Override
			String getEndString() {
				return fcpMessage.getEndString();
			}

			@Override
			public SimpleFieldSet getFieldSet() {
				SimpleFieldSet fieldSet = fcpMessage.getFieldSet();
				fieldSet.putOverwrite("ListRequestIdentifier", listRequestIdentifier);
				return fieldSet;
			}

			@Override
			public String getName() {
				return fcpMessage.getName();
			}

			@Override
			public void run(FCPConnectionHandler handler, Node node) throws MessageInvalidException {
				fcpMessage.run(handler, node);
			}
		};
	}

	/** Do whatever it is that we do with this type of message. 
	 * @throws MessageInvalidException */
	public abstract void run(FCPConnectionHandler handler, Node node) throws MessageInvalidException;

}
