package brn.swans.route;

import java.util.*;

import jist.runtime.JistAPI;
import jist.swans.Constants;
import jist.swans.radio.RadioInterface;
import jist.swans.mac.MacAddress;
import jist.swans.misc.Message;
import jist.swans.misc.MessageAnno;
import jist.swans.net.NetAddress;
import jist.swans.net.NetMessage;

import org.apache.log4j.Logger;

import brn.swans.mac.MacMcExORMessage;
import brn.swans.mac.MacRXTXFeedbackInterface;
import brn.swans.route.cs.McExORCandidateSelection;
import brn.swans.route.metric.ArpTableInterface;
import brn.swans.route.metric.RouteMetricInterface;
import brn.swans.misc.FeedbackMessageAnno;
import brn.analysis.mcexor.ImgCandidateSet;
import brn.analysis.mcexor.TestConstants;

/**
 * The Routing Layer for McExOR.
 *
 * @author Zubow
 */
public class NGRouteMcExOR extends RouteDsrBrn implements MacRXTXFeedbackInterface {

  // ////////////////////////////////////////////////
  // constants
  //

  public class DuplicateTable {

    private class PacketId {
      protected int id;
      protected NetAddress src;

      public PacketId(NetAddress src, int id) {
        this.src = src;
        this.id = id;
      }

      public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        final NGRouteMcExOR.DuplicateTable.PacketId that = (NGRouteMcExOR.DuplicateTable.PacketId) o;

        if (id != that.id) return false;
        if (!src.equals(that.src)) return false;

        return true;
      }

      public int hashCode() {
        int result;
        result = id;
        result = 29 * result + src.hashCode();
        return result;
      }
    }

    /**
     * Keep track of ExOR routed data packets
     */
    private Set dataPacketTable;

    public DuplicateTable() {
      dataPacketTable = new HashSet();
    }

    /**
     * Checks if we have recently forwarded this data packet with the given seqNum
     * coming from the given source.
     *
     * @param src the source address of the data packet
     * @param id  the ID number
     * @return whether the given data packet has been forwarded recently.
     */
    private boolean seenDataPacketBefore(NetAddress src, int id) {
      NGRouteMcExOR.DuplicateTable.PacketId entry = new NGRouteMcExOR.DuplicateTable.PacketId(src, id);

      return (dataPacketTable.contains(entry));
    }

    /**
     * Enters a new Data packet ID number into the ...
     *
     * @param src the address of the originator of the data packet
     * @param id  the ID number
     */
    protected void addDataPacketId(NetAddress src, int id) {
      // Otherwise add this id to the table
      dataPacketTable.add(new NGRouteMcExOR.DuplicateTable.PacketId(src, id));
    }

