package test.sim.scenario.mac;

import jargs.gnu.CmdLineParser;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.SortedSet;
import java.util.TreeSet;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.MacDcf;
import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.net.NetMessage;
import jist.swans.rate.ConstantRate;
import jist.swans.rate.RateControlAlgorithmIF;

import org.apache.log4j.Logger;


import brn.distsim.ormapper.util.DBSaver;
import brn.distsim.ormapper.util.DbBinaryLoader;
import brn.sim.AbstractDriver;
import brn.sim.AbstractParams;
import brn.sim.builder.MacBuilder;
import brn.sim.builder.NetBuilder;
import brn.sim.builder.RadioBuilder;
import brn.sim.builder.RateBuilder;
import brn.sim.builder.RouteBuilder;
import brn.sim.builder.MacBuilder.M802_11Params;
import brn.sim.data.DiagramData;
import brn.sim.data.DiagramDataHist;
import brn.sim.data.FlowStats;
import brn.sim.data.MacStats;
import brn.sim.data.PropertiesData;
import brn.swans.Strings;

/**
 * Test the MAC layer functionality
 * 
 * @author ofriedrich
 */
public class MacTest extends AbstractDriver {
	public static final Logger log = Logger.getLogger(MacTest.class.getName());

	public MacTestHandler macTestHandler;

	// ////////////////////////////////////////////////
	// initialize
	//

	/**
	 * Construct a new test object.
	 */
	public MacTest() {
		macTestHandler = new MacTestHandler();
	}

	// ////////////////////////////////////////////////
	// setup
	//

	// @override
	protected void installBuilders(final AbstractParams options) {
		super.installBuilders(options);
		builderProvider.addBuilder(new RouteBuilder.Dumb());
		builderProvider.addBuilder(new NetBuilder.IpMacTest());
	}

	// @override
	protected void installHandlers(final AbstractParams options)
			throws Exception {
		super.installHandlers(options);
		macTestHandler.registerHandlers();
	}

	// @override
	// protected void postHookUp(AbstractParams pars) throws BuilderException {
	// if (log.isInfoEnabled())
	// log.info("postHookUp");
	//    
	// super.postHookUp(pars);
	//    
	// MacTestParams params = (MacTestParams) pars;
	// JistAPI.sleep(1 * Constants.MICRO_SECOND);
	// runTests(params);
	// }

	/** Prints a usage statement. */
	protected void showUsage() {
		System.out.println("Usage: swans brn.analysis.mac.MacTest [options]");
		System.out.println();
		System.out.println("  -h, --help           print this message");
		System.out.println("  -m, --mac            a | b | blong | bg | g");
		System.out.println("  -r, --rate           bitrate in bits/s");
		System.out.println();
		System.out.println("e.g.");
		System.out.println("  swans brn.analysis.mac.MacTest -m b -r 2000000");
		System.out.println();
	}

	// ////////////////////////////////////////////////
	// implementations
	//
	public void showStatistics() {
		if (log.isDebugEnabled()) {
			StringBuffer sb = new StringBuffer("\n\tNet msg statistics:\n\t");
			SortedSet ss = new TreeSet(macTestHandler.netMsgs.keySet());
			Iterator it = ss.iterator();
			while (it.hasNext()) {
				Integer id = (Integer) it.next();
				// sb.append(" net msg id:"); sb.append(id); sb.append(" ");
				sb.append(macTestHandler.netMsgs.get(id).toString());
				sb.append("\n\t");
			}
			if (log.isDebugEnabled())
				log.debug(sb.toString());

			sb = new StringBuffer("\n\tMac pkt statistics:\n\t");
			ss = new TreeSet(macTestHandler.macPkts.keySet());
			it = ss.iterator();
			while (it.hasNext()) {
				Integer id = (Integer) it.next();
				// sb.append(" mac pkt id:"); sb.append(id);
				sb.append(macTestHandler.macPkts.get(id).toString());
				sb.append("\n\t");
			}
			if (log.isDebugEnabled())
				log.debug(sb.toString());

			sb.replace(0, sb.length(), "\n\tBackoff statistics:\n\t");
			it = macTestHandler.backoffs.iterator();
			while (it.hasNext()) {
				sb.append(it.next().toString());
				sb.append("\n\t");
			}
			if (log.isDebugEnabled())
				log.debug(sb.toString());
		}
	}

	/**
	 * Run tests to verify the MAC implemented transmission times.
	 */
	public void runTxTimesTests(MacTestParams params) {
		// Need 2 nodes to test transmission times.
		List nodes = getNodes();
		if (options.assertion)
			Util.assertion(nodes.size() > 1);

		/* iterate through the bit-rates to test */
		int bitrate;
		for (int i = 0; i < params.bitrates.length; ++i) {
			// Start time for next test: +1 sec
			JistAPI.sleepBlock(1 * Constants.SECOND);

			bitrate = params.bitrates[i];
			final RateControlAlgorithmIF rca = new ConstantRate(bitrate,
					bitrate);
			MacDcf mac = null;
			for (int n = 0; n < nodes.size(); n++) {
				mac = (MacDcf) ((Node) nodes.get(n)).getMac(0);
				mac.setRateControlAlgorithm(rca);
			}

			if (log.isInfoEnabled())
				log.info("[t:" + Util.getTimeDotted() + "] Testing "
						+ mac.getMacInfo().getMacTypeString() + " with "
						+ bitrate / Constants.BANDWIDTH_1Mbps + "Mbps ...");

			testMsgSizes(params, params.txTimes);
		}
	}

