package test.sim.scenario.mac;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;

import jist.swans.Constants;
import jist.swans.misc.Util;
import jist.swans.radio.RadioFactory;
import jist.swans.radio.RadioInfo;

public class RadioInfoTest {

  private static int testCtr = 0;
  /**
   * @param args
   * @throws Exception 
   */
  public static void main(String[] args) throws Exception {
    TestRadioFactory_Channels();
    TestRadioFactory_Bitrates();
    TestRadioFactory_Empty();
  }

  private static void TestRadioFactory_Channels() throws Exception {
    System.out.println("---------- Channel tests ----------");
    short macType = Constants.MAC_802_11g_PURE;
    RadioInfo riComp = RadioFactory.createRadioInfoDefault(macType);
    RadioInfo ri;
    
    List<Double> bi = Arrays.asList(new Double[] { 6., 12., 24. });
    List<Double> ba = Arrays.asList(new Double[] { 6., 12. });
    List<Double> po = Arrays.asList(new Double[] { 21., 19., 18. });
    List<Double> th = Arrays.asList(new Double[] { 10., 13., 17. });
    
    // Standard test
    List<Long> fr = Arrays.asList(new Long[] { 2400L });
    List<Long> sp = Arrays.asList(new Long[] { 5L });
    List<Long> wi = Arrays.asList(new Long[] { 20L });
    List<Integer> nr = Arrays.asList(new Integer[] { 3 });
    List<Integer> cd = Arrays.asList(new Integer[] { 2 }); // channel default
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    fr = Arrays.asList(new Long[] { });
    sp = Arrays.asList(new Long[] { 5L });
    wi = Arrays.asList(new Long[] { });
    nr = Arrays.asList(new Integer[] { 3 });
    cd = Arrays.asList(new Integer[] { 2 });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
    
    /***************************************************************************
     * Tests that should fail
     **************************************************************************/
    try {
      fr = Arrays.asList(new Long[] { });
      sp = Arrays.asList(new Long[] { 5L });
      wi = Arrays.asList(new Long[] { });
      nr = Arrays.asList(new Integer[] { 1 });
      cd = Arrays.asList(new Integer[] { 2 }); // channel default
      ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
      verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
      throw new Exception("Test " + testCtr + " should have failed!");
    } catch (Error e) {
      Util.assertion("assertion".equals(e.getMessage()));
    } catch (RuntimeException re) {
      System.out.println(re.getMessage());
    }

  }

