package de.klimek.scanner;

import android.graphics.ImageFormat;
import android.graphics.Rect;
import android.hardware.Camera;
import android.os.AsyncTask;
import android.util.Log;

import com.google.zxing.MultiFormatReader;

import java.util.Timer;
import java.util.TimerTask;

class Decoder implements Camera.PreviewCallback {
    private static final String TAG = Decoder.class.getSimpleName();

    private Camera mCamera;
    private Camera.Size mPreviewSize;
    private int mCameraDisplayOrientation;
    private byte[] mPreviewBuffer;
    private byte[] mPreviewRotationBuffer;
    private float mReticleFraction;
    private Rect mBoundingRect;

    private volatile boolean mDecoding = false;
    private int mDecodeInterval;
    private Timer mDelayTimer;
    private DecodeTask mDecodeTask;
    private MultiFormatReader mMultiFormatReader = new MultiFormatReader();
    private OnDecodedCallback mCallback;


    Decoder(int decodeInterval, float reticleFraction) {
        mDecodeInterval = decodeInterval;
        mReticleFraction = reticleFraction;
    }

    void setOnDecodedCallback(OnDecodedCallback callback) {
        mCallback = callback;
    }

    void startDecoding(Camera camera, int cameraDisplayOrientation) {
        mDecoding = true;
        mDelayTimer = new Timer();

        mCamera = camera;
        mCameraDisplayOrientation = cameraDisplayOrientation;
        mPreviewSize = camera.getParameters().getPreviewSize();
        mBoundingRect = getBoundingRect(mPreviewSize, mReticleFraction);

        // add buffer to camera to prevent garbage collection spam
        mPreviewBuffer = createPreviewBuffer(mPreviewSize);
        camera.addCallbackBuffer(mPreviewBuffer);
        // temp buffer to allow manipulating/rotating the first buffer
        mPreviewRotationBuffer = new byte[mPreviewBuffer.length];
        camera.setPreviewCallbackWithBuffer(this);
    }

    void stopDecoding() {
        mDecoding = false;
        mDelayTimer.cancel();
        mDelayTimer = null;
        if (mDecodeTask != null) {
            mDecodeTask.cancel(true);
        }
    }

    private static Rect getBoundingRect(Camera.Size previewSize, double fraction) {
        int height = (int) (previewSize.height * fraction);
        int width = (int) (previewSize.width * fraction);
        int reticleDim = Math.min(height, width);

        int left = (previewSize.width - reticleDim) / 2;
        int top = (previewSize.height - reticleDim) / 2;
        int right = left + reticleDim;
        int bottom = top + reticleDim;

        return new Rect(left, top, right, bottom);
    }

    private static byte[] createPreviewBuffer(Camera.Size mPreviewSize) {
        int width = mPreviewSize.width;
        int height = mPreviewSize.height;
        int bitsPerPixel = ImageFormat.getBitsPerPixel(ImageFormat.NV21);  // default camera format
        int sizeInBits = width * height * bitsPerPixel;
        int sizeInBytes = (int) Math.ceil((float) sizeInBits / Byte.SIZE);
        return new byte[sizeInBytes];
    }

    /*
     * Called when the camera has a buffer, e.g. by calling
     * camera.addCallbackBuffer(buffer). This buffer is automatically removed,
     * but added again after decoding, resulting in a loop until stopDecoding()
     * is called. The data is not affected by Camera#setDisplayOrientation(),
     * so it may be rotated.
     */
    @Override
    public void onPreviewFrame(byte[] data, Camera camera) {
        if (mDecoding) {
            mDecodeTask = new DecodeTask(this,
                    mPreviewSize,
                    mCameraDisplayOrientation,
                    mBoundingRect,
                    mMultiFormatReader,
                    mPreviewRotationBuffer);
            mDecodeTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, data);
        }
    }

    /*
     * Called by mDecodeTask
     */
    void onDecodeSuccess(String string) {
        Log.i(Decoder.TAG, "Decode success.");
        if (mDecoding) {
            mCallback.onDecoded(string);
            // request next frame after delay
            mDelayTimer.schedule(new RequestPreviewFrameTask(), mDecodeInterval);
        }
    }

    /*
     * Called by mDecodeTask
     */
    void onDecodeFail() {
        // Log.i(Decoder.TAG, "Decode fail.");
        if (mDecoding) {
            // request next frame after delay
            mDelayTimer.schedule(new RequestPreviewFrameTask(), mDecodeInterval);
        }
    }

    private class RequestPreviewFrameTask extends TimerTask {
        @Override
        public void run() {
            if (mDecoding) {
                // adding in other thread is okay as onPreviewFrame will be called on main thread
                mCamera.addCallbackBuffer(mPreviewBuffer);
            }
        }
    }
}

@@ -149,16 +149,43 @@
 ncTask;%0A
+import android.os.Handler;%0A
 import a
@@ -250,61 +250,8 @@
 r;%0A%0A
-import java.util.Timer;%0Aimport java.util.TimerTask;%0A%0A
 clas
@@ -715,19 +715,21 @@
 private 
-Tim
+Handl
 er mDela
@@ -729,21 +729,39 @@
 r mDelay
-Timer
+Handler = new Handler()
 ;%0A    pr
@@ -790,16 +790,71 @@
 deTask;%0A
+    private RequestPreviewFrameTask mRequestFrameTask;%0A
     priv
@@ -1310,43 +1310,8 @@
 rue;
-%0A        mDelayTimer = new Timer();
 %0A%0A  
@@ -1665,58 +1665,8 @@
 e);%0A
-        camera.addCallbackBuffer(mPreviewBuffer);%0A
     
@@ -1849,16 +1849,45 @@
 (this);%0A
+        requestNextFrame(0);%0A
     %7D%0A%0A 
@@ -1950,38 +1950,50 @@
     
-mDelayTimer.cancel();%0A
+if (mRequestFrameTask != null) %7B%0A 
         
 mDel
@@ -1992,27 +1992,70 @@
     
+   
 mDelay
-Timer = null;
+Handler.removeCallbacks(mRequestFrameTask);%0A        %7D
 %0A   
@@ -4070,35 +4070,32 @@
 %0A           
- //
  request
  next frame 
@@ -4086,96 +4086,18 @@
 uest
- next frame after delay%0A            mDelayTimer.schedule(new RequestPreviewFrameTask(), 
+NextFrame(
 mDec
@@ -4284,19 +4284,16 @@
     
- //
  request
  nex
@@ -4292,123 +4292,332 @@
 uest
- next f
+NextF
 rame
- after delay%0A            mDelayTimer.schedule(new RequestPreviewFrameTask(), mDecodeInterval);%0A        %7D
+(mDecodeInterval);%0A        %7D%0A    %7D%0A%0A    private void requestNextFrame(int delay) %7B%0A        if (mRequestFrameTask != null) %7B%0A            mDelayHandler.removeCallbacks(mRequestFrameTask);%0A        %7D%0A        mRequestFrameTask = new RequestPreviewFrameTask();%0A        mDelayHandler.postDelayed(mRequestFrameTask, delay);
 %0A   
@@ -4666,25 +4666,27 @@
 ask 
-extends TimerTask
+implements Runnable
  %7B%0A 

Fix potential leak by using handler instead of timer