	public void runRcaTest(MacTestParams params) {
		// Need 2 nodes to test transmission times.
		List nodes = getNodes();
		if (options.assertion)
			Util.assertion(nodes.size() > 1);

		// log.info("Format:\n" + SampleRateState.logFormat());

		// for (int n1 = 0; n1 < nodes.size(); n1++) {
		int n1 = 0;
		Node node1 = (Node) nodes.get(n1);
		NetIpMacTest net1 = (NetIpMacTest) node1.getNet();

		// for (int n2 = 0; n2 < nodes.size(); n2++) {
		// for (int n2 = nodes.size() - 1; n2 >= 0; n2--) {
		// if (n1 == n2) continue;
		int n2 = 1;
		Node node2 = (Node) nodes.get(n2);
		NetIpMacTest net2 = (NetIpMacTest) node2.getNet();

		for (int i = 0; i < 1000; i++) {
			// Start time for next test: +15 ms
			JistAPI.sleepBlock(100 * Constants.MILLI_SECOND);
			if (log.isDebugEnabled())
				log.debug("\n" + i + ";");
			sendMsgs(net1, net2, 1, new int[] { 1500 });
		}
		// }
		// }
	}

	/**
	 * Do a test run with three messages.
	 * 
	 * @param test
	 * @param options
	 */
	private void testMsgSizes(final MacTestParams options,
			final MacTransmissionTimes times) {
		macTestHandler.setMacTxTimes(times);

		// Need 2 nodes to test transmission times.
		List nodes = getNodes();
		if (options.assertion)
			Util.assertion(nodes.size() > 1);

		int nodeSnd = 0; // Constants.random.nextInt(options.nodes);
		int nodeRcv = 1; // Constants.random.nextInt(options.nodes);

		Node node1 = (Node) nodes.get(nodeSnd);
		Node node2 = (Node) nodes.get(nodeRcv);

		NetIpMacTest net1 = (NetIpMacTest) node1.getNet();
		NetIpMacTest net2 = (NetIpMacTest) node2.getNet();

		sendMsgs(net1, net2, 10, new int[] { 250, 1500, 2500 });
	}

	private void sendMsgs(NetIpMacTest net1, NetIpMacTest net2, int ct,
			int[] sizes) {
		for (int i = 0; i < ct; ++i) {
			int idx = i % sizes.length;
			// if (log.isDebugEnabled())
			// log.debug(JistAPI.getTime() + ": Queue net msg " + i + ": "
			// + net1.getAddress() + " --> " + net2.getAddress()
			// + " with size " + sizes[idx] + "B");
			net1.send(new MacTestMessage("1-->2 size:" + sizes[idx], sizes[idx]
					- NetMessage.Ip.BASE_SIZE), net2.getAddress(),
					Constants.NET_PROTOCOL_DUMB,
					Constants.NET_PRIORITY_D_UNDEFINED, (byte) 255, null);
		}
	}

	/**
	 * Main method for MAC tests.
	 * 
	 * @param args
	 *            command line args
	 * @throws Exception
	 */
	public static void main(final String[] args) throws Exception {
		final MacTest test = new MacTest();
		/* read and parse the command line options */
		CommandLineOptions opts = parseCommandLineOptions(args);

		opts.bitrate = Constants.BANDWIDTH_54Mbps;

		/* show usage */
		if (opts.help) {
			test.showUsage();
			return;
		}

		/* Show statistics at the end */
		JistAPI.runAt(new Runnable() {
			public void run() {
				try {
					test.showStatistics();
				} catch (Exception e) {
					log.error("Unexpected error while showing statistics: ", e);
				}
			}
		}, JistAPI.END);

		try {
			MacTestParams options = (MacTestParams) (Util.readObject(args[0]));
			RateBuilder.ConstantParams rate = new RateBuilder.ConstantParams();
			rate.setControlBitrate(opts.bitrate);
			rate.setDataBitrate(opts.bitrate);
			((M802_11Params) options.node.mac).rateSelection = rate;
			test.run(options);
			test.testMsgSizes(options, new MacTransmissionTimes(false, false,
					false, Constants.MAC_THRESHOLD_RTS));
		} catch (Throwable e) {
			log.error("Something went wrong in MacTest main: " + opts.mac, e);
			System.exit(1);
		}
	}

