// Copyright 2018 Google LLC
//
// 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.googleinterns.smb.barcodescanning;

import android.graphics.Bitmap;
import android.util.Log;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

import com.google.android.gms.tasks.OnFailureListener;
import com.google.android.gms.tasks.OnSuccessListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.ml.vision.FirebaseVision;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcode;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetector;
import com.google.firebase.ml.vision.barcode.FirebaseVisionBarcodeDetectorOptions;
import com.google.firebase.ml.vision.common.FirebaseVisionImage;
import com.googleinterns.smb.VisionProcessorBase;
import com.googleinterns.smb.common.CameraImageGraphic;
import com.googleinterns.smb.common.FrameMetadata;
import com.googleinterns.smb.common.GraphicOverlay;

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

/**
 * Barcode Detector
 */
public class BarcodeScanningProcessor extends VisionProcessorBase<List<FirebaseVisionBarcode>> {

    private static final String TAG = BarcodeScanningProcessor.class.getName();

    private final FirebaseVisionBarcodeDetector detector;
    private Set<String> mDetectedBarcodes = new HashSet<>();

    public BarcodeScanningProcessor() {
        // using EAN_13 barcode format
        FirebaseVisionBarcodeDetectorOptions options =
                new FirebaseVisionBarcodeDetectorOptions.Builder()
                        .setBarcodeFormats(FirebaseVisionBarcode.FORMAT_EAN_13)
                        .build();
        // remove 'options' parameter to detect all types of barcode formats. However, this leads to slower processing
        detector = FirebaseVision.getInstance().getVisionBarcodeDetector(options);
    }

    @Override
    public void stop() {
        try {
            detector.close();
        } catch (IOException e) {
            Log.e(TAG, "Exception thrown while trying to close Barcode Detector: " + e);
        }
    }

    @Override
    protected Task<List<FirebaseVisionBarcode>> detectInImage(FirebaseVisionImage image) {
        return detector.detectInImage(image);
    }

    @Override
    protected void onSuccess(
            @Nullable Bitmap originalCameraImage,
            @NonNull List<FirebaseVisionBarcode> barcodes,
            @NonNull FrameMetadata frameMetadata,
            @NonNull GraphicOverlay graphicOverlay) {
        graphicOverlay.clear();
        if (originalCameraImage != null) {
            CameraImageGraphic imageGraphic = new CameraImageGraphic(graphicOverlay, originalCameraImage);
            graphicOverlay.add(imageGraphic);
        }
        for (int i = 0; i < barcodes.size(); ++i) {
            FirebaseVisionBarcode barcode = barcodes.get(i);
            Log.d(TAG, "Detected barcode: " + barcode.getRawValue());
            // add barcode to hash set
            mDetectedBarcodes.add(barcode.getRawValue());
            BarcodeGraphic barcodeGraphic = new BarcodeGraphic(graphicOverlay, barcode);
            graphicOverlay.add(barcodeGraphic);
        }
        graphicOverlay.postInvalidate();
    }

    @Override
    protected void onFailure(@NonNull Exception e) {
        Log.e(TAG, "Barcode detection failed " + e);
    }

    public List<String> getDetectedBarCodes() {
        return new ArrayList<>(mDetectedBarcodes);
    }

    public void getFromBitmap(Bitmap bitmap, final BarcodeStatusListener listener) {
        detectInImage(FirebaseVisionImage.fromBitmap(bitmap))
                .addOnSuccessListener(
                        new OnSuccessListener<List<FirebaseVisionBarcode>>() {
                            @Override
                            public void onSuccess(List<FirebaseVisionBarcode> barcodes) {
                                List<String> mBarcodes = new ArrayList<>();
                                for (int i = 0; i < barcodes.size(); i++) {
                                    FirebaseVisionBarcode barcode = barcodes.get(i);
                                    mBarcodes.add(barcode.getRawValue());
                                }
                                listener.onSuccess(mBarcodes);
                            }
                        })
                .addOnFailureListener(
                        new OnFailureListener() {
                            @Override
                            public void onFailure(@NonNull Exception e) {
                                listener.onFailure(e);
                            }
                        });
    }
}

@@ -1921,16 +1921,279 @@
 Set%3C%3E();
+%0A    // number of iterations to check to confirm reliability of barcode detected%0A    private static final int DEBOUNCE = 3;%0A    private int frameNumber = 0;%0A    // set of barcodes detected in previous frames%0A    private Set%3CString%3E%5B%5D last = new HashSet%5BDEBOUNCE%5D;
 %0A%0A    pu
@@ -3578,32 +3578,146 @@
 hic);%0A        %7D%0A
+        Set%3CString%3E currentFrameBarcodes = new HashSet%3C%3E();%0A        int currentFrameIdx = frameNumber %25 DEBOUNCE;%0A
         for (int
@@ -3829,105 +3829,563 @@
     
-Log.d(TAG, %22Detected barcode: %22 + barcode.getRawValue());%0A            // add barcode to hash set%0A
+currentFrameBarcodes.add(barcode.getRawValue());%0A            boolean exists = true;%0A            // check if barcode present in previous frames%0A            for (int j = 0; j %3C DEBOUNCE; j++) %7B%0A                // present in current frame so ignore%0A                if (j == currentFrameIdx)%0A                    continue;%0A                if (!last%5Bj%5D.contains(barcode.getRawValue()))%0A                    exists = false;%0A            %7D%0A            // if present in all DEBOUNCE frames, then add to final set. This improves accuracy%0A            if (exists) %7B%0A   
     
@@ -4392,16 +4392,17 @@
         
+ 
 mDetecte
@@ -4431,32 +4431,36 @@
 getRawValue());%0A
+    
             Barc
@@ -4536,32 +4536,36 @@
 e);%0A            
+    
 graphicOverlay.a
@@ -4584,33 +4584,194 @@
 aphic);%0A        
+   
-%7D
+ %7D%0A        %7D%0A        // update current idx barcodes%0A        last%5BcurrentFrameIdx%5D = currentFrameBarcodes;%0A        // update frame number%0A        frameNumber++;
 %0A        graphic

Implement debounce strategy for better scan accuracy (#9)