package freenet.clients.http.wizardsteps;

import java.text.DecimalFormat;

import freenet.clients.http.FirstTimeWizardToadlet;
import freenet.config.Config;
import freenet.config.InvalidConfigValueException;
import freenet.l10n.NodeL10n;
import freenet.node.NodeClientCore;
import freenet.pluginmanager.PluginNotFoundException;
import freenet.support.*;
import freenet.support.api.HTTPRequest;

/**
 * Allows the user to set bandwidth limits with an emphasis on limiting to certain download and upload rates.
 */
public class BANDWIDTH_RATE extends BandwidthManipulator implements Step {

	private final BandwidthLimit[] limits;

	public BANDWIDTH_RATE(NodeClientCore core, Config config) {
		super(core, config);
		final int KiB = 1024;
		limits = new BandwidthLimit[] {
				// FIXME feedback on typical real world ratios on slow connections would be helpful.
				
//				// Dial-up
//				// 57.6/33.6; call it 4KB/sec each way
//				new BandwidthLimit(4*KiB, 4*KiB, "bandwidthConnectionDialUp"),
//				// 128kbps symmetrical = 16KB/sec each way, take half so 8KB/sec each way
//				new BandwidthLimit(8*KiB, 8*KiB, "bandwidthConnectionISDN"),
//				// 256kbps/64kbps developing world broadband
//				new BandwidthLimit(16*KiB, 4*KiB, "bandwidthConnectionSlow256"),
//				// 512kbps/128kbps very slow broadband
//				new BandwidthLimit(32*KiB, 8*KiB, "bandwidthConnectionSlow512"),
//				// 1Mbps/128kbps
//				new BandwidthLimit(64*KiB, 8*KiB, "bandwidthConnection1M"),
//				// 2Mbps/128kbps (slow often => poor ratios)
//				new BandwidthLimit(128*KiB, 8*KiB, "bandwidthConnection2M"),
				// 6Mbps/256kbps - 6Mbps is common in parts of china, as well as being the real value in lots of DSL areas
				new BandwidthLimit(384*KiB, 16*KiB, "bandwidthConnection6M", false),
				// 8Mbps/512kbps - UK DSL1 is either 448k up or 832k up
				new BandwidthLimit(512*KiB, 32*KiB, "bandwidthConnection8M", false),
				// 12Mbps/1Mbps - typical DSL2
				new BandwidthLimit(768*KiB, 64*KiB, "bandwidthConnection12M", false),
				// Typical DSL as of 2024
				new BandwidthLimit(768*KiB, 160*KiB, "bandwidthConnectionHalfVDSL", true),
				// 20Mbps/5Mbps - Slow end of VDSL
				new BandwidthLimit(1280*KiB, 320*KiB, "bandwidthConnectionVDSL", false),
				// 100Mbps fibre etc
				new BandwidthLimit(2048*KiB, 2048*KiB, "bandwidthConnection100M", false)
		};
	}

	@Override
	public void getStep(HTTPRequest request, PageHelper helper) {
		HTMLNode contentNode = helper.getPageContent(WizardL10n.l10n("bandwidthLimit"));
		
		HTMLNode formNode = helper.addFormChild(contentNode, ".", "limit");

		if (request.isParameterSet("parseError")) {
			parseErrorBox(contentNode, helper, request.getParam("parseTarget"));
		}

		HTMLNode infoBox = helper.getInfobox("infobox-normal", WizardL10n.l10n("bandwidthLimitRateTitle"),
		        formNode, null, false);
		NodeL10n.getBase().addL10nSubstitution(infoBox, "FirstTimeWizardToadlet.bandwidthLimitRate",
		        new String[] { "bold", "coreSettings" }, new HTMLNode[] { HTMLNode.STRONG, 
		                new HTMLNode("#", NodeL10n.getBase().getString("ConfigToadlet.node"))});

		//Table header
		HTMLNode table = infoBox.addChild("table");
		HTMLNode headerRow = table.addChild("tr");
		headerRow.addChild("th", WizardL10n.l10n("bandwidthConnectionHeader"));
		headerRow.addChild("th", WizardL10n.l10n("bandwidthDownloadHeader"));
		headerRow.addChild("th", WizardL10n.l10n("bandwidthUploadHeader"));
		headerRow.addChild("th", WizardL10n.l10n("bandwidthSelect"));

		boolean addedDefault = false;

		try {
			BandwidthLimit detected = detectBandwidthLimits(core.getNode().getIpDetector().getBandwidthIndicator());

			//Detected limits reasonable; add half of both as recommended option.
			BandwidthLimit usable = new BandwidthLimit(detected.downBytes/2, detected.upBytes/2, "bandwidthDetected", true);
			addLimitRow(table, helper, usable, true, true);
			addedDefault = true;
		} catch (PluginNotFoundException | IllegalValueException e) {
			Logger.normal(this, e.getMessage(), e);
		}

		BandwidthLimit current = getCurrentBandwidthLimitsOrNull();
		if(current != null) {
			addLimitRow(table, helper, current, false, !addedDefault);
			addedDefault = true;
		}
		
		for (BandwidthLimit limit : limits) {
			addLimitRow(table, helper, limit, false, !addedDefault);
		}

		//Add custom option.
		HTMLNode customForm = table.addChild("tr");
		customForm.addChild("td", WizardL10n.l10n("bandwidthCustom"));
		customForm.addChild("td").addChild("input",
		        new String[] { "type", "name" },
		        new String[] { "text", "customDown" });
		customForm.addChild("td").addChild("input",
		        new String[] { "type", "name" },
		        new String[] { "text", "customUp" });
		// This is valid if it's filled in. So don't show the selector.
		// FIXME javascript to auto-select it?
//		customForm.addChild("td").addChild("input",
//				new String[] { "type", "name", "value" },
//				new String[] { "radio", "bandwidth", "custom" });

		infoBox.addChild("input",
		        new String[] { "type", "name", "value" },
		        new String[] { "submit", "back", NodeL10n.getBase().getString("Toadlet.back")});
		infoBox.addChild("input",
		        new String[] { "type", "name", "value" },
		        new String[] { "submit", "next", NodeL10n.getBase().getString("Toadlet.next")});
	}

