/**
   * After passing all shift blocks via the addSnapshotSummary() method, this method may be called
   * to fetch a synthesized CFMS protocol message (including CFMS header) which contains a "CFMS
   * data upload (a.k.a. vPerf report)"
   *
   * @return An InputStream which contains the synthesized CFMS data upload message.
   * @throws AqErrorException One or more snapshot summaries were not added to the vPerf data
   *     builder.
   */
  public InputStream getCfmsUploadRequest() throws AqErrorException {
    InputStream retval = null;
    AqVperfData vp = this.get_vPerfData();
    if (null != vp) {
      CfmsVperfByteEncoder be = new CfmsVperfByteEncoder();
      be.setCfmsProtocolVersion(AqVperfData.CFMS_VPERF_VERSION);
      vp.encodePackedData(be);

      try {
        retval =
            new ByteArrayInputStream(
                CfmsHeader.createCfmsPacket(
                    be.getContent(),
                    AqConsts.ATP_CFMS_DATA_UPLOAD_REQ,
                    false,
                    this.cfmsUploadRequestTransId));
      } catch (Exception e) {
        throw new AqErrorException("Unable to create cfmsUploadRequest");
      }
    }
    return retval;
  }
  /**
   * After passing all shift blocks via the addSnapshotSummary() method, this method may be called
   * to fetch AqVperfData object.
   *
   * @return A populated AqVperfData object which can be used to perform vPerf report data
   *     transforms.
   * @throws AqErrorException One or more snapshot summaries were not added to the vPerf data
   *     builder.
   */
  public AqVperfData get_vPerfData() throws AqErrorException {
    AqVperfData retval = null;
    if ((null == logonEvent) || (null == logoffEvent)) {
      throw new AqErrorException("incomplete vPerf data upload.");
    } else {
      long logonTime = getEventTimeInSecs(logonEvent);
      long logoffTime = getEventTimeInSecs(logoffEvent);
      int shiftDurationInSeconds = (int) (logoffTime - logonTime);

      vPerf.setCfmsConfigManifest(cfmsConfigManifest);
      vPerf.setShiftStartTimeInSeconds(logonTime);
      vPerf.setShiftDuration(shiftDurationInSeconds);
      vPerf.setShiftId(this.shiftId);
      vPerf.setIvmsMinDrvRstPeriodViol(shiftDurationInSeconds, this.ivmsAccumulatedBreakTime);
      vPerf.setIvmsDrvHoursViol(shiftDurationInSeconds > AqVperfData.IVMS_MAX_SHIFT_DURATION);
      vPerf.setFmsSource(fmsSource);
      vPerf.setNumShiftPeriods(this.lastSummaryProcessed);
      retval = vPerf;
    }
    return retval;
  }
  /**
   * Add a SnapshotSummary row read form the DB to a AqVperfData object. After all SnapshotSummaries
   * are added IN ORDER for a given shift The vPerf is fully re-constructed.
   *
   * @param shiftId The shift Id that was consumed from the Original SnapshotSummaryUploadRequest
   *     and stored in the SnapshotSummarytable
   * @param seq_num The seq_num that was consumed from the Original SnapshotSummaryUploadRequest and
   *     stored in the SnapshotSummarytable
   * @param isLast The isLast indicator that was consumed from the Original
   *     SnapshotSummaryUploadRequest and stored in the SnapshotSummarytable
   * @param vperf_vsn The vperf_vsn that was consumed from the Original SnapshotSummaryUploadRequest
   *     and stored in the SnapshotSummarytable
   * @param summaryData byte array, or maybe inputStream? This should be some entity which contains
   *     the rest of the remaing data that was received in the SnapshotSummaryUploadRequest message
   *     and wasn't consumed.
   */
  public void addSnapshotSummary(
      String shiftId, int seq_num, boolean isLast, int vperf_vsn, byte[] summaryData)
      throws AqErrorException {
    if (seq_num != (lastSummaryProcessed + 1)) {
      System.out.println("seq_num,lastSummaryProcessed: " + seq_num + "," + lastSummaryProcessed);
      throw new AqErrorException("seq_num out of order");
    } else if (isLastBlockProcessed) {
      throw new AqErrorException("last block already processed");
    } else {
      try {
        CfmsVperfByteDecoder bd = new CfmsVperfByteDecoder(summaryData);
        CfmsVperfByteDecoder shiftPeriodBd;
        bd.setCfmsProtocolVersion(vperf_vsn);

        int magic = bd.readInt(4);
        if (SNAPSHOT_SUMMARY_MAGIC != magic) {
          throw new AqErrorException("Invalid magic number for snapshot summary");
        }
        int vPerfVsn = bd.readInt(1);
        AqLog.getInstance().debug("gotvPerfVsn: " + vPerfVsn);

        int shiftPeriodIvmsBreakTime = bd.readInt(4);
        AqLog.getInstance().debug("got shiftPeriodIvmsBreakTime:" + shiftPeriodIvmsBreakTime);

        int shiftPeriodNetricSz = bd.readInt(2);
        AqLog.getInstance().debug("got shiftPeriodNetricSz:" + shiftPeriodNetricSz);

        byte[] shiftPeriodMetrics = bd.readBytes(shiftPeriodNetricSz);
        AqLog.getInstance().debug("got shiftPeriodMetrics: " + shiftPeriodMetrics);

        // Read operational events
        int shiftPeriodNumOperationalEvents = bd.readInt(2);
        AqLog.getInstance()
            .debug("got shiftPeriodNumOperationalEvents:" + shiftPeriodNumOperationalEvents);

        int shiftPeriodOperationalEvtSz = bd.readInt(2);
        AqLog.getInstance()
            .debug("got shiftPeriodOperationalEvtSz: " + shiftPeriodOperationalEvtSz);

        byte[] shiftPeriodOperationalEvents = null;
        AqLog.getInstance()
            .debug("got shiftPeriodOperationalEvents: " + shiftPeriodOperationalEvents);

        if (shiftPeriodOperationalEvtSz > 0)
          shiftPeriodOperationalEvents = bd.readBytes(shiftPeriodOperationalEvtSz);
        AqLog.getInstance()
            .debug("got shiftPeriodOperationalEvents: " + shiftPeriodOperationalEvents);

        // Read health events
        int shiftPeriodNumHealthEvents = bd.readInt(2);
        AqLog.getInstance().debug("got shiftPeriodNumHealthEvents: " + shiftPeriodNumHealthEvents);

        int shiftPeriodHealthEvtSz = bd.readInt(2);
        AqLog.getInstance().debug("got shiftPeriodHealthEvtSz: " + shiftPeriodHealthEvtSz);

        byte[] shiftPeriodHealthEvents = null;

        if (shiftPeriodHealthEvtSz > 0)
          shiftPeriodHealthEvents = bd.readBytes(shiftPeriodHealthEvtSz);
        AqLog.getInstance().debug("got shiftPeriodHealthEvents: " + shiftPeriodHealthEvents);

        BitDecoder bits = new BitDecoder(bd.readBytes(1));

        /* int reserved = */ bits.readInt(7);
        boolean isLastShiftPeriod = bits.readBoolean();
        AqLog.getInstance().debug("got isLastShiftPeriod: " + isLastShiftPeriod);

        if (isLastShiftPeriod) {
          // Read CFMS config manifest
          cfmsConfigManifest = new CfmsConfigManifest(false);
          cfmsConfigManifest.decodePackedData(bd);
          AqLog.getInstance().debug("got cfmsConfigManifest: " + cfmsConfigManifest);
          AqLog.getInstance()
              .debug(
                  "mfstVsn,ObuSwVsn,tspCfgVsn,coreVsn,gfSetVsn,fmsVsn: "
                      + cfmsConfigManifest.getCfgManifestVersion()
                      + ","
                      + cfmsConfigManifest.getObuSwVersion()
                      + ","
                      + cfmsConfigManifest.getTspConfigVersion()
                      + ","
                      + cfmsConfigManifest.getCoreVersion()
                      + ","
                      + cfmsConfigManifest.getGeoFenceSetVersion()
                      + ","
                      + cfmsConfigManifest.getFmsVersion());

          // read the FMS source
          fmsSource = bd.readShortString();
          AqLog.getInstance().debug("got fmsSource: " + fmsSource);

          // read the next service distance
          try {
            nextServiceDistance = bd.checkedReadInt(3);
          } catch (Exception e) {
            nextServiceDistance = 99999;
          }
          AqLog.getInstance().debug("got nextServiceDistance: " + nextServiceDistance);

          // read logon
          int logonEventSz = bd.readInt(1);
          AqLog.getInstance().debug("got logonEventSz: " + logonEventSz);

          byte[] logonEventData = bd.readBytes(logonEventSz);
          CfmsVperfByteDecoder logonBd = new CfmsVperfByteDecoder(logonEventData);
          logonBd.setCfmsProtocolVersion(vPerfVsn);
          logonEvent = readShiftEventFromCfmsVperfByteDecoder(logonBd);
          if (null != logonEvent) vPerf.addLogonEvent(logonEvent);
          AqLog.getInstance().debug("got logonEvent: " + logonEvent);

          // read logoff
          int logoffEventSz = bd.readInt(1);
          byte[] logoffEventData = bd.readBytes(logoffEventSz);
          CfmsVperfByteDecoder logoffBd = new CfmsVperfByteDecoder(logoffEventData);
          logoffBd.setCfmsProtocolVersion(vPerfVsn);
          logoffEvent = readShiftEventFromCfmsVperfByteDecoder(logoffBd);
          AqLog.getInstance().debug("got logoffEvent: " + logoffEvent);

          cfmsUploadRequestTransId = bd.readLong(8);
          AqLog.getInstance().debug("got cfmsUploadRequestTransId: " + cfmsUploadRequestTransId);

          healthReportUploadRequestTransId = bd.readLong(8);
          AqLog.getInstance()
              .debug("got healthReportUploadRequestTransId: " + healthReportUploadRequestTransId);

          healthLogonStatus = bd.readInt(1);
          AqLog.getInstance().debug("got healthLogonStatus: " + healthLogonStatus);
          healthProcessorStatus = bd.readInt(2);
          AqLog.getInstance().debug("got healthProcessorStatus: " + healthProcessorStatus);

          healthMinRam = bd.readInt(2);
          AqLog.getInstance().debug("got healthMinRam: " + healthMinRam);
          healthAvgRam = bd.readInt(2);
          AqLog.getInstance().debug("got healthAvgRam: " + healthAvgRam);
          healthBatteryAndDtcoStats = bd.readInt(1);
          AqLog.getInstance().debug("got healthBatteryAndDtcoStats: " + healthBatteryAndDtcoStats);
          healthFmsStatus = bd.readInt(2);
          AqLog.getInstance().debug("got healthFmsStatus: " + healthFmsStatus);
          healthFmsErrorCount = bd.readInt(2);
          AqLog.getInstance().debug("got healthFmsErrorCount: " + healthFmsErrorCount);
        }

        // Sanity check the API call parameters against what is inside the SnapshoSummaryRequest
        // Decode the components
        shiftPeriodBd = new CfmsVperfByteDecoder(shiftPeriodMetrics);
        shiftPeriodBd.setCfmsProtocolVersion(vPerfVsn);
        AqShiftPeriod sp = new AqShiftPeriod();
        sp.decodePackedData(shiftPeriodBd);

        // Make sure that all reported values for shiftId match
        if (null == this.shiftId) {
          this.shiftId = new String(shiftId);
        } else if ((null == shiftId) || (!this.shiftId.equals(shiftId))) {
          throw new AqErrorException("integrety mismatch");
        }

        if ((vPerfVsn != vperf_vsn)
            || (isLastShiftPeriod != isLast)
            || (seq_num != sp.getSequenceNum())) {
          throw new AqErrorException("integrety mismatch");
        }

        // TODO would it be better to add protected method for this?  then make shift period private
        // member
        vPerf.shiftPeriods[seq_num - 1] = sp;
        this.ivmsAccumulatedBreakTime += shiftPeriodIvmsBreakTime;

        if (null != shiftPeriodOperationalEvents) {
          CfmsVperfByteDecoder opEventsBd = new CfmsVperfByteDecoder(shiftPeriodOperationalEvents);
          opEventsBd.setCfmsProtocolVersion(vPerfVsn);
          int i;
          for (i = 0; i < shiftPeriodNumOperationalEvents; i++) {
            AqShiftEvent evt = readShiftEventFromCfmsVperfByteDecoder(opEventsBd);
            if (null != evt) vPerf.addOperationalEvent(evt);
          }
        }

        // This will only be true during the last shift period ...
        // make sure it is the last event recorded for the shift to
        // match current event listing order
        if (null != logoffEvent) vPerf.addOperationalEvent(logoffEvent);

        this.totalNumHealthEvents += shiftPeriodNumHealthEvents;
        if (null != shiftPeriodHealthEvents) {
          healthReportEventBytes.write(shiftPeriodHealthEvents);
        }

        this.lastSummaryProcessed = seq_num;
      } catch (Exception e) {
        AqLog.getInstance().error("Unable to decode SnapshotSummaryRequest payload", e);
        throw new AqErrorException("Unable to decode SnapshotSummaryRequest payload");
      }
    }
  }