@ActivityObject
@Permissions(values={"android.permission.CAMERA"})
@ShortName("Camera")
@Version(2.01f)
@Events(values={"Ready (Success As Boolean)", "PictureTaken (Data() As Byte)",
      "Preview (Data() As Byte)"})
public class CameraW {
   @Hide
   public Camera camera;
   private static ConcurrentHashMap<Integer, Camera> closingCameras = new ConcurrentHashMap<Integer, Camera>();
   private SurfaceView sv;
   private String eventName;
   private BA ba;
   private int currentId;
   private AtomicInteger readyCount = new AtomicInteger();
   /**
    * Initializes the back camera.
    *Panel - The preview images will be displayed on the panel.
    *EventName - Events subs prefix.
    *The Ready event will be raised when the camera has finished opening.
    */
   public void Initialize(final BA ba, ViewGroup Panel, String EventName) {
      shared(ba, Panel, EventName, -1);
   }
   /**
    * Same as Initialize. CameraId is the id of the hardware camera.
    *<b>This method is only available from Android 2.3+.</b>
    */
   public void Initialize2(final BA ba, ViewGroup Panel, String EventName, int CameraId) {
      shared(ba, Panel, EventName, CameraId);
   }
   private void shared(final BA ba, ViewGroup Panel, String EventName, final int CameraId) {
      this.ba = ba;
      currentId = CameraId;
      readyCount.set(0);
      this.eventName = EventName.toLowerCase(BA.cul);
      sv = new SurfaceView(ba.context);
      anywheresoftware.b4a.BALayout.LayoutParams lp = new anywheresoftware.b4a.BALayout.LayoutParams(0, 0,
            Panel.getLayoutParams().width, Panel.getLayoutParams().height);
      Panel.addView(sv, lp);
      camera = closingCameras.get(CameraId);
      closingCameras.remove(CameraId);
      if (camera == null) {
         //release other cached cameras (relevant when switching cameras)
         synchronized (closingCameras) { //make sure that release is only called once.
            for (Camera c : closingCameras.values()) {
               c.release();
            }
            closingCameras.clear();
         }
      }
      
      sv.getHolder().setType(SurfaceHolder.SURFACE_TYPE_PUSH_BUFFERS);
      sv.getHolder().setFixedSize(Panel.getLayoutParams().width, Panel.getLayoutParams().height);
      sv.getHolder().addCallback(new SurfaceHolder.Callback() {
         @Override
         public void surfaceChanged(SurfaceHolder holder, int format,
               int width, int height) {
         }
         @Override
         public void surfaceCreated(SurfaceHolder holder) {
            if (readyCount.addAndGet(1) == 2) {
               ba.raiseEvent(null, eventName + "_ready", true);
            }
            
         }
         @Override
         public void surfaceDestroyed(SurfaceHolder holder) {
         }
      });
      ba.submitRunnable(new Runnable() {
         @Override
         public void run() {
            try {
               if (camera == null) {
                  if (CameraId == -1)
                     camera = Camera.open();
                  else {
                     Method m = Camera.class.getMethod("open", int.class);
                     camera = (Camera)m.invoke(null, CameraId);
                  }
               }
               if (readyCount.addAndGet(1) == 2) {
                  ba.raiseEventFromDifferentThread(null, CameraW.this, -1,eventName +  "_ready", false, new Object[] {true});
               }
               if (ba.subExists(eventName + "_preview")) {
                  camera.setPreviewCallback(new Camera.PreviewCallback() {
                     @Override
                     public void onPreviewFrame(byte[] data,
                           Camera camera) {
                        ba.raiseEvent(null, eventName + "_preview", data);
                     }
                     
                  });
               }
            }
            catch (Exception e) {
               e.printStackTrace();
               Release();
               ba.raiseEventFromDifferentThread(null, CameraW.this, -1,eventName +  "_ready", false, new Object[] {false});
            }
         }
      }, this, -1);
   }
   /**
    * Starts displaying the preview images.
    */
   public void StartPreview() throws IOException {
      camera.setPreviewDisplay(sv.getHolder());
      camera.startPreview();
   }
   /**
    * Stops displaying the preview images.
    */
   public void StopPreview() {
      if (camera != null)
         camera.stopPreview();
   }
   /**
    * Releases the camera object and allows other processes to access the camera.
    */
   public void Release() {
if (camera != null) {
         camera.setPreviewCallback(null);
         camera.stopPreview();
         closingCameras.put(currentId, camera);
         final Camera c = camera;
         camera = null;
         Thread t = new Thread(new Runnable() {
            @Override
            public void run() {
               try {
                  Thread.sleep(5000);
                  synchronized (closingCameras) {
                     if (closingCameras.remove(currentId) != null) {
                        c.release();
                     }
                  }
               } catch (Exception e) {
                  e.printStackTrace();
               }
            }
            
         });
         t.start();
         
      }
      if (sv != null) {
         ViewGroup vg = (ViewGroup) sv.getParent();
if (vg != null)
         vg.removeView(sv);
         sv = null;
      }
      
   }
   /**
    * Takes a picture. When the picture is ready, the PictureTaken event will be raised.
    *You should not call TakePicture while another picture is currently taken.
    *The preview images are stopped after calling this method. You can call StartPreview to restart the preview images.
    */
   public void TakePicture() {
      camera.takePicture(null , null, new Camera.PictureCallback() {
         @Override
         public void onPictureTaken(byte[] data, Camera camera) {
            ba.raiseEvent(null, eventName + "_picturetaken", data);
         }
         
      });
   }
}