	@Override
	public String postStep(HTTPRequest request)  {

		String limitSelected = request.getPartAsStringFailsafe("bandwidth", 100);

		String down = request.getPartAsStringFailsafe("customDown", 20);
		String up = request.getPartAsStringFailsafe("customUp", 20);

		// Try to parse custom limit first.
		if(!down.isEmpty() && !up.isEmpty()) {
			String failedLimits = attemptSet(up, down);

			if (!failedLimits.isEmpty()) {
				//Some at least one limit failed to parse.
				return "BANDWIDTH_RATE&parseError=true&parseTarget="+
				        URLEncoder.encode(failedLimits, true);
			}

			//Success
			setWizardComplete();
			return FirstTimeWizardToadlet.WIZARD_STEP.COMPLETE.name();
		}

		if(!limitSelected.isEmpty()) {
			int x = limitSelected.indexOf('/');
			if(x != -1) {
				String downString = limitSelected.substring(0, x);
				String upString = limitSelected.substring(x+1);
				//Pre-defined limit selected.
				String preset = attemptSet(upString, downString);
				if(!preset.isEmpty()) {
					//Error parsing predefined limit.
					//This should not happen, as there are no units to confound the parser.
					Logger.error(this, "Failed to parse pre-defined limit! Please report.");
					return FirstTimeWizardToadlet.WIZARD_STEP.BANDWIDTH_RATE+"&parseError=true&parseTarget="+
							URLEncoder.encode(preset, true);
				}
			}
		} else {
			Logger.error(this, "No bandwidth limit set!");
			return FirstTimeWizardToadlet.WIZARD_STEP.BANDWIDTH_RATE.name();
		}
		
		setWizardComplete();
		return FirstTimeWizardToadlet.WIZARD_STEP.COMPLETE.name();
	}

	/**
	 * Attempts to set bandwidth limits.
	 * @param up output limit
	 * @param down input limit
	 * @return a space-separated string of the messages from any exceptions thrown when setting limits. If both are successful, an empty string.
	 */
	private String attemptSet(String up, String down) {
		String failedLimits = "";
		try {
			setBandwidthLimit(down, false);
		} catch (InvalidConfigValueException e) {
			failedLimits = e.getMessage();
		}
		try {
			setBandwidthLimit(up, true);
		} catch (InvalidConfigValueException e) {
			if (!failedLimits.isEmpty()) failedLimits += ' ';
			failedLimits += e.getMessage();
		}
		return failedLimits;
	}

	/**
	 * Adds a row to the table for the given limit. Adds download limit, upload limit, and selection button.
	 * @param table Table to add a row to.
	 * @param helper To make a form for the button and hidden fields.
	 * @param limit Limit to display.
	 * @param recommended Whether to mark the limit with (Recommended) next to the select button.
	 */
	private void addLimitRow(HTMLNode table, PageHelper helper, BandwidthLimit limit, boolean recommended, boolean useMaybeDefault) {
		HTMLNode row = table.addChild("tr");
		row.addChild("td", WizardL10n.l10n(limit.descriptionKey));
		String downColumn = SizeUtil.formatSize(limit.downBytes)+WizardL10n.l10n("bandwidthPerSecond");
		if(limit.downBytes >= 32*1024) {
			downColumn += " (= ";
			if(limit.downBytes < 256*1024)
				downColumn += new DecimalFormat("0.0").format(((double)((limit.downBytes*8)))/(1024*1024));
			else
				downColumn += ((limit.downBytes*8)/(1024*1024));
			downColumn+="Mbps)";
		}
		row.addChild("td", downColumn);
		row.addChild("td", SizeUtil.formatSize(limit.upBytes)+WizardL10n.l10n("bandwidthPerSecond"));

		HTMLNode buttonCell = row.addChild("td");
		
		HTMLNode radio = 
			buttonCell.addChild("input",
					new String[] { "type", "name", "value" },
					new String[] { "radio", "bandwidth", limit.downBytes+"/"+limit.upBytes });
		if(recommended || (useMaybeDefault && limit.maybeDefault))
			radio.addAttribute("checked", "checked");
		if (recommended) {
			buttonCell.addChild("#", WizardL10n.l10n("autodetectedSuggestedLimit"));
		}
	}
}
