/* Copyright 2007 Freenet Project Inc.
 * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
 */
package freenet.clients.http;

import java.io.IOException;
import java.net.URI;

import freenet.client.HighLevelSimpleClient;
import freenet.io.AddressTracker;
import freenet.io.AddressTrackerItem;
import freenet.io.InetAddressAddressTrackerItem;
import freenet.io.PeerAddressTrackerItem;
import freenet.io.AddressTrackerItem.Gap;
import freenet.io.comm.UdpSocketHandler;
import freenet.l10n.NodeL10n;
import freenet.node.FSParseException;
import freenet.node.Node;
import freenet.support.HTMLNode;
import freenet.support.SimpleFieldSet;
import freenet.support.TimeUtil;
import freenet.support.api.HTTPRequest;

/**
 * Toadlet displaying information on the node's connectivity status.
 * Eventually this will include all information gathered by the node on its
 * connectivity from plugins, local IP detection, packet monitoring etc.
 * For the moment it's just a dump of the AddressTracker.
 * @author toad
 */
public class ConnectivityToadlet extends Toadlet {
	
	private final Node node;

	protected ConnectivityToadlet(HighLevelSimpleClient client, Node node) {
		super(client);
		this.node = node;
	}

	public void handleMethodGET(URI uri, final HTTPRequest request, ToadletContext ctx) throws ToadletContextClosedException, IOException {
		PageMaker pageMaker = ctx.getPageMaker();
		
		PageNode page = pageMaker.getPageNode(NodeL10n.getBase().getString("ConnectivityToadlet.title"), ctx);
		HTMLNode pageNode = page.outer;
		HTMLNode contentNode = page.content;

		/* add alert summary box */
		if(ctx.isAllowedFullAccess())
			contentNode.addChild(ctx.getAlertManager().createSummary());

		// our ports
		HTMLNode portInfobox = contentNode.addChild("div", "class", "infobox infobox-normal");
		portInfobox.addChild("div", "class", "infobox-header", l10nConn("nodePortsTitle"));
		HTMLNode portInfoboxContent = portInfobox.addChild("div", "class", "infobox-content");
		HTMLNode portInfoList = portInfoboxContent.addChild("ul");
		SimpleFieldSet fproxyConfig = node.config.get("fproxy").exportFieldSet(true);
		SimpleFieldSet fcpConfig = node.config.get("fcp").exportFieldSet(true);
		SimpleFieldSet tmciConfig = node.config.get("console").exportFieldSet(true);
		portInfoList.addChild("li", NodeL10n.getBase().getString("DarknetConnectionsToadlet.darknetFnpPort", new String[] { "port" }, new String[] { Integer.toString(node.getFNPPort()) }));
		int opennetPort = node.getOpennetFNPPort();
		if(opennetPort > 0)
			portInfoList.addChild("li", NodeL10n.getBase().getString("DarknetConnectionsToadlet.opennetFnpPort", new String[] { "port" }, new String[] { Integer.toString(opennetPort) }));
		try {
			if(fproxyConfig.getBoolean("enabled", false)) {
				portInfoList.addChild("li", NodeL10n.getBase().getString("DarknetConnectionsToadlet.fproxyPort", new String[] { "port" }, new String[] { Integer.toString(fproxyConfig.getInt("port")) }));
			} else {
				portInfoList.addChild("li", l10nConn("fproxyDisabled"));
			}
			if(fcpConfig.getBoolean("enabled", false)) {
				portInfoList.addChild("li", NodeL10n.getBase().getString("DarknetConnectionsToadlet.fcpPort", new String[] { "port" }, new String[] { Integer.toString(fcpConfig.getInt("port")) }));
			} else {
				portInfoList.addChild("li", l10nConn("fcpDisabled"));
			}
			if(tmciConfig.getBoolean("enabled", false)) {
				portInfoList.addChild("li", NodeL10n.getBase().getString("DarknetConnectionsToadlet.tmciPort", new String[] { "port" }, new String[] { Integer.toString(tmciConfig.getInt("port")) }));
			} else {
				portInfoList.addChild("li", l10nConn("tmciDisabled"));
			}
		} catch (FSParseException e) {
			// ignore
		}
		
		// Add connection type box.
		
		node.ipDetector.addConnectionTypeBox(contentNode);
		
		UdpSocketHandler[] handlers = node.getPacketSocketHandlers();
		
		HTMLNode summaryContent = pageMaker.getInfobox("#", NodeL10n.getBase().getString("ConnectivityToadlet.summaryTitle"), contentNode, "connectivity-summary", true);
		
		HTMLNode table = summaryContent.addChild("table", "border", "0");
		
		for(UdpSocketHandler handler: handlers) {
			AddressTracker tracker = handler.getAddressTracker();
			HTMLNode row = table.addChild("tr");
			row.addChild("td", handler.getTitle());
			row.addChild("td", AddressTracker.statusString(tracker.getPortForwardStatus()));
		}
		
		if(ctx.isAdvancedModeEnabled()) {
		
		// One box per port
		
		String noreply = l10n("noreply");
		String local = l10n("local");
		String remote = l10n("remote");
		long now = System.currentTimeMillis();
		
		for(UdpSocketHandler handler: handlers) {
			// Peers
			AddressTracker tracker = handler.getAddressTracker();
			HTMLNode portsContent = pageMaker.getInfobox("#", NodeL10n.getBase().getString("ConnectivityToadlet.byPortTitle", new String[] { "port", "status", "tunnelLength" }, new String[] { handler.getTitle(), AddressTracker.statusString(tracker.getPortForwardStatus()), TimeUtil.formatTime(tracker.getLongestSendReceiveGap()) }), contentNode, "connectivity-port", false);
			PeerAddressTrackerItem[] items = tracker.getPeerAddressTrackerItems();
			table = portsContent.addChild("table");
			HTMLNode row = table.addChild("tr");
			row.addChild("th", l10n("addressTitle"));
			row.addChild("th", l10n("sentReceivedTitle"));
			row.addChild("th", l10n("localRemoteTitle"));
			row.addChild("th", l10n("firstSendLeadTime"));
			row.addChild("th", l10n("firstReceiveLeadTime"));
			for(int j=0;j<AddressTrackerItem.TRACK_GAPS;j++) {
				row.addChild("th", " "); // FIXME is <th/> valid??
			}
			for(PeerAddressTrackerItem item: items) {
				row = table.addChild("tr");
				// Address
				row.addChild("td", item.peer.toString());
				// Sent/received packets
				row.addChild("td", item.packetsSent() + "/ " + item.packetsReceived());
				// Initiator: local/remote FIXME something more graphical e.g. colored cells
				row.addChild("td", item.packetsReceived() == 0 ? noreply :
						(item.weSentFirst() ? local : remote));
				// Lead in time to first packet sent
				row.addChild("td", TimeUtil.formatTime(item.timeFromStartupToFirstSentPacket()));
				// Lead in time to first packet received
				row.addChild("td", TimeUtil.formatTime(item.timeFromStartupToFirstReceivedPacket()));
				Gap[] gaps = item.getGaps();
				for(int k=0;k<AddressTrackerItem.TRACK_GAPS;k++) {
					row.addChild("td", gaps[k].receivedPacketAt == 0 ? "" : 
						(TimeUtil.formatTime(gaps[k].gapLength)+" @ "+TimeUtil.formatTime(now - gaps[k].receivedPacketAt)+" ago" /* fixme l10n */));
				}
			}

			// IPs
			portsContent = pageMaker.getInfobox("#", NodeL10n.getBase().getString("ConnectivityToadlet.byIPTitle", new String[] { "ip", "status", "tunnelLength" }, new String[] { handler.getTitle(), AddressTracker.statusString(tracker.getPortForwardStatus()), TimeUtil.formatTime(tracker.getLongestSendReceiveGap()) }), contentNode, "connectivity-ip", false);
			InetAddressAddressTrackerItem[] ipItems = tracker.getInetAddressTrackerItems();
			table = portsContent.addChild("table");
			row = table.addChild("tr");
			row.addChild("th", l10n("addressTitle"));
			row.addChild("th", l10n("sentReceivedTitle"));
			row.addChild("th", l10n("localRemoteTitle"));
			row.addChild("th", l10n("firstSendLeadTime"));
			row.addChild("th", l10n("firstReceiveLeadTime"));
			for(int j=0;j<AddressTrackerItem.TRACK_GAPS;j++) {
				row.addChild("th", " "); // FIXME is <th/> valid??
			}
			for(InetAddressAddressTrackerItem item: ipItems) {
				row = table.addChild("tr");
				// Address
				row.addChild("td", item.addr.toString());
				// Sent/received packets
				row.addChild("td", item.packetsSent() + "/ " + item.packetsReceived());
				// Initiator: local/remote FIXME something more graphical e.g. colored cells
				row.addChild("td", item.packetsReceived() == 0 ? noreply :
						(item.weSentFirst() ? local : remote));
				// Lead in time to first packet sent
				row.addChild("td", TimeUtil.formatTime(item.timeFromStartupToFirstSentPacket()));
				// Lead in time to first packet received
				row.addChild("td", TimeUtil.formatTime(item.timeFromStartupToFirstReceivedPacket()));
				Gap[] gaps = item.getGaps();
				for(int k=0;k<AddressTrackerItem.TRACK_GAPS;k++) {
					row.addChild("td", gaps[k].receivedPacketAt == 0 ? "" : 
						(TimeUtil.formatTime(gaps[k].gapLength)+" @ "+TimeUtil.formatTime(now - gaps[k].receivedPacketAt)+" ago" /* fixme l10n */));
				}
			}

		}
		
		}
		
		writeHTMLReply(ctx, 200, "OK", pageNode.generate());
	}
	
	private String l10nConn(String string) {
		return NodeL10n.getBase().getString("DarknetConnectionsToadlet."+string);
	}

	private String l10n(String key) {
		return NodeL10n.getBase().getString("ConnectivityToadlet."+key);
	}

	public static final String PATH = "/connectivity/";
	
	@Override
	public String path() {
		return PATH;
	}
}
