/** Method to find the different periodic connection and periodic duration. */
  private void validatePeriodicConnections() {
    int burstSize = burstCollection.size();
    Burst lastPeriodicalBurst = null;
    int periodicCount = 0;
    double minimumRepeatTime = Double.MAX_VALUE;
    PacketInfo packetId = null;
    for (int i = 0; i < burstSize; i++) {
      Burst burst = burstCollection.get(i);
      if (burst.getBurstCategory() == BurstCategory.BURSTCAT_PERIODICAL) {
        if (periodicCount != 0) {
          double time = burst.getBeginTime() - lastPeriodicalBurst.getBeginTime();
          if (time < minimumRepeatTime) {
            minimumRepeatTime = time;
            packetId = burst.getFirstUplinkDataPacket();
            if (packetId == null) {
              packetId = burst.getBeginPacket();
            }
          }
        }
        lastPeriodicalBurst = burst;
        periodicCount++;
      }
    }

    if (packetId != null) {
      shortestPeriodPacketInfo = packetId;
      shortestPeriodTCPSession = packetId.getSession();
    }
    if (minimumRepeatTime != Double.MAX_VALUE) {
      minimumPeriodicRepeatTime = minimumRepeatTime;
    }
  }
  /** To Validate the simultaneous TCP connections */
  private void validateUnnecessaryConnections() {
    int setCount = 0;
    int maxCount = 0;
    Burst maxBurst = null;
    for (int i = 0; i < burstCollection.size(); ++i) {
      Burst burstInfo = burstCollection.get(i);
      if (burstInfo.getBurstCategory() == BurstCategory.BURSTCAT_USER
          || burstInfo.getBurstCategory() == BurstCategory.BURSTCAT_SCREEN_ROTATION) {
        continue;
      }
      double startTime = burstInfo.getBeginTime();
      double endTime = startTime + 60.0;
      int count = 1;
      for (int j = i + 1;
          j < burstCollection.size() && burstCollection.get(j).getEndTime() <= endTime;
          ++j) {
        if (burstCollection.get(j).getBurstCategory() != BurstCategory.BURSTCAT_USER
            || burstInfo.getBurstCategory() == BurstCategory.BURSTCAT_SCREEN_ROTATION) {
          ++count;
        }
      }
      if (count >= 4) {
        ++setCount;
        if (count > maxCount) {
          maxCount = count;
          maxBurst = burstInfo;
        }
        i = i + count;
      } else if (count == 3) {
        endTime = startTime + 15.0;
        count = 1;
        for (int j = i + 1;
            j < burstCollection.size() && burstCollection.get(j).getEndTime() <= endTime;
            ++j) {
          if (burstCollection.get(j).getBurstCategory() != BurstCategory.BURSTCAT_USER
              || burstInfo.getBurstCategory() == BurstCategory.BURSTCAT_SCREEN_ROTATION) {
            ++count;
          }
        }
        if (count >= 3) {
          ++setCount;
          if (count > maxCount) {
            maxCount = count;
            maxBurst = burstInfo;
          }
          i = i + count;
        }
      }
    }

    tightlyCoupledBurstCount = setCount;
    if (maxBurst != null) {
      tightlyCoupledBurstTime = maxBurst.getBeginTime();
    }
  }
  /** Method to assign the burst states. */
  private void analyzeBursts() {
    List<UserEvent> userEvents = analysis.getUserEvents();
    List<CpuActivity> cpuEvents = analysis.getCpuActivityList();
    int userEventsSize = userEvents.size();
    int cpuEventsSize = cpuEvents.size();
    int userEventPointer = 0;
    int cpuPointer = 0;
    // Analyze each burst
    Burst b = null;
    Burst lastBurst;
    for (Iterator<Burst> i = burstCollection.iterator(); i.hasNext(); ) {
      lastBurst = b;
      b = i.next();
      // Step 1: Remove background packets
      List<PacketInfo> pktIdx = new ArrayList<PacketInfo>(b.getPackets().size());
      int payloadLen = 0;
      Set<TcpInfo> tcpInfo = new HashSet<TcpInfo>();
      for (PacketInfo p : b.getPackets()) {
        if (p.getAppName() != null) {
          payloadLen += p.getPayloadLen();
          pktIdx.add(p);
          TcpInfo tcp = p.getTcpInfo();
          if (tcp != null) {
            tcpInfo.add(tcp);
          }
        }
      }
      if (pktIdx.size() == 0) {
        assert (payloadLen == 0);
        b.addBurstInfo(BurstInfo.BURST_BKG);
        continue;
      }
      PacketInfo pkt0 = pktIdx.get(0);
      TcpInfo info0 = pkt0.getTcpInfo();
      double time0 = pkt0.getTimeStamp();

      // Step 2: a long burst?
      if (b.getEndTime() - b.getBeginTime() > profile.getLargeBurstDuration()
          && payloadLen > profile.getLargeBurstSize()) {
        b.addBurstInfo(BurstInfo.BURST_LONG);
        ++longBurstCount;
        continue;
      }

      // Step 3: Contains no payload?
      if (payloadLen == 0) {
        if (tcpInfo.contains(TcpInfo.TCP_CLOSE)) {
          b.addBurstInfo(BurstInfo.BURST_FIN);
        }
        if (tcpInfo.contains(TcpInfo.TCP_ESTABLISH)) {
          b.addBurstInfo(BurstInfo.BURST_SYN);
        }
        if (tcpInfo.contains(TcpInfo.TCP_RESET)) {
          b.addBurstInfo(BurstInfo.BURST_RST);
        }
        if (tcpInfo.contains(TcpInfo.TCP_KEEP_ALIVE)
            || tcpInfo.contains(TcpInfo.TCP_KEEP_ALIVE_ACK)) {
          b.addBurstInfo(BurstInfo.BURST_KEEPALIVE);
        }
        if (tcpInfo.contains(TcpInfo.TCP_ZERO_WINDOW)) {
          b.addBurstInfo(BurstInfo.BURST_ZEROWIN);
        }
        if (tcpInfo.contains(TcpInfo.TCP_WINDOW_UPDATE)) {
          b.addBurstInfo(BurstInfo.BURST_WINUPDATE);
        }
        if (b.getBurstInfos().size() == 0
            && (info0 == TcpInfo.TCP_ACK_RECOVER || info0 == TcpInfo.TCP_ACK_DUP)) {
          b.addBurstInfo(BurstInfo.BURST_LOSS_RECOVER);
        }
        if (b.getBurstInfos().size() > 0) continue;
      }

      // Step 4: Server delay
      if (pkt0.getDir() == PacketInfo.Direction.DOWNLINK
          && (info0 == TcpInfo.TCP_DATA || info0 == TcpInfo.TCP_ACK)) {
        b.addBurstInfo(BurstInfo.BURST_SERVER_DELAY);
        continue;
      }

      // Step 5: Loss recover
      if (info0 == TcpInfo.TCP_ACK_DUP || info0 == TcpInfo.TCP_DATA_DUP) {
        b.addBurstInfo(BurstInfo.BURST_LOSS_DUP);
        continue;
      }

      if (info0 == TcpInfo.TCP_DATA_RECOVER || info0 == TcpInfo.TCP_ACK_RECOVER) {
        b.addBurstInfo(BurstInfo.BURST_LOSS_RECOVER);
        continue;
      }

      // Step 6: User triggered
      final double USER_EVENT_SMALL_TOLERATE = profile.getUserInputTh();
      if (payloadLen > 0) {
        UserEvent ue = null;
        while ((userEventPointer < userEventsSize)
            && ((ue = userEvents.get(userEventPointer)).getReleaseTime()
                < (time0 - USER_EVENT_TOLERATE))) ++userEventPointer;
        BurstInfo bi =
            ue != null
                ? ((ue.getEventType() == UserEventType.SCREEN_LANDSCAPE
                        || ue.getEventType() == UserEventType.SCREEN_PORTRAIT)
                    ? BurstInfo.BURST_SCREEN_ROTATION_INPUT
                    : BurstInfo.BURST_USER_INPUT)
                : null;
        int j = userEventPointer;
        double minGap = Double.MAX_VALUE;
        while (j < userEventsSize) {
          UserEvent uEvent = userEvents.get(j);
          if (withinTolerate(uEvent.getPressTime(), time0)) {
            double gap = time0 - uEvent.getPressTime();
            if (gap < minGap) {
              minGap = gap;
            }
          }
          if (withinTolerate(uEvent.getReleaseTime(), time0)) {
            double gap = time0 - uEvent.getReleaseTime();
            if (gap < minGap) {
              minGap = gap;
            }
          }
          if (uEvent.getPressTime() > time0) {
            break;
          }
          j++;
        }
        if (minGap < USER_EVENT_SMALL_TOLERATE) {
          b.addBurstInfo(bi);
          continue;
        } else if (minGap < USER_EVENT_TOLERATE
            && (lastBurst == null || lastBurst.getEndTime() < b.getBeginTime() - minGap)) {
          double cpuBegin = time0 - minGap;
          double cpuEnd = time0;
          // Check CPU usage
          while (cpuPointer < cpuEventsSize) {
            double t = cpuEvents.get(cpuPointer).getBeginTimeStamp();
            if (t < b.getBeginTime() - USER_EVENT_TOLERATE) {
              ++cpuPointer;
            } else {
              break;
            }
          }
          int k = cpuPointer;
          double s = 0.0f;
          int ns = 0;
          while (k < cpuEventsSize) {
            CpuActivity cpuAct = cpuEvents.get(k);
            double t = cpuAct.getBeginTimeStamp();
            if (t > cpuBegin && t < cpuEnd) {
              s += cpuAct.getUsage();
              ns++;
            }
            if (t >= cpuEnd) {
              break;
            }
            k++;
          }
          if (ns > 0 && (s / ns) > AVG_CPU_USAGE_THRESHOLD) {
            b.addBurstInfo(bi);
            b.addBurstInfo(BurstInfo.BURST_CPU_BUSY);
            continue;
          }
        }
      }

      // Step 7: Client delay
      if (b.getBurstInfos().size() == 0 && payloadLen == 0) {
        b.addBurstInfo(BurstInfo.BURST_UNKNOWN);
      } else {
        b.addBurstInfo(BurstInfo.BURST_CLIENT_DELAY);
      }
    }
  }
  /** Computes the total burst energy. */
  private void computeBurstEnergyRadioResource() {
    List<RrcStateRange> rrcCollection = analysis.getRrcStateMachine().getRRcStateRanges();
    int rrcCount = rrcCollection.size();
    if (rrcCount == 0) {
      return;
    }
    int p = 0;
    double time2 = -1;
    double totalEnergy = 0.0f;
    Iterator<Burst> iter = burstCollection.iterator();
    Burst currentBurst = iter.next();
    double time1 = rrcCollection.get(0).getBeginTime();
    while (true) {
      Burst nextBurst = iter.hasNext() ? iter.next() : null;
      time2 =
          nextBurst != null
              ? nextBurst.getBeginTime()
              : rrcCollection.get(rrcCount - 1).getEndTime();
      double e = 0.0f;
      double activeTime = 0.0f;
      while (p < rrcCount) {
        RrcStateRange rrCntrl = rrcCollection.get(p);
        if (rrCntrl.getEndTime() < time1) {
          p++;
        } else {
          if (time2 > rrCntrl.getEndTime()) {
            e +=
                profile.energy(
                    time1, rrCntrl.getEndTime(), rrCntrl.getState(), analysis.getPackets());
            if ((rrCntrl.getState() == RRCState.STATE_DCH
                    || rrCntrl.getState() == RRCState.TAIL_DCH)
                || (rrCntrl.getState() == RRCState.LTE_CONTINUOUS
                    || rrCntrl.getState() == RRCState.LTE_CR_TAIL)
                || (rrCntrl.getState() == RRCState.WIFI_ACTIVE
                    || rrCntrl.getState() == RRCState.WIFI_TAIL)) {
              activeTime += rrCntrl.getEndTime() - time1;
            }
            p++;
          }
          break;
        }
      }
      while (p < rrcCount) {
        RrcStateRange rrCntrl = rrcCollection.get(p);
        if (rrCntrl.getEndTime() < time2) {
          e +=
              profile.energy(
                  Math.max(rrCntrl.getBeginTime(), time1),
                  rrCntrl.getEndTime(),
                  rrCntrl.getState(),
                  analysis.getPackets());
          if ((rrCntrl.getState() == RRCState.STATE_DCH || rrCntrl.getState() == RRCState.TAIL_DCH)
              || (rrCntrl.getState() == RRCState.LTE_CONTINUOUS
                  || rrCntrl.getState() == RRCState.LTE_CR_TAIL)
              || (rrCntrl.getState() == RRCState.WIFI_ACTIVE
                  || rrCntrl.getState() == RRCState.WIFI_TAIL)) {
            activeTime += rrCntrl.getEndTime() - Math.max(rrCntrl.getBeginTime(), time1);
          }
          p++;
        } else {
          e +=
              profile.energy(
                  Math.max(rrCntrl.getBeginTime(), time1),
                  time2,
                  rrCntrl.getState(),
                  analysis.getPackets());
          if ((rrCntrl.getState() == RRCState.STATE_DCH || rrCntrl.getState() == RRCState.TAIL_DCH)
              || (rrCntrl.getState() == RRCState.LTE_CONTINUOUS
                  || rrCntrl.getState() == RRCState.LTE_CR_TAIL)
              || (rrCntrl.getState() == RRCState.WIFI_ACTIVE
                  || rrCntrl.getState() == RRCState.WIFI_TAIL)) {
            activeTime += time2 - Math.max(rrCntrl.getBeginTime(), time1);
          }
          break;
        }
      }
      currentBurst.setEnergy(e);
      totalEnergy += e;
      currentBurst.setActiveTime(activeTime);

      time1 = time2;
      if (nextBurst != null) {
        currentBurst = nextBurst;
      } else {
        break;
      }
    }
    this.totalEnergy = totalEnergy;
  }
  /** Groups packets into Burst Collections */
  private void groupIntoBursts() {
    // Validate that there are packets
    List<PacketInfo> packets = this.analysis.getPackets();
    if (packets.size() <= 0) {
      this.burstCollection = Collections.emptyList();
      return;
    }
    ArrayList<Burst> result = new ArrayList<Burst>();
    double burstThresh = profile.getBurstTh();
    double longBurstThresh = profile.getLongBurstTh();
    List<PacketInfo> burstPackets = new ArrayList<PacketInfo>();
    // Step 1: Build bursts using burst time threshold
    PacketInfo lastPacket = null;
    for (PacketInfo packet : packets) {
      if (lastPacket == null
          || (packet.getTimeStamp() - lastPacket.getTimeStamp() > burstThresh
              && !mss.contains(lastPacket.getPayloadLen()))) {
        if (burstPackets.size() > 0) {
          result.add(new Burst(burstPackets));
          burstPackets.clear();
        }
      }
      burstPackets.add(packet);
      lastPacket = packet;
    }
    result.add(new Burst(burstPackets));

    // Step 2: Remove promotion delays and merge bursts if possible
    Map<PacketInfo, Double> timestampList = normalizeCore(packets);
    List<Burst> newBurstColl = new ArrayList<Burst>(result.size());
    int n = result.size();
    Burst newBurst = result.get(0);
    for (int i = 0; i < n - 1; i++) {
      Burst bnext = result.get(i + 1);
      double time1 = timestampList.get(newBurst.getEndPacket());
      double time2 = timestampList.get(bnext.getBeginPacket());
      if ((time2 - time1) < burstThresh) {
        newBurst.merge(bnext);
      } else {
        newBurstColl.add(newBurst);
        newBurst = bnext;
      }
    }
    newBurstColl.add(newBurst);
    this.burstCollection = newBurstColl;

    // Step 3: compute burstID for each packet
    n = burstCollection.size();
    for (Burst b : burstCollection) {
      for (PacketInfo p : b.getPackets()) {
        p.setBurst(b);
      }
    }

    // Step 4: determine short/long IBTs
    n = burstCollection.size();
    for (int i = 0; i < n; i++) {
      Burst b = burstCollection.get(i);
      assert (b.getEndTime() >= b.getBeginTime());
      if (i < n - 1) {
        double ibt = burstCollection.get(i + 1).getBeginTime() - b.getEndTime();
        assert (ibt >= burstThresh);
        b.setbLong((ibt > longBurstThresh));
      } else {
        b.setbLong(true);
      }
    }
  }