  // Do bitrate related tests
  private static void TestRadioFactory_Bitrates() throws Exception {
    System.out.println("---------- Bitrates tests ---------");
    short macType = Constants.MAC_802_11g_PURE;
    RadioInfo riComp = RadioFactory.createRadioInfoDefault(macType);
    RadioInfo ri;
    
    List<Long> fr = Arrays.asList(new Long[] {}); // freq
    List<Long> sp = Arrays.asList(new Long[] {}); // ch spacing
    List<Long> wi = Arrays.asList(new Long[] {}); // ch width
    List<Integer> nr = Arrays.asList(new Integer[] {}); // ch number
    List<Integer> cd = Arrays.asList(new Integer[] {}); // ch default

    // Standar. test
    List<Double> bi = Arrays.asList(new Double[] { 6., 12., 24. });
    List<Double> ba = Arrays.asList(new Double[] { 6., 12., 24. });
    List<Double> po = Arrays.asList(new Double[] { 21., 19., 18. });
    List<Double> th = Arrays.asList(new Double[] { 10., 13., 17. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    // Check all permutations that must work
    ba = Arrays.asList(new Double[] { 6., 12. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    bi = ba = Arrays.asList(new Double[] { 6. });
    po = Arrays.asList(new Double[] { 21. });
    th = Arrays.asList(new Double[] { 10. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    // Setting one power and one threshold level
    bi = Arrays.asList(new Double[] {});
    ba = Arrays.asList(new Double[] {});
    po = Arrays.asList(new Double[] { 21. });
    th = Arrays.asList(new Double[] { 10. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    // Setting one power and many threshold levels
    bi = Arrays.asList(new Double[] { 6., 24., 48., 54.});
    ba = Arrays.asList(new Double[] { 6. });
    po = Arrays.asList(new Double[] { 21. });
    th = Arrays.asList(new Double[] { 10., 17., 26., 30. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);

    /***************************************************************************
     * Tests that should fail
     **************************************************************************/
    try {
      bi = Arrays.asList(new Double[] { 6., 12., 24. });
      ba = Arrays.asList(new Double[] { 6., 12., 18. }); // <- basic rate
      po = Arrays.asList(new Double[] { 21., 19., 18. });
      th = Arrays.asList(new Double[] { 10., 13., 17. });
      ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
      verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
      throw new Exception("Test " + testCtr + " should have failed!");
    } catch (Error e) {
      Util.assertion("assertion".equals(e.getMessage()));
    } catch (RuntimeException re) {
      System.out.println(re.getMessage());
    }

    // Setting two power levels
    try {
      bi = Arrays.asList(new Double[] {});
      ba = Arrays.asList(new Double[] {});
      po = Arrays.asList(new Double[] { 21., 19. });
      th = Arrays.asList(new Double[] { 10. });
      ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
      verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
      throw new Exception("Test " + testCtr + " should have failed!");
    } catch (Error e) {
      Util.assertion("assertion".equals(e.getMessage()));
    } catch (RuntimeException re) {
      System.out.println(re.getMessage());
    }
  }
  
  private static void TestRadioFactory_Empty() {
    System.out.println("----------- Empty tests ------------");
    short macType = Constants.MAC_802_11g_PURE;
    RadioInfo riComp = RadioFactory.createRadioInfoDefault(macType);
    RadioInfo ri;
    
    List<Long> fr = Arrays.asList(new Long[] {}); // freq
    List<Long> sp = Arrays.asList(new Long[] {}); // ch spacing
    List<Long> wi = Arrays.asList(new Long[] {}); // ch width
    List<Integer> nr = Arrays.asList(new Integer[] {}); // ch number
    List<Integer> cd = Arrays.asList(new Integer[] {}); // ch default

    // One half empty
    List<Double> bi = Arrays.asList(new Double[] { 6., 12., 24. });
    List<Double> ba = Arrays.asList(new Double[] { 6., 12., 24. });
    List<Double> po = Arrays.asList(new Double[] { 21., 19., 18. });
    List<Double> th = Arrays.asList(new Double[] { 10., 13., 17. });
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
    
    // Second half empty
    bi = Arrays.asList(new Double[] {});
    ba = Arrays.asList(new Double[] {});
    po = Arrays.asList(new Double[] {});
    th = Arrays.asList(new Double[] {});
    ri = getRadioInfo1(macType, bi, ba, po, th, fr, sp, wi, nr, cd);
    verifyRadioInfo(ri, riComp, bi, ba, po, th, fr, sp, wi, nr, cd);
  }

  private static RadioInfo getRadioInfo1(short mac, List<Double> bi, List<Double> ba,
      List<Double> po, List<Double> th, List<Long> fr, List<Long> sp,
      List<Long> wi, List<Integer> nr, List<Integer> cd) {
    String properties = "bitrates=" + getStr(bi) + "\r\nbasicRates="
        + getStr(ba) + "\r\ntxpowers=" + getStr(po) + "\r\nthresholds="
        + getStr(th) + "\r\nfrequencies=" + getStr(fr)
        + "\r\nfrequency_spacings=" + getStr(sp) + "\r\nfrequency_widths="
        + getStr(wi) + "\r\nnumber_of_channels=" + getStr(nr)
        + "\r\ndefault_channel=" + getStr(cd);
    System.out.println("------------- Test " + (++testCtr)
        + " --------------\r\n" + properties);

    return RadioFactory.createRadioInfo(mac, new ByteArrayInputStream(
        properties.getBytes()));
  }
  
  private static RadioInfo getRadioInfo2(short mac, List<Double> bi, List<Double> ba,
      List<Double> po, List<Double> th, List<Long> fr, List<Long> sp,
      List<Long> wi, List<Integer> nr, List<Integer> cd) {
    String properties = (bi.size() > 0 ? "bitrates=" + getStr(bi) : "")
        + (ba.size() > 0 ? "\r\nbasicRates=" + getStr(ba) : "")
        + (po.size() > 0 ? "\r\ntxpowers=" + getStr(po) : "")
        + (th.size() > 0 ? "\r\nthresholds=" + getStr(th) : "")
        + (ba.size() > 0 ? "\r\nfrequencies=" + getStr(fr) : "")
        + (ba.size() > 0 ? "\r\nfrequency_spacings=" + getStr(sp) : "")
        + (ba.size() > 0 ? "\r\nfrequency_widths=" + getStr(wi) : "")
        + (ba.size() > 0 ? "\r\nnumber_of_channels=" + getStr(nr) : "")
        + (ba.size() > 0 ? "\r\ndefault_channel=" + getStr(cd) : "");
    System.out.println("------------- Test " + (++testCtr)
        + " --------------\r\n" + properties);

    return RadioFactory.createRadioInfo(mac, new ByteArrayInputStream(
        properties.getBytes()));
  }
  
  private static void verifyRadioInfo(RadioInfo ri, RadioInfo riComp,
      List<Double> bi, List<Double> ba, List<Double> po, List<Double> th,
      List<Long> fr, List<Long> sp, List<Long> wi, List<Integer> nr,
      List<Integer> cd) {
    Collection coll;
    // bitrates
    coll = ri.getBitrateSnrThresholds().keySet();
    if (bi != null && !bi.isEmpty()) {
      Util.assertion(AreSetsEqual(bi, coll, Constants.BANDWIDTH_1Mbps));
    } else {
      Util.assertion(AreSetsEqual(riComp.getBitrateSnrThresholds().keySet(),
          coll, 1));
    }
    bi = null; // unset to avoid wrong assertions below

    // basic rates
    int[] basics = ri.getBasicRateSet();
    if (ba != null && !ba.isEmpty()) {
      List temp = new ArrayList();
      for (int i = 0; i < basics.length; i++)
        temp.add(new Integer(basics[i]));
      Util.assertion(AreSetsEqual(ba, temp, Constants.BANDWIDTH_1Mbps));
    } else {
      int[] baComp = riComp.getBasicRateSet();
      for (int i = 0; i < basics.length; i++)
        Util.assertion(Util.isInArray(baComp, basics[i]) >= 0);
      for (int i = 0; i < basics.length; i++)
        Util.assertion(Util.isInArray(basics, baComp[i]) >= 0);
    }
    ba = null; // unset to avoid wrong assertions below

    // bitrates vs. basic rates
    int[] rates = ri.getBitratesSupported();
    for (int i = 0; i < basics.length; i++)
      Util.assertion(Util.isInArray(rates, basics[i]) >= 0);

    // power : ignore size of set bc one value can be set for all bitrates
    coll = ri.getBitrateTxPower().values();
    if (po != null && !po.isEmpty()) {
      Util.assertion(AreSetsEqual(po, coll, 1, true));
    } else {
      Util
          .assertion(AreSetsEqual(riComp.getBitrateTxPower().values(), coll, 1));
    }
    po = null; // unset to avoid wrong assertions below

    // thresholds : ignore size of set bc one value can be set for all bitrates
    coll = ri.getBitrateSnrThresholds().values();
    if (th != null && !th.isEmpty()) {
      Util.assertion(AreSetsEqual(th, coll, Constants.THRESHOLD_UNIT, true));
    } else {
      Util.assertion(AreSetsEqual(riComp.getBitrateSnrThresholds().values(),
          coll, 1));
    }
    th = null; // unset to avoid wrong assertions below

    // frequency
    coll = ri.getSupportedFrequencies().values();
    if (fr != null && !fr.isEmpty()) {
      Util.assertion(AreSetsEqual(fr, coll, Constants.FREQUENCY_UNIT));
    } else {
      Util.assertion(AreSetsEqual(riComp.getSupportedFrequencies().values(),
          coll, 1));
    }
    fr = null; // unset to avoid wrong assertions below

    // channel spacing
    coll = ri.getSupportedChannelSpacings().values();
    if (sp != null && !sp.isEmpty()) {
      Util.assertion(AreSetsEqual(sp, coll, Constants.FREQUENCY_UNIT));
    } else {
      Util.assertion(AreSetsEqual(
          riComp.getSupportedChannelSpacings().values(), coll, 1));
    }
    sp = null; // unset to avoid wrong assertions below

    // channel width
    coll = ri.getSupportedChannelWidths().values();
    if (wi != null && !wi.isEmpty()) {
      Util.assertion(AreSetsEqual(wi, coll, Constants.FREQUENCY_UNIT));
    } else {
      Util.assertion(AreSetsEqual(riComp.getSupportedChannelWidths().values(),
          coll, 1));
    }
    wi = null; // unset to avoid wrong assertions below

    // channel number
    coll = ri.getSupportedNumberOfChannels().values();
    if (nr != null && !nr.isEmpty()) {
      Util.assertion(AreSetsEqual(nr, coll, 1));
    } else {
      Util.assertion(AreSetsEqual(riComp.getSupportedNumberOfChannels()
          .values(), coll, 1));
    }
    nr = null; // unset to avoid wrong assertions below

  }

  public static String getStr(List l) {
    String s = Arrays.toString(l.toArray());
    return s != null && s.length() >= 2 ? s.substring(1, s.length() - 1) : "";
  }

  private static boolean AreSetsEqual(Collection set1, Collection set2,
      double factor1to2) {
    return AreSetsEqual(set1, set2, factor1to2, false);
  }
  
  private static boolean AreSetsEqual(Collection set1, Collection set2,
      double factor1to2, boolean ignoreSize) {
    if (set1 == null || set2 == null)
      return false;
    if (!ignoreSize && set1.size() != set2.size())
      return false;
    Collection c1 = ConvertToDouble(set1);
    Collection c2 = ConvertToDouble(set2);
    try {
      for (Object object : c1) {
        try {
          Double d1 = (Double) object;
          if (!c2.contains(new Double(Double.valueOf(d1) * factor1to2)))
            return false;
        } catch (NumberFormatException e1) {
        }
      }
      for (Object object : c2) {
        try {
          Double d1 = (Double) object;
          if (!c1.contains(new Double(Double.valueOf(d1) / factor1to2)))
            return false;
        } catch (NumberFormatException e1) {
        }
      }
    } catch (NumberFormatException e) {
    }
    return true;
  }

  private static Collection ConvertToDouble(Collection set) {
    Collection newSet = new HashSet();
    for (Object o : set) {
      try {
        newSet.add(new Double(Double.parseDouble(o.toString())));
      } catch (NumberFormatException e) {
        newSet.add(o);
      }
    }
    return newSet;
  }
}
