package freenet.clients.http;

import java.io.IOException;
import java.net.URI;
import java.util.Comparator;
import java.util.HashMap;

import freenet.client.HighLevelSimpleClient;
import freenet.config.ConfigException;
import freenet.l10n.NodeL10n;
import freenet.node.DarknetPeerNode;
import freenet.node.DarknetPeerNode.FRIEND_TRUST;
import freenet.node.DarknetPeerNode.FRIEND_VISIBILITY;
import freenet.node.DarknetPeerNodeStatus;
import freenet.node.Node;
import freenet.node.NodeClientCore;
import freenet.node.PeerManager;
import freenet.node.PeerNodeStatus;
import freenet.support.HTMLNode;
import freenet.support.Logger;
import freenet.support.MultiValueTable;
import freenet.support.SimpleFieldSet;
import freenet.support.api.HTTPRequest;
import freenet.support.io.FileUtil;

public class DarknetConnectionsToadlet extends ConnectionsToadlet {
	
	DarknetConnectionsToadlet(Node n, NodeClientCore core, HighLevelSimpleClient client) {
		super(n, core, client);
	}

	private static String l10n(String string) {
		return NodeL10n.getBase().getString("DarknetConnectionsToadlet."+string);
	}
	
	protected class DarknetComparator extends ComparatorByStatus {

		DarknetComparator(String sortBy, boolean reversed) {
			super(sortBy, reversed);
		}
	
		@Override
		protected int customCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode, String sortBy) {
			switch (sortBy) {
				case "name":
					return ((DarknetPeerNodeStatus) firstNode).getName().compareToIgnoreCase(((DarknetPeerNodeStatus) secondNode).getName());
				case "privnote":
					return ((DarknetPeerNodeStatus) firstNode).getPrivateDarknetCommentNote().compareToIgnoreCase(((DarknetPeerNodeStatus) secondNode).getPrivateDarknetCommentNote());
				case "trust":
					return ((DarknetPeerNodeStatus) firstNode).getTrustLevel().compareTo(((DarknetPeerNodeStatus) secondNode).getTrustLevel());
				case "visibility":
					int ret = ((DarknetPeerNodeStatus) firstNode).getOurVisibility().compareTo(((DarknetPeerNodeStatus) secondNode).getOurVisibility());
					if (ret != 0) return ret;
					return ((DarknetPeerNodeStatus) firstNode).getTheirVisibility().compareTo(((DarknetPeerNodeStatus) secondNode).getTheirVisibility());
				default:
					return super.customCompare(firstNode, secondNode, sortBy);
			}
		}
		
