/**
 *    Copyright 2014 Galois, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 *
 */
package com.galois.qrstream.qrpipe;

import java.util.List;
import java.util.Map;

import com.google.common.collect.ImmutableList;
import com.google.common.primitives.Bytes;
import com.google.zxing.Result;
import com.google.zxing.ResultMetadataType;
import com.google.zxing.client.result.ParsedResult;
import com.google.zxing.client.result.ParsedResultType;
import com.google.zxing.client.result.ResultParser;

/**
 * Stores data from a single decoded QR code that's part of a larger sequence of QR codes.
 */
public final class PartialMessage {
  private final int chunkId;
  private final int totalChunks;
  private final byte[] payload;

  /**
   * Initialize partial message with its data and sequence information.
   * @param chunkId Unique number identifying this chunk of data within a sequence.
   * @param totalChunks The number of chunks in a sequence of transmitted data.
   * @param payload The partial message contains within the QR code.
   */
  private PartialMessage(int chunkId, int totalChunks, byte[] payload) {
    this.chunkId = chunkId;
    this.totalChunks = totalChunks;
    this.payload = payload.clone();
  }

  public int getTotalChunks() {
    return totalChunks;
  }

  public int getChunkId() {
    return chunkId;
  }

  public byte[] getPayload() {
    return payload.clone();
  }

  /**
   * Extracts information about the message within the decoded QR code
   * and initialize a new {@code PartialMessage} with that information.
   * It retrieves, its position in data transmission ({@code chunkId}),
   * the total number of chunks in a sequence of transmitted data
   * ({@code totalChunks}), and the partial message contained within
   * the QR {@code payload}.
   *
   * If the decoded QR code is not properly formatted streaming QR code
   * then return {@code null} PartialMessage. We do not throw exceptions
   * anymore since the upstream code handles the {@code null} message.
   *
   * @param decodedQR The result from decoding a QR code within an image.
   * @param maxChunks The maximum number of chunks expected for a QR stream.
   */
  protected static PartialMessage createFromResult(Result decodedQR, int maxChunks) {
    final int chunkId;
    final int totalChunks;
    final byte[] payload;

    // Check that the QR code has enough bytes for extracting
    // the sequence data needed to identify it as streaming QR.
    byte[] message = getRawData(decodedQR);
    if (message == null || message.length < Utils.getNumberOfReservedBytes()) {
      return null;
    }

    // Check that the extracted sequence data is well formed.
    try {
      chunkId = Utils.extractChunkId(message);
      totalChunks = Utils.extractTotalNumberChunks(message);
      payload = Utils.extractPayload(message);
    }catch (IllegalArgumentException e) {
      return null;
    }

    // Ensure positive chunk data
    if (chunkId < 1 || totalChunks < 1) {
      return null;
    }

    // Can only stream the QR code if its total chunks is less than threshold.
    // This is necessary since a BitVector is used to track the chunks
    // we've seen and it cannot get too large without running out of memory.
    if (totalChunks > maxChunks) {
      return null;
    }

    // Properly formatted streaming QR code must have all chunkIds
    // less than or equal to the expected totalChunks encoded with the QR code.
    if (chunkId > totalChunks) {
      return null;
    }

    return new PartialMessage(chunkId,totalChunks,payload);
  }

  /**
   * Extract raw bytes from decoded QR code.  If none can be extracted,
   * then this method returns {@code null}.
   */
  private static byte[] getRawData(final Result decodedQR) {

    // Whenever the ZXing result metadata is missing or does not
    // have the BYTE_SEGMENTS object return null to indicate error.
    // Might happen if we're trying to read a non-streaming type of QR code.
    Map<ResultMetadataType,?> meta = decodedQR.getResultMetadata();
    if (meta == null || !meta.containsKey(ResultMetadataType.BYTE_SEGMENTS)) {
      return null;
    }

    // Expect any streaming QR codes to have this type
    // All other qr codes have to be handled in a different way
    ParsedResult result = ResultParser.parseResult(decodedQR);
    if (result.getType() != ParsedResultType.TEXT) {
      return null;
    }

    ImmutableList.Builder<Byte> msgBuilder = new ImmutableList.Builder<Byte>();

    @SuppressWarnings("unchecked")
    List<byte[]> dataSegments = (List<byte[]>) meta.get(ResultMetadataType.BYTE_SEGMENTS);
    for (byte[] bs : dataSegments) {
      msgBuilder.addAll(Bytes.asList(bs));
    }
    return Bytes.toArray(msgBuilder.build());
  }
}

@@ -845,168 +845,8 @@
 ype;
-%0Aimport com.google.zxing.client.result.ParsedResult;%0Aimport com.google.zxing.client.result.ParsedResultType;%0Aimport com.google.zxing.client.result.ResultParser;
 %0A%0A/*
@@ -4486,269 +4486,8 @@
  %7D%0A%0A
-    // Expect any streaming QR codes to have this type%0A    // All other qr codes have to be handled in a different way%0A    ParsedResult result = ResultParser.parseResult(decodedQR);%0A    if (result.getType() != ParsedResultType.TEXT) %7B%0A      return null;%0A    %7D%0A%0A
     

Remove QR code type check since it was rejecting valid streaming QR codes.