	/**
	 * Parses command-line arguments.
	 * 
	 * @param args
	 *            command-line arguments
	 * @return parsed command-line options
	 * @throws CmdLineParser.OptionException
	 *             if the command-line arguments are not well-formed.
	 */
	private static CommandLineOptions parseCommandLineOptions(String[] args)
			throws CmdLineParser.OptionException {
		if (args.length == 0) {
			args = new String[] { "-h" };
		}
		CmdLineParser parser = new CmdLineParser();
		CmdLineParser.Option opt_help = parser.addBooleanOption('h', "help");
		CmdLineParser.Option opt_mac = parser.addStringOption('m', "mac");
		CmdLineParser.Option opt_rate = parser.addIntegerOption('r', "rate");

		parser.parse(args);

		CommandLineOptions cmdOpts = new CommandLineOptions();
		// help
		if (parser.getOptionValue(opt_help) != null) {
			cmdOpts.help = true;
		}
		// mac
		if (parser.getOptionValue(opt_mac) != null) {
			String macType = (String) parser.getOptionValue(opt_mac);
			if ("a".equals(macType))
				cmdOpts.mac = Constants.MAC_802_11a;
			else if ("b".equals(macType))
				cmdOpts.mac = Constants.MAC_802_11b;
			else if ("blong".equals(macType))
				cmdOpts.mac = Constants.MAC_802_11b_LONG;
			else if ("bg".equals(macType))
				cmdOpts.mac = Constants.MAC_802_11bg;
			else if ("g".equals(macType))
				cmdOpts.mac = Constants.MAC_802_11g_PURE;
		}
		// rate
		if (parser.getOptionValue(opt_rate) != null) {
			cmdOpts.bitrate = ((Integer) parser.getOptionValue(opt_rate))
					.intValue();
		}

		return cmdOpts;
	}

	public static class MacTestMessage implements Message {
		String msg;

		int size;

		public MacTestMessage(final String msg) {
			this(msg, msg.length());
		}

		public MacTestMessage(final String msg, final int size) {
			this.msg = msg;
			this.size = size;
		}

		public void getBytes(final byte[] msg, final int offset) {
			// not needed
		}

		public int getSize() {
			return size;
		}

		public String toString() {
			return msg;
		}

	} // class MacTestMessage

	/** Simulation parameters with default values. */
	private static class CommandLineOptions {
		/** Bit-rate to use for constant bit-rate test. */
		private int bitrate = Constants.BANDWIDTH_1Mbps;

		/** Whether to print a usage statement. */
		private boolean help = false;

		/** MAC layer to use: 802.11a, b or g. */
		private short mac = Constants.MAC_802_11bg;
	} // class: CommandLineOptions

	public List getSimulationSuite(String version) {
		return getSimulationSuite1();
	}

	private List getSimulationSuite1() {
		List sims = new ArrayList();
		short[] macs = new short[] { Constants.MAC_802_11a,
				Constants.MAC_802_11b, Constants.MAC_802_11bg,
				Constants.MAC_802_11b_LONG, Constants.MAC_802_11g_PURE };

		for (int nodes = 5; nodes <= 5; nodes += 5) {
			// for (int stdDev = 4; stdDev <= 12; stdDev += 4) {
			for (int macIdx = 0; macIdx < macs.length; macIdx++) {
				MacTestParams params = new MacTestParams().setDbParams();
				params.nodes = nodes;
				((RadioBuilder.NoiseParams) params.node.radio).radioType = macs[macIdx];
				MacBuilder.M802_11Params mac = (MacBuilder.M802_11Params) params.node.mac;
//				mac.macType = macs[macIdx];
				mac.useAnnos = false;
				mac.useBitRateAnnos = false;

				// Constants.RCA_CONSTANT_RATE:
				// {
				// MacInfo macInfo = MacInfo.create(macs[macIdx]);
				// int[] rates = macInfo.getBitrateSet();
				// for (int rateIdx = 0; rateIdx < rates.length; rateIdx++) {
				// sims.add(MacTestParams.getConstParams(params,
				// rates[rateIdx], rates[0]));
				// }
				// }

				// Constants.RCA_AUTO_RATE_FALLBACK:
				sims.add(params.setArfParams());

				// Constants.RCA_ADAPTIVE_AUTO_RATE_FALLBACK: // nothing yet

				// Constants.RCA_SAMPLE_RATE: // nothing yet

			} // macIdx
			// } // stdev
		} // nodes

		return sims;
	}

	protected Object load(DbBinaryLoader loader, int simulationId,
			String configPath, boolean error) throws Exception {
		Object ret = loader.load(simulationId, configPath);
		if (null == ret && error) {
			throw new Error("Object with path " + configPath
					+ " not found for id " + simulationId);
		}
		return ret;
	}

	protected Object load(DbBinaryLoader loader, int simulationId,
			String configPath) throws Exception {
		Object ret = loader.load(simulationId, configPath);
		if (null == ret) {
			throw new Error("Object with path " + configPath
					+ " not found for id " + simulationId);
		}
		return ret;
	}

	protected void evalSimulationResults(String dbUrl, String dbUser,
			String dbPasswd, String driver, String version, String dbResUrl,
			String dbResUser, String dbResPasswd) throws Throwable {
		/* nothing */
	}
	
	private List getSimulationSuiteRadioFactory() {
		return null;
	}
} // class MacTest