		/** Default comparison, after taking into account status */
		@Override
		protected int lastResortCompare(PeerNodeStatus firstNode, PeerNodeStatus secondNode) {
			return ((DarknetPeerNodeStatus)firstNode).getName().compareToIgnoreCase(((DarknetPeerNodeStatus)secondNode).getName());
		}

	}
	
	@Override
	protected Comparator<PeerNodeStatus> comparator(String sortBy, boolean reversed) {
		return new DarknetComparator(sortBy, reversed);
	}
		
	@Override
	protected boolean hasNameColumn() {
		return true;
	}
	
	@Override
	protected void drawNameColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advanced) {
		// name column
		HTMLNode cell = peerRow.addChild("td", "class", "peer-name");
		cell.addChild("a", "href", "/send_n2ntm/?peernode_hashcode=" + peerNodeStatus.hashCode(), ((DarknetPeerNodeStatus)peerNodeStatus).getName());
		if(advanced && peerNodeStatus.hasFullNoderef) {
			cell.addChild("#", " (");
			cell.addChild("a", "href", path()+"friend-"+peerNodeStatus.hashCode()+".fref", l10n("noderefLink"));
			cell.addChild("#", ")");
		}
	}
	
	@Override
	protected boolean hasTrustColumn() {
		return true;
	}

	@Override
	protected void drawTrustColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus) {
		peerRow.addChild("td", "class", "peer-trust").addChild("#", ((DarknetPeerNodeStatus)peerNodeStatus).getTrustLevel().name());
	}

	@Override
	protected boolean hasVisibilityColumn() {
		return true;
	}

	@Override
	protected void drawVisibilityColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean advancedModeEnabled) {
		String content = ((DarknetPeerNodeStatus)peerNodeStatus).getOurVisibility().name();
		if(advancedModeEnabled)
			content += " ("+((DarknetPeerNodeStatus)peerNodeStatus).getTheirVisibility().name()+")";
		peerRow.addChild("td", "class", "peer-trust").addChild("#", content);
	}

	@Override
	protected boolean hasPrivateNoteColumn() {
		return true;
	}

	@Override
	protected void drawPrivateNoteColumn(HTMLNode peerRow, PeerNodeStatus peerNodeStatus, boolean fProxyJavascriptEnabled) {
		// private darknet node comment note column
		DarknetPeerNodeStatus status = (DarknetPeerNodeStatus) peerNodeStatus;
		if(fProxyJavascriptEnabled) {
			peerRow.addChild("td", "class", "peer-private-darknet-comment-note").addChild("input", new String[] { "type", "name", "size", "maxlength", "onChange", "value" }, new String[] { "text", "peerPrivateNote_" + peerNodeStatus.hashCode(), "16", "250", "peerNoteChange();", status.getPrivateDarknetCommentNote() });
		} else {
			peerRow.addChild("td", "class", "peer-private-darknet-comment-note").addChild("input", new String[] { "type", "name", "size", "maxlength", "value" }, new String[] { "text", "peerPrivateNote_" + peerNodeStatus.hashCode(), "16", "250", status.getPrivateDarknetCommentNote() });
		}
	}

	@Override
	protected SimpleFieldSet getNoderef() {
		return node.exportDarknetPublicFieldSet();
	}

	@Override
	protected PeerNodeStatus[] getPeerNodeStatuses(boolean noHeavy) {
		return node.getPeers().getDarknetPeerNodeStatuses(noHeavy);
	}

	@Override
	protected String getPageTitle(String titleCountString) {
		return NodeL10n.getBase().getString("DarknetConnectionsToadlet.fullTitle",
				new String[] {"counts"}, new String[] {titleCountString} );
	}
	
	@Override
	protected boolean shouldDrawNoderefBox(boolean advancedModeEnabled) {
		return advancedModeEnabled; // Convenient for advanced users, but normally we will use the "Add a friend" box.
	}

	@Override
	protected boolean showPeerActionsBox() {
		return true;
	}

	@Override
	protected void drawPeerActionSelectBox(HTMLNode peerForm, boolean advancedModeEnabled) {
		peerForm.addChild("input",
			 new String[] { "type", "name", "value" },
			 new String[] { "submit", "doSendMessageToPeers", l10n("sendConfidentialMessage") });
		peerForm.addChild("br");

		HTMLNode actionSelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "action", "action" });
		actionSelect.addChild("option", "value", "", l10n("selectAction"));
		actionSelect.addChild("option", "value", "update_notes", l10n("updateChangedPrivnotes"));
		if(advancedModeEnabled) {
			actionSelect.addChild("option", "value", "enable", l10n("peersEnable"));
			actionSelect.addChild("option", "value", "disable", l10n("peersDisable"));
			actionSelect.addChild("option", "value", "set_burst_only", l10n("peersSetBurstOnly"));
			actionSelect.addChild("option", "value", "clear_burst_only", l10n("peersClearBurstOnly"));
			actionSelect.addChild("option", "value", "set_listen_only", l10n("peersSetListenOnly"));
			actionSelect.addChild("option", "value", "clear_listen_only", l10n("peersClearListenOnly"));
			actionSelect.addChild("option", "value", "set_allow_local", l10n("peersSetAllowLocal"));
			actionSelect.addChild("option", "value", "clear_allow_local", l10n("peersClearAllowLocal"));
			actionSelect.addChild("option", "value", "set_ignore_source_port", l10n("peersSetIgnoreSourcePort"));
			actionSelect.addChild("option", "value", "clear_ignore_source_port", l10n("peersClearIgnoreSourcePort"));
			actionSelect.addChild("option", "value", "set_dont_route", l10n("peersSetDontRoute"));
			actionSelect.addChild("option", "value", "clear_dont_route", l10n("peersClearDontRoute"));
		}
		actionSelect.addChild("option", "value", "", l10n("separator"));
		actionSelect.addChild("option", "value", "remove", l10n("removePeers"));
		peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doAction", l10n("go") });
		peerForm.addChild("br");
		peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doChangeTrust", l10n("changeTrustButton") });
		HTMLNode changeTrustLevelSelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "changeTrust", "changeTrust" });
		for(FRIEND_TRUST trust : FRIEND_TRUST.valuesBackwards()) {
			changeTrustLevelSelect.addChild("option", "value", trust.name(), l10n("peerTrust."+trust.name()));
		}
		peerForm.addChild("br");
		peerForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "doChangeVisibility", l10n("changeVisibilityButton") });
		HTMLNode changeVisibilitySelect = peerForm.addChild("select", new String[] { "id", "name" }, new String[] { "changeVisibility", "changeVisibility" });
		for(FRIEND_VISIBILITY trust : FRIEND_VISIBILITY.values()) {
			changeVisibilitySelect.addChild("option", "value", trust.name(), l10n("peerVisibility."+trust.name()));
		}
	}

	@Override
	protected String getPeerListTitle() {
		return l10n("myFriends");
	}

	@Override
	protected boolean acceptRefPosts() {
		return true;
	}

	@Override
	protected String defaultRedirectLocation() {
		return "/friends/"; // FIXME
	}

	/**
	 * Implement other post actions than adding nodes.
	 * @throws IOException 
	 * @throws ToadletContextClosedException 
	 * @throws RedirectException 
	 */
	@Override
	protected void handleAltPost(URI uri, HTTPRequest request, ToadletContext ctx, boolean logMINOR) throws ToadletContextClosedException, IOException, RedirectException {
		if (request.isPartSet("doSendMessageToPeers")) {
			PageNode page = ctx.getPageMaker().getPageNode(l10n("sendMessageTitle"), ctx);
			HTMLNode pageNode = page.outer;
			HTMLNode contentNode = page.content;
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			HashMap<String, String> peers = new HashMap<String, String>();
			for(DarknetPeerNode pn : peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					String peer_name = pn.getName();
					String peer_hash = String.valueOf(pn.hashCode());
					if(!peers.containsKey(peer_hash)) {
						peers.put(peer_hash, peer_name);
					}
				}
			}
			N2NTMToadlet.createN2NTMSendForm( pageNode, ctx.isAdvancedModeEnabled(), contentNode, ctx, peers);
			writeHTMLReply(ctx, 200, "OK", pageNode.generate());
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("update_notes")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("peerPrivateNote_"+pn.hashCode())) {
					if(!request.getPartAsStringFailsafe("peerPrivateNote_"+pn.hashCode(),250).equals(pn.getPrivateDarknetCommentNote())) {
						pn.setPrivateDarknetCommentNote(request.getPartAsStringFailsafe("peerPrivateNote_"+pn.hashCode(),250));
					}
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("enable")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.enablePeer();
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("disable")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.disablePeer();
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_burst_only")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setBurstOnly(true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_burst_only")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setBurstOnly(false);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_ignore_source_port")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setIgnoreSourcePort(true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_ignore_source_port")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setIgnoreSourcePort(false);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_dont_route")) {
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setRoutingStatus(true, true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_dont_route")) {
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if(request.isPartSet("node_" + pn.hashCode())) {
					pn.setRoutingStatus(false, true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_listen_only")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setListenOnly(true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_listen_only")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setListenOnly(false);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("set_allow_local")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setAllowLocalAddresses(true);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("clear_allow_local")) {
			//int hashcode = Integer.decode(request.getParam("node")).intValue();
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					pn.setAllowLocalAddresses(false);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("changeTrust") && request.isPartSet("doChangeTrust")) {
			FRIEND_TRUST trust = FRIEND_TRUST.valueOf(request.getPartAsStringFailsafe("changeTrust", 10));
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {	
					pn.setTrustLevel(trust);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("changeVisibility") && request.isPartSet("doChangeVisibility")) {
			FRIEND_VISIBILITY trust = FRIEND_VISIBILITY.valueOf(request.getPartAsStringFailsafe("changeVisibility", 10));
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {	
					pn.setVisibility(trust);
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("remove") || (request.isPartSet("doAction") && request.getPartAsStringFailsafe("action",25).equals("remove"))) {			
			if(logMINOR) Logger.minor(this, "Remove node");
			
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {	
					if((pn.timeLastConnectionCompleted() < (System.currentTimeMillis() - 1000*60*60*24*7) /* one week */) ||  (pn.peerNodeStatus == PeerManager.PEER_NODE_STATUS_NEVER_CONNECTED) || request.isPartSet("forceit")){
						this.node.removePeerConnection(pn);
						if(logMINOR) Logger.minor(this, "Removed node: node_"+pn.hashCode());
					}else{
						if(logMINOR) Logger.minor(this, "Refusing to remove : node_"+pn.hashCode()+" (trying to prevent network churn) : let's display the warning message.");
						PageNode page = ctx.getPageMaker().getPageNode(l10n("confirmRemoveNodeTitle"), ctx);
						HTMLNode pageNode = page.outer;
						HTMLNode contentNode = page.content;
						HTMLNode content =ctx.getPageMaker().getInfobox("infobox-warning", l10n("confirmRemoveNodeWarningTitle"), contentNode, "darknet-remove-node", true);
						content.addChild("p").addChild("#",
								NodeL10n.getBase().getString("DarknetConnectionsToadlet.confirmRemoveNode", new String[] { "name" }, new String[] { pn.getName() }));
						HTMLNode removeForm = ctx.addFormChild(content, "/friends/", "removeConfirmForm");
						removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "node_"+pn.hashCode(), "remove" });
						removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "cancel", NodeL10n.getBase().getString("Toadlet.cancel") });
						removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "submit", "remove", l10n("remove") });
						removeForm.addChild("input", new String[] { "type", "name", "value" }, new String[] { "hidden", "forceit", l10n("forceRemove") });

						writeHTMLReply(ctx, 200, "OK", pageNode.generate());
						return; // FIXME: maybe it breaks multi-node removing
					}				
				} else {
					if(logMINOR) Logger.minor(this, "Part not set: node_"+pn.hashCode());
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("acceptTransfer")) {
			// FIXME this is ugly, should probably move both this code and the PeerNode code somewhere.
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					long id = Long.parseLong(request.getPartAsStringFailsafe("id", 32)); // FIXME handle NumberFormatException
					pn.acceptTransfer(id);
					break;
				}
			}
			redirectHere(ctx);
			return;
		} else if (request.isPartSet("rejectTransfer")) {
			// FIXME this is ugly, should probably move both this code and the PeerNode code somewhere.
			DarknetPeerNode[] peerNodes = node.getDarknetConnections();
			for(DarknetPeerNode pn: peerNodes) {
				if (request.isPartSet("node_"+pn.hashCode())) {
					long id = Long.parseLong(request.getPartAsStringFailsafe("id", 32)); // FIXME handle NumberFormatException
					pn.rejectTransfer(id);
					break;
				}
			}
			redirectHere(ctx);
			return;
		} else {
			this.handleMethodGET(uri, new HTTPRequestImpl(uri, "GET"), ctx);
		}
	}

	private void redirectHere(ToadletContext ctx) throws ToadletContextClosedException, IOException {
		MultiValueTable<String, String> headers = MultiValueTable.from("Location", "/friends/");
		ctx.sendReplyHeaders(302, "Found", headers, null, 0);
	}

	@Override
	protected boolean isOpennet() {
		return false;
	}

	@Override
	SimpleColumn[] endColumnHeaders(boolean advancedMode) {
		return null;
	}

	@Override
	public String path() {
		return "/friends/";
	}

	@Override
	public void handleMethodGET(URI uri, HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException, RedirectException {
		if(tryHandlePeerNoderef(uri, request, ctx)) return;
		super.handleMethodGET(uri, request, ctx);
	}

	private boolean tryHandlePeerNoderef(URI uri, HTTPRequest request,
			ToadletContext ctx) throws ToadletContextClosedException, IOException {
		String path = uri.getPath();
		if(path.endsWith(".fref") && path.startsWith(path()+"friend-")) {
			// Get noderef for a peer
			String input_hashcode_string = path.substring((path()+"friend-").length());
			input_hashcode_string = input_hashcode_string.substring(0, input_hashcode_string.length() - ".fref".length());
			int input_hashcode;
			try {
				input_hashcode = Integer.parseInt(input_hashcode_string);
			} catch (NumberFormatException e) {
				// ignore here, handle below
				return false;
			}
			String peernode_name = null;
			SimpleFieldSet fs = null;
			if (input_hashcode != -1) {
				DarknetPeerNode[] peerNodes = node.getDarknetConnections();
				for(DarknetPeerNode pn: peerNodes) {
					int peer_hashcode = pn.hashCode();
					if (peer_hashcode == input_hashcode) {
						peernode_name = pn.getName();
						fs = pn.getFullNoderef();
						break;
					}
				}
			}
			
			if(fs == null) return false;
			String filename = FileUtil.sanitizeFileNameWithExtras(peernode_name+".fref", "\" ");
			String content = fs.toString();
			MultiValueTable<String, String> extraHeaders = MultiValueTable.from(
				// Force download to disk
				"Content-Disposition", "attachment; filename="+filename
			);
			this.writeReply(ctx, 200, "application/x-freenet-reference", "OK", extraHeaders, content);
			return true;
		} else return false;
	}

	@Override
	public void handleMethodPOST(URI uri, HTTPRequest request, ToadletContext ctx)
			throws ToadletContextClosedException, IOException, RedirectException, ConfigException {
		super.handleMethodPOST(uri, request, ctx);
	}

}
