package test.rate;

import java.util.ArrayList;

import jargs.gnu.CmdLineParser;

import org.apache.log4j.Logger;

import jargs.gnu.CmdLineParser.OptionException;

import jist.runtime.JistAPI;
import jist.runtime.Main;
import jist.runtime.JistAPI.Continuable;
import jist.swans.Constants;
import jist.swans.Node;
import jist.swans.mac.Mac802_11;
import jist.swans.mac.MacAddress;
import jist.swans.mac.MacMessageFactory;
import jist.swans.misc.Message;
import jist.swans.misc.Util;
import jist.swans.net.NetAddress;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioInfo;
import jist.swans.rate.ConstantRate;
import jist.swans.rate.RateControlAlgorithmIF;

public class BitrateTest implements BitrateTestNet.NetHandler{

  public static final Logger log = Logger.getLogger(BitrateTest.class);
  
  // Radio info for two stations
  RadioInfo                  radioInfo1;

  RadioInfo                  radioInfo2;

  // Radio/Field entity
  BitrateTestRadioField      radio;

  // MAC interfaces
  MacAddress                 macAddr1;

  MacAddress                 macAddr2;

  Mac802_11                  mac1;

  Mac802_11                  mac2;

  // NET interfaces
  NetAddress                 netAddr1;

  NetAddress                 netAddr2;

  BitrateTestNet             net1;

  BitrateTestNet             net2;

  // Test variables
  int                        msgCtr = 1;
  
  long                       txEndTime;
  
  /** Handler for feedback */
  public void onEndSend()
  {
//    log.debug("Exp:" + txEndTime + " now:" + JistAPI.getTime());
    if (Main.ASSERT)
      Util.assertion(txEndTime == JistAPI.getTime());
  }

  public void onReceive()
  {
    // TODO Auto-generated method stub

  }

  public void onSend()
  {
    // TODO Auto-generated method stub

  }
  
  /** 
   * 
   *
   */
  public BitrateTest(short mac, int bitrate) {

    radioInfo1 = RadioFactory.createRadioInfoDefault(mac);
    radioInfo2 = RadioFactory.createRadioInfoDefault(mac);
    
    // Radio/Field entity
    radio = new BitrateTestRadioField(0, 0);
    
    log.debug(radioInfo1.toString());
    log.debug(radioInfo2.toString());
    
    // MAC interfaces
    macAddr1   = new MacAddress(radioInfo1.getId());
    macAddr2   = new MacAddress(radioInfo2.getId());
    mac1       = new Mac802_11(macAddr1, radioInfo1, new ConstantRate(bitrate));
    mac2       = new Mac802_11(macAddr2, radioInfo2, new ConstantRate(bitrate));
//    mac1.setUseBitRateAnnotations(false);
    mac1.setUseAnnotations(false);
//    mac2.setUseBitRateAnnotations(false);
    mac2.setUseAnnotations(false);
    MacMessageFactory.M802_11 msgFact = new MacMessageFactory.M802_11();
    mac1.setMsgFactory(msgFact);
    mac2.setMsgFactory(msgFact);
    
    Node node1 = new Node(1);
    Node node2 = new Node(2);
    
    mac1.setNode(node1);
    mac2.setNode(node2);
    
    // NET interfaces
    netAddr1   = new NetAddress(radioInfo1.getId());
    netAddr2   = new NetAddress(radioInfo2.getId());
    net1       = new BitrateTestNet(netAddr1, mac1, macAddr2);
    net2       = new BitrateTestNet(netAddr2, mac2, macAddr1);
    net1.addNetHandler(this);
    net2.addNetHandler(this);

    // Entity hookup
    mac1.setRadioEntity(radio.getProxy());
    mac1.setNetEntity(net1.getProxy(), (byte)radioInfo1.getId());
    
    mac2.setRadioEntity(radio.getProxy());
    mac2.setNetEntity(net2.getProxy(), (byte)radioInfo2.getId());
    
    ArrayList al = new ArrayList();
    al.add(mac1);
    al.add(mac2);
    radio.setMacs(al);
    
  }
  
