/**
   * This is called when a controller issues a FlowMod. The goal here is to rewrite flowmods so that
   * they guarantee traffic isolation between slices. A flowmod is rewritten following these rules:
   *
   * <p>1. Its field is wildcarded, then for every potentially matching rule we rewrite the field
   * with the value in the rule.
   *
   * <p>2. Its field is specified, then leave it untouched, but use it for matching purposes.
   *
   * <p>3. We have a potential partial match: 3.1. The field and the flowrule field do not overlap,
   * return no match. 3.2. The fields overlap, return the smallest possible intersection of the two.
   *
   * <p>Currently rule 3 only applies to IP addresses, but this could change in the future.
   *
   * @param dpid - The dpid this flowmod is directed to, could be wildcarded.
   * @param match - The set of field modifications issued by this flowmod.
   * @return a list of intersections (ie. flowmod rewrites) created by the flowmod issued from the
   *     controller.
   */
  public List<FlowIntersect> intersect(long dpid, FVMatch match) {
    FVLog.log(LogLevel.DEBUG, null, "dpid: ", dpid, " match: ", match.toString());
    BitSet set = new BitSet();
    normalize(match);
    int wildcards = match.getWildcards();
    TreeSet<FlowIntersect> ret = new TreeSet<FlowIntersect>();
    HashMap<Integer, FlowIntersect> intersections = new HashMap<Integer, FlowIntersect>();
    HashMap<Integer, Pair<Boolean, BitSet>> rewrites =
        new HashMap<Integer, Pair<Boolean, BitSet>>();

    set.or(allRules);

    try {

      testEmpty(set, dpids, dpid, FlowEntry.ALL_DPIDS, wildcards, 0);

      rewrites.put(
          FVMatch.OFPFW_IN_PORT,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set, port, match.getInputPort(), ANY_IN_PORT, wildcards, FVMatch.OFPFW_IN_PORT),
              set));

      rewrites.put(
          FVMatch.OFPFW_DL_SRC,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  dl_src,
                  FVMatch.toLong(match.getDataLayerSource()),
                  ANY_MAC,
                  wildcards,
                  FVMatch.OFPFW_DL_SRC),
              set));

      rewrites.put(
          FVMatch.OFPFW_DL_DST,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  dl_dst,
                  FVMatch.toLong(match.getDataLayerDestination()),
                  ANY_MAC,
                  wildcards,
                  FVMatch.OFPFW_DL_DST),
              set));

      rewrites.put(
          FVMatch.OFPFW_DL_VLAN,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  vlan,
                  (int) match.getDataLayerVirtualLan(),
                  (int) ANY_VLAN_ID,
                  wildcards,
                  FVMatch.OFPFW_DL_VLAN),
              set));

      rewrites.put(
          FVMatch.OFPFW_DL_VLAN_PCP,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  vlan,
                  match.getDataLayerVirtualLanPriorityCodePoint() << 16,
                  ANY_VLAN_PCP << 16,
                  wildcards,
                  FVMatch.OFPFW_DL_VLAN_PCP),
              set));
      rewrites.put(
          FVMatch.OFPFW_DL_TYPE,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  dl_type,
                  match.getDataLayerType(),
                  ANY_ETHER,
                  wildcards,
                  FVMatch.OFPFW_DL_TYPE),
              set));

      rewrites.put(
          FVMatch.OFPFW_NW_PROTO,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  nw,
                  (short) match.getNetworkProtocol(),
                  (short) ANY_NW_PROTO_TOS,
                  wildcards,
                  FVMatch.OFPFW_NW_PROTO),
              set));

      rewrites.put(
          FVMatch.OFPFW_NW_TOS,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  nw,
                  (short) (match.getNetworkTypeOfService() << 8),
                  (short) (ANY_NW_PROTO_TOS << 8),
                  wildcards,
                  FVMatch.OFPFW_NW_TOS),
              set));

      rewrites.put(
          FVMatch.OFPFW_TP_SRC,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  tp,
                  (int) match.getTransportSource(),
                  (int) ANY_TP,
                  wildcards,
                  FVMatch.OFPFW_TP_SRC),
              set));

      rewrites.put(
          FVMatch.OFPFW_TP_DST,
          new Pair<Boolean, BitSet>(
              testEmpty(
                  set,
                  tp,
                  match.getTransportDestination() << 16,
                  ANY_TP << 16,
                  wildcards,
                  FVMatch.OFPFW_TP_DST),
              set));

      int field = 0;
      boolean rewrite = false;
      BitSet inters = null;
      FlowIntersect flow = null;
      FlowEntry rule = null;

      for (Entry<Integer, Pair<Boolean, BitSet>> entry : rewrites.entrySet()) {
        field = entry.getKey();
        rewrite = entry.getValue().getFirst();
        inters = entry.getValue().getSecond();
        FVLog.log(LogLevel.DEBUG, null, "Rule ids which intersect: ", inters.toString());
        for (int i = inters.nextSetBit(0); i >= 0; i = inters.nextSetBit(i + 1)) {
          rule = rules.get(i).clone();
          flow = getIntersect(rule, intersections);

          if (!rewrite) {
            rule.setRuleMatch(match);
            setField(flow, rule.getRuleMatch(), field);
          }

          FVMatch ruleMatch = rules.get(i).getRuleMatch();
          FlowIntersect inter = flow;
          FVMatch interMatch = inter.getMatch();
          interMatch.setNetworkDestination(
              testIP(
                  inter,
                  FVMatch.OFPFW_NW_DST_SHIFT,
                  match.getNetworkDestinationMaskLen(),
                  ruleMatch.getNetworkDestinationMaskLen(),
                  match.getNetworkDestination(),
                  ruleMatch.getNetworkDestination()));
          if (inter.getMatchType() == MatchType.NONE) continue;

          interMatch.setNetworkSource(
              testIP(
                  inter,
                  FVMatch.OFPFW_NW_SRC_SHIFT,
                  match.getNetworkSourceMaskLen(),
                  ruleMatch.getNetworkSourceMaskLen(),
                  match.getNetworkSource(),
                  ruleMatch.getNetworkSource()));
          if (inter.getMatchType() == MatchType.NONE) continue;
          intersections.put(flow.getFlowEntry().getId(), flow);
        }
      }

      /*
       * Need to resolve intersection by priority.
       *
       * In the worst case this will be O(n) where n is the number of
       * rules. But in average we will only have a small subset of the
       * rules. Right??
       */
      ret.addAll(intersections.values());
      FVLog.log(LogLevel.DEBUG, null, "Intersections: ", intersections);

    } catch (NoMatch e) {
      FVLog.log(LogLevel.FATAL, null, "Failed to intersect flow mod " + match);
      return new ArrayList<FlowIntersect>(ret);
    } catch (UnknownMatchField umf) {
      FVLog.log(LogLevel.FATAL, null, umf.getMessage());
    }

    return new ArrayList<FlowIntersect>(ret);
  }