    public boolean isDuplicate(RouteDsrBrnMsg msg, NetAddress src) {
      RouteDsrBrnMsg.Option optId = msg.getOption(RouteDsrBrnMsg.OPT_ID);
      int id = ((RouteDsrBrnMsg.OptionId)optId).getId();
      boolean seen = seenDataPacketBefore(src, id);
      addDataPacketId(src, id);

      NGRouteMcExOR.log.warn(localAddr + "(" + JistAPI.getTime() + "): received dupl packet (" +
          src + "," + id + ").");
      if (seen) {
        NGRouteMcExOR.log.warn(localAddr + "(" + JistAPI.getTime() + "): received "
                + "duplicate packet (" + src + "," + id + "); drop it.");
      }

      return seen;
    }

  }

  public class NodePref {
    public NetAddress addr;
    public byte pref;
    public long timestamp;

    public NodePref(NetAddress addr, byte pref, long timestamp) {
      this.addr = addr;
      this.pref = pref;
      this.timestamp = timestamp;
    }

    public boolean equals(Object o) {
      if (this == o) return true;
      if (o == null || getClass() != o.getClass()) return false;

      final NodePref nodePref = (NodePref) o;

      if (pref != nodePref.pref) return false;
      if (timestamp != nodePref.timestamp) return false;
      if (addr != null ? !addr.equals(nodePref.addr) : nodePref.addr != null) return false;

      return true;
    }

    public int hashCode() {
      int result;
      result = (addr != null ? addr.hashCode() : 0);
      result = 29 * result + (int) pref;
      result = 29 * result + (int) (timestamp ^ (timestamp >>> 32));
      return result;
    }
  }

  /** Timeout value for relay preferences */
  public static final long TIMEOUT_RELAYPREF = 20 * Constants.SECOND;

  // ////////////////////////////////////////////////
  // locals
  //

  /** McExOR logger. */
  private static Logger log = Logger.getLogger(NGRouteMcExOR.class.getName());

  /** receives rx/tx feedbacks from MAC */
  private MacRXTXFeedbackInterface selfFeedback;

  /** table holding information about duplicate packets */
  private NGRouteMcExOR.DuplicateTable duplicateTable;
  /** mac address of local node */
  protected MacAddress localMacAddress;
  /** metric to use for routing */
  protected RouteMetricInterface routeMetric;
  /** candidate set selection algorithm */
  protected McExORCandidateSelection candidateSet;

  /** Sometimes it is good to know if a node wants to act as a relay node */
  private Hashtable /** NetAddress -> {@link NodePref} */ relayPreferences;
  /** CrossLayer: the layer below makes use of RTS/CTS. The has the following implications:
   * - since we use RTS/CTS to detect deaf nodes it is possible that it makes no sense to use some
   * of the candidates from the candidate set (deafness). Therefore not only one candidate set,
   * but a set of candidate sets is calculated. The MAC will choose the best one. */
  private boolean useRtsCts;

  /** Used only for tests */
  private boolean testRun;
  private List testRunOpts;
  private List testRunOpts2;

  // ////////////////////////////////////////////////
  // initialization
  //

  /** Constructor */
  public NGRouteMcExOR(int csSize, NetAddress localAddr,
                       RouteMetricInterface routeMetric, ArpTableInterface arp,
                       boolean useRtsCts, boolean testRun, Integer[] testRunOpts, Integer[] testRunOpts2) {
    super(localAddr, arp);

    this.routeMetric      = routeMetric;
    this.localMacAddress  = arp.getArpEntry(localAddr);
    this.relayPreferences = new Hashtable();
    this.candidateSet     = new McExORCandidateSelection(csSize, localAddr, routeMetric, relayPreferences, useRtsCts);

    this.duplicateTable   = new NGRouteMcExOR.DuplicateTable();
    this.useRtsCts        = useRtsCts;

    this.testRun = testRun;
    if (testRunOpts != null)
      this.testRunOpts = Arrays.asList(testRunOpts);
    if (testRunOpts2 != null)
      this.testRunOpts2 = Arrays.asList(testRunOpts2);

    selfFeedback = (MacRXTXFeedbackInterface) JistAPI.proxy(new MacRXTXFeedbackInterface.Dlg(this),
        MacRXTXFeedbackInterface.class);
  }

  // ////////////////////////////////////////////////
  // accessors
  //

  public RouteMetricInterface getRouteMetric() {
    return routeMetric;
  }

  public MacRXTXFeedbackInterface getFeedbackProxy() {
    return selfFeedback;
  }

  // ////////////////////////////////////////////////
  // overwrites
  //

  /*
   * (non-Javadoc)
   *
   * @see brn.swans.route.RouteDsrBrnInterface#send(jist.swans.net.NetMessage.Ip,
   *      int, jist.swans.mac.MacAddress, jist.swans.misc.MessageAnno)
   */
  public void send(NetMessage.Ip msg, int interfaceId, MacAddress nextHop, MessageAnno anno) {

    // delegate broadcasts to network layer
    if (msg.getDst().equals(NetAddress.ANY)) {
      netEntity.send(msg, interfaceId, nextHop, anno);
      return;
    }

    /** retrieves and updates the RF channels the packet has taken on its way. */
    byte[] nwArray = retrieveAndUpdateUsedChannels(msg);
    // prefer the home channel for the first hop
    boolean preferHomeChannel = msg.getSrc().equals(localAddr);

    // Calculate candidate set and give to network layer
    List lstCandidates, lstRtsCandidates = null;

    RadioInterface.RFChannel testRunRfChannel = null;
    if (testRun) {
      /** use artificial values */
      lstCandidates     = testRunCalculate();

      if (testRunOpts2 != null)
        lstRtsCandidates  = testRunCalculateRts();
      else
        lstRtsCandidates = new ArrayList(lstCandidates);

      testRunRfChannel = routeMetric.getHomeChannel((NetAddress)lstCandidates.get(0));
    } else {
      /** clean-up {@link #relayPreferences} */
      cleanUpExpiredNodePrefs();
      /** estimate best candidate set (e.g. A,B,C) */
      lstCandidates = candidateSet.calculate(localAddr, msg.getDst(), preferHomeChannel, nwArray);
      lstRtsCandidates = new ArrayList(lstCandidates);
    }

    if (null == lstCandidates || lstCandidates.size() == 0) {
      NGRouteMcExOR.log.error(this + "(" + JistAPI.getTime() + "): no route found to " + msg.getDst());
      handleTransmitError(msg, anno);
      return;
    }
    // IP -> MAC conversion
    List lstMacCandidates = arp(lstCandidates);
    MacMcExORMessage.CSetAddress destination
            = new MacMcExORMessage.CSetAddress(lstMacCandidates);

    List lstRtsMacCandidates = arp(lstRtsCandidates);
    MacMcExORMessage.CSetAddress rtsDestination
            = new MacMcExORMessage.CSetAddress(lstRtsMacCandidates);

    RadioInterface.RFChannel destRfChannel = candidateSet.getRFChannelForSelectedCandidateSet();

    anno.put(MessageAnno.ANNO_MAC_CANDIDATES, destination);
    anno.put(MessageAnno.ANNO_MAC_RTS_CANDIDATES, rtsDestination);
    anno.put(MessageAnno.ANNO_MAC_RF_CHANNEL, destRfChannel);

    // special handling for RTS/CTS version of McExOR
    if (useRtsCts) {
      /**
       * The algorithm works as follows: Given the previously estimated cset of e.g. A,B,C we do the following:
       * - by removing A we are trying to find an alternative to A e.g. A' with the resulting cset: A',B,C
       * - by removing B we are trying to find an alternative to B e.g. B' with the resulting cset: A,B',C
       * - by removing C we are trying to find an alternative to C e.g. C' with the resulting cset: A,B,C'
       */
      List dstOptions = new ArrayList();
      for (int i = 0; i < lstCandidates.size(); i++) {
        // exclude this neighbor from computation
        NetAddress exclNb = (NetAddress)lstCandidates.get(i);
        List optCds = candidateSet.calculate(localAddr, msg.getDst(), preferHomeChannel, nwArray, exclNb, destRfChannel);
        dstOptions.add(new MacMcExORMessage.CSetAddress(arp(optCds)));
      }
      anno.put(MessageAnno.ANNO_MAC_OPT_CANDS, dstOptions);
    }
    
    if (testRun) {
      // estimate RF channel
      anno.put(MessageAnno.ANNO_MAC_RF_CHANNEL, testRunRfChannel);
    }
    //TODO make is dynamic
    anno.put(MessageAnno.ANNO_MAC_FORWARD_PREF, new Byte(brn.swans.Constants.PREFERENCE_MAX));

    if (log.isDebugEnabled())
      log.debug(this + "(" + JistAPI.getTime() + "): using candidate set " + lstCandidates);

    netEntity.send(msg, interfaceId, MacAddress.NULL, anno);
  }

  /** Helper method resolves mac addresses. */
  private List arp(List lstCandidates) {
    List lstMacCandidates = new ArrayList(lstCandidates.size());
    for (int i = 0; i < lstCandidates.size(); i++)
      lstMacCandidates.add(arp.getArpEntry((NetAddress)lstCandidates.get(i)));
    return lstMacCandidates;
  }

  /**
   * Helper method retrieves the array of used channels from the routing header.
   * @param msg the incoming message
   * @return an array of channels this packet has used on its way.
   */
  private byte[] retrieveAndUpdateUsedChannels(NetMessage.Ip msg) {
    byte[] nwArray = new byte[0];
    /** append McExOR used channels header */
    if (!(msg.getPayload() instanceof RouteDsrBrnMsg))
        throw new RuntimeException("wrong routing header");
    else {
      RouteDsrBrnMsg brnMsg = (RouteDsrBrnMsg)msg.getPayload();

      // TODO hack: we should not use the DSR packet headers ...
      if (brnMsg.hasOption(RouteDsrBrnMsg.OPT_SOURCE_ROUTE)) {
        brnMsg.removeOption(RouteDsrBrnMsg.OPT_SOURCE_ROUTE);
      }

      if (brnMsg.hasOption(RouteDsrBrnMsg.OPT_USED_CHANNELS)) {

        RouteDsrBrnMsg.OptionUsedChannels option
            = (RouteDsrBrnMsg.OptionUsedChannels)brnMsg.getOption(RouteDsrBrnMsg.OPT_USED_CHANNELS);
        byte[] chArray = option.usedChannels;

        int chArrLen = (chArray != null) ? chArray.length : 0;
        nwArray = new byte[chArrLen + 1];
        if (chArray != null)
          System.arraycopy(chArray, 0, nwArray, 0, chArrLen);

        // last hop was on my home channel
        //TODO hack : we have to save the also the frequency
        int chLastHop = this.getRouteMetric().getHomeChannel().getChannel();// linkStat.getNeighborHomeChannel(lastIp);

        nwArray[chArrLen] = (byte) chLastHop;

        brnMsg.removeOption(RouteDsrBrnMsg.OPT_USED_CHANNELS);
      }

      // create new options
      brnMsg.addOption(new RouteDsrBrnMsg.OptionUsedChannels(nwArray));
    }
    return nwArray;
  }

  /*
   * (non-Javadoc)
   *
   * @see jist.swans.route.RouteInterface#peek(jist.swans.net.NetMessage,
   *      jist.swans.misc.MessageAnno)
   */
  public void peek(NetMessage msg, MacAddress lastHop, MessageAnno anno) {
    discovery.peek(msg, lastHop, anno);

    if (!(msg instanceof NetMessage.Ip))
      return;

    NetMessage.Ip ipMsg = (NetMessage.Ip) msg;
    if (ipMsg.getProtocol() != Constants.NET_PROTOCOL_MCEXOR)
      return;

    if (testRun && testRunOpts.contains(TestConstants.ROUTE_DONT_FORWARD_PACKETS)) {
      log.warn(this + "(" + JistAPI.getTime() + "): TEST-RUN; dont forward packet; drop it.");
      return;
    }

    RouteDsrBrnMsg mcExORMsg = (RouteDsrBrnMsg) ipMsg.getPayload();

    if (NGRouteMcExOR.log.isDebugEnabled())
      NGRouteMcExOR.log.debug(this + "(" + JistAPI.getTime() + "): saw message from "
          + ipMsg.getSrc() + "(" + lastHop + ") to " + ipMsg.getDst() + " ["
          + mcExORMsg + "]");

    MacMcExORMessage.CSetAddress candidates =
            (MacMcExORMessage.CSetAddress) anno.get(MessageAnno.ANNO_MAC_CANDIDATES);
    if (/*sourceRoute != null
        && localAddr.equals(sourceRoute.nextRecipient(ipMsg.getDst()))
        ||*/ candidates != null && candidates.contains(localMacAddress)) {
      forwardPacket(mcExORMsg, anno, lastHop, null, ipMsg.getSrc(),
          ipMsg.getDst(), ipMsg.getProtocol(), ipMsg.getPriority(), ipMsg
              .getTTL(), ipMsg.getId(), ipMsg.getFragOffset());
    }
  }

  /* (non-Javadoc)
   * @see brn.swans.route.RouteDsrBrn#receive(jist.swans.misc.Message, jist.swans.net.NetAddress, jist.swans.mac.MacAddress, byte, jist.swans.net.NetAddress, byte, byte, jist.swans.misc.MessageAnno)
   */
  public void receive(Message msg, NetAddress src, MacAddress lastHop,
                      byte macId, NetAddress dst, byte priority, byte ttl, MessageAnno anno) {
    if (NGRouteMcExOR.log.isInfoEnabled() && localAddr.equals(dst))
      // Don't count received broadcast packets?
      NGRouteMcExOR.log.info(JistAPI.getTime() + ":" + this + " Received packet from " + src
          + " at " + dst);

    // Don't process any options here -- that's all done by peek. Just forward
    // any content on to the transport layer (or whatever).
    RouteDsrBrnMsg mcExORMsg = (RouteDsrBrnMsg) msg;

    if (mcExORMsg.getContent() != null) {
      // eliminate duplicates
      if (duplicateTable.isDuplicate((RouteDsrBrnMsg)msg, src)) {
        if (duplicateEvent.isActive())
          duplicateEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);
        return;
      }
      if (packetForwardedEvent.isActive())
        packetForwardedEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);

      // Now go through some strange contortions to get this message
      // received by the proper protocol handler
      NetMessage.Ip newIp = new NetMessage.Ip(mcExORMsg.getContent(),
              src, dst, mcExORMsg.getNextHeaderType(), priority, ttl);

      // Preserve flow and packet id
      RouteDsrBrnMsg.OptionId id = (RouteDsrBrnMsg.OptionId)
          mcExORMsg.getOption(RouteDsrBrnMsg.OPT_ID);
      anno.put(MessageAnno.ANNO_RTG_FLOWID, new Integer(id.getAnnoFlowId()));
      anno.put(MessageAnno.ANNO_RTG_PACKETID, new Integer(id.getAnnoPacketId()));

      netEntity.receive(newIp, lastHop, macId, false, anno);

      if (NGRouteMcExOR.log.isInfoEnabled())
        NGRouteMcExOR.log.info(JistAPI.getTime() + ":" + this + " Received data packet from "
            + src + " at " + dst);
    }
  }

  /**
   * Forwards a McExOR packet containing a Source Route option to the next
   * intended recipient. An Acknowledgement Request option is added to the
   * headers, and the packet is retransmitted if no acknowledgement is
   * received before the allotted timeout elapses.
   *
   * @param msg        the <code>RouteMcExORMsg</code> to be forwarded
   * @param anno
   * @param src        the address of the originator of this packet
   * @param dest       the address of the ultimate destination of this packet
   * @param protocol   the IP protocol of this packet (usually McExOR)
   * @param priority   the IP priority of this packet
   * @param ttl        the IP time to live of this packet
   * @param id         the IP identification of this packet
   * @param fragOffset the IP fragmentation offset of this packet
   */
  protected void forwardPacket(RouteDsrBrnMsg msg, MessageAnno anno,
                               MacAddress lastHop, RouteDsrBrnMsg.OptionSourceRoute opt, NetAddress src,
                               NetAddress dest, short protocol, byte priority, byte ttl, short id,
                               short fragOffset) {
//    MacMcExORMessage.CSetAddress candidates
//            = (MacMcExORMessage.CSetAddress) anno.get(MessageAnno.ANNO_MAC_CANDIDATES);

    // If the packet is for me it doesn't need to be forwarded
    if (localAddr.equals(dest)/* || !candidates.contains(localMacAddress)*/)
      return;

    // eliminate duplicates
    if (duplicateTable.isDuplicate(msg, src)) {
      if (duplicateEvent.isActive())
        duplicateEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);
      return;
    }

    // increment forwarding counter and forward the packet
    if (packetForwardedEvent.isActive())
      packetForwardedEvent.handle(msg, anno, arp.getArpEntry(lastHop), localAddr);

    RouteDsrBrnMsg newMsg = (RouteDsrBrnMsg) msg.clone();

    /*
    RouteDsrBrnMsg.OptionUsedChannels usedCh = new RouteDsrBrnMsg.
      OptionUsedChannels(usedChannels);

    newMsg.addOption();
    */

    NetMessage.Ip ipMsg = new NetMessage.Ip(newMsg, src, dest, protocol,
        priority, (byte) (ttl - 1), id, fragOffset);

    // Construct new anno (we do not want to get in confusion with old annos)
    anno = new MessageAnno();

    send(ipMsg, Constants.NET_INTERFACE_DEFAULT, null, anno);
  }

  // ////////////////////////////////////////////////
  // Feedback Callbacks (from MAC)
  //

  public void txfeedback(Message msg, FeedbackMessageAnno fAnno) {
    //log.info("txfeedback called.");
    // TODO nexthops is now a list of lists
//    for (int i = 0; i < lstAcks.size(); i++) {
//      MacMcExORMessage.Ack ack = (MacMcExORMessage.Ack) lstAcks.get(i);
//
//      //NetAddress dst = rarp(ack.getDst());
//
//      // src --> myOwnPref
//      NetAddress src = rarp(ack.getSrc());
//      byte myOwnPref = ack.getOwnPref();
//
//      // addrId --> highestPref
//      byte id = ack.getCandidateId();
//      byte highestPref = ack.getForwardPref();
//      NetAddress addrId = rarp((MacAddress) nextHops.get(id));
//
//      if (myOwnPref <= MacMcExOR.PREFERENCE_MAX)
//        updateNodePref(src, myOwnPref);
//      if (highestPref <= MacMcExOR.PREFERENCE_MAX)
//        updateNodePref(addrId, highestPref);
//    }
  }

  public void rxfeedback(Message msg, MessageAnno anno) {

    //log.info("rxfeedback called.");
/*
    NetAddress src = null;
    byte fwdPref = -1;

    MacMcExORMessage mcExORMsg = (MacMcExORMessage)msg;

    switch (mcExORMsg.getType()) {
      case MacMcExORMessage.TYPE_DATA:
        MacMcExORMessage.Data msgData = (MacMcExORMessage.Data) msg;
        src = getArpEntry(msgData.getSrc());
        fwdPref = msgData.getForwardPref();
        break;
      case MacMcExORMessage.TYPE_ACK:
        MacMcExORMessage.Ack msgAck = (MacMcExORMessage.Ack) msg;
        src = getArpEntry(msgAck.getSrc());
        fwdPref = msgAck.getForwardPref();
        break;
      default:
        throw new RuntimeException("unknown frame type");
    }

    if (src == null)
      log.warn("Failed to rarp() ... relay preference skipped.");

    if (src != null && (fwdPref <= brn.swans.Constants.PREFERENCE_MAX)
        && (fwdPref >= brn.swans.Constants.PREFERENCE_MIN))
      updateNodePref(src, fwdPref);
*/
  }

  private void updateNodePref(NetAddress ip, byte pref) {
    NodePref nodePref = (NodePref) relayPreferences.get(ip);

    long now = JistAPI.getTime();
    if (nodePref == null) {
      // create new entry
      relayPreferences.put(ip, new NodePref(ip, pref, now));
    } else {
      // update entry
      nodePref.pref = pref;
      nodePref.timestamp = now;
    }
  }

  private void cleanUpExpiredNodePrefs() {
    long now = JistAPI.getTime();
    Enumeration keys = relayPreferences.keys();
    while (keys.hasMoreElements()) {
      NetAddress netAddress = (NetAddress) keys.nextElement();
      NodePref nodePref = (NodePref) relayPreferences.get(netAddress);
      if (now - TIMEOUT_RELAYPREF > nodePref.timestamp) { // entry timed out
        relayPreferences.remove(netAddress);
      }
    }
  }


  // --------------------------------------------------------------------------
  // TEST AREA
  // --------------------------------------------------------------------------

  // we need a global arp table
  private NetAddress getArpEntry(MacAddress addr) {
    NetAddress raddr = arp.getArpEntry(addr);
    if (raddr == null) {
      // TODO: BEGIN hack
      raddr = new NetAddress((int)addr.getId());
      log.warn("Failed to rarp() ... using hack solution.");
      // END hack
    }
    return raddr;
  }

  private List testRunCalculate() {
    List lstCandidates = null;
    if (testRunOpts.contains(TestConstants.IMG_CANDIDATE_SET_SNGL_KEY)) {
      int idx = testRunOpts.indexOf(TestConstants.IMG_CANDIDATE_SET_SNGL_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateSingleCs((Integer)testRunOpts.get(idx));
    } else if (testRunOpts.contains(TestConstants.IMG_CANDIDATE_SET_TWIN_KEY)) {
      int idx = testRunOpts.indexOf(TestConstants.IMG_CANDIDATE_SET_TWIN_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateTwinCs((Integer)testRunOpts.get(idx));
    } else if (testRunOpts.contains(TestConstants.IMG_CANDIDATE_SET_TRPL_KEY)) {
      int idx = testRunOpts.indexOf(TestConstants.IMG_CANDIDATE_SET_TRPL_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateTripleCs((Integer)testRunOpts.get(idx));
    } else if (testRunOpts.contains(TestConstants.IMG_CANDIDATE_SET_QUAD_KEY)) {
      int idx = testRunOpts.indexOf(TestConstants.IMG_CANDIDATE_SET_QUAD_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateQuadCs((Integer)testRunOpts.get(idx));
    }
    return lstCandidates;
  }

  private List testRunCalculateRts() {
    List lstCandidates = null;

    if (testRunOpts2.contains(TestConstants.IMG_CANDIDATE_SET_RTS_SNGL_KEY)) {
      int idx = testRunOpts2.indexOf(TestConstants.IMG_CANDIDATE_SET_RTS_SNGL_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateSingleCs((Integer)testRunOpts2.get(idx));
    } else if (testRunOpts2.contains(TestConstants.IMG_CANDIDATE_SET_RTS_TWIN_KEY)) {
      int idx = testRunOpts2.indexOf(TestConstants.IMG_CANDIDATE_SET_RTS_TWIN_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateTwinCs((Integer)testRunOpts2.get(idx));
    } else if (testRunOpts2.contains(TestConstants.IMG_CANDIDATE_SET_RTS_TRPL_KEY)) {
      int idx = testRunOpts2.indexOf(TestConstants.IMG_CANDIDATE_SET_RTS_TRPL_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateTripleCs((Integer)testRunOpts2.get(idx));
    } else if (testRunOpts2.contains(TestConstants.IMG_CANDIDATE_SET_RTS_QUAD_KEY)) {
      int idx = testRunOpts2.indexOf(TestConstants.IMG_CANDIDATE_SET_RTS_QUAD_KEY) + 1;
      lstCandidates = ImgCandidateSet.calculateQuadCs((Integer)testRunOpts2.get(idx));
    }
    return lstCandidates;
  }
}