  public void sendMessage(int sizeInBytes) throws Continuable{
    byte[] byteMsg = new byte[sizeInBytes];
    String strMsg = "Message nr. " + msgCtr;
    System.arraycopy(strMsg.getBytes(), 0, byteMsg, 0, strMsg.length());
    Message msg = new TestMessage(msgCtr, new String(byteMsg));

    net1.send(msg, netAddr2, (short)0, (byte)0, (byte)255, null);
    msgCtr++;
  }
  
  public void setRateSelection(RateControlAlgorithmIF rateSel1, RateControlAlgorithmIF rateSel2) {
    mac1.setRateControlAlgorithm(rateSel1);
    mac2.setRateControlAlgorithm(rateSel2);
  }

  private void setEndSendTime(long endSendTime)
  {
    txEndTime = endSendTime;
  }

  /** Simulation parameters with default values. */
  private static class CommandLineOptions
  {
    /** 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_11b;
    /** Bit-rate to use for constant bit-rate test. */
    private int rate = Constants.BANDWIDTH_1Mbps;
    
  } // class: CommandLineOptions

  /** Prints a usage statement. */
  private static void showUsage() 
  {
    System.out.println("Usage: java driver.aodvsim [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 test.rate.BitrateTest -m b -r 2000000");
    System.out.println();
  }

  /**
   * 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.rate = ((Integer)parser.getOptionValue(opt_rate)).intValue();
    }

    return cmdOpts;
  }
  
  
  /**
   * @param args
   */
  public static void main(String[] args) {
    
    // Do a specific test: TODO
    if (args.length > 1) {
      CommandLineOptions options;
      try {
        options = parseCommandLineOptions(args);
        if(options.help) 
        {
          showUsage();
          return;
        }
        // to do: single tests
      } catch (OptionException e1) {
        log.error("", e1);
      }
    } else { // Do a whole test suite
      
      short[] macs = new short[]{Constants.MAC_802_11a, Constants.MAC_802_11bg};
      int sendCtr = 0;
      // Sending a message every:
      final int sleep = 30000000;
      
      for (int m = 0; m < macs.length; ++m) {
        short mac = macs[m];
        boolean useShortDifs = mac == Constants.MAC_802_11a; 
        // Get tx reference time generator 
        TransmissionTimes tt = new TransmissionTimes(false, true, useShortDifs, 2346);
        // Initialization
        int bitrate = Constants.BANDWIDTH_1Mbps;
        BitrateTest test = new BitrateTest(mac, bitrate);
        int size;
        int[] rates = mac == Constants.MAC_802_11a ? Constants.BITRATES_OFDM :
          Constants.BITRATES_ALL;
        int[] sizes = new int[]{250, 1500, 2500};
        
        // Iterate over all bit-rates and message sizes and send messages
        for(int i = 0; i < rates.length; ++i) {
          bitrate = rates[i];
          test.setRateSelection(new ConstantRate(bitrate), new ConstantRate(bitrate));

          for(int j = 0; j < sizes.length; ++j) {
            size = sizes[j];
            long txTime = tt.get(bitrate, size);
            log.info("t:" + Util.getTimeDotted() + ":\trate:" + bitrate
                + "\tsize:" + size + "\ttx time:" + txTime);
            test.setEndSendTime(JistAPI.getTime() + txTime);
            test.sendMessage(size);
//            JistAPI.sleep(txTime);
//            log.info("t:" + Util.getTimeDotted() + " - slept after sendMsg");
            JistAPI.sleepBlock(++sendCtr*sleep - JistAPI.getTime());
          }
        }
      }
    }
  }

  /**
   * Separate class to return the expected total tx time for a packet tx 
   * 
   * @author Oliver
   *
   */
  private static class TransmissionTimes {
    boolean longHeaders;
    boolean minimalBasicRates;
    boolean shortDifs;
    int     rtsThreshold;
    
    public TransmissionTimes(boolean longHeaders, boolean minimalBasicRates,
                             boolean shortDifs, int rtsThreshold)
    {
      this.longHeaders = longHeaders;
      this.minimalBasicRates = minimalBasicRates;
      this.shortDifs = shortDifs;
      this.rtsThreshold = rtsThreshold;
    }
    
    public long get(int dataRate, int msgSize)
    {
      if (Util.isInArray(Constants.BITRATES_DSSS, dataRate) >= 0)
        return getB(dataRate, msgSize);
      if (Util.isInArray(Constants.BITRATES_OFDM, dataRate) >= 0)
        return getG(dataRate, msgSize);
      throw new RuntimeException("BitrateTest: data rate '" + dataRate
          + "' not (yet) supported");
    }
    
    private long getB(int dataRate, int msgSize)
    {
      // DIFS
      long difs = 50;
      // SIFS
      long sifs = 10;
      // Preamble + PLCP header
      long plcp = 0;
      if (dataRate == Constants.BANDWIDTH_1Mbps || longHeaders)
        plcp += 144 + 48;
      else plcp += 72 + 24;
      
      long rts = 0;
      long cts = 0;
      long data = 0;
      long ack = 0;
      long tt = 0;
      
      /* RTS + CTS */
      if (msgSize > rtsThreshold) {
        /* RTS */
        rts = difs
            + plcp
            // MAC headers + payload
            + (long) Math.ceil(((float) 20 * 8)
                / ((float) dataRate / (float) Constants.BANDWIDTH_1Mbps));

        /* CTS */
        cts = sifs
            + plcp
            // MAC headers + payload
            + (long) Math.ceil(((float) 14 * 8)
                / ((float) dataRate / (float) Constants.BANDWIDTH_1Mbps));

//        log.debug("rts:" + rts);
//        log.debug("cts:" + cts);
        tt += rts + cts;
      }

      /* DATA */
      data = (rts > 0 ? sifs : difs)
          + plcp
          // LLC + MAC headers + payload
          + (long) Math.ceil(((float) (8 + 28 + msgSize) * 8.)
              / ((float) dataRate / (float) Constants.BANDWIDTH_1Mbps));

      /* ACK */
      // SIFS
      ack = sifs
          + plcp
          // MAC headers + payload
          + (long) Math.ceil(((float) 14 * 8)
              / ((float) dataRate / (float) Constants.BANDWIDTH_1Mbps));

//      log.debug("dat:" + data);
//      log.debug("ack:" + ack);

      tt += data + ack;
      return tt * Constants.MICRO_SECOND;
    }
    
    private long getG(int dataRate, int msgSize)
    {
      // DIFS
      long difs = shortDifs ? 28 : 50;
      // SIFS
      long sifs = 10;
      // Preamble + PLCP header
      long plcp = 16 + 4;
      // signal extension
      long sign = 6;
      
      long rts = 0;
      long cts = 0;
      long data = 0;
      long ack = 0;
      long tt = 0;
      
      /* RTS + CTS */
      if (msgSize > rtsThreshold) {
        /* RTS */
        rts = difs
            + plcp
            // MAC headers + payload
            + (long) (Math.ceil((float)(16 + 20 * 8 + 6)
                / ((float) dataRate * 4 / Constants.BANDWIDTH_1Mbps))) * 4
            + sign;
        
        /* CTS */
        cts = sifs
            + plcp
            // MAC headers + payload
            + (long) (Math.ceil((float)(16 + 14 * 8 + 6)
                / ((float) dataRate * 4 / Constants.BANDWIDTH_1Mbps))) * 4
            + sign;

//        log.debug("rts:" + rts);
//        log.debug("cts:" + cts);
        tt += rts + cts;
      }
      
      /* DATA */
      data = (rts > 0 ? sifs : difs)
          + plcp
          // LLC + MAC headers + payload
          + (long) (Math.ceil((float)(16 + (8 + 28 + msgSize) * 8 + 6)
              / ((float) dataRate * 4 / Constants.BANDWIDTH_1Mbps))) * 4
          + sign;

      /* ACK */
      // SIFS
      ack = sifs
          + plcp
          // MAC headers + payload
          + (long) (Math.ceil((float)(16 + 14 * 8 + 6)
              / ((float) dataRate * 4 / Constants.BANDWIDTH_1Mbps))) * 4
          + sign;

//      log.debug("dat:" + data);
//      log.debug("ack:" + ack);

      tt += data + ack;
      return tt * Constants.MICRO_SECOND;
    }
  } // class TransmissionTimes 
  
}
